Compare commits

...
Author SHA1 Message Date
Armin Schrenk 8f42c1c9ce Merge branch 'develop' into release/1.7.0 2022-11-18 10:47:33 +01:00
Armin SchrenkandGitHub 37f89fd235 Move release precondition checks to own workflow (#2535) 2022-11-18 10:46:22 +01:00
Armin Schrenk 668feb73f9 Merge branch 'feature/mount-provider' into release/1.7.0 2022-11-17 09:59:46 +01:00
Armin Schrenk ba0569deb1 preparing 1.7.0 2022-11-17 09:59:38 +01:00
Armin Schrenk a70d8e1567 update nonModularGroupIds 2022-11-17 09:50:39 +01:00
Armin Schrenk 96b7a8df3f bump dokany-nio-adapter 2022-11-17 09:24:51 +01:00
Armin Schrenk 5143fdccbb Refactor mount point setting and controller :
* remove CustomMountPath, winDriveLetter and usesCustomMountPath
* new property mountPoint (can be null)
* differentiation between driveLetter and directory happens in controller
2022-11-11 19:42:59 +01:00
Armin Schrenk 3c5502955a bump version of dokany-nio-adapter 2022-11-11 15:20:13 +01:00
Armin Schrenk 2d3a035d9e renamed mount option controller members 2022-11-11 11:44:50 +01:00
Sebastian Stenzel 4029f86a0d reveal OR copy mount point depending on chosen mounter 2022-11-10 14:11:06 +01:00
Sebastian Stenzel 6dac00625c updated mount API 2022-11-09 15:33:20 +01:00
Sebastian Stenzel 41b7bd54f1 use vault id as a volume id 2022-11-08 16:38:45 +01:00
Sebastian Stenzel f236614bd0 allow repeated mount & unmount of the same vault 2022-11-08 16:38:31 +01:00
Sebastian Stenzel 31c69f145e use a fallback mount point absed on -Dcryptomator.mountPointsDir=... vm param 2022-11-07 18:17:20 +01:00
Sebastian Stenzel b5a00f5ebe added --enable-preview --enable-native-access to run config 2022-11-07 18:07:45 +01:00
Sebastian Stenzel 40c4300012 fixed json syntax error 2022-11-07 16:20:19 +01:00
Sebastian Stenzel 5369ddb1f9 adjust suppression.xml to jfuse 2022-11-07 16:17:50 +01:00
Sebastian Stenzel 1b4117de38 volume settings → change tcp port 2022-11-07 16:16:10 +01:00
Sebastian Stenzel 3e5a7fadd0 only set the mount point when a mount point is explicitly required
(didn't work for the webdav fallback provider, which doesn't _not_ MOUNT_TO_SYSTEM_CHOSEN_PATH, as it does not mount at all)
2022-11-07 15:55:26 +01:00
Sebastian Stenzel eff2530e70 pre-select chosen mount service 2022-11-07 15:52:48 +01:00
Armin Schrenk 0f84d0c990 adapt to new APIs 2022-11-07 14:49:56 +01:00
Armin Schrenk fbe33e21fb Fixes #2498
Bump winfsp for windows bundle distribution
2022-11-03 10:16:53 +01:00
Sebastian StenzelandGitHub 7091839f08 Merge pull request #2505 from cryptomator/feature/javafx-19 2022-10-28 22:09:29 +02:00
Sebastian StenzelandGitHub 79bf1759c7 Merge pull request #2509 from cryptomator/feature/jdk19 2022-10-28 22:09:06 +02:00
Sebastian Stenzel 70e5f53009 Merge branch 'develop' into feature/jdk19 2022-10-27 18:13:42 +02:00
Sebastian Stenzel c79cfe1c9d Merge branch 'develop' into feature/javafx-19
# Conflicts:
#	pom.xml
2022-10-26 11:43:07 +02:00
Sebastian Stenzel d619a0cbf4 bump jwt version 2022-10-26 11:40:09 +02:00
Sebastian StenzelandGitHub 517b5958b0 Merge pull request #2487 from cryptomator/feature/ppa-jdk-19
Use `coffeelibs-jdk-19` during PPA build
2022-10-18 16:48:28 +02:00
Armin Schrenk 01a6475d5f replace deprecated method call 2022-10-17 17:13:12 +02:00
Sebastian Stenzel 69641ed3b6 Merge branch 'develop' into feature/jdk19
[ci skip]
2022-10-14 13:55:15 +02:00
Sebastian Stenzel 1f01923169 build on Ubuntu Focal 2022-10-14 13:44:50 +02:00
Sebastian Stenzel 687bed6984 use the correct name of required build-dependency 2022-10-14 13:24:08 +02:00
Sebastian Stenzel 01698d1393 install required build-dependency 2022-10-14 13:21:28 +02:00
Sebastian Stenzel 6367cc2dfb depend on coffeelibs-jdk-19 2022-10-14 13:10:37 +02:00
Armin Schrenk bcfda68bef bump used gh actions 2022-10-13 12:48:48 +02:00
Sebastian Stenzel 2399e29d75 update logback 2022-10-12 13:42:11 +02:00
322779ee88 Refactor winget release to own workflow file (#2471)
... and call it on release windows build

Co-authored-by: Sebastian Stenzel <overheadhunter@users.noreply.github.com>
2022-10-11 20:16:24 +02:00
Julian Raufelder fd6eb2139d Minor cleanup in build.yml 2022-10-11 17:45:59 +02:00
Julian Raufelder 26ecd67a79 Update linux common metainfo.xml file 2022-10-11 15:33:44 +02:00
Julian Raufelder e06240cbb9 Merge pull request #2475 from cryptomator/feature/add-release-check-precondition
Check POM version and linux metainfo.xml file during release steps
2022-10-11 15:11:54 +02:00
Julian Raufelder b10d892b12 Skip release tests too when ci-skip provided in commit message 2022-10-11 15:09:19 +02:00
Julian Raufelder 45c2dd0358 Validate release check precondition in separate job 2022-10-11 12:45:49 +02:00
Julian Raufelder 34d6e8c419 Validate POM version and linux metainfo.xml file during build 2022-10-10 15:28:16 +02:00
Julian Raufelder ae6a15b391 Check POM version and linux metainfo.xml file during release steps 2022-10-10 14:49:36 +02:00
Sebastian Stenzel 8040b71a51 simplified some more bindings 2022-10-10 12:09:30 +02:00
Sebastian Stenzel 76e7a0a7b8 bump OpenJFX to version 19
replaced EasyBind with `ObservableValue.map()` where possible
2022-10-10 10:24:42 +02:00
Sebastian Stenzel 910efc0b7b prepare macOS builds for diverging Xcode versions
[ci skip]
2022-10-07 12:03:00 +02:00
Sebastian Stenzel c805a820a4 Merge branch 'main' into develop
[ci skip]
2022-10-06 17:42:00 +02:00
Sebastian Stenzel 3ed8316b60 Merge branch 'release/1.6.15' 2022-10-06 17:40:22 +02:00
Sebastian Stenzel b6e381f0bf apply cryptofs hotfix 2022-10-06 17:37:03 +02:00
Sebastian Stenzel aef3f4c6e2 prepare 1.6.15 2022-10-06 16:49:12 +02:00
Sebastian Stenzel 7bc5a336e4 dependency bump 2022-10-06 16:48:12 +02:00
Sebastian Stenzel 672b4aee2d added Belgian, Danish, Farsi, Sinhala, Vietnamese to language chooser 2022-10-06 16:19:16 +02:00
CryptobotandGitHub d4ab17b38c New Crowdin updates (#2427)
[ci skip]
2022-10-06 16:11:29 +02:00
Sebastian Stenzel 91f49c5642 update cryptolib + cryptofs
fixes #2451
2022-10-06 15:20:21 +02:00
Julian Raufelder da2f97b1c4 Fix communitcation with hub if it supports only HTTP version 1.1 2022-10-06 09:38:02 +02:00
Tobias HagemannandGitHub 5d647d20a5 Update README.md 2022-09-22 15:00:22 +02:00
Armin SchrenkandGitHub ce96d1c65e Merge pull request #2452 from chewitt1/chewitt-next-button
Fixes #2447
2022-09-21 21:31:33 +02:00
Carrima Hewitt f02a8e14ff Fixed Trim and Bidirectional Bind 2022-09-21 11:11:00 -04:00
Armin Schrenk dd31908f3a Use for windows exe and msi build same JDKs 2022-09-21 15:50:08 +02:00
Sebastian Stenzel 495a1884c3 Merge branch 'develop' into feature/jdk19
[ci skip]
2022-09-21 13:21:18 +02:00
Sebastian Stenzel 49c3b1fa43 fix font loading warning visible in console
see https://stackoverflow.com/a/65127949/4014509
2022-09-21 13:20:34 +02:00
Sebastian Stenzel cde67966da fix test 2022-09-21 09:49:57 +02:00
Sebastian Stenzel d19e77432e build with jdk 19 2022-09-21 09:28:37 +02:00
Sebastian StenzelandGitHub cc24a4c9ae Merge pull request #2445 from purejava/disable-keyrings
fixes #2047, fixes #2219
2022-09-21 09:12:40 +02:00
Ralph Plawetzki 1101a7574c Beautify the code 2022-09-20 20:12:44 +02:00
Ralph Plawetzki 30de04e291 Move CheckBox (injected from JavaFX) below the final fields (injected by Dagger) 2022-09-20 20:12:43 +02:00
Ralph Plawetzki 2e3d2e86e2 Cut off the application from accessing keychains at the earliest point possible
as suggested in the discussion to #2445
2022-09-20 20:12:43 +02:00
Ralph Plawetzki 78cf082096 Rename properties as suggested in the discussion to #2445 2022-09-20 20:12:43 +02:00
Ralph Plawetzki 64c90f8107 Move checkbox to the front and rename members 2022-09-20 20:12:43 +02:00
Ralph Plawetzki b34bf6f161 Handle further queries of the backend for disabled keyrings 2022-09-20 20:12:43 +02:00
Ralph Plawetzki 17d3d7307d Wire disable all keyrings setting checkbox to the KeychainManager 2022-09-20 20:12:43 +02:00
Ralph Plawetzki e6c7fed662 Add disable all keyrings setting checkbox
and connect it to the settings file
2022-09-20 20:12:42 +02:00
Ralph Plawetzki 23fad16742 Revert "Add disable all keyrings setting checkbox"
This reverts commit d037c6bc140495b5469c2a25ae2a7a2752bc35dc.
2022-09-20 20:12:42 +02:00
Ralph Plawetzki ec794cdca2 Add disable all keyrings setting checkbox
and connect it to the settings file
2022-09-20 20:12:42 +02:00
Carrima Hewitt 2def9216ec Trimmed Vault Name StringProperty 2022-09-20 10:33:26 -04:00
Carrima Hewitt 4f9759af58 Merge branch 'chewitt-next-button' of https://github.com/chewitt1/cryptomator into chewitt-next-button 2022-09-20 10:20:10 -04:00
Carrima Hewitt 382f6b7851 Reverted Misc 2022-09-20 10:19:38 -04:00
Carrima HewittandGitHub d60eb470ea Merge branch 'cryptomator:develop' into chewitt-next-button 2022-09-20 08:41:38 -04:00
Carrima Hewitt 97a61ef717 Removed Comments 2022-09-20 08:37:38 -04:00
Armin Schrenk 08fc5e9a77 Fixes #2454 2022-09-20 10:58:48 +02:00
Carrima Hewitt 4c96c285bf Fixed Next button for Invalid Vault Name 2022-09-19 21:17:48 -04:00
Edward ChowandGitHub 0021f2d37c Use 2x icon on Mac menu bar (#2345) (#2346)
* Use 2x icon on Mac menu bar

Use 2x icon on Mac menu bar

* Fix #2357

Changed Windows tray icon to 2x
2022-09-15 17:38:42 +02:00
Tobias HagemannandGitHub 31d32f2018 Added macOS ARM64 workflow (#2432) 2022-09-10 08:30:18 +02:00
Armin SchrenkandGitHub b9c087b48e Merge pull request #2430 from youscam/patch-1
Update README.md
2022-09-07 09:53:52 +02:00
KAMAL AZZOUZIandGitHub 797dbc0a57 Update README.md
Update Windows profile name
2022-09-07 00:52:40 +01:00
Armin Schrenk 46745d030b [ci skip] fix winget release action 2022-08-31 18:12:59 +02:00
Armin Schrenk 3cdf124e54 Merge branch 'main' into develop
[ci skip]
2022-08-31 16:02:28 +02:00
Armin Schrenk d37147c73e Merge branch 'release/1.6.14' 2022-08-31 15:59:56 +02:00
Armin Schrenk 67dd1548cc finalize release 2022-08-31 15:47:23 +02:00
Armin Schrenk 008bb38e30 preparing 1.6.14 2022-08-31 15:41:18 +02:00
CryptobotandGitHub 1be6128523 New Crowdin updates (#2350)
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; Spanish; Swahili, Tanzania; Swedish; Tamil; Telugu; Thai; Turkish; Ukrainian; Vietnamese; 

[ci skip]
2022-08-31 15:17:19 +02:00
Armin Schrenk b4f95c465a Fixes #2408 2022-08-31 13:35:27 +02:00
Sebastian Stenzel 40fa961b9b clean up module-info 2022-08-31 11:40:18 +02:00
Sebastian Stenzel 659123fe7a regenerate keypair if .p12 _or_ keychain item is missing
it used to depend only on the keychain item
2022-08-31 11:25:25 +02:00
Sebastian Stenzel 2448963161 class does not need to be generic 2022-08-31 10:57:41 +02:00
Sebastian Stenzel 16c0431f6f use release versions of slf4j + logback 2022-08-31 10:52:53 +02:00
Sebastian Stenzel 2d99d1bdd4 Adding EaseUS as a sponsor
[ci skip]
2022-08-31 10:46:11 +02:00
Sebastian StenzelandGitHub 86ed0d3c0e Merge pull request #2390 from cryptomator/feature/slf4j-2
Upgrade to SLF4J 2.x and Logback 1.3.x
2022-08-12 09:53:27 +02:00
Sebastian Stenzel 0501b0ef39 fixed test 2022-08-11 18:23:33 +02:00
Sebastian Stenzel 2c2b208de7 no need to reset anything, since there is no previous config 2022-08-11 18:18:48 +02:00
Sebastian Stenzel ec7ad0f479 use new ServiceLoader based log configurator 2022-08-11 18:14:54 +02:00
Sebastian Stenzel 5267a04491 make org.cryptomator.desktop an open module 2022-08-11 17:36:10 +02:00
Sebastian Stenzel 04714cd0de make singleton Environment accessible outside of Dagger graph 2022-08-11 17:35:35 +02:00
Sebastian Stenzel 46dbe15e4a update dependencies 2022-08-11 17:34:34 +02:00
Armin SchrenkandGitHub 7abf167ba2 Merge pull request #2381 from cryptomator/feature/hub-handle-402
handle 402 error when authenticating with Cryptomator Hub
2022-08-09 10:55:41 +02:00
44d19bdefe Apply suggestions from code review
Co-authored-by: Tobias Hagemann <tobias.hagemann@skymatic.de>
2022-08-09 10:37:22 +02:00
Armin Schrenk f84eef5639 actually use the scene in ui flow 2022-08-08 16:37:30 +02:00
Armin Schrenk 281f6f727f show info for 402 response on unlock 2022-08-08 16:33:07 +02:00
Sebastian Stenzel bf83df3cd3 Merge branch 'main' into develop
# Conflicts:
#	pom.xml

[ci skip]
2022-08-05 14:31:59 +02:00
Sebastian Stenzel be4f6f2b9b Merge branch 'hotfix/1.6.13' 2022-08-05 14:31:25 +02:00
Sebastian Stenzel bf84598131 set version to 1.6.13 2022-08-05 14:31:10 +02:00
Sebastian Stenzel 943be4557f fixes #2374 2022-08-05 14:28:47 +02:00
Sebastian Stenzel 649e029d4f added cryptomator.integrationsMac.keychainServiceName system property 2022-08-05 11:09:49 +02:00
Sebastian StenzelandGitHub 1cd3d1d67f Merge pull request #2365 from purejava/fix-2301
Pass on CLI parameters from launch scripts to executables
2022-08-02 08:44:04 +02:00
Ralph Plawetzki 8290df9ea6 Pass on CLI parameters for Cryptomator started as an AppImage or installed from PPA
Fixes #2301
2022-08-01 18:17:41 +02:00
Armin Schrenk 47f2d423b9 Remove code smells 2022-07-28 11:16:59 +02:00
Armin Schrenk 80b5b6af00 simplify app version code 2022-07-28 10:58:50 +02:00
Armin Schrenk 414c4e20b8 remove code smell 2022-07-28 10:47:46 +02:00
Armin Schrenk 11a55f382a fix winget release 2022-07-27 17:16:33 +02:00
Armin Schrenk 0c5021913c Merge branch 'main' into develop 2022-07-27 12:11:13 +02:00
Armin Schrenk 8bc0b3a700 Merge branch 'hotfix/1.6.12' 2022-07-27 12:10:01 +02:00
Armin Schrenk 57110ff417 add release to metainfo 2022-07-27 11:57:45 +02:00
Armin Schrenk d5bdea4a75 fix debian build script
Fixes #2352
2022-07-27 11:57:00 +02:00
Armin Schrenk 72c8ca3368 preparing 1.6.12 2022-07-27 11:55:24 +02:00
Armin Schrenk 2ca824696c Merge branch 'main' into develop 2022-07-26 13:24:11 +02:00
Armin Schrenk 43d8f7c47f Merge branch 'release/1.6.11' 2022-07-26 13:23:54 +02:00
Armin Schrenk 9209e7698c add release info to linux metainfo file 2022-07-26 13:23:31 +02:00
Armin Schrenk 6e75f1fea8 Merge branch 'main' into develop 2022-07-26 13:11:13 +02:00
Armin Schrenk 2bdb0c3e6a Merge branch 'release/1.6.11' 2022-07-26 13:08:59 +02:00
Armin Schrenk 677634930d Fix windows run config [ci skip] 2022-07-26 13:03:43 +02:00
Armin Schrenk 546da7d11f preparing 1.6.11 2022-07-26 12:39:45 +02:00
Armin Schrenk 8662abae5a Merge branch 'feature/hub' into develop
# Conflicts:
#	.idea/runConfigurations/Cryptomator_Windows.xml
#	.idea/runConfigurations/Cryptomator_Windows_Dev.xml
#	dist/win/build.ps1
#	src/main/java/org/cryptomator/common/Environment.java
2022-07-26 12:29:33 +02:00
CryptobotandGitHub f98782ba4e New Crowdin updates (#2313)
New translations strings.properties

Arabic; Belarusian; Bengali; Bosnian; Catalan; Chinese Simplified; Chinese Traditional; Chinese Traditional, Hong Kong; Croatian; Serbian (Cyrillic); Czech; 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; Spanish; Swahili, Tanzania; Swedish; Tamil; Telugu; Thai; Turkish; Ukrainian; Vietnamese; 

[ci skip]
2022-07-26 12:14:32 +02:00
Armin Schrenk 422077ac08 adjust pom 2022-07-20 17:01:29 +02:00
Armin Schrenk 10b9e75972 degenerify button description and rename things in resetPasswordController 2022-07-19 16:36:32 +02:00
Armin Schrenk 00e420a248 Improve add vault wizard:
* adjust ok/not-ok-design to other textfields
* add info, what characters to use
2022-07-19 15:51:58 +02:00
Armin Schrenk f7ddb6e714 Update and deduplicate winfsp download link
Fixes #2117
2022-07-18 14:56:39 +02:00
Armin Schrenk f1c8e3d3f2 Change CatchingExecutor:
* ignore cancellation exception
* remove assertion (Closes #2270)
2022-07-18 12:22:57 +02:00
Armin Schrenk cfc8c7bed7 remove unused instance var 2022-07-18 12:15:48 +02:00
Armin Schrenk 71b876b32d Fixes #1900 2022-07-18 11:42:12 +02:00
Armin Schrenk cdc60abf66 Fixes #2292 2022-07-18 10:23:12 +02:00
Armin Schrenk b73ab4645b only publish to winget, if workflow triggered by release or explicitly stated 2022-07-15 11:14:21 +02:00
Sebastian Stenzel c70961405c updated code sign cert
[ci skip]
2022-07-15 09:52:25 +02:00
d814c1cfca Update integrations-win lib (#2331)
* update to integrations-win 1.1.2

* refactor environment class to reduce maintenance

* fix test

Co-authored-by: Sebastian Stenzel <overheadhunter@users.noreply.github.com>
2022-07-11 15:59:54 +02:00
Armin SchrenkandGitHub 50413ca9d8 Merge pull request #2329 from barimess/update-link
updated links to the latest version.
2022-07-11 13:37:58 +02:00
Sebastian Stenzel 2a91403f2f bumped webdav-nio-adapter to version 1.2.8, containing vulnerability fixes in jetty 2022-07-11 10:09:22 +02:00
barimess 5ec499ff01 updated links to the latest version.
updated links from 1.5 to 1.6
2022-07-08 18:30:49 +01:00
Armin SchrenkandGitHub 86cf0d4d4e Merge pull request #2328 from cryptomator/feature/hub-device-name-taken
Display message when device name is already in use
2022-07-08 13:44:04 +02:00
Sebastian StenzelandGitHub f2af6f355c remove debug step
[ci skip]
2022-07-08 13:09:26 +02:00
Sebastian StenzelandGitHub 58d01b5bf5 fixed errors
[ci skip]
2022-07-08 13:07:30 +02:00
Sebastian StenzelandGitHub 5e19cd409f use different action for getting the gist
[ci skip]
2022-07-08 12:56:02 +02:00
Sebastian StenzelandGitHub 4dd58a664e Update error-db.yml
add discussion data to errorcodes.json gist

[ci skip]
2022-07-08 12:48:50 +02:00
Armin Schrenk a7fc8d6fc4 hide alreadyExistingLabel when deviceName changed by user 2022-07-08 12:03:38 +02:00
Armin Schrenk 5c31a34d5f Show message in register dialog if device name is already in use 2022-07-08 11:33:59 +02:00
Armin SchrenkandGitHub 67264c0d8c Merge pull request #2319 from cryptomator/feature/hub-better-dialogs
Improve Hub dialogs
2022-07-07 20:54:11 +02:00
mindmonkandGitHub 153ccef8a2 Merge pull request #2325 from cryptomator/feature/recoverykey-success-notification
implemented recoverykey reset password success notification screen
2022-07-07 18:12:12 +02:00
Armin Schrenk 0e853b25f7 simplify the prefill of device registration name textbox 2022-07-07 17:54:53 +02:00
Sebastian StenzelandGitHub 12302162b1 Update error-db.yml
[ci skip]
2022-07-07 17:35:48 +02:00
Jan-Peter Klein 7d14b55bcb Merge branch 'develop' into feature/recoverykey-success-notification 2022-07-07 17:23:32 +02:00
Jan-Peter Klein 69fe5b8c27 changed buttonOrder and buttonData in recovery_reset_password.fxml and removed blank line in string.properties 2022-07-07 17:23:10 +02:00
Sebastian StenzelandGitHub df099df334 Create error-db.yml
Eventually append discussion data to error database
2022-07-07 17:13:27 +02:00
Jan-Peter Klein 6bbdb4baa0 Merge branch 'develop' into feature/recoverykey-success-notification 2022-07-07 16:18:26 +02:00
Jan-Peter Klein ea6318dfc4 comments of the PR were taken into account as well as a few small changes in the recoverykey recover dialog 2022-07-07 16:17:41 +02:00
Armin Schrenk 93500a4efd use try-with-resource 2022-07-07 12:27:20 +02:00
Jan-Peter Klein b132bb6412 implemented recoverykey reset password success notification screen 2022-07-07 12:16:04 +02:00
Armin Schrenk cf38a10284 Change text from registration to "naming" and prefill device name 2022-07-07 12:01:34 +02:00
Sebastian Stenzel 15781b685d use new github "inputs" context, where booleans are supposed to actually be booleans
see https://github.com/actions/runner/issues/1483
see https://docs.github.com/en/actions/learn-github-actions/contexts#inputs-context

[ci skip]
2022-07-06 17:09:02 +02:00
Armin Schrenk 73442cddc8 Change register device dialog text to make intent clear 2022-07-06 10:41:13 +02:00
mindmonkandGitHub bce9833929 Merge pull request #2273 from cryptomator/feature/lock-and-quit-without-asking
implemented functionality of feature request issue #1713 "On closing …
2022-07-05 16:46:45 +02:00
Jan-Peter Klein 29d0e258f1 deleted unused and unnecessary code in QuitController 2022-07-05 16:30:32 +02:00
Jan-Peter Klein 1305e45564 Merge branch 'develop' into feature/lock-and-quit-without-asking 2022-07-05 16:18:06 +02:00
Jan-Peter Klein 9e18215d10 removed unused method, del .idea/uiDesigner.xml and code cleanup 2022-07-05 16:17:42 +02:00
Jan-Peter Klein 683276a6c6 switch scene in quitDialog to quitForcedDialog instead of init new window 2022-07-05 14:22:05 +02:00
Sebastian Stenzel f16a30ef29 Merge branch 'develop' into feature/hub
# Conflicts:
#	pom.xml
2022-07-05 13:28:41 +02:00
Sebastian Stenzel 82db3b59fb bump jwt dependency 2022-07-05 13:22:43 +02:00
Armin Schrenk c191df9ee3 add device registration failed controller 2022-07-05 11:20:52 +02:00
Armin Schrenk a2d6db0415 Add device registrations success screen 2022-07-05 11:09:29 +02:00
Armin Schrenk 077825f98a Add desgin to recieve key dialog 2022-07-05 09:29:49 +02:00
Armin Schrenk 381c1cd8d3 Bump tinyoauth2 dependecy and adjust to new api 2022-07-04 17:15:00 +02:00
Armin Schrenk ae2c67a88b fit unauthorized device dialog to new design 2022-07-04 11:50:07 +02:00
Armin Schrenk 3ce0270cde Adjust hub register device to new design 2022-07-04 11:37:49 +02:00
Armin Schrenk e011a98fa0 add window title to hub keyloading 2022-07-04 10:36:15 +02:00
Armin Schrenk 9386804216 redesign auth processing dialog:
* adjust to new design
* add translation keys
2022-07-04 10:33:58 +02:00
Jan-Peter Klein b791df01f7 removed quitforced package, moved QuitForcedController into quit package and deleted not required files 2022-06-30 15:41:33 +02:00
Jan-Peter Klein 376733106b Merge branch 'develop' into feature/lock-and-quit-without-asking
# Conflicts (solved):
#	src/main/resources/i18n/strings.properties
2022-06-30 15:22:57 +02:00
Jan-Peter Klein 8e6d2cf45a Merge branch 'develop' into feature/lock-and-quit-without-asking 2022-06-30 15:13:06 +02:00
Armin Schrenk 63cd3a02de Merge branch 'develop' into feature/hub 2022-06-30 12:13:29 +02:00
Armin SchrenkandGitHub 1795a7aaa4 Merge pull request #2311 from cryptomator/feature/1430-embark-on-unity
Embark on unity.

Closes #1430
2022-06-30 11:07:25 +02:00
Armin Schrenk c8e4ed1ce4 Merge branch 'feature/translations' into feature/1430-embark-on-unity
# Conflicts:
#	src/main/resources/i18n/strings_de.properties
#	src/main/resources/i18n/strings_pl.properties
#	src/main/resources/i18n/strings_pt_BR.properties
#	src/main/resources/i18n/strings_zh_TW.properties
2022-06-30 10:20:44 +02:00
Armin Schrenk 99b25d0792 migrate translations 2022-06-30 10:00:22 +02:00
Jan-Peter Klein e640e36744 show forced dialog if normal lock and quit fails in quit dialog 2022-06-29 15:27:19 +02:00
Jan-Peter Klein d10d8fb208 implemented force quit dialog 2022-06-29 15:12:43 +02:00
Tobias Hagemann 43e6ca41aa more consistency 2022-06-29 11:34:07 +02:00
e2b4566174 Update src/main/resources/i18n/strings.properties
Co-authored-by: Tobias Hagemann <tobias.hagemann@skymatic.de>
2022-06-29 11:15:26 +02:00
d203db8075 Apply suggestions from code review
Co-authored-by: Tobias Hagemann <tobias.hagemann@skymatic.de>
2022-06-29 11:14:14 +02:00
Armin Schrenk 7c4f5b28a7 use correct translation key 2022-06-29 11:10:55 +02:00
Armin Schrenk 7edd1dfa40 Reintegrate forget password dialog into application 2022-06-29 11:10:15 +02:00
Armin Schrenk 09550b77dc Merge branch 'feature/1430-embark-on-unity' of https://github.com/cryptomator/cryptomator into feature/1430-embark-on-unity 2022-06-29 10:54:59 +02:00
3bd43908ac Apply suggestions from code review
Co-authored-by: Tobias Hagemann <tobias.hagemann@skymatic.de>
2022-06-29 10:54:42 +02:00
Armin Schrenk f69efb85a5 more translation adjustments 2022-06-29 10:54:17 +02:00
Armin Schrenk bc5091a0d7 adjust forget password dialog 2022-06-29 10:52:39 +02:00
Armin Schrenk a199a2a06b add ux todo 2022-06-29 10:08:18 +02:00
Armin Schrenk dffd4d9dd9 change translation keys to use unified dialog scheme 2022-06-29 10:07:03 +02:00
Armin Schrenk 737d98775b Revert "rename recovery_create.fxml file"
This reverts commit 78e458f01a.
2022-06-28 16:41:53 +02:00
Armin Schrenk bee9c9f452 closes #1713
quit application by locking open vaults, if user wished so.
2022-06-28 15:39:04 +02:00
Armin Schrenk 2b19fd6852 adjust lock failed dialog 2022-06-28 10:32:47 +02:00
Armin Schrenk a5e9837c99 Adjust design of unlock invalid mp dialog 2022-06-28 10:15:39 +02:00
Armin Schrenk 81a1052a76 cleanup 2022-06-27 19:08:00 +02:00
Armin Schrenk ab2d740722 adjust choose masterkeyfile dialog 2022-06-27 19:04:14 +02:00
Armin Schrenk bf3a62a6b4 Rename translation key for unlock window title 2022-06-27 18:56:53 +02:00
Armin Schrenk d163d02077 Change window title for removeVault dialog 2022-06-27 18:49:26 +02:00
Armin Schrenk 677607d210 adjust remove vault dialog 2022-06-27 18:24:54 +02:00
Armin Schrenk fd3c8d7f78 Adjust design of lock_forced dialog 2022-06-27 18:07:41 +02:00
Armin Schrenk cce74ef5e4 add TODO 2022-06-27 17:52:49 +02:00
Armin Schrenk 74058238ec adjust design of wrong file alert 2022-06-27 17:52:34 +02:00
Armin Schrenk 78e458f01a rename recovery_create.fxml file 2022-06-27 17:26:21 +02:00
Armin Schrenk 07cfef2e41 adjust design of auth for recovery key 2022-06-27 17:21:45 +02:00
Armin Schrenk 35c5bd03de Changed design for unlock success dialog 2022-06-27 16:26:35 +02:00
Jan-Peter Klein 7cc1a1728c removed unused parameter 2022-06-27 14:40:20 +02:00
Cryptobot a79bf1952d New translations strings.properties (Vietnamese)
[ci skip]
2022-06-25 18:41:43 +02:00
Armin Schrenk c7ad587aa2 Changed design for quit screen as template for other dialogs 2022-06-24 15:34:01 +02:00
Armin Schrenk 22b842e6c2 Rename styleclass 2022-06-24 12:27:38 +02:00
Armin Schrenk f96c52cdb1 Disable Password tab if vault.config.keyid.scheme is not masterkeyfile 2022-06-24 10:26:34 +02:00
Cryptobot ca426f6535 New translations strings.properties (Vietnamese)
[ci skip]
2022-06-22 17:10:33 +02:00
Armin Schrenk d6f489ea98 bump dependencies 2022-06-22 12:36:00 +02:00
Armin Schrenk 45f038bae2 Merge branch 'develop' into feature/hub
# Conflicts:
#	dist/mac/dmg/build.sh
#	dist/win/build.ps1
#	pom.xml
2022-06-22 09:55:02 +02:00
Cryptobot 3580728a5a New translations strings.properties (Hindi)
[ci skip]
2022-06-21 12:42:13 +02:00
Cryptobot 749c5d9f24 New translations strings.properties (Vietnamese)
[ci skip]
2022-06-21 00:39:49 +02:00
Cryptobot 6ae872fa85 New translations strings.properties (Vietnamese)
[ci skip]
2022-06-20 21:27:37 +02:00
Cryptobot 773fa9804a New translations strings.properties (Hindi)
[ci skip]
2022-06-20 14:07:39 +02:00
Cryptobot cfa9beec8b New translations strings.properties (Hindi)
[ci skip]
2022-06-20 11:11:16 +02:00
Cryptobot 5f4d13f662 New translations strings.properties (Japanese)
[ci skip]
2022-06-20 04:29:20 +02:00
Cryptobot f0d69b645b New translations strings.properties (Hindi)
[ci skip]
2022-06-19 13:23:39 +02:00
Cryptobot 797724e640 New translations strings.properties (Portuguese, Brazilian)
[ci skip]
2022-06-17 01:12:50 +02:00
Cryptobot 3488db8c0e New translations strings.properties (Vietnamese)
[ci skip]
2022-06-15 21:47:37 +02:00
Cryptobot d53e0b24a4 New translations strings.properties (German)
[ci skip]
2022-06-15 15:40:48 +02:00
Cryptobot 347a7b3ef7 New translations strings.properties (Vietnamese)
[ci skip]
2022-06-15 12:35:10 +02:00
Cryptobot ff80585a2f New translations strings.properties (Portuguese, Brazilian)
[ci skip]
2022-06-15 09:29:10 +02:00
Cryptobot ad7a8835b3 New translations strings.properties (German)
[ci skip]
2022-06-07 20:28:24 +02:00
Cryptobot 9f2b5b6ee3 New translations strings.properties (Croatian)
[ci skip]
2022-06-06 11:29:54 +02:00
Cryptobot 15726c6b21 New translations strings.properties (Indonesian)
[ci skip]
2022-06-03 05:53:25 +02:00
Cryptobot 1c824a37b0 New translations strings.properties (French)
[ci skip]
2022-06-01 12:36:32 +02:00
Cryptobot 7c04ba48a8 New translations strings.properties (Turkish)
[ci skip]
2022-06-01 12:36:24 +02:00
Jan-Peter Klein 6f33cf8df7 removed checkbox in quit prompt 2022-05-31 14:09:12 +02:00
Jan-Peter Klein 8f85777887 implemented functionality of feature request issue #1713 "On closing of the application: Always lock and quit – without asking" 2022-05-31 11:18:20 +02:00
Cryptobot 931d6f0048 New translations strings.properties (Polish)
[ci skip]
2022-05-30 22:16:25 +02:00
Cryptobot 692b3828b8 New translations strings.properties (Sinhala)
[ci skip]
2022-05-28 19:53:25 +02:00
Cryptobot 320be05a66 New translations strings.properties (Sinhala)
[ci skip]
2022-05-28 10:53:36 +02:00
Cryptobot 2c7b407e94 New translations strings.properties (Sinhala)
[ci skip]
2022-05-28 07:54:01 +02:00
Cryptobot 20c66a6005 New translations strings.properties (Sinhala)
[ci skip]
2022-05-27 21:40:27 +02:00
Cryptobot 75633a10bd New translations strings.properties (Czech)
[ci skip]
2022-05-25 13:41:37 +02:00
Cryptobot dda526b83e New translations strings.properties (Ukrainian)
[ci skip]
2022-05-24 00:49:03 +02:00
Cryptobot a7405b4fca New translations strings.properties (Dutch)
[ci skip]
2022-05-21 11:41:58 +02:00
Cryptobot 88020fcaf4 New translations strings.properties (Ukrainian)
[ci skip]
2022-05-19 15:51:15 +02:00
Cryptobot 4e89b6365a New translations strings.properties (Chinese Traditional, Hong Kong)
[ci skip]
2022-05-17 19:47:14 +02:00
Cryptobot 52396f68f5 New translations strings.properties (Chinese Traditional)
[ci skip]
2022-05-17 19:47:13 +02:00
Cryptobot 3c469c80be New translations strings.properties (Chinese Traditional)
[ci skip]
2022-05-17 16:44:35 +02:00
Cryptobot e8751d2116 New translations strings.properties (Swahili, Tanzania)
[ci skip]
2022-05-17 10:51:44 +02:00
Cryptobot 9bc439a913 New translations strings.properties (Indonesian)
[ci skip]
2022-05-16 21:08:12 +02:00
Cryptobot c7b257eb66 New translations strings.properties (Polish)
[ci skip]
2022-05-15 23:47:27 +02:00
Cryptobot 2f76b724a7 New translations strings.properties (Swedish)
[ci skip]
2022-05-14 18:55:21 +02:00
Julian Raufelder 2833015ff1 Merge branch 'hotfix/1.6.10' 2022-05-03 16:18:05 +02:00
Sebastian Stenzel d4b3eff42f restored success/error redirects 2022-04-28 12:59:27 +02:00
Sebastian Stenzel 9f0e269baf Merge branch 'develop' into feature/hub
# Conflicts:
#	pom.xml
#	src/main/resources/license/THIRD-PARTY.txt
2022-04-28 12:55:55 +02:00
Sebastian Stenzel 9d4f9c12b9 outsourced authorization flow to https://github.com/coffeelibs/tiny-oauth2-client 2022-04-25 16:28:54 +02:00
Sebastian Stenzel d1c4eda072 Merge branch 'develop' into feature/hub
# Conflicts:
#	.github/workflows/appimage.yml
#	dist/linux/appimage/build.sh
#	dist/linux/debian/rules
#	dist/mac/dmg/build.sh
#	dist/win/build.ps1
2022-04-25 15:30:56 +02:00
Sebastian Stenzel 2108a77e12 adjusted build scripts for Cryptomator Hub 2022-04-08 20:45:22 +02:00
Sebastian Stenzel f3bfe7b64c Merge branch 'develop' into feature/hub
# Conflicts:
#	.github/workflows/release.yml
#	.idea/runConfigurations/Cryptomator_Linux.xml
#	.idea/runConfigurations/Cryptomator_Linux_Dev.xml
#	.idea/runConfigurations/Cryptomator_Windows.xml
#	.idea/runConfigurations/Cryptomator_Windows_Dev.xml
#	.idea/runConfigurations/Cryptomator_macOS.xml
#	.idea/runConfigurations/Cryptomator_macOS_Dev.xml
#	pom.xml
#	src/main/java/module-info.java
#	src/main/java/org/cryptomator/ui/controls/NiceSecurePasswordField.java
#	src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java
#	src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java
#	src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryController.java
#	src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/SelectMasterkeyFileController.java
#	src/main/resources/license/THIRD-PARTY.txt
2022-04-08 20:27:02 +02:00
Sebastian Stenzel 9f7442c1c0 read clientId from vault config 2022-02-23 12:49:39 +01:00
Sebastian Stenzel 64abbc8205 Merge branch 'develop' into feature/hub
# Conflicts:
#	.github/workflows/release.yml
#	pom.xml
#	src/main/resources/license/THIRD-PARTY.txt
2022-02-18 09:38:25 +01:00
Sebastian Stenzel 165f190004 read device registration url from vault config 2022-02-18 09:13:33 +01:00
Sebastian StenzelandGitHub b142904cc3 Merge pull request #1980 from cryptomator/feature/hub-integrated-device-registration
Register device from within client application
2021-12-16 18:02:00 +01:00
Sebastian Stenzel abc0f952e0 closing the window will cancel implicitly 2021-12-15 17:32:11 +01:00
Sebastian Stenzel e6f43d1f05 no need to run code in ui thread 2021-12-15 17:31:08 +01:00
Sebastian Stenzel 25b9722019 Use CompletableFuture instead of UserInteractionLock + AtomicReference 2021-12-15 17:19:59 +01:00
Sebastian Stenzel 157bef5df6 close dialog after successful device registration (to be improved lol) 2021-12-15 17:19:00 +01:00
Sebastian Stenzel ef281f810f register device via rest api 2021-12-14 17:35:24 +01:00
921dd8fe67 Add query to redirection to provide more context in the frontend (#1973)
Co-authored-by: Sebastian Stenzel <sebastian.stenzel@gmail.com>
2021-12-10 13:42:37 +01:00
Sebastian Stenzel fb580ff79d read masterkey from JWE 2021-12-09 14:03:39 +01:00
Sebastian Stenzel 0110e5bedd added JWE decryption helper 2021-12-09 14:02:39 +01:00
Sebastian Stenzel 3cd99f680a Merge branch 'develop' into feature/hub 2021-11-24 16:23:10 +01:00
Sebastian Stenzel 34e4383c1e adjust release build to support hub features 2021-11-24 14:46:21 +01:00
Tobias Hagemann d4c3f02d8a beautified hub_register_device 2021-11-18 17:02:58 +01:00
Sebastian Stenzel 839175a5e6 Merge branch 'develop' into feature/hub
# Conflicts:
#	pom.xml
2021-11-11 16:04:26 +01:00
Sebastian Stenzel 5924e443fb Merge branch 'develop' into feature/hub
# Conflicts:
#	.idea/runConfigurations/Cryptomator_Linux.xml
#	.idea/runConfigurations/Cryptomator_Linux_Dev.xml
#	.idea/runConfigurations/Cryptomator_Windows.xml
#	.idea/runConfigurations/Cryptomator_Windows_Dev.xml
#	.idea/runConfigurations/Cryptomator_macOS.xml
#	.idea/runConfigurations/Cryptomator_macOS_Dev.xml
#	pom.xml
2021-11-02 15:53:23 +01:00
Sebastian Stenzel 346ce67bc4 add "unauthorized device" scene 2021-08-24 14:43:26 +02:00
Sebastian Stenzel 1477bf07a9 use public key hash as device id 2021-08-24 13:59:51 +02:00
Sebastian Stenzel 056990151a adjusted vault config 2021-08-24 12:39:29 +02:00
Sebastian Stenzel fa86d890fe use application-global device key that requires a system keychain 2021-08-20 17:16:07 +02:00
Sebastian Stenzel 5922743f19 removed dead code
[ci skip]
2021-08-17 16:52:28 +02:00
Sebastian Stenzel afc853f5f5 append device registration params to hub url 2021-08-13 21:41:43 +02:00
Sebastian Stenzel be8243d9d1 cleanup 2021-08-13 21:41:10 +02:00
Sebastian Stenzel 1322b872b6 adjusted to new vault config format and unlock status codes 2021-08-13 19:59:31 +02:00
Sebastian Stenzel e46072c726 Merge branch 'develop' into feature/hub
[ci skip]
2021-08-13 12:13:49 +02:00
Sebastian Stenzel 33ed41307e Merge branch 'develop' into feature/hub 2021-08-12 17:39:25 +02:00
Sebastian Stenzel e865eaf412 added quick & dirty device registration 2021-08-12 17:38:50 +02:00
Sebastian Stenzel e866b64352 fix build 2021-08-12 17:05:59 +02:00
Sebastian Stenzel a3a96496b6 use new auth flow
talking directly to Authorization Server and Resource Server instead of web frontend
2021-08-12 16:18:22 +02:00
Sebastian Stenzel 75644a35ec cleanup
[ci skip]
2021-08-12 12:19:09 +02:00
Sebastian Stenzel ec09413575 renamed controller 2021-08-12 12:18:36 +02:00
Sebastian Stenzel f9c2807ce1 added basic OAuth 2.0 Authorization Code Flow + PKCE impl 2021-08-12 10:54:58 +02:00
Sebastian Stenzel 8075d33d39 fix key/iv extraction from sharedSecret 2021-08-11 16:13:14 +02:00
Sebastian Stenzel d7dcc46988 spec correction
[ci skip]
2021-08-10 13:23:29 +02:00
Sebastian Stenzel 01b2b47823 Switching to P-384 + X9.63 KDF SHA-256 + AES-GCM 2021-08-10 12:36:22 +02:00
Sebastian Stenzel d087a5fdde derive masterkey from received ECIES params 2021-08-09 23:03:36 +02:00
Sebastian Stenzel 43dbdb3e8f prepare local webserver for cross-origin requests 2021-08-09 18:11:44 +02:00
Sebastian Stenzel d938b1c3f7 keep window open while waiting for http callback 2021-07-30 16:55:05 +02:00
Sebastian Stenzel 59eda3159b Merge branch 'develop' into feature/hub 2021-07-30 14:04:07 +02:00
Sebastian Stenzel 7fabc6f52d spawn server listening on localhost, used for oauth redirect_uri 2021-07-29 16:57:28 +02:00
Sebastian Stenzel b21ea61342 add first draft for hub+http / hub+https keyloading scheme 2021-07-28 17:29:44 +02:00
Sebastian Stenzel 2952733a11 add PKCS12 support for on-demand creation and storage of an EC keypair 2021-07-28 17:04:12 +02:00
253 changed files with 8074 additions and 4184 deletions
+6 -5
View File
@@ -10,20 +10,20 @@ on:
required: false
env:
JAVA_VERSION: 17
JAVA_VERSION: 19
jobs:
build:
name: Build AppImage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Java
uses: actions/setup-java@v2
uses: actions/setup-java@v3
with:
distribution: 'temurin'
distribution: 'zulu'
java-version: ${{ env.JAVA_VERSION }}
cache: 'maven'
- id: versions
@@ -60,7 +60,7 @@ jobs:
--verbose
--output runtime
--module-path "${JAVA_HOME}/jmods"
--add-modules java.base,java.desktop,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,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr
--strip-native-commands
--no-header-files
--no-man-pages
@@ -92,6 +92,7 @@ jobs:
--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"
+6 -7
View File
@@ -6,26 +6,25 @@ on:
types: [labeled]
env:
JAVA_VERSION: 17
JAVA_VERSION: 19
defaults:
run:
shell: bash
jobs:
jobs:
test:
name: Compile and Test
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')"
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v2
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
distribution: 'zulu'
java-version: ${{ env.JAVA_VERSION }}
cache: 'maven'
- name: Cache SonarCloud packages
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
+8 -7
View File
@@ -15,24 +15,25 @@ on:
required: false
env:
JAVA_VERSION: 17
JAVA_VERSION: 19
jobs:
build:
name: Build Debian Package
runs-on: ubuntu-18.04
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install build tools
run: |
sudo add-apt-repository ppa:coffeelibs/openjdk
sudo apt-get update
sudo apt-get install debhelper devscripts dput
sudo apt-get install debhelper devscripts dput coffeelibs-jdk-19
- name: Setup Java
uses: actions/setup-java@v2
uses: actions/setup-java@v3
with:
distribution: 'temurin'
distribution: 'zulu'
java-version: ${{ env.JAVA_VERSION }}
cache: 'maven'
- id: versions
@@ -111,7 +112,7 @@ jobs:
cryptomator_*_amd64.deb
cryptomator_*.asc
- name: Publish on PPA
if: startsWith(github.ref, 'refs/tags/') || github.event.inputs.dput == 'true'
if: startsWith(github.ref, 'refs/tags/') || inputs.dput
run: dput ppa:sebastian-stenzel/cryptomator-beta cryptomator_*_source.changes
- name: Publish Debian package on GitHub Releases
if: startsWith(github.ref, 'refs/tags/')
+60
View File
@@ -0,0 +1,60 @@
name: Update Error Database
on:
discussion:
types: [created, edited, category_changed, answered, unanswered]
discussion_comment:
types: [created, edited, deleted]
jobs:
update-error-db:
runs-on: ubuntu-latest
if: github.event.discussion.category.name == 'Errors'
steps:
- name: Query Discussion Data
id: query-data
uses: actions/github-script@v6
with:
script: |
const query = `query ($owner: String!, $name: String!, $discussionNumber: Int!) {
repository(owner: $owner, name: $name) {
discussion(number: $discussionNumber) {
id
upvoteCount
title
url
answer {
url
upvoteCount
}
comments {
totalCount
}
}
}
}`;
const variables = {
owner: context.repo.owner,
name: context.repo.repo,
discussionNumber: context.payload.discussion.number
}
return await github.graphql(query, variables)
- name: Get Gist
id: get-gist
uses: andymckay/get-gist-action@master
with:
gistURL: https://gist.github.com/cryptobot/accba9fb9555e7192271b85606f97230
- name: Merge Error Code Data
run: |
jq -c '.' ${{ steps.get-gist.outputs.file }} > original.json
echo $DISCUSSION | jq -c '.repository.discussion | .comments = .comments.totalCount | {(.id|tostring) : .}' > new.json
jq -s '.[0] * .[1]' original.json new.json > merged.json
env:
DISCUSSION: ${{ steps.query-data.outputs.result }}
- name: Patch Gist
uses: exuanbo/actions-deploy-gist@v1
with:
token: ${{ secrets.CRYPTOBOT_GIST_TOKEN }}
gist_id: accba9fb9555e7192271b85606f97230
gist_file_name: errorcodes.json
file_path: merged.json
+26 -13
View File
@@ -10,23 +10,36 @@ on:
required: false
env:
JAVA_VERSION: 17
JAVA_VERSION: 19
jobs:
build:
name: Build Cryptomator.app
runs-on: macos-11
name: Build Cryptomator.app for ${{ matrix.output-suffix }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: macos-11
architecture: x64
output-suffix: x64
xcode-path: '/Applications/Xcode_13.2.1.app'
- os: [self-hosted, macOS, ARM64]
architecture: aarch64
output-suffix: arm64
xcode-path: '/Applications/Xcode_13.2.1.app'
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Java
uses: actions/setup-java@v2
uses: actions/setup-java@v3
with:
distribution: 'temurin'
distribution: 'zulu'
java-version: ${{ env.JAVA_VERSION }}
architecture: ${{ matrix.architecture }}
cache: 'maven'
- id: versions
- id: versions
name: Apply version information
run: |
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
@@ -60,7 +73,7 @@ jobs:
--verbose
--output runtime
--module-path "${JAVA_HOME}/jmods"
--add-modules java.base,java.desktop,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,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr
--strip-native-commands
--no-header-files
--no-man-pages
@@ -89,6 +102,7 @@ jobs:
--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.showTrayIcon=true"
@@ -185,7 +199,7 @@ jobs:
--icon ".background" 128 758
--icon ".fseventsd" 320 758
--icon ".VolumeIcon.icns" 512 758
Cryptomator-${VERSION_NO}.dmg dmg
Cryptomator-${VERSION_NO}-${{ matrix.output-suffix }}.dmg dmg
env:
VERSION_NO: ${{ steps.versions.outputs.semVerNum }}
- name: Notarize .dmg
@@ -196,8 +210,9 @@ jobs:
apple-id: ${{ secrets.MACOS_NOTARIZATION_APPLE_ID }}
password: ${{ secrets.MACOS_NOTARIZATION_PW }}
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 }}.dmg
run: mv Cryptomator-*.dmg Cryptomator-${{ steps.versions.outputs.semVerStr }}-${{ matrix.output-suffix }}.dmg
- name: Create detached GPG signature with key 615D449FE6E6A235
run: |
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
@@ -212,7 +227,7 @@ jobs:
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: dmg
name: dmg-${{ matrix.output-suffix }}
path: Cryptomator-*.dmg
if-no-files-found: error
- name: Publish dmg on GitHub Releases
@@ -224,5 +239,3 @@ jobs:
files: |
Cryptomator-*.dmg
Cryptomator-*.asc
+4 -4
View File
@@ -4,7 +4,7 @@ on:
pull_request:
env:
JAVA_VERSION: 17
JAVA_VERSION: 19
defaults:
run:
@@ -16,10 +16,10 @@ jobs:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')"
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v2
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
distribution: 'zulu'
java-version: ${{ env.JAVA_VERSION }}
cache: 'maven'
- name: Build and Test
+43
View File
@@ -0,0 +1,43 @@
name: Release Check
on:
push:
branches:
- 'release/**'
- 'hotfix/**'
env:
JAVA_VERSION: 19
defaults:
run:
shell: bash
jobs:
release-check-precondition:
name: Validate commits pushed to release/hotfix branch to fulfill release requirements
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- id: validate-pom-version
name: Validate POM version
run: |
if [[ $GITHUB_REF =~ refs/heads/(hotfix|release)/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
SEM_VER_STR=${GITHUB_REF##*/}
else
echo "Failed to parse version"
exit 1
fi
if [[ ${SEM_VER_STR} == `mvn help:evaluate -Dexpression=project.version -q -DforceStdout` ]]; then
echo "::set-output name=semVerStr::${SEM_VER_STR}"
else
echo "Version not set in POM"
exit 1
fi
- name: Validate release in org.cryptomator.Cryptomator.metainfo.xml file
run: |
if ! grep -q "<release date=\".*\" version=\"${{ steps.validate-pom-version.outputs.semVerStr }}\"/>" dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml; then
echo "Release not set in dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml"
exit 1
fi
+29 -30
View File
@@ -10,8 +10,9 @@ on:
required: false
env:
JAVA_VERSION: 17
WINFSP_MSI: https://github.com/winfsp/winfsp/releases/download/v1.10/winfsp-1.10.22006.msi
JAVA_VERSION: 19
JAVA_DIST: 'zulu'
JAVA_CACHE: 'maven'
defaults:
run:
@@ -22,15 +23,15 @@ jobs:
name: Build .msi Installer
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Java
uses: actions/setup-java@v2
uses: actions/setup-java@v3
with:
distribution: 'temurin'
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
cache: 'maven'
cache: ${{ env.JAVA_CACHE }}
- id: versions
name: Apply version information
run: |
@@ -65,7 +66,7 @@ jobs:
--verbose
--output runtime
--module-path "${JAVA_HOME}/jmods"
--add-modules java.base,java.desktop,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,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr
--strip-native-commands
--no-header-files
--no-man-pages
@@ -92,12 +93,13 @@ jobs:
--java-options "-Dcryptomator.logDir=\"~/AppData/Roaming/Cryptomator\""
--java-options "-Dcryptomator.pluginDir=\"~/AppData/Roaming/Cryptomator/Plugins\""
--java-options "-Dcryptomator.settingsPath=\"~/AppData/Roaming/Cryptomator/settings.json\""
--java-options "-Dcryptomator.p12Path=\"~/AppData/Roaming/Cryptomator/key.p12\""
--java-options "-Dcryptomator.ipcSocketPath=\"~/AppData/Roaming/Cryptomator/ipc.socket\""
--java-options "-Dcryptomator.keychainPath=\"~/AppData/Roaming/Cryptomator/keychain.json\""
--java-options "-Dcryptomator.mountPointsDir=\"~/Cryptomator\""
--java-options "-Dcryptomator.showTrayIcon=true"
--java-options "-Dcryptomator.buildNumber=\"msi-${{ steps.versions.outputs.revNum }}\""
--java-options "-Dcryptomator.integrationsWin.autoStartShellLinkName=\"Cryptomator\""
--java-options "-Dcryptomator.integrationsWin.keychainPaths=\"~/AppData/Roaming/Cryptomator/keychain.json\""
--resource-dir dist/win/resources
--icon dist/win/resources/Cryptomator.ico
- name: Patch Application Directory
@@ -111,7 +113,7 @@ jobs:
with:
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
certificatesha1: FF52240075AD7D14AF25629FDF69635357C7D14B
certificatesha1: 5FC94CE149E5B511E621F53A060AC67CBD446B3A
description: Cryptomator
timestampUrl: 'http://timestamp.digicert.com'
folder: appdir/Cryptomator
@@ -154,7 +156,7 @@ jobs:
with:
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
certificatesha1: FF52240075AD7D14AF25629FDF69635357C7D14B
certificatesha1: 5FC94CE149E5B511E621F53A060AC67CBD446B3A
description: Cryptomator Installer
timestampUrl: 'http://timestamp.digicert.com'
folder: installer
@@ -189,37 +191,32 @@ jobs:
semVerStr: ${{ steps.versions.outputs.semVerStr }}
revNum: ${{ steps.versions.outputs.revNum }}
publish-winget:
name: Publish on winget repo
runs-on: windows-latest
call-winget-flow:
needs: [build-msi]
steps:
- name: Submit package to Windows Package Manager Community Repository
run: |
iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe
$github = Get-Content '${{ github.event_path }}' | ConvertFrom-Json
$installerUrl = $github.release.assets | Where-Object -Property name -match 'Cryptomator-*.msi' | Select -ExpandProperty browser_download_url -First 1
.\wingetcreate.exe update Cryptomator.Cryptomator -s -v $github.release.tag_name -u $installerUrl -t ${{ secrets.CRYPTOBOT_WINGET_TOKEN }}
shell: pwsh
if: github.event.action == 'published'
uses: ./.github/workflows/winget.yml
with:
releaseTag: ${{ github.event.release.tag_name }}
build-exe:
name: Build .exe installer
runs-on: windows-latest
needs: [build-msi]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Download .msi
uses: actions/download-artifact@v2
uses: actions/download-artifact@v3
with:
name: msi
path: dist/win/bundle/resources
- name: Strip version info from msi file name
run: mv dist/win/bundle/resources/Cryptomator*.msi dist/win/bundle/resources/Cryptomator.msi
- uses: actions/setup-java@v2
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
cache: 'maven'
cache: ${{ env.JAVA_CACHE }}
- name: Generate license for exe
run: >
mvn -B license:add-third-party
@@ -232,8 +229,10 @@ jobs:
"-Dlicense.licenseMergesUrl=file:///${{ github.workspace }}/license/merges"
shell: pwsh
- name: Download WinFsp
run:
curl --output dist/win/bundle/resources/winfsp.msi -L ${{ env.WINFSP_MSI }}
run: |
$winfspUrl= (Select-String -Path ".\dist\win\bundle\resources\winfsp-download.url" -Pattern 'https:.*').Matches.Value
curl --output dist/win/bundle/resources/winfsp.msi -L $winfspUrl
shell: pwsh
- name: Compile to wixObj file
run: >
"${WIX}/bin/candle.exe" dist/win/bundle/bundleWithWinfsp.wxs
@@ -260,7 +259,7 @@ jobs:
with:
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
certificatesha1: FF52240075AD7D14AF25629FDF69635357C7D14B
certificatesha1: 5FC94CE149E5B511E621F53A060AC67CBD446B3A
description: Cryptomator Installer
timestampUrl: 'http://timestamp.digicert.com'
folder: tmp
@@ -274,7 +273,7 @@ jobs:
with:
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
certificatesha1: FF52240075AD7D14AF25629FDF69635357C7D14B
certificatesha1: 5FC94CE149E5B511E621F53A060AC67CBD446B3A
description: Cryptomator Installer
timestampUrl: 'http://timestamp.digicert.com'
folder: installer
+49
View File
@@ -0,0 +1,49 @@
name: Release to Winget
on:
workflow_call:
inputs:
releaseTag:
required: true
type: string
workflow_dispatch:
inputs:
releaseTag:
description: 'Release tag name'
required: true
type: string
jobs:
publish-winget:
name: Publish on winget repo
runs-on: windows-latest
steps:
- name: Get download url for msi artifacts
id: get-release-assets
uses: actions/github-script@v6
with:
script: |
const query =`query($tag:String!) {
repository(owner:"cryptomator", name:"cryptomator"){
release(tagName: $tag) {
releaseAssets(first:20) {
nodes {
name
downloadUrl
}
}
}
}
}`;
const variables = {
tag: "${{ inputs.releaseTag }}"
}
return await github.graphql(query, variables)
- name: Submit package to Windows Package Manager Community Repository
id: submit-winget
run: |
iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe
$releaseAssets = (ConvertFrom-Json '${{ steps.get-release-assets.outputs.result }}').repository.release.releaseAssets.nodes
$installerUrl = $releaseAssets | Where-Object -Property name -match '^Cryptomator-.*\.msi$' | Select -ExpandProperty downloadUrl -First 1
.\wingetcreate.exe update Cryptomator.Cryptomator -s -v "${{ inputs.releaseTag }}" -u "$installerUrl" -t ${{ secrets.CRYPTOBOT_WINGET_TOKEN }}
shell: pwsh
+11 -11
View File
@@ -14,10 +14,10 @@
<option name="dagger.fastInit" value="enabled" />
<option name="dagger.formatGeneratedSource" value="enabled" />
<processorPath useClasspath="false">
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.41/dagger-compiler-2.41.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.41/dagger-2.41.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.44/dagger-compiler-2.44.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.44/dagger-2.44.jar" />
<entry name="$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-producers/2.41/dagger-producers-2.41.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-producers/2.44/dagger-producers-2.44.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/guava/guava/31.0.1-jre/guava-31.0.1-jre.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" />
@@ -26,18 +26,18 @@
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.7.1/error_prone_annotations-2.7.1.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3.jar" />
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-compat-qual/2.5.5/checker-compat-qual-2.5.5.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.41/dagger-spi-2.41.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/devtools/ksp/symbol-processing-api/1.5.30-1.0.0/symbol-processing-api-1.5.30-1.0.0.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.5.32/kotlin-stdlib-jdk8-1.5.32.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.5.32/kotlin-stdlib-1.5.32.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.44/dagger-spi-2.44.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/devtools/ksp/symbol-processing-api/1.7.0-1.0.6/symbol-processing-api-1.7.0-1.0.6.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.7.0/kotlin-stdlib-1.7.0.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.7.0/kotlin-stdlib-common-1.7.0.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.5.32/kotlin-stdlib-common-1.5.32.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.5.32/kotlin-stdlib-jdk7-1.5.32.jar" />
<entry name="$MAVEN_REPOSITORY$/com/squareup/javapoet/1.13.0/javapoet-1.13.0.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.7.0/kotlin-stdlib-jdk8-1.7.0.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.7.0/kotlin-stdlib-jdk7-1.7.0.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/googlejavaformat/google-java-format/1.5/google-java-format-1.5.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/javac-shaded/9-dev-r4023-3/javac-shaded-9-dev-r4023-3.jar" />
<entry name="$MAVEN_REPOSITORY$/net/ltgt/gradle/incap/incap/0.2/incap-0.2.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-metadata-jvm/0.3.0/kotlinx-metadata-jvm-0.3.0.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-metadata-jvm/0.5.0/kotlinx-metadata-jvm-0.5.0.jar" />
</processorPath>
<module name="cryptomator" />
</profile>
@@ -45,7 +45,7 @@
</component>
<component name="JavacSettings">
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
<module name="cryptomator" options="-Adagger.fastInit=enabled -Adagger.formatGeneratedSource=enabled" />
<module name="cryptomator" options="-Adagger.fastInit=enabled -Adagger.formatGeneratedSource=enabled --enable-preview" />
</option>
</component>
</project>
+1 -1
View File
@@ -8,7 +8,7 @@
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="17" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_19" default="true" project-jdk-name="19" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
+1 -1
View File
@@ -2,7 +2,7 @@
<configuration default="false" name="Cryptomator Linux" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;~/.config/Cryptomator/settings.json&quot; -Dcryptomator.ipcSocketPath=&quot;~/.config/Cryptomator/ipc.socket&quot; -Dcryptomator.logDir=&quot;~/.local/share/Cryptomator/logs&quot; -Dcryptomator.pluginDir=&quot;~/.local/share/Cryptomator/plugins&quot; -Dcryptomator.mountPointsDir=&quot;~/.local/share/Cryptomator/mnt&quot; -Dcryptomator.showTrayIcon=true -Xss20m -Xmx512m" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;~/.config/Cryptomator/settings.json&quot; -Dcryptomator.p12Path=&quot;~/.config/Cryptomator/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;~/.config/Cryptomator/ipc.socket&quot; -Dcryptomator.logDir=&quot;~/.local/share/Cryptomator/logs&quot; -Dcryptomator.pluginDir=&quot;~/.local/share/Cryptomator/plugins&quot; -Dcryptomator.mountPointsDir=&quot;~/.local/share/Cryptomator/mnt&quot; -Dcryptomator.showTrayIcon=true -Xss20m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64" />
<method v="2">
<option name="Make" enabled="true" />
</method>
+1 -1
View File
@@ -2,7 +2,7 @@
<configuration default="false" name="Cryptomator Linux Dev" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;~/.config/Cryptomator-Dev/settings.json&quot; -Dcryptomator.ipcSocketPath=&quot;~/.config/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;~/.local/share/Cryptomator-Dev/logs&quot; -Dcryptomator.pluginDir=&quot;~/.local/share/Cryptomator-Dev/plugins&quot; -Dcryptomator.mountPointsDir=&quot;~/.local/share/Cryptomator-Dev/mnt&quot; -Dcryptomator.showTrayIcon=true -Dfuse.experimental=&quot;true&quot; -Xss20m -Xmx512m" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;~/.config/Cryptomator-Dev/settings.json&quot; -Dcryptomator.p12Path=&quot;~/.config/Cryptomator-Dev/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;~/.config/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;~/.local/share/Cryptomator-Dev/logs&quot; -Dcryptomator.pluginDir=&quot;~/.local/share/Cryptomator-Dev/plugins&quot; -Dcryptomator.mountPointsDir=&quot;~/.local/share/Cryptomator-Dev/mnt&quot; -Dcryptomator.showTrayIcon=true -Dfuse.experimental=&quot;true&quot; -Xss20m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64" />
<method v="2">
<option name="Make" enabled="true" />
</method>
+1 -1
View File
@@ -2,7 +2,7 @@
<configuration default="false" name="Cryptomator Windows" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;~/AppData/Roaming/Cryptomator/settings.json&quot; -Dcryptomator.ipcSocketPath=&quot;~/AppData/Roaming/Cryptomator/ipc.socket&quot; -Dcryptomator.logDir=&quot;~/AppData/Roaming/Cryptomator&quot; -Dcryptomator.pluginDir=&quot;~/AppData/Roaming/Cryptomator/Plugins&quot; -Dcryptomator.keychainPath=&quot;~/AppData/Roaming/Cryptomator/keychain.json&quot; -Dcryptomator.mountPointsDir=&quot;~/Cryptomator&quot; -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;~/AppData/Roaming/Cryptomator/settings.json&quot; -Dcryptomator.ipcSocketPath=&quot;~/AppData/Roaming/Cryptomator/ipc.socket&quot; -Dcryptomator.logDir=&quot;~/AppData/Roaming/Cryptomator&quot; -Dcryptomator.pluginDir=&quot;~/AppData/Roaming/Cryptomator/Plugins&quot; -Dcryptomator.integrationsWin.keychainPaths=&quot;~/AppData/Roaming/Cryptomator/keychain.json&quot; -Dcryptomator.p12Path=&quot;~/AppData/Roaming/Cryptomator/key.p12&quot; -Dcryptomator.mountPointsDir=&quot;~/Cryptomator&quot; -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.win" />
<method v="2">
<option name="Make" enabled="true" />
</method>
+1 -1
View File
@@ -2,7 +2,7 @@
<configuration default="false" name="Cryptomator Windows Dev" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;~/AppData/Roaming/Cryptomator-Dev/settings.json&quot; -Dcryptomator.ipcSocketPath=&quot;~/AppData/Roaming/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;~/AppData/Roaming/Cryptomator-Dev&quot; -Dcryptomator.pluginDir=&quot;~/AppData/Roaming/Cryptomator-Dev/Plugins&quot; -Dcryptomator.keychainPath=&quot;~/AppData/Roaming/Cryptomator-Dev/keychain.json&quot; -Dcryptomator.mountPointsDir=&quot;~/Cryptomator-Dev&quot; -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;~/AppData/Roaming/Cryptomator-Dev/settings.json&quot; -Dcryptomator.ipcSocketPath=&quot;~/AppData/Roaming/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;~/AppData/Roaming/Cryptomator-Dev&quot; -Dcryptomator.pluginDir=&quot;~/AppData/Roaming/Cryptomator-Dev/Plugins&quot; -Dcryptomator.integrationsWin.keychainPaths=&quot;~/AppData/Roaming/Cryptomator-Dev/keychain.json&quot; -Dcryptomator.p12Path=&quot;~/AppData/Roaming/Cryptomator-Dev/key.p12&quot; -Dcryptomator.mountPointsDir=&quot;~/Cryptomator-Dev&quot; -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.win" />
<method v="2">
<option name="Make" enabled="true" />
</method>
+1 -1
View File
@@ -5,7 +5,7 @@
</envs>
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dapple.awt.enableTemplateImages=true -Dcryptomator.settingsPath=&quot;~/Library/Application Support/Cryptomator/settings.json&quot; -Dcryptomator.ipcSocketPath=&quot;~/Library/Application Support/Cryptomator/ipc.socket&quot; -Dcryptomator.logDir=&quot;~/Library/Logs/Cryptomator&quot; -Dcryptomator.pluginDir=&quot;~/Library/Application Support/Cryptomator/Plugins&quot; -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m -ea" />
<option name="VM_PARAMETERS" value="-Dapple.awt.enableTemplateImages=true -Dcryptomator.settingsPath=&quot;~/Library/Application Support/Cryptomator/settings.json&quot; -Dcryptomator.p12Path=&quot;~/Library/Application Support/Cryptomator/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;~/Library/Application Support/Cryptomator/ipc.socket&quot; -Dcryptomator.logDir=&quot;~/Library/Logs/Cryptomator&quot; -Dcryptomator.pluginDir=&quot;~/Library/Application Support/Cryptomator/Plugins&quot; -Dcryptomator.mountPointsDir=&quot;~/Cryptomator&quot; -Dcryptomator.showTrayIcon=true -Dcryptomator.integrationsMac.keychainServiceName=Cryptomator -Xss2m -Xmx512m -ea --enable-preview --enable-native-access=org.cryptomator.jfuse.mac" />
<method v="2">
<option name="Make" enabled="true" />
</method>
+1 -1
View File
@@ -5,7 +5,7 @@
</envs>
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dapple.awt.enableTemplateImages=true -Dcryptomator.settingsPath=&quot;~/Library/Application Support/Cryptomator-Dev/settings.json&quot; -Dcryptomator.ipcSocketPath=&quot;~/Library/Application Support/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;~/Library/Logs/Cryptomator-Dev&quot; -Dcryptomator.pluginDir=&quot;~/Library/Application Support/Cryptomator-Dev/Plugins&quot; -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m -ea" />
<option name="VM_PARAMETERS" value="-Dapple.awt.enableTemplateImages=true -Dcryptomator.settingsPath=&quot;~/Library/Application Support/Cryptomator-Dev/settings.json&quot; -Dcryptomator.p12Path=&quot;~/Library/Application Support/Cryptomator-Dev/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;~/Library/Application Support/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;~/Library/Logs/Cryptomator-Dev&quot; -Dcryptomator.pluginDir=&quot;~/Library/Application Support/Cryptomator-Dev/Plugins&quot; -Dcryptomator.mountPointsDir=&quot;~/Cryptomator&quot; -Dcryptomator.showTrayIcon=true -Dcryptomator.integrationsMac.keychainServiceName=Cryptomator -Xss2m -Xmx512m -ea --enable-preview --enable-native-access=org.cryptomator.jfuse.mac" />
<method v="2">
<option name="Make" enabled="true" />
</method>
+8 -1
View File
@@ -32,10 +32,17 @@ Cryptomator is provided free of charge as an open-source project despite the hig
<tbody>
<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>
</tr>
</tbody>
</table>
### Special Shoutout
Continuous integration hosting for ARM64 builds is provided by [MacStadium](https://www.macstadium.com/opensource).
<a href="https://www.macstadium.com/opensource"><img src="https://uploads-ssl.webflow.com/5ac3c046c82724970fc60918/5c019d917bba312af7553b49_MacStadium-developerlogo.png" alt="MacStadium" height="100"></a>
---
## Introduction
@@ -85,7 +92,7 @@ For more information on the security details visit [cryptomator.org](https://doc
```
mvn clean install
# or mvn clean install -Pwindows
# or mvn clean install -Pwin
# or mvn clean install -Pmac
# or mvn clean install -Plinux
```
+1 -1
View File
@@ -19,7 +19,7 @@ cp ../../../target/cryptomator-*.jar ../../../target/mods
${JAVA_HOME}/bin/jlink \
--output runtime \
--module-path "${JAVA_HOME}/jmods" \
--add-modules java.base,java.desktop,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,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \
--strip-native-commands \
--no-header-files \
--no-man-pages \
+2 -2
View File
@@ -19,7 +19,7 @@ fi
export LD_PRELOAD=lib/app/libjffi.so
if [ "$GTK2_PRESENT" -eq 0 ] && [ "$GTK3_PRESENT" -ne 0 ]; then
bin/Cryptomator-gtk2
bin/Cryptomator-gtk2 $@
else
bin/Cryptomator
bin/Cryptomator $@
fi
@@ -66,6 +66,10 @@
</content_rating>
<releases>
<release date="2022-10-06" version="1.6.15"/>
<release date="2022-08-31" version="1.6.14"/>
<release date="2022-07-27" version="1.6.12"/>
<release date="2022-07-26" version="1.6.11"/>
<release date="2022-05-03" version="1.6.10"/>
<release date="2022-04-27" version="1.6.9"/>
<release date="2022-03-30" version="1.6.8"/>
+1 -1
View File
@@ -1,4 +1,4 @@
cryptomator (${PPA_VERSION}) bionic; urgency=low
cryptomator (${PPA_VERSION}) focal; urgency=low
* Full changelog can be found on https://github.com/cryptomator/cryptomator/releases
+1 -1
View File
@@ -2,7 +2,7 @@ Source: cryptomator
Maintainer: Cryptobot <releases@cryptomator.org>
Section: utils
Priority: optional
Build-Depends: debhelper (>=10), openjdk-17-jdk
Build-Depends: debhelper (>=10), coffeelibs-jdk-19
Standards-Version: 4.5.0
Homepage: https://cryptomator.org
Vcs-Git: https://github.com/cryptomator/cryptomator.git
+1 -1
View File
@@ -3,4 +3,4 @@
# 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
/usr/lib/cryptomator/bin/cryptomator $@
+6 -3
View File
@@ -4,6 +4,8 @@
# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1
JAVA_HOME = /usr/lib/jvm/java-19-coffeelibs
%:
dh $@
@@ -16,15 +18,15 @@ override_dh_auto_clean:
override_dh_auto_build:
mkdir resources
ln -s ../common/org.cryptomator.Cryptomator512.png resources/cryptomator.png
jlink \
$(JAVA_HOME)/bin/jlink \
--output runtime \
--add-modules java.base,java.desktop,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,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \
--strip-native-commands \
--no-header-files \
--no-man-pages \
--strip-debug \
--compress=2
jpackage \
$(JAVA_HOME)/bin/jpackage \
--type app-image \
--runtime-image runtime \
--input libs \
@@ -40,6 +42,7 @@ override_dh_auto_build:
--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" \
+2 -1
View File
@@ -45,7 +45,7 @@ cp ../../../target/${MAIN_JAR_GLOB} ../../../target/mods
${JAVA_HOME}/bin/jlink \
--output runtime \
--module-path "${JAVA_HOME}/jmods" \
--add-modules java.base,java.desktop,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,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \
--strip-native-commands \
--no-header-files \
--no-man-pages \
@@ -75,6 +75,7 @@ ${JAVA_HOME}/bin/jpackage \
--java-options "-Dcryptomator.pluginDir=\"~/Library/Application Support/${APP_NAME}/Plugins\"" \
--java-options "-Dcryptomator.settingsPath=\"~/Library/Application Support/${APP_NAME}/settings.json\"" \
--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.showTrayIcon=true" \
--java-options "-Dcryptomator.buildNumber=\"dmg-${REVISION_NO}\"" \
+4 -3
View File
@@ -50,7 +50,7 @@ if ($clean -and (Test-Path -Path $runtimeImagePath)) {
--verbose `
--output runtime `
--module-path "$Env:JAVA_HOME/jmods" `
--add-modules java.base,java.desktop,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,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr `
--strip-native-commands `
--no-header-files `
--no-man-pages `
@@ -83,9 +83,10 @@ if ($clean -and (Test-Path -Path $appPath)) {
--java-options "-Dcryptomator.pluginDir=`"~/AppData/Roaming/$AppName/Plugins`"" `
--java-options "-Dcryptomator.settingsPath=`"~/AppData/Roaming/$AppName/settings.json`"" `
--java-options "-Dcryptomator.ipcSocketPath=`"~/AppData/Roaming/$AppName/ipc.socket`"" `
--java-options "-Dcryptomator.keychainPath=`"~/AppData/Roaming/$AppName/keychain.json`"" `
--java-options "-Dcryptomator.p12Path=`"~/AppData/Roaming/$AppName/key.p12`"" `
--java-options "-Dcryptomator.mountPointsDir=`"~/$AppName`"" `
--java-options "-Dcryptomator.integrationsWin.autoStartShellLinkName=`"$AppName`"" `
--java-options "-Dcryptomator.integrationsWin.keychainPaths=`"~/AppData/Roaming/$AppName/keychain.json`"" `
--java-options "-Dcryptomator.showTrayIcon=true" `
--java-options "-Dcryptomator.buildNumber=`"msi-$revisionNo`"" `
--resource-dir resources `
@@ -140,7 +141,7 @@ $Env:JP_WIXWIZARD_RESOURCES = "$buildDir\resources"
# download Winfsp
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$ProgressPreference = 'SilentlyContinue' # disables Invoke-WebRequest's progress bar, which slows down downloads to a few bytes/s
$winfspMsiUrl = "https://github.com/winfsp/winfsp/releases/download/v1.10/winfsp-1.10.22006.msi"
$winfspMsiUrl= (Select-String -Path ".\bundle\resources\winfsp-download.url" -Pattern 'https:.*').Matches.Value
Write-Output "Downloading ${winfspMsiUrl}..."
Invoke-WebRequest $winfspMsiUrl -OutFile ".\bundle\resources\winfsp.msi" # redirects are followed by default
+2
View File
@@ -0,0 +1,2 @@
[InternetShortcut]
URL=https://github.com/winfsp/winfsp/releases/download/v1.12/winfsp-1.12.22301.msi
+45 -25
View File
@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId>
<artifactId>cryptomator</artifactId>
<version>1.7.0-SNAPSHOT</version>
<version>1.7.0</version>
<name>Cryptomator Desktop App</name>
<organization>
@@ -21,46 +21,55 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.jdk.version>17</project.jdk.version>
<project.jdk.version>19</project.jdk.version>
<!-- Group IDs of jars that need to stay on the class path for now -->
<nonModularGroupIds>com.github.serceman,com.github.jnr,org.ow2.asm,net.java.dev.jna,org.apache.jackrabbit,org.apache.httpcomponents,de.swiesend,org.purejava,com.github.hypfvieh</nonModularGroupIds>
<!-- 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>
<!-- cryptomator dependencies -->
<cryptomator.cryptofs.version>2.4.2</cryptomator.cryptofs.version>
<cryptomator.integrations.version>1.1.0</cryptomator.integrations.version>
<cryptomator.integrations.win.version>1.1.1</cryptomator.integrations.win.version>
<cryptomator.integrations.mac.version>1.1.1</cryptomator.integrations.mac.version>
<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>1.3.4</cryptomator.fuse.version>
<cryptomator.dokany.version>1.3.3</cryptomator.dokany.version>
<cryptomator.webdav.version>1.2.7</cryptomator.webdav.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>
<!-- 3rd party dependencies -->
<javafx.version>18.0.1</javafx.version>
<commons-lang3.version>3.12.0</commons-lang3.version>
<jwt.version>3.19.1</jwt.version>
<dagger.version>2.44</dagger.version>
<easybind.version>2.2</easybind.version>
<guava.version>31.1-jre</guava.version>
<dagger.version>2.41</dagger.version>
<gson.version>2.9.0</gson.version>
<gson.version>2.9.1</gson.version>
<javafx.version>19</javafx.version>
<jwt.version>4.2.1</jwt.version>
<nimbus-jose.version>9.25.4</nimbus-jose.version>
<logback.version>1.4.4</logback.version>
<slf4j.version>2.0.3</slf4j.version>
<tinyoauth2.version>0.5.1</tinyoauth2.version>
<zxcvbn.version>1.7.0</zxcvbn.version>
<slf4j.version>1.7.36</slf4j.version>
<logback.version>1.2.11</logback.version>
<!-- test dependencies -->
<junit.jupiter.version>5.8.1</junit.jupiter.version>
<mockito.version>4.4.0</mockito.version>
<junit.jupiter.version>5.9.1</junit.jupiter.version>
<mockito.version>4.8.0</mockito.version>
<hamcrest.version>2.2</hamcrest.version>
<!-- build-time dependencies -->
<jetbrains.annotations.version>23.0.0</jetbrains.annotations.version>
<dependency-check.version>7.1.0</dependency-check.version>
<jacoco.version>0.8.7</jacoco.version>
<dependency-check.version>7.2.1</dependency-check.version>
<jacoco.version>0.8.8</jacoco.version>
</properties>
<dependencies>
<!-- Cryptomator Libs -->
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>cryptolib</artifactId>
<version>${cryptomator.cryptolib.version}</version>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>cryptofs</artifactId>
@@ -133,12 +142,22 @@
<version>${commons-lang3.version}</version>
</dependency>
<!-- JWT -->
<!-- OAuth/JWT -->
<dependency>
<groupId>io.github.coffeelibs</groupId>
<artifactId>tiny-oauth2-client</artifactId>
<version>${tinyoauth2.version}</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>${jwt.version}</version>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>${nimbus-jose.version}</version>
</dependency>
<!-- EasyBind -->
<dependency>
@@ -245,7 +264,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.2.0</version>
<version>3.3.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@@ -255,7 +274,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<version>3.0.0-M7</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
@@ -265,7 +284,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.2</version>
<version>3.3.0</version>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
@@ -295,13 +314,14 @@
<compilerArgs>
<arg>-Adagger.fastInit=enabled</arg>
<arg>-Adagger.formatGeneratedSource=enabled</arg>
<arg>--enable-preview</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.0.0</version>
<version>3.1.0</version>
<executions>
<execution>
<id>compile-light-theme</id>
+15 -33
View File
@@ -1,60 +1,42 @@
import ch.qos.logback.classic.spi.Configurator;
import org.cryptomator.integrations.tray.TrayMenuController;
import org.cryptomator.logging.LogbackConfiguratorFactory;
import org.cryptomator.ui.traymenu.AwtTrayMenuController;
module org.cryptomator.desktop {
open module org.cryptomator.desktop {
requires static org.jetbrains.annotations;
requires org.cryptomator.cryptolib;
requires org.cryptomator.cryptofs;
requires org.cryptomator.frontend.dokany;
requires org.cryptomator.frontend.fuse;
requires org.cryptomator.frontend.webdav;
requires org.cryptomator.integrations.api;
// jdk:
requires java.desktop;
requires java.net.http;
requires javafx.base;
requires javafx.graphics;
requires javafx.controls;
requires javafx.fxml;
requires com.tobiasdiez.easybind;
requires jdk.crypto.ec;
// 3rd party:
requires ch.qos.logback.classic;
requires ch.qos.logback.core;
requires com.auth0.jwt;
requires com.google.common;
requires com.google.gson;
requires com.nimbusds.jose.jwt;
requires com.nulabinc.zxcvbn;
requires com.tobiasdiez.easybind;
requires dagger;
requires io.github.coffeelibs.tinyoauth2client;
requires org.slf4j;
requires org.apache.commons.lang3;
requires dagger;
requires com.auth0.jwt;
/* TODO: filename-based modules: */
requires static javax.inject; /* ugly dagger/guava crap */
requires logback.classic;
requires logback.core;
exports org.cryptomator.ui.traymenu to org.cryptomator.integrations.api;
provides TrayMenuController with AwtTrayMenuController;
opens org.cryptomator.common.settings to com.google.gson;
opens org.cryptomator.launcher to javafx.graphics;
opens org.cryptomator.common to javafx.fxml;
opens org.cryptomator.common.vaults to javafx.fxml;
opens org.cryptomator.ui.addvaultwizard to javafx.fxml;
opens org.cryptomator.ui.changepassword to javafx.fxml;
opens org.cryptomator.ui.common to javafx.fxml;
opens org.cryptomator.ui.controls to javafx.fxml;
opens org.cryptomator.ui.forgetPassword to javafx.fxml;
opens org.cryptomator.ui.fxapp to javafx.fxml;
opens org.cryptomator.ui.health to javafx.fxml;
opens org.cryptomator.ui.keyloading.masterkeyfile to javafx.fxml;
opens org.cryptomator.ui.lock to javafx.fxml;
opens org.cryptomator.ui.mainwindow to javafx.fxml;
opens org.cryptomator.ui.migration to javafx.fxml;
opens org.cryptomator.ui.preferences to javafx.fxml;
opens org.cryptomator.ui.quit to javafx.fxml;
opens org.cryptomator.ui.recoverykey to javafx.fxml;
opens org.cryptomator.ui.removevault to javafx.fxml;
opens org.cryptomator.ui.stats to javafx.fxml;
opens org.cryptomator.ui.unlock to javafx.fxml;
opens org.cryptomator.ui.vaultoptions to javafx.fxml;
opens org.cryptomator.ui.wrongfilealert to javafx.fxml;
provides Configurator with LogbackConfiguratorFactory;
}
@@ -10,6 +10,7 @@ import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
@@ -28,6 +29,18 @@ public final class CatchingExecutors {
super(corePoolSize, threadFactory);
}
@Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
Runnable oneShot = () -> this.execute(command);
return super.scheduleAtFixedRate(oneShot, initialDelay, period, unit);
}
@Override
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
Runnable oneShot = () -> this.execute(command);
return super.scheduleWithFixedDelay(oneShot, initialDelay, delay, unit);
}
@Override
protected void afterExecute(Runnable runnable, Throwable throwable) {
super.afterExecute(runnable, throwable);
@@ -77,11 +90,16 @@ public final class CatchingExecutors {
}
private static void afterExecuteFuture(Future<?> future) {
assert future.isDone();
if (future instanceof ScheduledFuture<?> && !future.isDone()) {
//we assume that this must be a repeated ScheduledFutureTask, where the done-status is only set when not executed anymore
//see also https://github.com/cryptomator/cryptomator/pull/2422
return;
}
try {
future.get();
} catch (CancellationException ce) {
callHandler(Thread.currentThread(), ce);
//Ignore
} catch (ExecutionException ee) {
callHandler(Thread.currentThread(), ee.getCause());
} catch (InterruptedException ie) {
@@ -10,19 +10,18 @@ import dagger.Module;
import dagger.Provides;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.keychain.KeychainModule;
import org.cryptomator.common.mount.MountModule;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.SettingsProvider;
import org.cryptomator.common.vaults.VaultComponent;
import org.cryptomator.common.vaults.VaultListModule;
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
import org.cryptomator.frontend.webdav.WebDavServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Named;
import javax.inject.Singleton;
import javafx.beans.binding.Binding;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ObservableValue;
import java.net.InetSocketAddress;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
@@ -33,7 +32,7 @@ import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@Module(subcomponents = {VaultComponent.class}, includes = {VaultListModule.class, KeychainModule.class})
@Module(subcomponents = {VaultComponent.class}, includes = {VaultListModule.class, KeychainModule.class, MountModule.class})
public abstract class CommonsModule {
private static final Logger LOG = LoggerFactory.getLogger(CommonsModule.class);
@@ -41,6 +40,12 @@ public abstract class CommonsModule {
private static final int NUM_CORE_BG_THREADS = 6;
private static final long BG_THREAD_KEEPALIVE_SECONDS = 60l;
@Provides
@Singleton
static Environment provideEnvironment() {
return Environment.getInstance();
}
@SuppressWarnings("SpellCheckingInspection")
@Provides
@Singleton
@@ -124,20 +129,11 @@ public abstract class CommonsModule {
@Provides
@Singleton
static Binding<InetSocketAddress> provideServerSocketAddressBinding(Settings settings) {
return Bindings.createObjectBinding(() -> {
static ObservableValue<InetSocketAddress> provideServerSocketAddressBinding(Settings settings) {
return settings.port().map(port -> {
String host = SystemUtils.IS_OS_WINDOWS ? "127.0.0.1" : "localhost";
return InetSocketAddress.createUnresolved(host, settings.port().intValue());
}, settings.port());
}
@Provides
@Singleton
static WebDavServer provideWebDavServer(Binding<InetSocketAddress> serverSocketAddressBinding) {
WebDavServer server = WebDavServer.create();
// no need to unsubscribe eventually, because server is a singleton
EasyBind.subscribe(serverSocketAddressBinding, server::bind);
return server;
});
}
}
@@ -5,8 +5,6 @@ import com.google.common.base.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -17,31 +15,55 @@ import java.util.function.Predicate;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
@Singleton
public class Environment {
private static final Logger LOG = LoggerFactory.getLogger(Environment.class);
private static final Path RELATIVE_HOME_DIR = Paths.get("~");
private static final char PATH_LIST_SEP = ':';
private static final int DEFAULT_MIN_PW_LENGTH = 8;
private static final String SETTINGS_PATH_PROP_NAME = "cryptomator.settingsPath";
private static final String IPC_SOCKET_PATH_PROP_NAME = "cryptomator.ipcSocketPath";
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 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";
private static final String BUILD_NUMBER_PROP_NAME = "cryptomator.buildNumber";
private static final String PLUGIN_DIR_PROP_NAME = "cryptomator.pluginDir";
private static final String TRAY_ICON_PROP_NAME = "cryptomator.showTrayIcon";
@Inject
public Environment() {
LOG.debug("user.home: {}", System.getProperty("user.home"));
LOG.debug("java.library.path: {}", System.getProperty("java.library.path"));
LOG.debug("user.language: {}", System.getProperty("user.language"));
LOG.debug("user.region: {}", System.getProperty("user.region"));
LOG.debug("logback.configurationFile: {}", System.getProperty("logback.configurationFile"));
LOG.debug("cryptomator.settingsPath: {}", System.getProperty("cryptomator.settingsPath"));
LOG.debug("cryptomator.ipcSocketPath: {}", System.getProperty("cryptomator.ipcSocketPath"));
LOG.debug("cryptomator.keychainPath: {}", System.getProperty("cryptomator.keychainPath"));
LOG.debug("cryptomator.logDir: {}", System.getProperty("cryptomator.logDir"));
LOG.debug("cryptomator.pluginDir: {}", System.getProperty("cryptomator.pluginDir"));
LOG.debug("cryptomator.mountPointsDir: {}", System.getProperty("cryptomator.mountPointsDir"));
LOG.debug("cryptomator.minPwLength: {}", System.getProperty("cryptomator.minPwLength"));
LOG.debug("cryptomator.appVersion: {}", System.getProperty("cryptomator.appVersion"));
LOG.debug("cryptomator.buildNumber: {}", System.getProperty("cryptomator.buildNumber"));
LOG.debug("cryptomator.showTrayIcon: {}", System.getProperty("cryptomator.showTrayIcon"));
private Environment() {}
public void log() {
LOG.info("user.home: {}", System.getProperty("user.home"));
LOG.info("java.library.path: {}", System.getProperty("java.library.path"));
LOG.info("user.language: {}", System.getProperty("user.language"));
LOG.info("user.region: {}", System.getProperty("user.region"));
LOG.info("logback.configurationFile: {}", System.getProperty("logback.configurationFile"));
logCryptomatorSystemProperty(SETTINGS_PATH_PROP_NAME);
logCryptomatorSystemProperty(IPC_SOCKET_PATH_PROP_NAME);
logCryptomatorSystemProperty(KEYCHAIN_PATHS_PROP_NAME);
logCryptomatorSystemProperty(LOG_DIR_PROP_NAME);
logCryptomatorSystemProperty(PLUGIN_DIR_PROP_NAME);
logCryptomatorSystemProperty(MOUNTPOINT_DIR_PROP_NAME);
logCryptomatorSystemProperty(MIN_PW_LENGTH_PROP_NAME);
logCryptomatorSystemProperty(APP_VERSION_PROP_NAME);
logCryptomatorSystemProperty(BUILD_NUMBER_PROP_NAME);
logCryptomatorSystemProperty(TRAY_ICON_PROP_NAME);
logCryptomatorSystemProperty(P12_PATH_PROP_NAME);
}
public static Environment getInstance() {
final class Holder {
private static final Environment INSTANCE = new Environment();
}
return Holder.INSTANCE;
}
private void logCryptomatorSystemProperty(String propertyName) {
LOG.info("{}: {}", propertyName, System.getProperty(propertyName));
}
public boolean useCustomLogbackConfig() {
@@ -49,43 +71,52 @@ public class Environment {
}
public Stream<Path> getSettingsPath() {
return getPaths("cryptomator.settingsPath");
return getPaths(SETTINGS_PATH_PROP_NAME);
}
public Stream<Path> getP12Path() {
return getPaths(P12_PATH_PROP_NAME);
}
public Stream<Path> ipcSocketPath() {
return getPaths("cryptomator.ipcSocketPath");
return getPaths(IPC_SOCKET_PATH_PROP_NAME);
}
public Stream<Path> getKeychainPath() {
return getPaths("cryptomator.keychainPath");
return getPaths(KEYCHAIN_PATHS_PROP_NAME);
}
public Optional<Path> getLogDir() {
return getPath("cryptomator.logDir").map(this::replaceHomeDir);
return getPath(LOG_DIR_PROP_NAME).map(this::replaceHomeDir);
}
public Optional<Path> getPluginDir() {
return getPath("cryptomator.pluginDir").map(this::replaceHomeDir);
return getPath(PLUGIN_DIR_PROP_NAME).map(this::replaceHomeDir);
}
public Optional<Path> getMountPointsDir() {
return getPath("cryptomator.mountPointsDir").map(this::replaceHomeDir);
return getPath(MOUNTPOINT_DIR_PROP_NAME).map(this::replaceHomeDir);
}
public Optional<String> getAppVersion() {
return Optional.ofNullable(System.getProperty("cryptomator.appVersion"));
/**
* Returns the app version defined in the {@value APP_VERSION_PROP_NAME} property or returns "SNAPSHOT".
*
* @return App version or "SNAPSHOT", if undefined
*/
public String getAppVersion() {
return System.getProperty(APP_VERSION_PROP_NAME, "SNAPSHOT");
}
public Optional<String> getBuildNumber() {
return Optional.ofNullable(System.getProperty("cryptomator.buildNumber"));
return Optional.ofNullable(System.getProperty(BUILD_NUMBER_PROP_NAME));
}
public int getMinPwLength() {
return getInt("cryptomator.minPwLength", DEFAULT_MIN_PW_LENGTH);
return getInt(MIN_PW_LENGTH_PROP_NAME, DEFAULT_MIN_PW_LENGTH);
}
public boolean showTrayIcon() {
return Boolean.getBoolean("cryptomator.showTrayIcon");
return Boolean.getBoolean(TRAY_ICON_PROP_NAME);
}
private int getInt(String propertyName, int defaultValue) {
@@ -10,6 +10,7 @@ import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import java.util.Optional;
@Singleton
@@ -18,7 +19,7 @@ public class LicenseHolder {
private final Settings settings;
private final LicenseChecker licenseChecker;
private final ObjectProperty<DecodedJWT> validJwtClaims;
private final StringBinding licenseSubject;
private final ObservableValue<String> licenseSubject;
private final BooleanBinding validLicenseProperty;
@Inject
@@ -26,7 +27,7 @@ public class LicenseHolder {
this.settings = settings;
this.licenseChecker = licenseChecker;
this.validJwtClaims = new SimpleObjectProperty<>();
this.licenseSubject = Bindings.createStringBinding(this::getLicenseSubject, validJwtClaims);
this.licenseSubject = validJwtClaims.map(DecodedJWT::getSubject);
this.validLicenseProperty = validJwtClaims.isNotNull();
Optional<DecodedJWT> claims = licenseChecker.check(settings.licenseKey().get());
@@ -55,17 +56,12 @@ public class LicenseHolder {
}
}
public StringBinding licenseSubjectProperty() {
public ObservableValue<String> licenseSubjectProperty() {
return licenseSubject;
}
public String getLicenseSubject() {
DecodedJWT claims = validJwtClaims.get();
if (claims != null) {
return claims.getSubject();
} else {
return null;
}
return licenseSubject.getValue();
}
public BooleanBinding validLicenseProperty() {
@@ -23,11 +23,14 @@ public class KeychainModule {
@Singleton
static ObjectExpression<KeychainAccessProvider> provideKeychainAccessProvider(Settings settings, List<KeychainAccessProvider> providers) {
return Bindings.createObjectBinding(() -> {
if (!settings.useKeychain().get()) {
return null;
}
var selectedProviderClass = settings.keychainProvider().get();
var selectedProvider = providers.stream().filter(provider -> provider.getClass().getName().equals(selectedProviderClass)).findAny();
var fallbackProvider = providers.stream().findFirst().orElse(null);
return selectedProvider.orElse(fallbackProvider);
}, settings.keychainProvider());
}, settings.keychainProvider(), settings.useKeychain());
}
}
@@ -0,0 +1,30 @@
package org.cryptomator.common.mount;
import dagger.Module;
import dagger.Provides;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.integrations.mount.MountService;
import javax.inject.Singleton;
import javafx.beans.value.ObservableValue;
import java.util.List;
@Module
public class MountModule {
@Provides
@Singleton
static List<MountService> provideSupportedMountServices() {
return MountService.get().toList();
}
@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);
});
}
}
@@ -0,0 +1,70 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.common.mount;
import com.google.common.collect.Sets;
import org.apache.commons.lang3.SystemUtils;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.StreamSupport;
@Singleton
public final class WindowsDriveLetters {
private static final Set<Path> A_TO_Z;
static {
var sortedSet = new TreeSet<Path>();
IntStream.rangeClosed('A', 'Z').mapToObj(i -> Path.of((char) i + ":\\")).forEach(sortedSet::add);
A_TO_Z = Collections.unmodifiableSet(sortedSet);
}
@Inject
public WindowsDriveLetters() {
}
public Set<Path> getAll() {
return A_TO_Z;
}
public Set<Path> getOccupied() {
if (!SystemUtils.IS_OS_WINDOWS) {
return Set.of();
} else {
Iterable<Path> rootDirs = FileSystems.getDefault().getRootDirectories();
return StreamSupport.stream(rootDirs.spliterator(), false).collect(Collectors.toUnmodifiableSet());
}
}
public Set<Path> getAvailable() {
return Sets.difference(getAll(), getOccupied());
}
/**
* Skips A and B and only returns them if all others are occupied.
*
* @return an Optional containing either the letter of a free drive letter or empty, if none is available
*/
public Optional<Path> getFirstDesiredAvailable() {
var availableDriveLetters = getAvailable();
var optString = availableDriveLetters.stream().filter(this::notAOrB).findFirst();
return optString.or(() -> availableDriveLetters.stream().findFirst());
}
private boolean notAOrB(Path driveLetter) {
return !(Path.of("A:\\").equals(driveLetter) || Path.of("B:\\").equals(driveLetter));
}
}
@@ -1,29 +0,0 @@
package org.cryptomator.common.mountpoint;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.vaults.Volume;
import org.cryptomator.common.vaults.WindowsDriveLetters;
import javax.inject.Inject;
import java.nio.file.Path;
import java.util.Optional;
class AvailableDriveLetterChooser implements MountPointChooser {
private final WindowsDriveLetters windowsDriveLetters;
@Inject
public AvailableDriveLetterChooser(WindowsDriveLetters windowsDriveLetters) {
this.windowsDriveLetters = windowsDriveLetters;
}
@Override
public boolean isApplicable(Volume caller) {
return SystemUtils.IS_OS_WINDOWS;
}
@Override
public Optional<Path> chooseMountPoint(Volume caller) {
return this.windowsDriveLetters.getDesiredAvailableDriveLetterPath();
}
}
@@ -1,42 +0,0 @@
package org.cryptomator.common.mountpoint;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.common.vaults.Volume;
import javax.inject.Inject;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
class CustomDriveLetterChooser implements MountPointChooser {
private final VaultSettings vaultSettings;
@Inject
public CustomDriveLetterChooser(VaultSettings vaultSettings) {
this.vaultSettings = vaultSettings;
}
@Override
public boolean isApplicable(Volume caller) {
return SystemUtils.IS_OS_WINDOWS;
}
@Override
public Optional<Path> chooseMountPoint(Volume caller) {
return this.vaultSettings.getWinDriveLetter().map(letter -> letter.charAt(0) + ":\\").map(Paths::get);
}
@Override
public boolean prepare(Volume caller, Path driveLetter) throws InvalidMountPointException {
if (!Files.notExists(driveLetter, LinkOption.NOFOLLOW_LINKS)) {
//Drive already exists OR can't be determined
throw new InvalidMountPointException(new FileAlreadyExistsException(driveLetter.toString()));
}
return false;
}
}
@@ -1,150 +0,0 @@
package org.cryptomator.common.mountpoint;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Environment;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.common.settings.VolumeImpl;
import org.cryptomator.common.vaults.MountPointRequirement;
import org.cryptomator.common.vaults.Volume;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
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;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Optional;
class CustomMountPointChooser implements MountPointChooser {
private static final String HIDEAWAY_PREFIX = ".~$";
private static final String HIDEAWAY_SUFFIX = ".tmp";
private static final String WIN_HIDDEN = "dos:hidden";
private static final Logger LOG = LoggerFactory.getLogger(CustomMountPointChooser.class);
private final VaultSettings vaultSettings;
@Inject
public CustomMountPointChooser(VaultSettings vaultSettings) {
this.vaultSettings = vaultSettings;
}
@Override
public boolean isApplicable(Volume caller) {
return caller.getImplementationType() != VolumeImpl.WEBDAV;
}
@Override
public Optional<Path> chooseMountPoint(Volume caller) {
//VaultSettings#getCustomMountPath already checks whether the saved custom mountpoint should be used
return this.vaultSettings.getCustomMountPath().map(Paths::get);
}
@Override
public boolean prepare(Volume caller, Path mountPoint) throws InvalidMountPointException {
return switch (caller.getMountPointRequirement()) {
case PARENT_NO_MOUNT_POINT -> {
prepareParentNoMountPoint(mountPoint);
LOG.debug("Successfully checked custom mount point: {}", mountPoint);
yield true;
}
case EMPTY_MOUNT_POINT -> {
prepareEmptyMountPoint(mountPoint);
LOG.debug("Successfully checked custom mount point: {}", mountPoint);
yield false;
}
case NONE, UNUSED_ROOT_DIR, PARENT_OPT_MOUNT_POINT -> {
throw new InvalidMountPointException(new IllegalStateException("Illegal MountPointRequirement"));
}
};
}
//This is case on Windows when using FUSE
//See https://github.com/billziss-gh/winfsp/issues/320
void prepareParentNoMountPoint(Path mountPoint) throws InvalidMountPointException {
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 InvalidMountPointException(new FileAlreadyExistsException(hideaway.toString()));
} else if (!mpExists && !hideExists) { //neither mountpoint nor hideaway exist
throw new InvalidMountPointException(new NoSuchFileException(mountPoint.toString()));
} else if (!mpExists) { //only hideaway exists
checkIsDirectory(hideaway);
LOG.info("Mountpoint {} for winfsp mount seems to be not properly cleaned up. Will be fixed on unmount.", mountPoint);
try {
if (SystemUtils.IS_OS_WINDOWS) {
Files.setAttribute(hideaway, WIN_HIDDEN, true, LinkOption.NOFOLLOW_LINKS);
}
} catch (IOException e) {
throw new InvalidMountPointException(e);
}
} else { //only mountpoint exists
try {
checkIsDirectory(mountPoint);
checkIsEmpty(mountPoint);
Files.move(mountPoint, hideaway);
if (SystemUtils.IS_OS_WINDOWS) {
Files.setAttribute(hideaway, WIN_HIDDEN, true, LinkOption.NOFOLLOW_LINKS);
}
} catch (IOException e) {
throw new InvalidMountPointException(e);
}
}
}
private void prepareEmptyMountPoint(Path mountPoint) throws InvalidMountPointException {
//This is the case for Windows when using Dokany and for Linux and Mac
checkIsDirectory(mountPoint);
try {
checkIsEmpty(mountPoint);
} catch (IOException exception) {
throw new InvalidMountPointException("IOException while checking folder content", exception);
}
}
@Override
public void cleanup(Volume caller, Path mountPoint) {
if (caller.getMountPointRequirement() == MountPointRequirement.PARENT_NO_MOUNT_POINT) {
Path hideaway = getHideaway(mountPoint);
try {
Files.move(hideaway, mountPoint);
if (SystemUtils.IS_OS_WINDOWS) {
Files.setAttribute(mountPoint, WIN_HIDDEN, false);
}
} catch (IOException e) {
LOG.error("Unable to clean up mountpoint {} for Winfsp mounting.", mountPoint, e);
}
}
}
private void checkIsDirectory(Path toCheck) throws InvalidMountPointException {
if (!Files.isDirectory(toCheck, LinkOption.NOFOLLOW_LINKS)) {
throw new InvalidMountPointException(new NotDirectoryException(toCheck.toString()));
}
}
private void checkIsEmpty(Path toCheck) throws InvalidMountPointException, IOException {
try (var dirStream = Files.list(toCheck)) {
if (dirStream.findFirst().isPresent()) {
throw new InvalidMountPointException(new DirectoryNotEmptyException(toCheck.toString()));
}
}
}
//visible for testing
Path getHideaway(Path mountPoint) {
return mountPoint.resolveSibling(HIDEAWAY_PREFIX + mountPoint.getFileName().toString() + HIDEAWAY_SUFFIX);
}
}
@@ -1,16 +0,0 @@
package org.cryptomator.common.mountpoint;
public class InvalidMountPointException extends Exception {
public InvalidMountPointException(String message) {
super(message);
}
public InvalidMountPointException(Throwable cause) {
super(cause);
}
public InvalidMountPointException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -1,42 +0,0 @@
package org.cryptomator.common.mountpoint;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.common.vaults.Volume;
import javax.inject.Inject;
import java.nio.file.Path;
import java.util.Optional;
class MacVolumeMountChooser implements MountPointChooser {
private static final Path VOLUME_PATH = Path.of("/Volumes");
private final VaultSettings vaultSettings;
private final MountPointHelper helper;
@Inject
public MacVolumeMountChooser(VaultSettings vaultSettings, MountPointHelper helper) {
this.vaultSettings = vaultSettings;
this.helper = helper;
}
@Override
public boolean isApplicable(Volume caller) {
return SystemUtils.IS_OS_MAC;
}
@Override
public Optional<Path> chooseMountPoint(Volume caller) {
return Optional.of(helper.chooseTemporaryMountPoint(vaultSettings, VOLUME_PATH));
}
@Override
public boolean prepare(Volume caller, Path mountPoint) {
// https://github.com/osxfuse/osxfuse/issues/306#issuecomment-245114592:
// In order to allow non-admin users to mount FUSE volumes in `/Volumes`,
// starting with version 3.5.0, FUSE will create non-existent mount points automatically.
// Therefore we don't need to prepare anything.
return false;
}
}
@@ -1,138 +0,0 @@
package org.cryptomator.common.mountpoint;
import dagger.multibindings.IntKey;
import org.cryptomator.common.vaults.Volume;
import java.nio.file.Path;
import java.util.Optional;
import java.util.SortedSet;
/**
* Base interface for the Mountpoint-Choosing-Operation that results in the choice and
* preparation of a mountpoint or an exception otherwise.<br>
* <p>All <i>MountPointChoosers (MPCs)</i> need to implement this class and must be added to
* the pool of possible MPCs by the {@link MountPointChooserModule MountPointChooserModule.}
* The MountPointChooserModule will sort them according to their {@link IntKey IntKey priority.}
* The priority must be defined by the developer to reflect a useful execution order.<br>
* A specific priority <b>must not</b> be assigned to more than one MPC at a time;
* the result of having two MPCs with equal priority is undefined.
*
* <p>MPCs are executed by a {@link Volume} in descending order of their priority
* (higher priorities are tried first) to find and prepare a suitable mountpoint for the volume.
* The volume has access to a {@link SortedSet} of MPCs in this specific order,
* that is provided by the Module. The Set contains all available Choosers, even if they
* are not {@link #isApplicable(Volume) applicable} for the Vault/Volume. The Volume must
* check whether a MPC is applicable by invoking {@code #isApplicable(Volume)} on it
* <i>before</i> calling {@code #chooseMountPoint(Volume)}.
*
* <p>At execution of a MPC {@link #chooseMountPoint(Volume)} is called to choose a mountpoint
* according to the MPC's <i>strategy.</i> The <i>strategy</i> can involve reading configs,
* searching the filesystem, processing user-input or similar operations.
* If {@code #chooseMountPoint(Volume)} returns a non-null path (everything but
* {@linkplain Optional#empty()}) the MPC's {@link #prepare(Volume, Path)} method is called and the
* MountPoint is verified and/or prepared. In this case <i>no other MPC's will be called for
* this volume, even if {@code #prepare(Volume, Path)} fails.</i>
*
* <p>If {@code #chooseMountPoint(Volume)} yields no result, the next MPC is executed
* <i>without</i> first calling the {@code #prepare(Volume, Path)} method of the current MPC.
* This is repeated until<br>
* <ul>
* <li><b>either</b> a mountpoint is returned by {@code #chooseMountPoint(Volume)}
* and {@code #prepare(Volume, Path)} succeeds or fails, ending the entire operation</li>
* <li><b>or</b> no MPC remains and an {@link InvalidMountPointException} is thrown.</li>
* </ul>
* If the {@code #prepare(Volume, Path)} method of a MPC fails, the entire
* Mountpoint-Choosing-Operation is aborted and the method should do all necessary cleanup
* before throwing the exception.
* If the preparation succeeds {@link #cleanup(Volume, Path)} can be used after unmount to do any
* remaining cleanup.
*/
public interface MountPointChooser {
/**
* Called by the {@link Volume} to determine whether this MountPointChooser is
* applicable for mounting the Vault/Volume, especially with regard to the
* current system configuration and particularities of the Volume type.
*
* <p>Developers should override this method to check for system configurations
* that are unsuitable for this MPC.
*
* @param caller The Volume that is calling the method to determine applicability of the MPC
* @return a boolean flag; true if applicable, else false.
* @see #chooseMountPoint(Volume)
*/
boolean isApplicable(Volume caller);
/**
* Called by a {@link Volume} to choose a mountpoint according to the
* MountPointChoosers strategy.
*
* <p>This method must only be called for MPCs that were deemed
* {@link #isApplicable(Volume) applicable} by the {@link Volume Volume.}
* Developers should override this method to find or extract a mountpoint for
* the volume <b>without</b> preparing it. Preparation should be done by
* {@link #prepare(Volume, Path)} instead.
* Exceptions in this method should be handled gracefully and result in returning
* {@link Optional#empty()} instead of throwing an exception.
*
* @param caller The Volume that is calling the method to choose a mountpoint
* @return the chosen path or {@link Optional#empty()} if an exception occurred
* or no mountpoint could be found.
* @see #isApplicable(Volume)
* @see #prepare(Volume, Path)
*/
Optional<Path> chooseMountPoint(Volume caller);
/**
* Called by a {@link Volume} to prepare and/or verify the chosen mountpoint.<br>
* This method is only called if the {@link #chooseMountPoint(Volume)} method
* of the same MountPointChooser returned a path.
*
* <p>Developers should override this method to prepare the mountpoint for
* the volume and check for any obstacles that could hinder the mount operation.
* The mountpoint is deemed "prepared" if it can be used to mount a volume
* without any further filesystem actions or user interaction. If this is not possible,
* this method should fail. In other words: This method should not return without
* either failing or finalizing the preparation of the mountpoint.
* Generally speaking exceptions should be wrapped as
* {@link InvalidMountPointException} to allow efficient handling by the caller.
*
* <p>Often the preparation of a mountpoint involves creating files or others
* actions that require cleaning up after the volume is unmounted.
* In this case developers should override the {@link #cleanup(Volume, Path)}
* method and return {@code true} to the volume to indicate that the
* {@code #cleanup} method of this MPC should be called after unmount.
*
* <p><b>Please note:</b> If this method fails the entire
* Mountpoint-Choosing-Operation is aborted without calling
* {@link #cleanup(Volume, Path)} or any other MPCs. Therefore this method should
* do all necessary cleanup before throwing the exception.
*
* @param caller The Volume that is calling the method to prepare a mountpoint
* @param mountPoint the mountpoint chosen by {@link #chooseMountPoint(Volume)}
* @return a boolean flag; true if cleanup is needed, false otherwise
* @throws InvalidMountPointException if the preparation fails
* @see #chooseMountPoint(Volume)
* @see #cleanup(Volume, Path)
*/
default boolean prepare(Volume caller, Path mountPoint) throws InvalidMountPointException {
return false; //NO-OP
}
/**
* Called by a {@link Volume} to do any cleanup needed after unmount.
*
* <p>This method is only called if the {@link #prepare(Volume, Path)} method
* of the same MountPointChooser returned {@code true}. Typically developers want to
* delete any files created prior to mount or do similar tasks.<br>
* Exceptions in this method should be handled gracefully.
*
* @param caller The Volume that is calling the method to cleanup the prepared mountpoint
* @param mountPoint the mountpoint that was prepared by {@link #prepare(Volume, Path)}
* @see #prepare(Volume, Path)
*/
default void cleanup(Volume caller, Path mountPoint) {
//NO-OP
}
}
@@ -1,64 +0,0 @@
package org.cryptomator.common.mountpoint;
import com.google.common.collect.Iterables;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntKey;
import dagger.multibindings.IntoMap;
import org.cryptomator.common.vaults.PerVault;
import javax.inject.Named;
import java.util.Comparator;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* Dagger-Module for {@link MountPointChooser MountPointChoosers.}<br>
* See there for additional information.
*
* @see MountPointChooser
*/
@Module
public abstract class MountPointChooserModule {
@Binds
@IntoMap
@IntKey(1000)
@PerVault
public abstract MountPointChooser bindCustomMountPointChooser(CustomMountPointChooser chooser);
@Binds
@IntoMap
@IntKey(900)
@PerVault
public abstract MountPointChooser bindCustomDriveLetterChooser(CustomDriveLetterChooser chooser);
@Binds
@IntoMap
@IntKey(800)
@PerVault
public abstract MountPointChooser bindAvailableDriveLetterChooser(AvailableDriveLetterChooser chooser);
@Binds
@IntoMap
@IntKey(101)
@PerVault
public abstract MountPointChooser bindMacVolumeMountChooser(MacVolumeMountChooser chooser);
@Binds
@IntoMap
@IntKey(100)
@PerVault
public abstract MountPointChooser bindTemporaryMountPointChooser(TemporaryMountPointChooser chooser);
@Provides
@PerVault
@Named("orderedMountPointChoosers")
public static Iterable<MountPointChooser> provideOrderedMountPointChoosers(Map<Integer, MountPointChooser> choosers) {
SortedMap<Integer, MountPointChooser> sortedChoosers = new TreeMap<>(Comparator.reverseOrder());
sortedChoosers.putAll(choosers);
return Iterables.unmodifiableIterable(sortedChoosers.values());
}
}
@@ -1,122 +0,0 @@
package org.cryptomator.common.mountpoint;
import org.cryptomator.common.Environment;
import org.cryptomator.common.settings.VaultSettings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.File;
import java.io.IOException;
import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Optional;
@Singleton
class MountPointHelper {
public static Logger LOG = LoggerFactory.getLogger(MountPointHelper.class);
private static final int MAX_TMPMOUNTPOINT_CREATION_RETRIES = 10;
private final Optional<Path> tmpMountPointDir;
private volatile boolean unmountDebrisCleared = false;
@Inject
public MountPointHelper(Environment env) {
this.tmpMountPointDir = env.getMountPointsDir();
}
public Path chooseTemporaryMountPoint(VaultSettings vaultSettings, Path parentDir) {
String basename = vaultSettings.mountName().get();
//regular
Path mountPoint = parentDir.resolve(basename);
if (Files.notExists(mountPoint)) {
return mountPoint;
}
//with id
mountPoint = parentDir.resolve(basename + " (" + vaultSettings.getId() + ")");
if (Files.notExists(mountPoint)) {
return mountPoint;
}
//with id and count
for (int i = 1; i < MAX_TMPMOUNTPOINT_CREATION_RETRIES; i++) {
mountPoint = parentDir.resolve(basename + "_(" + vaultSettings.getId() + ")_" + i);
if (Files.notExists(mountPoint)) {
return mountPoint;
}
}
LOG.error("Failed to find feasible mountpoint at {}{}{}_x. Giving up after {} attempts.", parentDir, File.separator, basename, MAX_TMPMOUNTPOINT_CREATION_RETRIES);
return null;
}
public synchronized void clearIrregularUnmountDebrisIfNeeded() {
if (unmountDebrisCleared || tmpMountPointDir.isEmpty()) {
return; // nothing to do
}
if (Files.exists(tmpMountPointDir.get(), LinkOption.NOFOLLOW_LINKS)) {
clearIrregularUnmountDebris(tmpMountPointDir.get());
}
unmountDebrisCleared = true;
}
private void clearIrregularUnmountDebris(Path dirContainingMountPoints) {
IOException cleanupFailed = new IOException("Cleanup failed");
try (var ds = Files.newDirectoryStream(dirContainingMountPoints)) {
LOG.debug("Performing cleanup of mountpoint dir {}.", dirContainingMountPoints);
for (Path p : ds) {
try {
var attr = Files.readAttributes(p, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
if (attr.isOther() && attr.isDirectory()) { // yes, this is possible with windows junction points -.-
Files.delete(p);
} else if (attr.isDirectory()) {
deleteEmptyDir(p);
} else if (attr.isSymbolicLink()) {
deleteDeadLink(p);
} else {
LOG.debug("Found non-directory element in mountpoint dir: {}", p);
}
} catch (IOException e) {
cleanupFailed.addSuppressed(e);
}
}
if (cleanupFailed.getSuppressed().length > 0) {
throw cleanupFailed;
}
} catch (IOException e) {
LOG.warn("Unable to perform cleanup of mountpoint dir {}.", dirContainingMountPoints, e);
} finally {
unmountDebrisCleared = true;
}
}
private void deleteEmptyDir(Path dir) throws IOException {
assert Files.isDirectory(dir, LinkOption.NOFOLLOW_LINKS);
try {
ensureIsEmpty(dir);
Files.delete(dir); // attempt to delete dir non-recursively (will fail, if there are contents)
} catch (DirectoryNotEmptyException e) {
LOG.info("Found non-empty directory in mountpoint dir: {}", dir);
}
}
private void deleteDeadLink(Path symlink) throws IOException {
assert Files.isSymbolicLink(symlink);
if (Files.notExists(symlink)) { // following link: target does not exist
Files.delete(symlink);
}
}
private void ensureIsEmpty(Path dir) throws IOException {
try (var ds = Files.newDirectoryStream(dir)) {
if (ds.iterator().hasNext()){
throw new DirectoryNotEmptyException(dir.toString());
}
}
}
}
@@ -1,87 +0,0 @@
package org.cryptomator.common.mountpoint;
import org.cryptomator.common.Environment;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.common.vaults.Volume;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
class TemporaryMountPointChooser implements MountPointChooser {
private static final Logger LOG = LoggerFactory.getLogger(TemporaryMountPointChooser.class);
private final VaultSettings vaultSettings;
private final Environment environment;
private final MountPointHelper helper;
@Inject
public TemporaryMountPointChooser(VaultSettings vaultSettings, Environment environment, MountPointHelper helper) {
this.vaultSettings = vaultSettings;
this.environment = environment;
this.helper = helper;
}
@Override
public boolean isApplicable(Volume caller) {
if (this.environment.getMountPointsDir().isEmpty()) {
LOG.warn("\"cryptomator.mountPointsDir\" is not set to a valid path!");
return false;
}
return true;
}
@Override
public Optional<Path> chooseMountPoint(Volume caller) {
assert environment.getMountPointsDir().isPresent();
//clean leftovers of not-regularly unmounted vaults
//see https://github.com/cryptomator/cryptomator/issues/1013 and https://github.com/cryptomator/cryptomator/issues/1061
helper.clearIrregularUnmountDebrisIfNeeded();
return this.environment.getMountPointsDir().map(dir -> this.helper.chooseTemporaryMountPoint(this.vaultSettings, dir));
}
@Override
public boolean prepare(Volume caller, Path mountPoint) throws InvalidMountPointException {
try {
switch (caller.getMountPointRequirement()) {
case PARENT_NO_MOUNT_POINT -> {
Files.createDirectories(mountPoint.getParent());
LOG.debug("Successfully created folder for mount point: {}", mountPoint);
return false;
}
case EMPTY_MOUNT_POINT -> {
Files.createDirectories(mountPoint);
LOG.debug("Successfully created mount point: {}", mountPoint);
return true;
}
case NONE -> {
//Requirement "NONE" doesn't make any sense here.
//No need to prepare/verify a Mountpoint without requiring one...
throw new InvalidMountPointException(new IllegalStateException("Illegal MountPointRequirement"));
}
default -> {
//Currently the case for "UNUSED_ROOT_DIR, PARENT_OPT_MOUNT_POINT"
throw new InvalidMountPointException(new IllegalStateException("Not implemented"));
}
}
} catch (IOException exception) {
throw new InvalidMountPointException("IOException while preparing mountpoint", exception);
}
}
@Override
public void cleanup(Volume caller, Path mountPoint) {
try {
Files.delete(mountPoint);
LOG.debug("Successfully deleted mount point: {}", mountPoint);
} catch (IOException e) {
LOG.warn("Could not delete mount point: {}", e.getMessage());
}
}
}
@@ -0,0 +1,99 @@
package org.cryptomator.common.settings;
import com.google.common.base.Preconditions;
import com.google.common.base.Suppliers;
import org.cryptomator.common.Environment;
import org.cryptomator.common.keychain.KeychainManager;
import org.cryptomator.cryptolib.common.P384KeyPair;
import org.cryptomator.cryptolib.common.Pkcs12Exception;
import org.cryptomator.integrations.keychain.KeychainAccessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.IOException;
import java.nio.CharBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.UUID;
import java.util.function.Supplier;
@Singleton
public class DeviceKey {
private static final Logger LOG = LoggerFactory.getLogger(DeviceKey.class);
private static final String KEYCHAIN_KEY = "cryptomator-device-p12";
private static final String KEYCHAIN_DISPLAY_NAME = "Cryptomator Device Keypair .p12 Passphrase";
private final KeychainManager keychainManager;
private final Environment env;
private final SecureRandom csprng;
private final Supplier<P384KeyPair> keyPairSupplier;
@Inject
public DeviceKey(KeychainManager keychainManager, Environment env, SecureRandom csprng) {
this.keychainManager = keychainManager;
this.env = env;
this.csprng = csprng;
this.keyPairSupplier = Suppliers.memoize(this::loadOrCreate);
}
public P384KeyPair get() throws DeviceKeyRetrievalException {
Preconditions.checkState(keychainManager.isSupported());
return keyPairSupplier.get();
}
private P384KeyPair loadOrCreate() throws DeviceKeyRetrievalException {
Path p12File = env.getP12Path().findFirst().orElseThrow(() -> new DeviceKeyRetrievalException("No path for .p12 file configured"));
char[] passphrase = null;
try {
passphrase = keychainManager.loadPassphrase(KEYCHAIN_KEY);
if (passphrase != null && Files.isRegularFile(p12File)) {
return loadExistingKeyPair(passphrase, p12File);
} else { // (re)generate new key pair if either file or password got lost
passphrase = randomPassword();
keychainManager.storePassphrase(KEYCHAIN_KEY, KEYCHAIN_DISPLAY_NAME, CharBuffer.wrap(passphrase));
return createAndStoreNewKeyPair(passphrase, p12File);
}
} catch (KeychainAccessException e) {
throw new DeviceKeyRetrievalException("Failed to access system keychain", e);
} catch (Pkcs12Exception | IOException e) {
throw new DeviceKeyRetrievalException("Failed to access .p12 file", e);
} finally {
if (passphrase != null) {
Arrays.fill(passphrase, '\0');
}
}
}
private P384KeyPair loadExistingKeyPair(char[] passphrase, Path p12File) throws IOException {
LOG.debug("Loading existing device key from {}", p12File);
return P384KeyPair.load(p12File, passphrase);
}
private P384KeyPair createAndStoreNewKeyPair(char[] passphrase, Path p12File) throws IOException {
var keyPair = P384KeyPair.generate();
LOG.debug("Store new device key to {}", p12File);
keyPair.store(p12File, passphrase);
return keyPair;
}
private char[] randomPassword() {
// this is a fast & easy attempt to create a random string:
var uuid = new UUID(csprng.nextLong(), csprng.nextLong());
return uuid.toString().toCharArray();
}
public static class DeviceKeyRetrievalException extends RuntimeException {
private DeviceKeyRetrievalException(String message) {
super(message);
}
private DeviceKeyRetrievalException(String message, Throwable cause) {
super(message, cause);
}
}
}
@@ -32,11 +32,11 @@ public class Settings {
public static final boolean DEFAULT_ASKED_FOR_UPDATE_CHECK = false;
public static final boolean DEFAULT_CHECK_FOR_UPDATES = false;
public static final boolean DEFAULT_START_HIDDEN = false;
public static final boolean DEFAULT_AUTO_CLOSE_VAULTS = false;
public static final boolean DEFAULT_USE_KEYCHAIN = true;
public static final int DEFAULT_PORT = 42427;
public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
public static final WebDavUrlScheme DEFAULT_GVFS_SCHEME = WebDavUrlScheme.DAV;
public static final boolean DEFAULT_DEBUG_MODE = false;
public static final VolumeImpl DEFAULT_PREFERRED_VOLUME_IMPL = VolumeImpl.FUSE;
public static final UiTheme DEFAULT_THEME = UiTheme.LIGHT;
@Deprecated // to be changed to "whatever is available" eventually
public static final String DEFAULT_KEYCHAIN_PROVIDER = SystemUtils.IS_OS_WINDOWS ? "org.cryptomator.windows.keychain.WindowsProtectedKeychainAccess" : SystemUtils.IS_OS_MAC ? "org.cryptomator.macos.keychain.MacSystemKeychainAccess" : "org.cryptomator.linux.keychain.SecretServiceKeychainAccess";
@@ -51,11 +51,11 @@ public class Settings {
private final BooleanProperty askedForUpdateCheck = new SimpleBooleanProperty(DEFAULT_ASKED_FOR_UPDATE_CHECK);
private final BooleanProperty checkForUpdates = new SimpleBooleanProperty(DEFAULT_CHECK_FOR_UPDATES);
private final BooleanProperty startHidden = new SimpleBooleanProperty(DEFAULT_START_HIDDEN);
private final BooleanProperty autoCloseVaults = new SimpleBooleanProperty(DEFAULT_AUTO_CLOSE_VAULTS);
private final BooleanProperty useKeychain = new SimpleBooleanProperty(DEFAULT_USE_KEYCHAIN);
private final IntegerProperty port = new SimpleIntegerProperty(DEFAULT_PORT);
private final IntegerProperty numTrayNotifications = new SimpleIntegerProperty(DEFAULT_NUM_TRAY_NOTIFICATIONS);
private final ObjectProperty<WebDavUrlScheme> preferredGvfsScheme = new SimpleObjectProperty<>(DEFAULT_GVFS_SCHEME);
private final BooleanProperty debugMode = new SimpleBooleanProperty(DEFAULT_DEBUG_MODE);
private final ObjectProperty<VolumeImpl> preferredVolumeImpl = new SimpleObjectProperty<>(DEFAULT_PREFERRED_VOLUME_IMPL);
private final ObjectProperty<UiTheme> theme = new SimpleObjectProperty<>(DEFAULT_THEME);
private final ObjectProperty<String> keychainProvider = new SimpleObjectProperty<>(DEFAULT_KEYCHAIN_PROVIDER);
private final ObjectProperty<NodeOrientation> userInterfaceOrientation = new SimpleObjectProperty<>(DEFAULT_USER_INTERFACE_ORIENTATION);
@@ -70,6 +70,9 @@ public class Settings {
private final StringProperty language = new SimpleStringProperty(DEFAULT_LANGUAGE);
private final StringProperty mountService = new SimpleStringProperty();
private Consumer<Settings> saveCmd;
/**
@@ -82,11 +85,11 @@ public class Settings {
askedForUpdateCheck.addListener(this::somethingChanged);
checkForUpdates.addListener(this::somethingChanged);
startHidden.addListener(this::somethingChanged);
autoCloseVaults.addListener(this::somethingChanged);
useKeychain.addListener(this::somethingChanged);
port.addListener(this::somethingChanged);
numTrayNotifications.addListener(this::somethingChanged);
preferredGvfsScheme.addListener(this::somethingChanged);
debugMode.addListener(this::somethingChanged);
preferredVolumeImpl.addListener(this::somethingChanged);
theme.addListener(this::somethingChanged);
keychainProvider.addListener(this::somethingChanged);
userInterfaceOrientation.addListener(this::somethingChanged);
@@ -99,6 +102,7 @@ public class Settings {
windowHeight.addListener(this::somethingChanged);
displayConfiguration.addListener(this::somethingChanged);
language.addListener(this::somethingChanged);
mountService.addListener(this::somethingChanged);
}
void setSaveCmd(Consumer<Settings> saveCmd) {
@@ -133,6 +137,12 @@ public class Settings {
return startHidden;
}
public BooleanProperty autoCloseVaults() {
return autoCloseVaults;
}
public BooleanProperty useKeychain() { return useKeychain; }
public IntegerProperty port() {
return port;
}
@@ -141,16 +151,12 @@ public class Settings {
return numTrayNotifications;
}
public ObjectProperty<WebDavUrlScheme> preferredGvfsScheme() {
return preferredGvfsScheme;
}
public BooleanProperty debugMode() {
return debugMode;
}
public ObjectProperty<VolumeImpl> preferredVolumeImpl() {
return preferredVolumeImpl;
public StringProperty mountService() {
return mountService;
}
public ObjectProperty<UiTheme> theme() {
@@ -198,4 +204,5 @@ public class Settings {
public StringProperty languageProperty() {
return language;
}
}
@@ -41,14 +41,14 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
out.name("askedForUpdateCheck").value(value.askedForUpdateCheck().get());
out.name("checkForUpdatesEnabled").value(value.checkForUpdates().get());
out.name("startHidden").value(value.startHidden().get());
out.name("autoCloseVaults").value(value.autoCloseVaults().get());
out.name("port").value(value.port().get());
out.name("numTrayNotifications").value(value.numTrayNotifications().get());
out.name("preferredGvfsScheme").value(value.preferredGvfsScheme().get().name());
out.name("debugMode").value(value.debugMode().get());
out.name("preferredVolumeImpl").value(value.preferredVolumeImpl().get().name());
out.name("theme").value(value.theme().get().name());
out.name("uiOrientation").value(value.userInterfaceOrientation().get().name());
out.name("keychainProvider").value(value.keychainProvider().get());
out.name("useKeychain").value(value.useKeychain().get());
out.name("licenseKey").value(value.licenseKey().get());
out.name("showMinimizeButton").value(value.showMinimizeButton().get());
out.name("showTrayIcon").value(value.showTrayIcon().get());
@@ -58,7 +58,7 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
out.name("windowHeight").value((value.windowHeightProperty().get()));
out.name("displayConfiguration").value((value.displayConfigurationProperty().get()));
out.name("language").value((value.languageProperty().get()));
out.name("mountService").value(value.mountService().get());
out.endObject();
}
@@ -82,14 +82,14 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
case "askedForUpdateCheck" -> settings.askedForUpdateCheck().set(in.nextBoolean());
case "checkForUpdatesEnabled" -> settings.checkForUpdates().set(in.nextBoolean());
case "startHidden" -> settings.startHidden().set(in.nextBoolean());
case "autoCloseVaults" -> settings.autoCloseVaults().set(in.nextBoolean());
case "port" -> settings.port().set(in.nextInt());
case "numTrayNotifications" -> settings.numTrayNotifications().set(in.nextInt());
case "preferredGvfsScheme" -> settings.preferredGvfsScheme().set(parseWebDavUrlSchemePrefix(in.nextString()));
case "debugMode" -> settings.debugMode().set(in.nextBoolean());
case "preferredVolumeImpl" -> settings.preferredVolumeImpl().set(parsePreferredVolumeImplName(in.nextString()));
case "theme" -> settings.theme().set(parseUiTheme(in.nextString()));
case "uiOrientation" -> settings.userInterfaceOrientation().set(parseUiOrientation(in.nextString()));
case "keychainProvider" -> settings.keychainProvider().set(in.nextString());
case "useKeychain" -> settings.useKeychain().set(in.nextBoolean());
case "licenseKey" -> settings.licenseKey().set(in.nextString());
case "showMinimizeButton" -> settings.showMinimizeButton().set(in.nextBoolean());
case "showTrayIcon" -> settings.showTrayIcon().set(in.nextBoolean());
@@ -99,9 +99,15 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
case "windowHeight" -> settings.windowHeightProperty().set(in.nextInt());
case "displayConfiguration" -> settings.displayConfigurationProperty().set(in.nextString());
case "language" -> settings.languageProperty().set(in.nextString());
case "mountService" -> {
var token = in.peek();
if (JsonToken.STRING == token) {
settings.mountService().set(in.nextString());
}
}
default -> {
LOG.warn("Unsupported vault setting found in JSON: " + name);
LOG.warn("Unsupported vault setting found in JSON: {}", name);
in.skipValue();
}
}
@@ -111,24 +117,6 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
return settings;
}
private VolumeImpl parsePreferredVolumeImplName(String nioAdapterName) {
try {
return VolumeImpl.valueOf(nioAdapterName.toUpperCase());
} catch (IllegalArgumentException e) {
LOG.warn("Invalid volume type {}. Defaulting to {}.", nioAdapterName, Settings.DEFAULT_PREFERRED_VOLUME_IMPL);
return Settings.DEFAULT_PREFERRED_VOLUME_IMPL;
}
}
private WebDavUrlScheme parseWebDavUrlSchemePrefix(String webDavUrlSchemeName) {
try {
return WebDavUrlScheme.valueOf(webDavUrlSchemeName.toUpperCase());
} catch (IllegalArgumentException e) {
LOG.warn("Invalid WebDAV url scheme {}. Defaulting to {}.", webDavUrlSchemeName, Settings.DEFAULT_GVFS_SCHEME);
return Settings.DEFAULT_GVFS_SCHEME;
}
}
private UiTheme parseUiTheme(String uiThemeName) {
try {
return UiTheme.valueOf(uiThemeName.toUpperCase());
@@ -49,14 +49,12 @@ public class SettingsProvider implements Supplier<Settings> {
private final AtomicReference<ScheduledFuture<?>> scheduledSaveCmd = new AtomicReference<>();
private final Supplier<Settings> settings = Suppliers.memoize(this::load);
private final SettingsJsonAdapter settingsJsonAdapter;
private final Environment env;
private final ScheduledExecutorService scheduler;
private final Gson gson;
@Inject
public SettingsProvider(SettingsJsonAdapter settingsJsonAdapter, Environment env, ScheduledExecutorService scheduler) {
this.settingsJsonAdapter = settingsJsonAdapter;
this.env = env;
this.scheduler = scheduler;
this.gson = new GsonBuilder() //
@@ -118,7 +116,7 @@ public class SettingsProvider implements Supplier<Settings> {
try {
Files.createDirectories(settingsPath.getParent());
Path tmpPath = settingsPath.resolveSibling(settingsPath.getFileName().toString() + ".tmp");
try (OutputStream out = Files.newOutputStream(tmpPath, StandardOpenOption.CREATE_NEW); //
try (OutputStream out = Files.newOutputStream(tmpPath, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE); //
Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)) {
gson.toJson(settings, writer);
}
@@ -6,12 +6,11 @@
package org.cryptomator.common.settings;
import com.google.common.base.CharMatcher;
import com.google.common.base.Strings;
import com.google.common.io.BaseEncoding;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.StringBinding;
import javafx.beans.binding.StringExpression;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
@@ -22,7 +21,6 @@ import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import java.nio.file.Path;
import java.util.Objects;
import java.util.Optional;
import java.util.Random;
/**
@@ -32,7 +30,6 @@ public class VaultSettings {
public static final boolean DEFAULT_UNLOCK_AFTER_STARTUP = false;
public static final boolean DEFAULT_REVEAL_AFTER_MOUNT = true;
public static final boolean DEFAULT_USES_INDIVIDUAL_MOUNTPATH = false;
public static final boolean DEFAULT_USES_READONLY_MODE = false;
public static final String DEFAULT_MOUNT_FLAGS = "";
public static final int DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH = -1;
@@ -45,26 +42,32 @@ public class VaultSettings {
private final String id;
private final ObjectProperty<Path> path = new SimpleObjectProperty<>();
private final StringProperty displayName = new SimpleStringProperty();
private final StringProperty winDriveLetter = new SimpleStringProperty();
private final BooleanProperty unlockAfterStartup = new SimpleBooleanProperty(DEFAULT_UNLOCK_AFTER_STARTUP);
private final BooleanProperty revealAfterMount = new SimpleBooleanProperty(DEFAULT_REVEAL_AFTER_MOUNT);
private final BooleanProperty useCustomMountPath = new SimpleBooleanProperty(DEFAULT_USES_INDIVIDUAL_MOUNTPATH);
private final StringProperty customMountPath = new SimpleStringProperty();
private final BooleanProperty usesReadOnlyMode = new SimpleBooleanProperty(DEFAULT_USES_READONLY_MODE);
private final StringProperty mountFlags = new SimpleStringProperty(DEFAULT_MOUNT_FLAGS);
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);
private final IntegerProperty autoLockIdleSeconds = new SimpleIntegerProperty(DEFAULT_AUTOLOCK_IDLE_SECONDS);
private final StringBinding mountName;
private final StringExpression mountName;
private final ObjectProperty<Path> mountPoint = new SimpleObjectProperty<>();
public VaultSettings(String id) {
this.id = Objects.requireNonNull(id);
this.mountName = Bindings.createStringBinding(this::normalizeDisplayName, displayName);
this.mountName = StringExpression.stringExpression(Bindings.createStringBinding(() -> {
final String name;
if (displayName.isEmpty().get()) {
name = path.get().getFileName().toString();
} else {
name = displayName.get();
}
return normalizeDisplayName(name);
}, displayName, path));
}
Observable[] observables() {
return new Observable[]{path, displayName, winDriveLetter, unlockAfterStartup, revealAfterMount, useCustomMountPath, customMountPath, usesReadOnlyMode, mountFlags, maxCleartextFilenameLength, actionAfterUnlock, autoLockWhenIdle, autoLockIdleSeconds};
return new Observable[]{actionAfterUnlock, autoLockIdleSeconds, autoLockWhenIdle, displayName, maxCleartextFilenameLength, mountFlags, mountPoint, path, revealAfterMount, unlockAfterStartup, usesReadOnlyMode};
}
public static VaultSettings withRandomId() {
@@ -78,8 +81,7 @@ public class VaultSettings {
}
//visible for testing
String normalizeDisplayName() {
var original = displayName.getValueSafe();
static String normalizeDisplayName(String original) {
if (original.isBlank() || ".".equals(original) || "..".equals(original)) {
return "_";
}
@@ -105,22 +107,10 @@ public class VaultSettings {
return displayName;
}
public StringBinding mountName() {
public StringExpression mountName() {
return mountName;
}
public StringProperty winDriveLetter() {
return winDriveLetter;
}
public Optional<String> getWinDriveLetter() {
String current = this.winDriveLetter.get();
if (!Strings.isNullOrEmpty(current)) {
return Optional.of(current);
}
return Optional.empty();
}
public BooleanProperty unlockAfterStartup() {
return unlockAfterStartup;
}
@@ -129,20 +119,12 @@ public class VaultSettings {
return revealAfterMount;
}
public BooleanProperty useCustomMountPath() {
return useCustomMountPath;
public Path getMountPoint() {
return mountPoint.get();
}
public StringProperty customMountPath() {
return customMountPath;
}
public Optional<String> getCustomMountPath() {
if (useCustomMountPath.get()) {
return Optional.ofNullable(Strings.emptyToNull(customMountPath.get()));
} else {
return Optional.empty();
}
public ObjectProperty<Path> mountPoint() {
return mountPoint;
}
public BooleanProperty usesReadOnlyMode() {
@@ -188,5 +170,4 @@ public class VaultSettings {
return false;
}
}
}
@@ -6,11 +6,14 @@
package org.cryptomator.common.settings;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
class VaultSettingsJsonAdapter {
@@ -22,11 +25,10 @@ class VaultSettingsJsonAdapter {
out.name("id").value(value.getId());
out.name("path").value(value.path().get().toString());
out.name("displayName").value(value.displayName().get());
out.name("winDriveLetter").value(value.winDriveLetter().get());
out.name("unlockAfterStartup").value(value.unlockAfterStartup().get());
out.name("revealAfterMount").value(value.revealAfterMount().get());
out.name("useCustomMountPath").value(value.useCustomMountPath().get());
out.name("customMountPath").value(value.customMountPath().get());
var mountPoint = value.mountPoint().get();
out.name("mountPoint").value(mountPoint != null ? mountPoint.toAbsolutePath().toString() : null);
out.name("usesReadOnlyMode").value(value.usesReadOnlyMode().get());
out.name("mountFlags").value(value.mountFlags().get());
out.name("maxCleartextFilenameLength").value(value.maxCleartextFilenameLength().get());
@@ -36,18 +38,18 @@ 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;
String mountName = null; //see https://github.com/cryptomator/cryptomator/pull/1318
String displayName = null;
String customMountPath = null;
String winDriveLetter = null;
boolean unlockAfterStartup = VaultSettings.DEFAULT_UNLOCK_AFTER_STARTUP;
boolean revealAfterMount = VaultSettings.DEFAULT_REVEAL_AFTER_MOUNT;
boolean useCustomMountPath = VaultSettings.DEFAULT_USES_INDIVIDUAL_MOUNTPATH;
boolean usesReadOnlyMode = VaultSettings.DEFAULT_USES_READONLY_MODE;
String mountFlags = VaultSettings.DEFAULT_MOUNT_FLAGS;
Path mountPoint = null;
int maxCleartextFilenameLength = VaultSettings.DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH;
WhenUnlocked actionAfterUnlock = VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK;
boolean autoLockWhenIdle = VaultSettings.DEFAULT_AUTOLOCK_WHEN_IDLE;
@@ -61,19 +63,23 @@ class VaultSettingsJsonAdapter {
case "path" -> path = in.nextString();
case "mountName" -> mountName = in.nextString(); //see https://github.com/cryptomator/cryptomator/pull/1318
case "displayName" -> displayName = in.nextString();
case "winDriveLetter" -> winDriveLetter = in.nextString();
case "unlockAfterStartup" -> unlockAfterStartup = in.nextBoolean();
case "revealAfterMount" -> revealAfterMount = in.nextBoolean();
case "usesIndividualMountPath", "useCustomMountPath" -> useCustomMountPath = in.nextBoolean();
case "individualMountPath", "customMountPath" -> customMountPath = in.nextString();
case "usesReadOnlyMode" -> usesReadOnlyMode = in.nextBoolean();
case "mountFlags" -> mountFlags = in.nextString();
case "mountPoint" -> {
if (JsonToken.NULL == in.peek()) {
in.nextNull();
} else {
mountPoint = parseMountPoint(in.nextString());
}
}
case "maxCleartextFilenameLength" -> maxCleartextFilenameLength = in.nextInt();
case "actionAfterUnlock" -> actionAfterUnlock = parseActionAfterUnlock(in.nextString());
case "autoLockWhenIdle" -> autoLockWhenIdle = in.nextBoolean();
case "autoLockIdleSeconds" -> autoLockIdleSeconds = in.nextInt();
default -> {
LOG.warn("Unsupported vault setting found in JSON: " + name);
LOG.warn("Unsupported vault setting found in JSON: {}", name);
in.skipValue();
}
}
@@ -87,20 +93,27 @@ class VaultSettingsJsonAdapter {
vaultSettings.displayName().set(mountName);
}
vaultSettings.path().set(Paths.get(path));
vaultSettings.winDriveLetter().set(winDriveLetter);
vaultSettings.unlockAfterStartup().set(unlockAfterStartup);
vaultSettings.revealAfterMount().set(revealAfterMount);
vaultSettings.useCustomMountPath().set(useCustomMountPath);
vaultSettings.customMountPath().set(customMountPath);
vaultSettings.usesReadOnlyMode().set(usesReadOnlyMode);
vaultSettings.mountFlags().set(mountFlags);
vaultSettings.maxCleartextFilenameLength().set(maxCleartextFilenameLength);
vaultSettings.actionAfterUnlock().set(actionAfterUnlock);
vaultSettings.autoLockWhenIdle().set(autoLockWhenIdle);
vaultSettings.autoLockIdleSeconds().set(autoLockIdleSeconds);
vaultSettings.mountPoint().set(mountPoint);
return vaultSettings;
}
private Path parseMountPoint(String mountPoint) {
try {
return Path.of(mountPoint);
} catch (InvalidPathException e) {
LOG.warn("Invalid string as mount point. Defaulting to null.");
return null;
}
}
private WhenUnlocked parseActionAfterUnlock(String actionAfterUnlockName) {
try {
return WhenUnlocked.valueOf(actionAfterUnlockName.toUpperCase());
@@ -1,22 +0,0 @@
package org.cryptomator.common.settings;
public enum WebDavUrlScheme {
DAV("dav", "dav:// (Gnome, Nautilus, ...)"),
WEBDAV("webdav", "webdav:// (KDE, Dolphin, ...)");
private final String prefix;
private final String displayName;
WebDavUrlScheme(String prefix, String displayName) {
this.prefix = prefix;
this.displayName = displayName;
}
public String getPrefix() {
return prefix;
}
public String getDisplayName() {
return displayName;
}
}
@@ -1,46 +0,0 @@
package org.cryptomator.common.vaults;
import com.google.common.collect.Iterables;
import org.cryptomator.common.mountpoint.InvalidMountPointException;
import org.cryptomator.common.mountpoint.MountPointChooser;
import java.nio.file.Path;
import java.util.Optional;
public abstract class AbstractVolume implements Volume {
private final Iterable<MountPointChooser> choosers;
protected Path mountPoint;
private boolean cleanupRequired;
private MountPointChooser usedChooser;
public AbstractVolume(Iterable<MountPointChooser> choosers) {
this.choosers = choosers;
}
protected Path determineMountPoint() throws InvalidMountPointException {
var applicableChoosers = Iterables.filter(choosers, c -> c.isApplicable(this));
for (var chooser : applicableChoosers) {
Optional<Path> chosenPath = chooser.chooseMountPoint(this);
if (chosenPath.isEmpty()) { // chooser couldn't find a feasible mountpoint
continue;
}
this.cleanupRequired = chooser.prepare(this, chosenPath.get());
this.usedChooser = chooser;
return chosenPath.get();
}
throw new InvalidMountPointException(String.format("No feasible MountPoint found by choosers: %s", applicableChoosers));
}
protected void cleanupMountPoint() {
if (this.cleanupRequired) {
this.usedChooser.cleanup(this, this.mountPoint);
}
}
@Override
public Optional<Path> getMountPoint() {
return Optional.ofNullable(mountPoint);
}
}
@@ -1,11 +1,13 @@
package org.cryptomator.common.vaults;
import org.cryptomator.integrations.mount.UnmountFailedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import javafx.collections.ObservableList;
import java.io.IOException;
import java.time.Instant;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@@ -39,14 +41,13 @@ public class AutoLocker {
try {
vault.lock(false);
LOG.info("Autolocked {} after idle timeout", vault.getDisplayName());
} catch (Volume.VolumeException | LockNotCompletedException e) {
} catch (UnmountFailedException | IOException e) {
LOG.error("Autolocking failed.", e);
}
}
private boolean exceedsIdleTime(Vault vault) {
assert vault.isUnlocked();
// TODO: shouldn't we read these properties from within FX Application Thread?
if (vault.getVaultSettings().autoLockWhenIdle().get()) {
int maxIdleSeconds = vault.getVaultSettings().autoLockIdleSeconds().get();
var deadline = vault.getStats().getLastActivity().plusSeconds(maxIdleSeconds);
@@ -1,14 +0,0 @@
package org.cryptomator.common.vaults;
import javax.inject.Qualifier;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Qualifier
@Documented
@Retention(RUNTIME)
@interface DefaultMountFlags {
}
@@ -1,95 +0,0 @@
package org.cryptomator.common.vaults;
import org.cryptomator.common.mountpoint.InvalidMountPointException;
import org.cryptomator.common.mountpoint.MountPointChooser;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.common.settings.VolumeImpl;
import org.cryptomator.cryptofs.CryptoFileSystem;
import org.cryptomator.frontend.dokany.DokanyMountFailedException;
import org.cryptomator.frontend.dokany.Mount;
import org.cryptomator.frontend.dokany.MountFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import java.util.function.Consumer;
public class DokanyVolume extends AbstractVolume {
private static final Logger LOG = LoggerFactory.getLogger(DokanyVolume.class);
private static final String FS_TYPE_NAME = "CryptomatorFS";
private final VaultSettings vaultSettings;
private Mount mount;
@Inject
public DokanyVolume(VaultSettings vaultSettings, @Named("orderedMountPointChoosers") Iterable<MountPointChooser> choosers) {
super(choosers);
this.vaultSettings = vaultSettings;
}
@Override
public VolumeImpl getImplementationType() {
return VolumeImpl.DOKANY;
}
@Override
public void mount(CryptoFileSystem fs, String mountFlags, Consumer<Throwable> onExitAction) throws InvalidMountPointException, VolumeException {
this.mountPoint = determineMountPoint();
try {
this.mount = MountFactory.mount(fs.getPath("/"), mountPoint, vaultSettings.mountName().get(), FS_TYPE_NAME, mountFlags.strip(), onExitAction);
} catch (DokanyMountFailedException e) {
if (vaultSettings.getCustomMountPath().isPresent()) {
LOG.warn("Failed to mount vault into {}. Is this directory currently accessed by another process (e.g. Windows Explorer)?", mountPoint);
}
throw new VolumeException("Unable to mount Filesystem", e);
}
}
@Override
public void reveal(Revealer revealer) throws VolumeException {
try {
mount.reveal(revealer::reveal);
} catch (Exception e) {
throw new VolumeException(e);
}
}
@Override
public void unmount() throws VolumeException {
try {
mount.unmount();
} catch (IllegalStateException e) {
throw new VolumeException("Unmount Failed.", e);
}
cleanupMountPoint();
}
@Override
public void unmountForced() {
mount.unmountForced();
cleanupMountPoint();
}
@Override
public boolean supportsForcedUnmount() {
return true;
}
@Override
public boolean isSupported() {
return DokanyVolume.isSupportedStatic();
}
@Override
public MountPointRequirement getMountPointRequirement() {
return this.vaultSettings.getWinDriveLetter().isPresent() ? MountPointRequirement.UNUSED_ROOT_DIR : MountPointRequirement.EMPTY_MOUNT_POINT;
}
public static boolean isSupportedStatic() {
return MountFactory.isApplicable();
}
}
@@ -1,136 +0,0 @@
package org.cryptomator.common.vaults;
import com.google.common.collect.Iterators;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.mountpoint.InvalidMountPointException;
import org.cryptomator.common.mountpoint.MountPointChooser;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.common.settings.VolumeImpl;
import org.cryptomator.cryptofs.CryptoFileSystem;
import org.cryptomator.frontend.fuse.mount.EnvironmentVariables;
import org.cryptomator.frontend.fuse.mount.FuseMountException;
import org.cryptomator.frontend.fuse.mount.FuseMountFactory;
import org.cryptomator.frontend.fuse.mount.FuseNotSupportedException;
import org.cryptomator.frontend.fuse.mount.Mount;
import org.cryptomator.frontend.fuse.mount.Mounter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.regex.Pattern;
public class FuseVolume extends AbstractVolume {
private static final Logger LOG = LoggerFactory.getLogger(FuseVolume.class);
private static final Pattern NON_WHITESPACE_OR_QUOTED = Pattern.compile("[^\\s\"']+|\"([^\"]*)\"|'([^']*)'"); // Thanks to https://stackoverflow.com/a/366532
private final VaultSettings vaultSettings;
private Mount mount;
@Inject
public FuseVolume(VaultSettings vaultSettings, @Named("orderedMountPointChoosers") Iterable<MountPointChooser> choosers) {
super(choosers);
this.vaultSettings = vaultSettings;
}
@Override
public void mount(CryptoFileSystem fs, String mountFlags, Consumer<Throwable> onExitAction) throws InvalidMountPointException, VolumeException {
this.mountPoint = determineMountPoint();
mount(fs.getPath("/"), mountFlags, onExitAction);
}
private void mount(Path root, String mountFlags, Consumer<Throwable> onExitAction) throws VolumeException {
try {
Mounter mounter = FuseMountFactory.getMounter();
EnvironmentVariables envVars = EnvironmentVariables.create() //
.withFlags(splitFlags(mountFlags)) //
.withMountPoint(mountPoint) //
.withFileNameTranscoder(mounter.defaultFileNameTranscoder()) //
.build();
this.mount = mounter.mount(root, envVars, onExitAction);
} catch (FuseMountException | FuseNotSupportedException e) {
throw new VolumeException("Unable to mount Filesystem", e);
}
}
private String[] splitFlags(String str) {
List<String> flags = new ArrayList<>();
var matches = Iterators.peekingIterator(NON_WHITESPACE_OR_QUOTED.matcher(str).results().iterator());
while (matches.hasNext()) {
String flag = matches.next().group();
// check if flag is missing its argument:
if (flag.endsWith("=") && matches.hasNext() && matches.peek().group(1) != null) { // next is "double quoted"
// next is "double quoted" and flag is missing its argument
flag += matches.next().group(1);
} else if (flag.endsWith("=") && matches.hasNext() && matches.peek().group(2) != null) {
// next is 'single quoted' and flag is missing its argument
flag += matches.next().group(2);
}
flags.add(flag);
}
return flags.toArray(String[]::new);
}
@Override
public void reveal(Revealer revealer) throws VolumeException {
try {
mount.reveal(revealer::reveal);
} catch (Exception e) {
throw new VolumeException(e);
}
}
@Override
public boolean supportsForcedUnmount() {
return true;
}
@Override
public synchronized void unmountForced() throws VolumeException {
try {
mount.unmountForced();
} catch (FuseMountException e) {
throw new VolumeException(e);
}
cleanupMountPoint();
}
@Override
public synchronized void unmount() throws VolumeException {
try {
mount.unmount();
} catch (FuseMountException e) {
throw new VolumeException(e);
}
cleanupMountPoint();
}
@Override
public boolean isSupported() {
return FuseVolume.isSupportedStatic();
}
@Override
public VolumeImpl getImplementationType() {
return VolumeImpl.FUSE;
}
@Override
public MountPointRequirement getMountPointRequirement() {
if (!SystemUtils.IS_OS_WINDOWS) {
return MountPointRequirement.EMPTY_MOUNT_POINT;
}
return this.vaultSettings.getWinDriveLetter().isPresent() ? MountPointRequirement.UNUSED_ROOT_DIR : MountPointRequirement.PARENT_NO_MOUNT_POINT;
}
public static boolean isSupportedStatic() {
return FuseMountFactory.isFuseSupported();
}
}
@@ -1,12 +0,0 @@
package org.cryptomator.common.vaults;
public class LockNotCompletedException extends Exception {
public LockNotCompletedException(String reason) {
super(reason);
}
public LockNotCompletedException(Throwable cause) {
super(cause);
}
}
@@ -1,33 +0,0 @@
package org.cryptomator.common.vaults;
/**
* Enumeration used to indicate the requirements for mounting a vault
* using a specific {@link Volume VolumeProvider}, e.g. {@link FuseVolume}.
*/
public enum MountPointRequirement {
/**
* The Mountpoint needs to be a filesystem root and must not exist.
*/
UNUSED_ROOT_DIR,
/**
* No Mountpoint on the local filesystem required. (e.g. WebDAV)
*/
NONE,
/**
* A parent folder is required, but the actual Mountpoint must not exist.
*/
PARENT_NO_MOUNT_POINT,
/**
* A parent folder is required, but the actual Mountpoint may exist.
*/
PARENT_OPT_MOUNT_POINT,
/**
* The actual Mountpoint must exist and must be empty.
*/
EMPTY_MOUNT_POINT;
}
@@ -11,9 +11,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.mountpoint.InvalidMountPointException;
import org.cryptomator.common.Environment;
import org.cryptomator.common.mount.WindowsDriveLetters;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.common.vaults.Volume.VolumeException;
import org.cryptomator.cryptofs.CryptoFileSystem;
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags;
@@ -22,29 +23,42 @@ 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;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.binding.StringBinding;
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;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
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 {
@@ -52,15 +66,16 @@ 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 Provider<Volume> volumeProvider;
private final StringBinding defaultMountFlags;
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 displayName;
private final StringBinding displayablePath;
private final BooleanBinding locked;
private final BooleanBinding processing;
@@ -68,23 +83,24 @@ public class Vault {
private final BooleanBinding missing;
private final BooleanBinding needsMigration;
private final BooleanBinding unknownError;
private final StringBinding accessPoint;
private final BooleanBinding accessPointPresent;
private final ObjectBinding<Mountpoint> mountPoint;
private final WindowsDriveLetters windowsDriveLetters;
private final BooleanProperty showingStats;
private volatile Volume volume;
private AtomicReference<MountHandle> mountHandle = new AtomicReference<>(null);
@Inject
Vault(VaultSettings vaultSettings, VaultConfigCache configCache, Provider<Volume> volumeProvider, @DefaultMountFlags StringBinding defaultMountFlags, AtomicReference<CryptoFileSystem> cryptoFileSystem, VaultState state, @Named("lastKnownException") ObjectProperty<Exception> lastKnownException, VaultStats stats) {
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;
this.vaultSettings = vaultSettings;
this.configCache = configCache;
this.volumeProvider = volumeProvider;
this.defaultMountFlags = defaultMountFlags;
this.cryptoFileSystem = cryptoFileSystem;
this.state = state;
this.lastKnownException = lastKnownException;
this.mountService = mountService;
this.defaultMountFlags = mountService.map(MountService::getDefaultMountFlags);
this.stats = stats;
this.displayName = Bindings.createStringBinding(this::getDisplayName, vaultSettings.displayName());
this.displayablePath = Bindings.createStringBinding(this::getDisplayablePath, vaultSettings.path());
this.locked = Bindings.createBooleanBinding(this::isLocked, state);
this.processing = Bindings.createBooleanBinding(this::isProcessing, state);
@@ -92,8 +108,8 @@ public class Vault {
this.missing = Bindings.createBooleanBinding(this::isMissing, state);
this.needsMigration = Bindings.createBooleanBinding(this::isNeedsMigration, state);
this.unknownError = Bindings.createBooleanBinding(this::isUnknownError, state);
this.accessPoint = Bindings.createStringBinding(this::getAccessPoint, state);
this.accessPointPresent = this.accessPoint.isNotEmpty();
this.mountPoint = Bindings.createObjectBinding(this::getMountPoint, state);
this.windowsDriveLetters = windowsDriveLetters;
this.showingStats = new SimpleBooleanProperty(false);
}
@@ -143,7 +159,46 @@ public class Vault {
}
}
public synchronized void unlock(MasterkeyLoader keyLoader) throws CryptoException, IOException, VolumeException, InvalidMountPointException {
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.");
}
@@ -151,9 +206,10 @@ public class Vault {
boolean success = false;
try {
cryptoFileSystem.set(fs);
volume = volumeProvider.get();
volume.mount(fs, getEffectiveMountFlags(), this::lockOnVolumeExit);
success = true;
var rootPath = fs.getRootDirectories().iterator().next();
var supportsForcedUnmount = mountService.getValue().hasCapability(MountCapability.UNMOUNT_FORCED);
var mountHandle = new MountHandle(prepareMount(rootPath).mount(), supportsForcedUnmount);
success = this.mountHandle.compareAndSet(null, mountHandle);
} finally {
if (!success) {
destroyCryptoFileSystem();
@@ -161,37 +217,28 @@ public class Vault {
}
}
private void lockOnVolumeExit(Throwable t) {
LOG.info("Unmounted vault '{}'", getDisplayName());
destroyCryptoFileSystem();
state.set(VaultState.Value.LOCKED);
if (t != null) {
LOG.warn("Unexpected unmount and lock of vault " + getDisplayName(), t);
}
}
public synchronized void lock(boolean forced) throws VolumeException, LockNotCompletedException {
//initiate unmount
if (forced && volume.supportsForcedUnmount()) {
volume.unmountForced();
public synchronized void lock(boolean forced) throws UnmountFailedException, IOException {
var mountHandle = this.mountHandle.get();
if (mountHandle == null) {
//TODO: noop or InvalidStateException?
return;
}
if (forced && mountHandle.supportsUnmountForced) {
mountHandle.mount.unmountForced();
} else {
volume.unmount();
mountHandle.mount.unmount();
}
//wait for lockOnVolumeExit to be executed
try {
boolean locked = state.awaitState(VaultState.Value.LOCKED, 3000, TimeUnit.MILLISECONDS);
if (!locked) {
throw new LockNotCompletedException("Locking of vault " + this.getDisplayName() + " still in progress.");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new LockNotCompletedException(e);
mountHandle.mount.close();
} finally {
destroyCryptoFileSystem();
}
}
public void reveal(Volume.Revealer vaultRevealer) throws VolumeException {
volume.reveal(vaultRevealer);
this.mountHandle.set(null);
LOG.info("Locked vault '{}'", getDisplayName());
}
// ******************************************************************************
@@ -266,33 +313,21 @@ public class Vault {
return state.get() == VaultState.Value.ERROR;
}
public StringBinding displayNameProperty() {
return displayName;
public ReadOnlyStringProperty displayNameProperty() {
return vaultSettings.displayName();
}
public String getDisplayName() {
return vaultSettings.displayName().get();
}
public StringBinding accessPointProperty() {
return accessPoint;
public ObjectBinding<Mountpoint> mountPointProperty() {
return mountPoint;
}
public String getAccessPoint() {
if (state.getValue() == VaultState.Value.UNLOCKED) {
assert volume != null;
return volume.getMountPoint().orElse(Path.of("")).toString();
} else {
return "";
}
}
public BooleanBinding accessPointPresentProperty() {
return accessPointPresent;
}
public boolean isAccessPointPresent() {
return accessPointPresent.get();
public Mountpoint getMountPoint() {
var handle = mountHandle.get();
return handle == null ? null : handle.mount.getMountpoint();
}
public StringBinding displayablePathProperty() {
@@ -315,7 +350,7 @@ public class Vault {
}
public boolean isShowingStats() {
return accessPointPresent.get();
return mountHandle.get() != null;
}
@@ -344,18 +379,18 @@ public class Vault {
return !Strings.isNullOrEmpty(vaultSettings.mountFlags().get());
}
public StringBinding defaultMountFlagsProperty() {
public ObservableValue<String> defaultMountFlagsProperty() {
return defaultMountFlags;
}
public String getDefaultMountFlags() {
return defaultMountFlags.get();
return defaultMountFlags.getValue();
}
public String getEffectiveMountFlags() {
String mountFlags = vaultSettings.mountFlags().get();
if (Strings.isNullOrEmpty(mountFlags)) {
return getDefaultMountFlags();
return ""; //TODO: should the provider provide dem defaults??
} else {
return mountFlags;
}
@@ -373,10 +408,6 @@ public class Vault {
return vaultSettings.getId();
}
public Optional<Volume> getVolume() {
return Optional.ofNullable(this.volume);
}
// ******************************************************************************
// Hashcode / Equals
// *******************************************************************************/
@@ -395,7 +426,15 @@ public class Vault {
}
}
/* TODO: reactivate/ needed at all?
public boolean supportsForcedUnmount() {
return volume.supportsForcedUnmount();
}
*/
private record MountHandle(Mount mount, boolean supportsUnmountForced) {
}
}
@@ -8,13 +8,12 @@ package org.cryptomator.common.vaults;
import dagger.BindsInstance;
import dagger.Subcomponent;
import org.cryptomator.common.Nullable;
import org.cryptomator.common.mountpoint.MountPointChooserModule;
import org.cryptomator.common.settings.VaultSettings;
import javax.inject.Named;
@PerVault
@Subcomponent(modules = {VaultModule.class, MountPointChooserModule.class})
@Subcomponent(modules = {VaultModule.class})
public interface VaultComponent {
Vault vault();
@@ -7,26 +7,14 @@ package org.cryptomator.common.vaults;
import dagger.Module;
import dagger.Provides;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Nullable;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.common.settings.VolumeImpl;
import org.cryptomator.cryptofs.CryptoFileSystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Named;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.atomic.AtomicReference;
@Module
@@ -47,130 +35,4 @@ public class VaultModule {
return new SimpleObjectProperty<>(initialErrorCause);
}
@Provides
public Volume provideVolume(Settings settings, WebDavVolume webDavVolume, FuseVolume fuseVolume, DokanyVolume dokanyVolume) {
VolumeImpl preferredImpl = settings.preferredVolumeImpl().get();
if (VolumeImpl.DOKANY == preferredImpl && dokanyVolume.isSupported()) {
return dokanyVolume;
} else if (VolumeImpl.FUSE == preferredImpl && fuseVolume.isSupported()) {
return fuseVolume;
} else {
if (VolumeImpl.WEBDAV != preferredImpl) {
LOG.warn("Using WebDAV, because {} is not supported.", preferredImpl.getDisplayName());
}
assert webDavVolume.isSupported();
return webDavVolume;
}
}
@Provides
@PerVault
@DefaultMountFlags
public StringBinding provideDefaultMountFlags(Settings settings, VaultSettings vaultSettings) {
ObjectProperty<VolumeImpl> preferredVolumeImpl = settings.preferredVolumeImpl();
StringBinding mountName = vaultSettings.mountName();
BooleanProperty readOnly = vaultSettings.usesReadOnlyMode();
return Bindings.createStringBinding(() -> {
VolumeImpl v = preferredVolumeImpl.get();
if (v == VolumeImpl.FUSE && SystemUtils.IS_OS_MAC) {
return getMacFuseDefaultMountFlags(mountName, readOnly);
} else if (v == VolumeImpl.FUSE && SystemUtils.IS_OS_LINUX) {
return getLinuxFuseDefaultMountFlags(readOnly);
} else if (v == VolumeImpl.FUSE && SystemUtils.IS_OS_WINDOWS) {
return getWindowsFuseDefaultMountFlags(mountName, readOnly);
} else if (v == VolumeImpl.DOKANY && SystemUtils.IS_OS_WINDOWS) {
return getDokanyDefaultMountFlags(readOnly);
} else {
return "--flags-supported-on-FUSE-or-DOKANY-only";
}
}, mountName, readOnly, preferredVolumeImpl);
}
// see: https://github.com/osxfuse/osxfuse/wiki/Mount-options
private String getMacFuseDefaultMountFlags(StringBinding mountName, ReadOnlyBooleanProperty readOnly) {
assert SystemUtils.IS_OS_MAC_OSX;
StringBuilder flags = new StringBuilder();
if (readOnly.get()) {
flags.append(" -ordonly");
}
flags.append(" -ovolname=").append('"').append(mountName.get()).append('"');
flags.append(" -oatomic_o_trunc");
flags.append(" -oauto_xattr");
flags.append(" -oauto_cache");
flags.append(" -onoappledouble"); // vastly impacts performance for some reason...
flags.append(" -odefault_permissions"); // let the kernel assume permissions based on file attributes etc
try {
Path userHome = Paths.get(System.getProperty("user.home"));
int uid = (int) Files.getAttribute(userHome, "unix:uid");
int gid = (int) Files.getAttribute(userHome, "unix:gid");
flags.append(" -ouid=").append(uid);
flags.append(" -ogid=").append(gid);
} catch (IOException e) {
LOG.error("Could not read uid/gid from USER_HOME", e);
}
return flags.toString().strip();
}
// see https://manpages.debian.org/testing/fuse/mount.fuse.8.en.html
private String getLinuxFuseDefaultMountFlags(ReadOnlyBooleanProperty readOnly) {
assert SystemUtils.IS_OS_LINUX;
StringBuilder flags = new StringBuilder();
if (readOnly.get()) {
flags.append(" -oro");
}
flags.append(" -oauto_unmount");
try {
Path userHome = Paths.get(System.getProperty("user.home"));
int uid = (int) Files.getAttribute(userHome, "unix:uid");
int gid = (int) Files.getAttribute(userHome, "unix:gid");
flags.append(" -ouid=").append(uid);
flags.append(" -ogid=").append(gid);
} catch (IOException e) {
LOG.error("Could not read uid/gid from USER_HOME", e);
}
return flags.toString().strip();
}
// see https://github.com/billziss-gh/winfsp/blob/5d0b10d0b643652c00ebb4704dc2bb28e7244973/src/dll/fuse/fuse_main.c#L53-L62 for syntax guide
// see https://github.com/billziss-gh/winfsp/blob/5d0b10d0b643652c00ebb4704dc2bb28e7244973/src/dll/fuse/fuse.c#L295-L319 for options (-o <...>)
// see https://github.com/billziss-gh/winfsp/wiki/Frequently-Asked-Questions/5ba00e4be4f5e938eaae6ef1500b331de12dee77 (FUSE 4.) on why the given defaults were chosen
private String getWindowsFuseDefaultMountFlags(StringBinding mountName, ReadOnlyBooleanProperty readOnly) {
assert SystemUtils.IS_OS_WINDOWS;
StringBuilder flags = new StringBuilder();
//WinFSP has no explicit "readonly"-option, nut not setting the group/user-id has the same effect, tho.
//So for the time being not setting them is the way to go...
//See: https://github.com/billziss-gh/winfsp/issues/319
if (!readOnly.get()) {
flags.append(" -ouid=-1");
flags.append(" -ogid=11");
}
flags.append(" -ovolname=").append('"').append(mountName.get()).append('"');
//Dokany requires this option to be set, WinFSP doesn't seem to share this peculiarity,
//but the option exists. Let's keep this here in case we need it.
// flags.append(" -oThreadCount=").append(5);
return flags.toString().strip();
}
// see https://github.com/cryptomator/dokany-nio-adapter/blob/develop/src/main/java/org/cryptomator/frontend/dokany/MountUtil.java#L30-L34
private String getDokanyDefaultMountFlags(ReadOnlyBooleanProperty readOnly) {
assert SystemUtils.IS_OS_WINDOWS;
StringBuilder flags = new StringBuilder();
flags.append(" --options CURRENT_SESSION");
if (readOnly.get()) {
flags.append(",WRITE_PROTECTION");
}
flags.append(" --thread-count 5");
flags.append(" --timeout 10000");
flags.append(" --allocation-unit-size 4096");
flags.append(" --sector-size 4096");
return flags.toString().strip();
}
}
@@ -1,102 +0,0 @@
package org.cryptomator.common.vaults;
import org.cryptomator.common.mountpoint.InvalidMountPointException;
import org.cryptomator.common.settings.VolumeImpl;
import org.cryptomator.cryptofs.CryptoFileSystem;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Optional;
import java.util.concurrent.CompletionStage;
import java.util.function.Consumer;
import java.util.stream.Stream;
/**
* Takes a Volume and uses it to mount an unlocked vault
*/
public interface Volume {
/**
* Checks in constant time whether this volume type is supported on the system running Cryptomator.
*
* @return true if this volume can be mounted
*/
boolean isSupported();
/**
* Gets the corresponding enum type of the {@link VolumeImpl volume implementation ("VolumeImpl")} that is implemented by this Volume.
*
* @return the type of implementation as defined by the {@link VolumeImpl VolumeImpl enum}
*/
VolumeImpl getImplementationType();
/**
* @param fs
* @throws IOException
*/
void mount(CryptoFileSystem fs, String mountFlags, Consumer<Throwable> onExitAction) throws IOException, VolumeException, InvalidMountPointException;
/**
* Reveals the mounted volume.
* <p>
* The given {@code revealer} might be used to do it, but not necessarily.
*
* @param revealer An object capable of revealing the location of the mounted vault to view the content (e.g. in the default file browser).
* @throws VolumeException
*/
void reveal(Revealer revealer) throws VolumeException;
void unmount() throws VolumeException;
Optional<Path> getMountPoint();
MountPointRequirement getMountPointRequirement();
// optional forced unmounting:
default boolean supportsForcedUnmount() {
return false;
}
default void unmountForced() throws VolumeException {
throw new VolumeException("Operation not supported.");
}
static VolumeImpl[] getCurrentSupportedAdapters() {
return Stream.of(VolumeImpl.values()).filter(impl -> switch (impl) {
case WEBDAV -> WebDavVolume.isSupportedStatic();
case DOKANY -> DokanyVolume.isSupportedStatic();
case FUSE -> FuseVolume.isSupportedStatic();
}).toArray(VolumeImpl[]::new);
}
/**
* Exception thrown when a volume-specific command such as mount/unmount/reveal failed.
*/
class VolumeException extends Exception {
public VolumeException(String message) {
super(message);
}
public VolumeException(Throwable cause) {
super(cause);
}
public VolumeException(String message, Throwable cause) {
super(message, cause);
}
}
/**
* Hides and unifies the different Revealer implementations in the different nio-adapters.
*/
@FunctionalInterface
interface Revealer {
void reveal(Path p) throws VolumeException;
}
}
@@ -1,170 +0,0 @@
package org.cryptomator.common.vaults;
import com.google.common.base.CharMatcher;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.common.settings.VolumeImpl;
import org.cryptomator.cryptofs.CryptoFileSystem;
import org.cryptomator.frontend.webdav.WebDavServer;
import org.cryptomator.frontend.webdav.mount.MountParams;
import org.cryptomator.frontend.webdav.mount.Mounter;
import org.cryptomator.frontend.webdav.servlet.WebDavServletController;
import javax.inject.Inject;
import javax.inject.Provider;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.file.Path;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class WebDavVolume implements Volume {
private static final String LOCALHOST_ALIAS = "cryptomator-vault";
private final Provider<WebDavServer> serverProvider;
private final VaultSettings vaultSettings;
private final Settings settings;
private final WindowsDriveLetters windowsDriveLetters;
private WebDavServer server;
private WebDavServletController servlet;
private Mounter.Mount mount;
private Consumer<Throwable> onExitAction;
@Inject
public WebDavVolume(Provider<WebDavServer> serverProvider, VaultSettings vaultSettings, Settings settings, WindowsDriveLetters windowsDriveLetters) {
this.serverProvider = serverProvider;
this.vaultSettings = vaultSettings;
this.settings = settings;
this.windowsDriveLetters = windowsDriveLetters;
}
@Override
public void mount(CryptoFileSystem fs, String mountFlags, Consumer<Throwable> onExitAction) throws VolumeException {
startServlet(fs);
mountServlet();
this.onExitAction = onExitAction;
}
private void startServlet(CryptoFileSystem fs) {
if (server == null) {
server = serverProvider.get();
}
if (!server.isRunning()) {
server.start();
}
CharMatcher acceptable = CharMatcher.inRange('0', '9').or(CharMatcher.inRange('A', 'Z')).or(CharMatcher.inRange('a', 'z'));
String urlConformMountName = acceptable.negate().collapseFrom(vaultSettings.mountName().get(), '_');
servlet = server.createWebDavServlet(fs.getPath("/"), vaultSettings.getId() + "/" + urlConformMountName);
servlet.start();
}
private void mountServlet() throws VolumeException {
if (servlet == null) {
throw new IllegalStateException("Mounting requires unlocked WebDAV servlet.");
}
//on windows, prevent an automatic drive letter selection in the upstream library. Either we choose already a specific one or there is no free.
Supplier<String> driveLetterSupplier;
if (System.getProperty("os.name").toLowerCase().contains("windows") && vaultSettings.winDriveLetter().isEmpty().get()) {
driveLetterSupplier = () -> windowsDriveLetters.getDesiredAvailableDriveLetter().orElse(null);
} else {
driveLetterSupplier = () -> vaultSettings.winDriveLetter().get();
}
MountParams mountParams = MountParams.create() //
.withWindowsDriveLetter(driveLetterSupplier.get()) //
.withPreferredGvfsScheme(settings.preferredGvfsScheme().get().getPrefix())//
.withWebdavHostname(getLocalhostAliasOrNull()) //
.build();
try {
this.mount = servlet.mount(mountParams); // might block this thread for a while
} catch (Mounter.CommandFailedException e) {
throw new VolumeException(e);
}
}
@Override
public void reveal(Revealer revealer) throws VolumeException {
try {
mount.reveal(revealer::reveal);
} catch (Exception e) {
throw new VolumeException(e);
}
}
@Override
public synchronized void unmount() throws VolumeException {
try {
mount.unmount();
} catch (Mounter.CommandFailedException e) {
throw new VolumeException(e);
}
cleanup();
onExitAction.accept(null);
}
@Override
public synchronized void unmountForced() throws VolumeException {
try {
mount.forced().orElseThrow(IllegalStateException::new).unmount();
} catch (Mounter.CommandFailedException e) {
throw new VolumeException(e);
}
cleanup();
onExitAction.accept(null);
}
@Override
public Optional<Path> getMountPoint() {
return mount.getMountPoint();
}
@Override
public MountPointRequirement getMountPointRequirement() {
return MountPointRequirement.NONE;
}
private String getLocalhostAliasOrNull() {
try {
InetAddress alias = InetAddress.getByName(LOCALHOST_ALIAS);
if (alias.getHostAddress().equals("127.0.0.1")) {
return LOCALHOST_ALIAS;
} else {
return null;
}
} catch (UnknownHostException e) {
return null;
}
}
private void cleanup() {
if (servlet != null) {
servlet.stop();
}
}
@Override
public boolean isSupported() {
return WebDavVolume.isSupportedStatic();
}
@Override
public VolumeImpl getImplementationType() {
return VolumeImpl.WEBDAV;
}
@Override
public boolean supportsForcedUnmount() {
return mount != null && mount.forced().isPresent();
}
public static boolean isSupportedStatic() {
return true;
}
}
@@ -1,85 +0,0 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.common.vaults;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import org.apache.commons.lang3.SystemUtils;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.StreamSupport;
@Singleton
public final class WindowsDriveLetters {
private static final Set<String> A_TO_Z;
static {
try (IntStream stream = IntStream.rangeClosed('A', 'Z')) {
A_TO_Z = stream.mapToObj(i -> String.valueOf((char) i)).collect(ImmutableSet.toImmutableSet());
}
}
@Inject
public WindowsDriveLetters() {
}
public Set<String> getAllDriveLetters() {
return A_TO_Z;
}
public Set<String> getOccupiedDriveLetters() {
if (!SystemUtils.IS_OS_WINDOWS) {
return Set.of();
} else {
Iterable<Path> rootDirs = FileSystems.getDefault().getRootDirectories();
return StreamSupport.stream(rootDirs.spliterator(), false).map(p -> p.toString().substring(0, 1)).collect(Collectors.toSet());
}
}
public Set<String> getAvailableDriveLetters() {
return Sets.difference(getAllDriveLetters(), getOccupiedDriveLetters());
}
public Optional<String> getAvailableDriveLetter() {
return getAvailableDriveLetters().stream().findFirst();
}
public Optional<Path> getAvailableDriveLetterPath() {
return getAvailableDriveLetter().map(this::toPath);
}
/**
* Skips A and B and only returns them if all other are occupied.
*
* @return an Optional containing either the letter of a free drive letter or empty, if none is available
*/
public Optional<String> getDesiredAvailableDriveLetter() {
var availableDriveLetters = getAvailableDriveLetters();
var optString = availableDriveLetters.stream().filter(s -> !(s.equals("A") || s.equals("B"))).findFirst();
return optString.or(() -> availableDriveLetters.stream().findFirst());
}
/**
* Skips A and B and only returns them if all other are occupied.
*
* @return an Optional containing either the path to a free drive letter or empty, if none is available
*/
public Optional<Path> getDesiredAvailableDriveLetterPath() {
return getDesiredAvailableDriveLetter().map(this::toPath);
}
public Path toPath(String driveLetter) {
return Path.of(driveLetter + ":\\");
}
}
@@ -12,7 +12,6 @@ import org.cryptomator.common.Environment;
import org.cryptomator.common.ShutdownHook;
import org.cryptomator.ipc.IpcCommunicator;
import org.cryptomator.logging.DebugMode;
import org.cryptomator.logging.LoggerConfiguration;
import org.cryptomator.ui.fxapp.FxApplicationComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -35,7 +34,6 @@ public class Cryptomator {
private static final CryptomatorComponent CRYPTOMATOR_COMPONENT = DaggerCryptomatorComponent.factory().create(STARTUP_TIME);
private static final Logger LOG = LoggerFactory.getLogger(Cryptomator.class);
private final LoggerConfiguration logConfig;
private final DebugMode debugMode;
private final SupportedLanguages supportedLanguages;
private final Environment env;
@@ -43,8 +41,7 @@ public class Cryptomator {
private final ShutdownHook shutdownHook;
@Inject
Cryptomator(LoggerConfiguration logConfig, DebugMode debugMode, SupportedLanguages supportedLanguages, Environment env, Lazy<IpcMessageHandler> ipcMessageHandler, ShutdownHook shutdownHook) {
this.logConfig = logConfig;
Cryptomator(DebugMode debugMode, SupportedLanguages supportedLanguages, Environment env, Lazy<IpcMessageHandler> ipcMessageHandler, ShutdownHook shutdownHook) {
this.debugMode = debugMode;
this.supportedLanguages = supportedLanguages;
this.env = env;
@@ -79,9 +76,9 @@ public class Cryptomator {
* @return Nonzero exit code in case of an error.
*/
private int run(String[] args) {
logConfig.init();
env.log();
LOG.debug("Dagger graph initialized after {}ms", System.currentTimeMillis() - STARTUP_TIME);
LOG.info("Starting Cryptomator {} on {} {} ({})", env.getAppVersion().orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
LOG.info("Starting Cryptomator {} on {} {} ({})", env.getAppVersion(), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
debugMode.initialize();
supportedLanguages.applyPreferred();
@@ -3,14 +3,13 @@ package org.cryptomator.launcher;
import dagger.BindsInstance;
import dagger.Component;
import org.cryptomator.common.CommonsModule;
import org.cryptomator.logging.LoggerModule;
import org.cryptomator.ui.fxapp.FxApplicationComponent;
import javax.inject.Named;
import javax.inject.Singleton;
@Singleton
@Component(modules = {CryptomatorModule.class, CommonsModule.class, LoggerModule.class})
@Component(modules = {CryptomatorModule.class, CommonsModule.class})
public interface CryptomatorComponent {
Cryptomator application();
@@ -15,9 +15,9 @@ public class SupportedLanguages {
private static final Logger LOG = LoggerFactory.getLogger(SupportedLanguages.class);
// these are BCP 47 language codes, not ISO. Note the "-" instead of the "_":
public static final List<String> LANGUAGAE_TAGS = List.of("en", "ar", "bn", "bs", "ca", "cs", "de", "el", "es", "fil", "fr", "gl", "he", //
"hi", "hr", "hu", "id", "it", "ja", "ko", "lv", "mk", "nb", "nl", "nn", "no", "pa", "pl", "pt", "pt-BR", "ro", "ru", "sk", "sr", //
"sr-Latn", "sv", "sw", "ta", "te", "th", "tr", "uk", "zh", "zh-HK", "zh-TW");
public static final List<String> LANGUAGAE_TAGS = List.of("en", "ar", "be", "bn", "bs", "ca", "cs", "da", "de", "el", "es", "fil", "fa", "fr", "gl", "he", //
"hi", "hr", "hu", "id", "it", "ja", "ko", "lv", "mk", "nb", "nl", "nn", "no", "pa", "pl", "pt", "pt-BR", "ro", "ru", "si", "sk", "sr", "sr-Latn", "sv", "sw", //
"ta", "te", "th", "tr", "uk", "vi", "zh", "zh-HK", "zh-TW");
@Nullable
private final String preferredLanguage;
@@ -5,29 +5,24 @@
*******************************************************************************/
package org.cryptomator.logging;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import org.cryptomator.common.settings.Settings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import javafx.beans.value.ObservableValue;
import java.util.Map;
@Singleton
public class DebugMode {
private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(DebugMode.class);
private static final Logger LOG = LoggerFactory.getLogger(DebugMode.class);
private final Settings settings;
private final LoggerContext context;
@Inject
public DebugMode(Settings settings, LoggerContext context) {
public DebugMode(Settings settings) {
this.settings = settings;
this.context = context;
}
public void initialize() {
@@ -40,19 +35,13 @@ public class DebugMode {
}
private void setLogLevels(boolean debugMode) {
var configurator = LogbackConfiguratorFactory.provider();
if (debugMode) {
setLogLevels(LoggerModule.DEBUG_LOG_LEVELS);
configurator.setLogLevels(LogbackConfigurator.DEBUG_LOG_LEVELS);
LOG.debug("Debug mode enabled");
} else {
LOG.debug("Debug mode disabled");
setLogLevels(LoggerModule.DEFAULT_LOG_LEVELS);
}
}
private void setLogLevels(Map<String, Level> logLevels) {
for (Map.Entry<String, Level> loglevel : logLevels.entrySet()) {
Logger logger = context.getLogger(loglevel.getKey());
logger.setLevel(loglevel.getValue());
configurator.setLogLevels(LogbackConfigurator.DEFAULT_LOG_LEVELS);
}
}
@@ -0,0 +1,145 @@
package org.cryptomator.logging;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.Configurator;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.FileAppender;
import ch.qos.logback.core.helpers.NOPAppender;
import ch.qos.logback.core.rolling.FixedWindowRollingPolicy;
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.spi.ContextAwareBase;
import ch.qos.logback.core.util.FileSize;
import org.cryptomator.common.Environment;
import java.nio.file.Path;
import java.util.Map;
public class LogbackConfigurator extends ContextAwareBase implements Configurator {
private static final String LOG_PATTERN = "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n";
private static final String UPGRADE_FILENAME = "upgrade.log";
private static final String LOGFILE_NAME = "cryptomator0.log";
private static final String LOGFILE_ROLLING_PATTERN = "cryptomator%i.log";
private static final int LOGFILE_ROLLING_MIN = 1;
private static final int LOGFILE_ROLLING_MAX = 9;
private static final String LOG_MAX_SIZE = "100mb";
static final Map<String, Level> DEFAULT_LOG_LEVELS = Map.of( //
Logger.ROOT_LOGGER_NAME, Level.INFO, //
"org.cryptomator", Level.INFO //
);
static final Map<String, Level> DEBUG_LOG_LEVELS = Map.of( //
Logger.ROOT_LOGGER_NAME, Level.INFO, //
"org.cryptomator", Level.TRACE //
);
LogbackConfigurator() {}
/**
* Adjust the log levels
*
* @param logLevels new log levels to use
*/
void setLogLevels(Map<String, Level> logLevels) {
if (context instanceof LoggerContext lc) {
for (var loglevel : logLevels.entrySet()) {
Logger logger = lc.getLogger(loglevel.getKey());
logger.setLevel(loglevel.getValue());
}
}
}
@Override
public ExecutionStatus configure(LoggerContext context) {
var useCustomCfg = Environment.getInstance().useCustomLogbackConfig();
var logDir = Environment.getInstance().getLogDir().orElse(null);
if (useCustomCfg) {
addInfo("Using external logback configuration file.");
} else {
// configure appenders:
var stdout = stdOutAppender(context);
var noop = noopAppender(context);
var file = logDir == null ? noop : fileAppender(context, logDir);
var upgrade = logDir == null ? noop : upgradeAppender(context, logDir);
// configure loggers:
for (var loglevel : DEFAULT_LOG_LEVELS.entrySet()) {
Logger logger = context.getLogger(loglevel.getKey());
logger.setLevel(loglevel.getValue());
logger.setAdditive(false);
logger.addAppender(stdout);
logger.addAppender(file);
}
// configure upgrade logger:
Logger upgrades = context.getLogger("org.cryptomator.cryptofs.migration");
upgrades.setLevel(Level.DEBUG);
upgrades.addAppender(stdout);
upgrades.addAppender(upgrade);
upgrades.addAppender(file);
upgrades.setAdditive(false);
}
return ExecutionStatus.DO_NOT_INVOKE_NEXT_IF_ANY;
}
private Appender<ILoggingEvent> noopAppender(LoggerContext context) {
var appender = new NOPAppender<ILoggingEvent>();
appender.setContext(context);
return appender;
}
private Appender<ILoggingEvent> stdOutAppender(LoggerContext context) {
var appender = new ConsoleAppender<ILoggingEvent>();
appender.setContext(context);
appender.setName("STDOUT");
appender.setEncoder(encoder(context));
appender.start();
return appender;
}
private Appender<ILoggingEvent> upgradeAppender(LoggerContext context, Path logDir) {
var appender = new FileAppender<ILoggingEvent>();
appender.setContext(context);
appender.setName("UPGRADE");
appender.setFile(logDir.resolve(UPGRADE_FILENAME).toString());
appender.setEncoder(encoder(context));
appender.start();
return appender;
}
private Appender<ILoggingEvent> fileAppender(LoggerContext context, Path logDir) {
var appender = new RollingFileAppender<ILoggingEvent>();
appender.setContext(context);
appender.setName("FILE");
appender.setFile(logDir.resolve(LOGFILE_NAME).toString());
appender.setEncoder(encoder(context));
var triggeringPolicy = new LaunchAndSizeBasedTriggeringPolicy<ILoggingEvent>(FileSize.valueOf(LOG_MAX_SIZE));
triggeringPolicy.setContext(context);
triggeringPolicy.start();
appender.setTriggeringPolicy(triggeringPolicy);
var rollingPolicy = new FixedWindowRollingPolicy();
rollingPolicy.setContext(context);
rollingPolicy.setFileNamePattern(logDir.resolve(LOGFILE_ROLLING_PATTERN).toString());
rollingPolicy.setMinIndex(LOGFILE_ROLLING_MIN);
rollingPolicy.setMaxIndex(LOGFILE_ROLLING_MAX);
rollingPolicy.setParent(appender);
rollingPolicy.start();
appender.setRollingPolicy(rollingPolicy);
appender.start();
return appender;
}
private PatternLayoutEncoder encoder(LoggerContext context) {
var encoder = new PatternLayoutEncoder();
encoder.setContext(context);
encoder.setPattern(LOG_PATTERN);
encoder.start();
return encoder;
}
}
@@ -0,0 +1,12 @@
package org.cryptomator.logging;
public class LogbackConfiguratorFactory {
public static LogbackConfigurator provider() {
final class Holder {
private static final LogbackConfigurator INSTANCE = new LogbackConfigurator();
}
return Holder.INSTANCE;
}
}
@@ -1,70 +0,0 @@
package org.cryptomator.logging;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import org.cryptomator.common.Environment;
import org.cryptomator.common.ShutdownHook;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.util.Map;
@Singleton
public class LoggerConfiguration {
private final LoggerContext context;
private final Environment environment;
private final Appender<ILoggingEvent> stdout;
private final Appender<ILoggingEvent> upgrade;
private final Appender<ILoggingEvent> file;
private final ShutdownHook shutdownHook;
@Inject
LoggerConfiguration(LoggerContext context, //
Environment environment, //
@Named("stdoutAppender") Appender<ILoggingEvent> stdout, //
@Named("upgradeAppender") Appender<ILoggingEvent> upgrade, //
@Named("fileAppender") Appender<ILoggingEvent> file, //
ShutdownHook shutdownHook) {
this.context = context;
this.environment = environment;
this.stdout = stdout;
this.upgrade = upgrade;
this.file = file;
this.shutdownHook = shutdownHook;
}
public void init() {
if (environment.useCustomLogbackConfig()) {
Logger root = context.getLogger(Logger.ROOT_LOGGER_NAME);
root.info("Using external logback configuration file.");
} else {
context.reset();
// configure loggers:
for (Map.Entry<String, Level> loglevel : LoggerModule.DEFAULT_LOG_LEVELS.entrySet()) {
Logger logger = context.getLogger(loglevel.getKey());
logger.setLevel(loglevel.getValue());
logger.setAdditive(false);
logger.addAppender(stdout);
logger.addAppender(file);
}
// configure upgrade logger:
Logger upgrades = context.getLogger("org.cryptomator.cryptofs.migration");
upgrades.setLevel(Level.DEBUG);
upgrades.addAppender(stdout);
upgrades.addAppender(upgrade);
upgrades.addAppender(file);
upgrades.setAdditive(false);
// add shutdown hook
shutdownHook.runOnShutdown(ShutdownHook.PRIO_LAST, context::stop);
}
}
}
@@ -1,130 +0,0 @@
package org.cryptomator.logging;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.FileAppender;
import ch.qos.logback.core.helpers.NOPAppender;
import ch.qos.logback.core.rolling.FixedWindowRollingPolicy;
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.util.FileSize;
import dagger.Module;
import dagger.Provides;
import org.cryptomator.common.Environment;
import org.slf4j.ILoggerFactory;
import org.slf4j.LoggerFactory;
import javax.inject.Named;
import javax.inject.Singleton;
import java.nio.file.Path;
import java.util.Map;
@Module
public class LoggerModule {
private static final String UPGRADE_FILENAME = "upgrade.log";
private static final String LOGFILE_NAME = "cryptomator0.log";
private static final String LOGFILE_ROLLING_PATTERN = "cryptomator%i.log";
private static final int LOGFILE_ROLLING_MIN = 1;
private static final int LOGFILE_ROLLING_MAX = 9;
private static final String LOG_PATTERN = "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n";
private static final String LOG_MAX_SIZE = "100mb";
static final Map<String, Level> DEFAULT_LOG_LEVELS = Map.of( //
Logger.ROOT_LOGGER_NAME, Level.INFO, //
"org.cryptomator", Level.INFO //
);
static final Map<String, Level> DEBUG_LOG_LEVELS = Map.of( //
Logger.ROOT_LOGGER_NAME, Level.INFO, //
"org.cryptomator", Level.TRACE //
);
@Provides
@Singleton
static LoggerContext provideLoggerContext() {
ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory();
if (loggerFactory instanceof LoggerContext context) {
return context;
} else {
throw new IllegalStateException("SLF4J not bound to Logback.");
}
}
@Provides
@Singleton
static PatternLayoutEncoder provideLayoutEncoder(LoggerContext context) {
PatternLayoutEncoder ple = new PatternLayoutEncoder();
ple.setPattern(LOG_PATTERN);
ple.setContext(context);
ple.start();
return ple;
}
@Provides
@Singleton
@Named("stdoutAppender")
static Appender<ILoggingEvent> provideStdoutAppender(LoggerContext context, PatternLayoutEncoder encoder) {
ConsoleAppender<ILoggingEvent> appender = new ConsoleAppender<>();
appender.setContext(context);
appender.setEncoder(encoder);
appender.start();
return appender;
}
@Provides
@Singleton
@Named("fileAppender")
static Appender<ILoggingEvent> provideFileAppender(LoggerContext context, PatternLayoutEncoder encoder, Environment environment) {
var optionalLogDir = environment.getLogDir();
if (optionalLogDir.isPresent()) {
Path logDir = optionalLogDir.get();
RollingFileAppender<ILoggingEvent> appender = new RollingFileAppender<>();
appender.setContext(context);
appender.setFile(logDir.resolve(LOGFILE_NAME).toString());
appender.setEncoder(encoder);
LaunchAndSizeBasedTriggeringPolicy triggeringPolicy = new LaunchAndSizeBasedTriggeringPolicy(FileSize.valueOf(LOG_MAX_SIZE));
triggeringPolicy.setContext(context);
triggeringPolicy.start();
appender.setTriggeringPolicy(triggeringPolicy);
FixedWindowRollingPolicy rollingPolicy = new FixedWindowRollingPolicy();
rollingPolicy.setContext(context);
rollingPolicy.setFileNamePattern(logDir.resolve(LOGFILE_ROLLING_PATTERN).toString());
rollingPolicy.setMinIndex(LOGFILE_ROLLING_MIN);
rollingPolicy.setMaxIndex(LOGFILE_ROLLING_MAX);
rollingPolicy.setParent(appender);
rollingPolicy.start();
appender.setRollingPolicy(rollingPolicy);
appender.start();
return appender;
} else {
NOPAppender appender = new NOPAppender<>();
appender.setContext(context);
return appender;
}
}
@Provides
@Singleton
@Named("upgradeAppender")
static Appender<ILoggingEvent> provideUpgradeAppender(LoggerContext context, PatternLayoutEncoder encoder, Environment environment) {
var optionalLogDir = environment.getLogDir();
if (optionalLogDir.isPresent()) {
FileAppender<ILoggingEvent> appender = new FileAppender<>();
appender.setFile(optionalLogDir.get().resolve(UPGRADE_FILENAME).toString());
appender.setContext(context);
appender.setEncoder(encoder);
appender.start();
return appender;
} else {
NOPAppender appender = new NOPAppender<>();
appender.setContext(context);
return appender;
}
}
}
@@ -10,7 +10,6 @@ import javax.inject.Named;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
@@ -33,8 +32,6 @@ public class CreateNewVaultNameController implements FxController {
private final ObjectProperty<Path> vaultPath;
private final StringProperty vaultName;
private final BooleanBinding validVaultName;
private final BooleanBinding invalidVaultName;
private final StringBinding warningText;
@Inject
CreateNewVaultNameController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy<Scene> welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy<Scene> chooseLocationScene, ObjectProperty<Path> vaultPath, @Named("vaultName") StringProperty vaultName, ResourceBundle resourceBundle) {
@@ -44,20 +41,14 @@ public class CreateNewVaultNameController implements FxController {
this.vaultPath = vaultPath;
this.vaultName = vaultName;
this.validVaultName = Bindings.createBooleanBinding(this::isValidVaultName, vaultName);
this.invalidVaultName = validVaultName.not();
this.warningText = Bindings.when(vaultName.isNotEmpty().and(invalidVaultName)).then(resourceBundle.getString("addvaultwizard.new.invalidName")).otherwise((String) null);
}
@FXML
public void initialize() {
vaultName.bind(textField.textProperty());
vaultName.bindBidirectional(textField.textProperty());
vaultName.addListener(this::vaultNameChanged);
}
public boolean isValidVaultName() {
return vaultName.get() != null && VALID_NAME_PATTERN.matcher(vaultName.get().trim()).matches();
}
private void vaultNameChanged(@SuppressWarnings("unused") Observable observable) {
if (isValidVaultName()) {
if (vaultPath.get() != null) {
@@ -75,32 +66,17 @@ public class CreateNewVaultNameController implements FxController {
@FXML
public void next() {
window.setScene(chooseLocationScene.get());
vaultName.set(vaultName.get().trim());
}
/* Getter/Setter */
public BooleanBinding invalidVaultNameProperty() {
return invalidVaultName;
public BooleanBinding validVaultNameProperty() {
return validVaultName;
}
public boolean isInvalidVaultName() {
return invalidVaultName.get();
}
public StringBinding warningTextProperty() {
return warningText;
}
public String getWarningText() {
return warningText.get();
}
public BooleanBinding showWarningProperty() {
return warningText.isNotEmpty();
}
public boolean isShowWarning() {
return showWarningProperty().get();
public boolean isValidVaultName() {
return vaultName.get() != null && VALID_NAME_PATTERN.matcher(vaultName.get().trim()).matches();
}
}
@@ -102,7 +102,7 @@ public class CreateNewVaultPasswordController implements FxController {
this.masterkeyFileAccess = masterkeyFileAccess;
this.processing = new SimpleBooleanProperty();
this.readyToCreateVault = new SimpleBooleanProperty();
this.createVaultButtonState = Bindings.createObjectBinding(this::getCreateVaultButtonState, processing);
this.createVaultButtonState = Bindings.when(processing).then(ContentDisplay.LEFT).otherwise(ContentDisplay.TEXT_ONLY);
}
@FXML
@@ -231,6 +231,6 @@ public class CreateNewVaultPasswordController implements FxController {
}
public ContentDisplay getCreateVaultButtonState() {
return processing.get() ? ContentDisplay.LEFT : ContentDisplay.TEXT_ONLY;
return createVaultButtonState.get();
}
}
@@ -12,15 +12,15 @@ import javafx.beans.value.ObservableValue;
* <p>
* During creation the consumer can optionally define actions to be executed everytime before the animation starts and after it stops.
*/
public class AutoAnimator<T extends Animation> {
public class AutoAnimator {
private final T animation;
private final Animation animation;
private final ObservableValue<Boolean> condition;
private final Runnable beforeStart;
private final Runnable afterStop;
private final Subscription sub;
AutoAnimator(T animation, ObservableValue<Boolean> condition, Runnable beforeStart, Runnable afterStop) {
AutoAnimator(Animation animation, ObservableValue<Boolean> condition, Runnable beforeStart, Runnable afterStop) {
this.animation = animation;
this.condition = condition;
this.beforeStart = beforeStart;
@@ -52,7 +52,7 @@ public class AutoAnimator<T extends Animation> {
public static class Builder {
private Animation animation;
private final Animation animation;
private ObservableValue<Boolean> condition = new SimpleBooleanProperty(true);
private Runnable beforeStart = () -> {};
private Runnable afterStop = () -> {};
@@ -77,7 +77,7 @@ public class ErrorController implements FxController {
var enhancedTemplate = String.format(REPORT_BODY_TEMPLATE, //
System.getProperty("os.name"), //
System.getProperty("os.version"), //
environment.getAppVersion().orElse("undefined"), //
environment.getAppVersion(), //
environment.getBuildNumber().orElse("undefined"));
var body = URLEncoder.encode(enhancedTemplate, StandardCharsets.UTF_8);
application.getHostServices().showDocument(REPORT_URL_FORMAT.formatted(title, body));
@@ -13,6 +13,13 @@ public enum FxmlFile {
FORGET_PASSWORD("/fxml/forget_password.fxml"), //
HEALTH_START("/fxml/health_start.fxml"), //
HEALTH_CHECK_LIST("/fxml/health_check_list.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"), //
HUB_REGISTER_DEVICE("/fxml/hub_register_device.fxml"), //
HUB_REGISTER_SUCCESS("/fxml/hub_register_success.fxml"), //
HUB_REGISTER_FAILED("/fxml/hub_register_failed.fxml"),
HUB_UNAUTHORIZED_DEVICE("/fxml/hub_unauthorized_device.fxml"), //
LOCK_FORCED("/fxml/lock_forced.fxml"), //
LOCK_FAILED("/fxml/lock_failed.fxml"), //
MAIN_WINDOW("/fxml/main_window.fxml"), //
@@ -23,9 +30,11 @@ public enum FxmlFile {
MIGRATION_SUCCESS("/fxml/migration_success.fxml"), //
PREFERENCES("/fxml/preferences.fxml"), //
QUIT("/fxml/quit.fxml"), //
QUIT_FORCED("/fxml/quit_forced.fxml"), //
RECOVERYKEY_CREATE("/fxml/recoverykey_create.fxml"), //
RECOVERYKEY_RECOVER("/fxml/recoverykey_recover.fxml"), //
RECOVERYKEY_RESET_PASSWORD("/fxml/recoverykey_reset_password.fxml"), //
RECOVERYKEY_RESET_PASSWORD_SUCCESS("/fxml/recoverykey_reset_password_success.fxml"), //
RECOVERYKEY_SUCCESS("/fxml/recoverykey_success.fxml"), //
REMOVE_VAULT("/fxml/remove_vault.fxml"), //
UNLOCK_ENTER_PASSWORD("/fxml/unlock_enter_password.fxml"),
@@ -1,25 +0,0 @@
package org.cryptomator.ui.common;
import dagger.Lazy;
import org.cryptomator.common.vaults.Volume;
import org.cryptomator.ui.fxapp.FxApplicationScoped;
import javax.inject.Inject;
import javafx.application.Application;
import java.nio.file.Path;
@FxApplicationScoped
public class HostServiceRevealer implements Volume.Revealer {
private final Lazy<Application> application;
@Inject
public HostServiceRevealer(Lazy<Application> application) {
this.application = application;
}
@Override
public void reveal(Path p) throws Volume.VolumeException {
application.get().getHostServices().showDocument(p.toUri().toString());
}
}
@@ -1,6 +1,5 @@
package org.cryptomator.ui.common;
import com.tobiasdiez.easybind.EasyBind;
import org.cryptomator.ui.controls.FontAwesome5IconView;
import org.cryptomator.ui.controls.NiceSecurePasswordField;
@@ -42,7 +41,7 @@ public class NewPasswordController implements FxController {
passwordStrength.bind(Bindings.createIntegerBinding(() -> strengthRater.computeRate(passwordField.getCharacters()), passwordField.textProperty()));
passwordStrengthLabel.graphicProperty().bind(Bindings.createObjectBinding(this::getIconViewForPasswordStrengthLabel, passwordField.textProperty(), passwordStrength));
passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
passwordStrengthLabel.textProperty().bind(passwordStrength.map(strengthRater::getStrengthDescription));
BooleanBinding passwordsMatch = Bindings.createBooleanBinding(this::passwordFieldsMatch, passwordField.textProperty(), reenterField.textProperty());
BooleanBinding reenterFieldNotEmpty = reenterField.textProperty().isNotEmpty();
@@ -1,16 +1,20 @@
package org.cryptomator.ui.common;
import org.cryptomator.common.vaults.LockNotCompletedException;
import dagger.Lazy;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.common.vaults.Volume;
import org.cryptomator.integrations.mount.Mountpoint;
import org.cryptomator.integrations.mount.UnmountFailedException;
import org.cryptomator.ui.fxapp.FxApplicationScoped;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.application.Application;
import javafx.application.HostServices;
import javafx.concurrent.Task;
import javafx.stage.Stage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
@@ -24,13 +28,13 @@ public class VaultService {
private static final Logger LOG = LoggerFactory.getLogger(VaultService.class);
private final Lazy<Application> application;
private final ExecutorService executorService;
private final HostServiceRevealer vaultRevealer;
@Inject
public VaultService(ExecutorService executorService, HostServiceRevealer vaultRevealer) {
public VaultService(Lazy<Application> application, ExecutorService executorService) {
this.application = application;
this.executorService = executorService;
this.vaultRevealer = vaultRevealer;
}
public void reveal(Vault vault) {
@@ -43,7 +47,7 @@ public class VaultService {
* @param vault The vault to reveal
*/
public Task<Vault> createRevealTask(Vault vault) {
Task<Vault> task = new RevealVaultTask(vault, vaultRevealer);
Task<Vault> task = new RevealVaultTask(vault, application.get().getHostServices());
task.setOnSucceeded(evt -> LOG.info("Revealed {}", vault.getDisplayName()));
task.setOnFailed(evt -> LOG.error("Failed to reveal " + vault.getDisplayName(), evt.getSource().getException()));
return task;
@@ -86,7 +90,8 @@ public class VaultService {
}
/**
* Creates but doesn't start a lock-all task.
* Creates a lock-all task.
* This task itself is _not started_, but its subtasks locking each vault will be already executed.
*
* @param vaults The list of vaults to be locked
* @param forced Whether to attempt a forced lock
@@ -105,22 +110,21 @@ public class VaultService {
private static class RevealVaultTask extends Task<Vault> {
private final Vault vault;
private final Volume.Revealer revealer;
private final HostServices hostServices;
/**
* @param vault The vault to lock
* @param revealer The object to use to show the vault content to the user.
*/
public RevealVaultTask(Vault vault, Volume.Revealer revealer) {
public RevealVaultTask(Vault vault, HostServices hostServices) {
this.vault = vault;
this.revealer = revealer;
this.hostServices = hostServices;
setOnFailed(evt -> LOG.error("Failed to reveal " + vault.getDisplayName(), getException()));
}
@Override
protected Vault call() throws Volume.VolumeException {
vault.reveal(revealer);
protected Vault call() {
switch (vault.getMountPoint()) {
case null -> LOG.warn("Not currently mounted");
case Mountpoint.WithPath m -> hostServices.showDocument(m.uri().toString());
case Mountpoint.WithUri m -> LOG.info("Vault mounted at {}", m.uri()); // TODO show in UI?
}
return vault;
}
}
@@ -179,7 +183,7 @@ public class VaultService {
}
@Override
protected Vault call() throws Volume.VolumeException, LockNotCompletedException {
protected Vault call() throws UnmountFailedException, IOException {
vault.lock(forced);
return vault;
}
@@ -46,7 +46,7 @@ public class NiceSecurePasswordField extends StackPane {
nonPrintableCharsIcon.managedProperty().bind(passwordField.containingNonPrintableCharsProperty());
revealPasswordIcon.setGlyph(FontAwesome5Icon.EYE);
revealPasswordIcon.glyphProperty().bind(Bindings.createObjectBinding(this::getRevealPasswordGlyph, revealPasswordButton.selectedProperty()));
revealPasswordIcon.glyphProperty().bind(Bindings.when(revealPasswordButton.selectedProperty()).then(FontAwesome5Icon.EYE_SLASH).otherwise(FontAwesome5Icon.EYE));
revealPasswordIcon.setGlyphSize(ICON_SIZE);
revealPasswordButton.setContentDisplay(ContentDisplay.LEFT);
@@ -61,10 +61,6 @@ public class NiceSecurePasswordField extends StackPane {
disabledProperty().addListener(this::disabledChanged);
}
private FontAwesome5Icon getRevealPasswordGlyph() {
return revealPasswordButton.isSelected() ? FontAwesome5Icon.EYE_SLASH : FontAwesome5Icon.EYE;
}
private void disabledChanged(@SuppressWarnings("unused") Observable observable) {
revealPasswordButton.setSelected(false);
}
@@ -71,9 +71,11 @@ public class SecurePasswordField extends TextField {
}
public void cut() {
//not implemented by design
}
public void copy() {
//not implemented by design
}
public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
@@ -43,9 +43,11 @@ public class ForgetPasswordController implements FxController {
LOG.debug("Forgot password for vault {}.", vault.getDisplayName());
confirmedResult.setValue(true);
} catch (KeychainAccessException e) {
LOG.error("Failed to remove entry from system keychain.", e);
LOG.error("Failed to delete passphrase from system keychain.", e);
confirmedResult.setValue(false);
}
} else {
LOG.warn("Keychain not supported. Doing nothing.");
}
window.close();
}
@@ -4,6 +4,8 @@ import org.cryptomator.common.vaults.Vault;
import javax.inject.Inject;
import javafx.collections.ObservableList;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
@FxApplicationScoped
public class AutoUnlocker {
@@ -18,9 +20,11 @@ public class AutoUnlocker {
}
public void unlock() {
vaults.stream().filter(Vault::isLocked).filter(v -> v.getVaultSettings().unlockAfterStartup().get()).forEach(v -> {
appWindows.startUnlockWorkflow(v, null);
});
vaults.stream().filter(Vault::isLocked) //
.filter(v -> v.getVaultSettings().unlockAfterStartup().get()) //
.<CompletionStage<Void>>reduce(CompletableFuture.completedFuture(null), //
(unlockFlow, v) -> unlockFlow.handle((voit, ex) -> appWindows.startUnlockWorkflow(v, null)).thenCompose(stage -> stage), //we don't care here about the exception, logged elsewhere
(unlockChain1, unlockChain2) -> unlockChain1.handle((voit, ex) -> unlockChain2).thenCompose(stage -> stage));
}
}
@@ -14,6 +14,7 @@ import org.cryptomator.ui.lock.LockComponent;
import org.cryptomator.ui.mainwindow.MainWindowComponent;
import org.cryptomator.ui.preferences.PreferencesComponent;
import org.cryptomator.ui.quit.QuitComponent;
import org.cryptomator.ui.traymenu.TrayMenuComponent;
import org.cryptomator.ui.unlock.UnlockComponent;
@@ -57,4 +58,4 @@ abstract class FxApplicationModule {
static QuitComponent provideQuitComponent(QuitComponent.Builder builder) {
return builder.build();
}
}
}
@@ -2,10 +2,11 @@ package org.cryptomator.ui.fxapp;
import com.google.common.base.Preconditions;
import org.cryptomator.common.ShutdownHook;
import org.cryptomator.common.vaults.LockNotCompletedException;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.common.vaults.Volume;
import org.cryptomator.integrations.mount.UnmountFailedException;
import org.cryptomator.ui.common.VaultService;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -16,6 +17,7 @@ import javafx.collections.ObservableList;
import java.awt.Desktop;
import java.awt.desktop.QuitResponse;
import java.awt.desktop.QuitStrategy;
import java.io.IOException;
import java.util.EnumSet;
import java.util.EventObject;
import java.util.Set;
@@ -27,18 +29,24 @@ import static org.cryptomator.common.vaults.VaultState.Value.*;
public class FxApplicationTerminator {
private static final Set<VaultState.Value> STATES_ALLOWING_TERMINATION = EnumSet.of(LOCKED, NEEDS_MIGRATION, MISSING, ERROR);
private static final Set<VaultState.Value> STATES_PREVENT_TERMINATION = EnumSet.of(PROCESSING);
private static final Logger LOG = LoggerFactory.getLogger(FxApplicationTerminator.class);
private final ObservableList<Vault> vaults;
private final ShutdownHook shutdownHook;
private final FxApplicationWindows appWindows;
private final AtomicBoolean allowQuitWithoutPrompt = new AtomicBoolean();
private final AtomicBoolean preventQuitWithGracefulLock = new AtomicBoolean();
private final Settings settings;
private final VaultService vaultService;
@Inject
public FxApplicationTerminator(ObservableList<Vault> vaults, ShutdownHook shutdownHook, FxApplicationWindows appWindows) {
public FxApplicationTerminator(ObservableList<Vault> vaults, ShutdownHook shutdownHook, FxApplicationWindows appWindows, Settings settings, VaultService vaultService) {
this.vaults = vaults;
this.shutdownHook = shutdownHook;
this.appWindows = appWindows;
this.settings = settings;
this.vaultService = vaultService;
}
public void initialize() {
@@ -72,6 +80,10 @@ public class FxApplicationTerminator {
private void vaultListChanged(@SuppressWarnings("unused") Observable observable) {
boolean allowSuddenTermination = vaults.stream().map(Vault::getState).allMatch(STATES_ALLOWING_TERMINATION::contains);
boolean stateChanged = allowQuitWithoutPrompt.compareAndSet(!allowSuddenTermination, allowSuddenTermination);
boolean preventGracefulTermination = vaults.stream().map(Vault::getState).anyMatch(STATES_PREVENT_TERMINATION::contains);
preventQuitWithGracefulLock.set(preventGracefulTermination);
Desktop desktop = Desktop.getDesktop();
if (stateChanged && desktop.isSupported(Desktop.Action.APP_SUDDEN_TERMINATION)) {
if (allowSuddenTermination) {
@@ -92,10 +104,22 @@ public class FxApplicationTerminator {
*/
private void handleQuitRequest(@SuppressWarnings("unused") @Nullable EventObject e, QuitResponse response) {
var exitingResponse = new ExitingQuitResponse(response);
if (allowQuitWithoutPrompt.get()) {
exitingResponse.performQuit();
} else if (settings.autoCloseVaults().get() && !preventQuitWithGracefulLock.get()) {
var lockAllTask = vaultService.createLockAllTask(vaults.filtered(Vault::isUnlocked), false);
lockAllTask.setOnSucceeded(event -> {
LOG.info("Locked remaining vaults was succesful.");
exitingResponse.performQuit();
});
lockAllTask.setOnFailed(event -> {
LOG.warn("Unable to lock all vaults.");
appWindows.showQuitWindow(exitingResponse, true);
});
lockAllTask.run();
} else {
appWindows.showQuitWindow(exitingResponse);
appWindows.showQuitWindow(exitingResponse, false);
}
}
@@ -104,10 +128,8 @@ public class FxApplicationTerminator {
if (vault.isUnlocked()) {
try {
vault.lock(true);
} catch (Volume.VolumeException e) {
} catch (UnmountFailedException | IOException e) {
LOG.error("Failed to unmount vault " + vault.getPath(), e);
} catch (LockNotCompletedException e) {
LOG.error("Failed to lock vault " + vault.getPath(), e);
}
}
}
@@ -115,7 +137,7 @@ public class FxApplicationTerminator {
/**
* A dummy QuitResponse that ignores the response.
*
* <p>
* To be used with {@link #handleQuitRequest(EventObject, QuitResponse)} if the invoking method is not interested in the response.
*/
private static class NoopQuitResponse implements QuitResponse {
@@ -40,7 +40,7 @@ public class FxApplicationWindows {
private final Optional<TrayIntegrationProvider> trayIntegration;
private final Lazy<MainWindowComponent> mainWindow;
private final Lazy<PreferencesComponent> preferencesWindow;
private final Lazy<QuitComponent> quitWindow;
private final QuitComponent.Builder quitWindowBuilder;
private final UnlockComponent.Factory unlockWorkflowFactory;
private final LockComponent.Factory lockWorkflowFactory;
private final ErrorComponent.Factory errorWindowFactory;
@@ -48,12 +48,12 @@ public class FxApplicationWindows {
private final FilteredList<Window> visibleWindows;
@Inject
public FxApplicationWindows(@PrimaryStage Stage primaryStage, Optional<TrayIntegrationProvider> trayIntegration, Lazy<MainWindowComponent> mainWindow, Lazy<PreferencesComponent> preferencesWindow, Lazy<QuitComponent> quitWindow, UnlockComponent.Factory unlockWorkflowFactory, LockComponent.Factory lockWorkflowFactory, ErrorComponent.Factory errorWindowFactory, ExecutorService executor) {
public FxApplicationWindows(@PrimaryStage Stage primaryStage, Optional<TrayIntegrationProvider> trayIntegration, Lazy<MainWindowComponent> mainWindow, Lazy<PreferencesComponent> preferencesWindow, QuitComponent.Builder quitWindowBuilder, UnlockComponent.Factory unlockWorkflowFactory, LockComponent.Factory lockWorkflowFactory, ErrorComponent.Factory errorWindowFactory, ExecutorService executor) {
this.primaryStage = primaryStage;
this.trayIntegration = trayIntegration;
this.mainWindow = mainWindow;
this.preferencesWindow = preferencesWindow;
this.quitWindow = quitWindow;
this.quitWindowBuilder = quitWindowBuilder;
this.unlockWorkflowFactory = unlockWorkflowFactory;
this.lockWorkflowFactory = lockWorkflowFactory;
this.errorWindowFactory = errorWindowFactory;
@@ -104,8 +104,8 @@ public class FxApplicationWindows {
return CompletableFuture.supplyAsync(() -> preferencesWindow.get().showPreferencesWindow(selectedTab), Platform::runLater).whenComplete(this::reportErrors);
}
public CompletionStage<Stage> showQuitWindow(QuitResponse response) {
return CompletableFuture.supplyAsync(() -> quitWindow.get().showQuitWindow(response), Platform::runLater).whenComplete(this::reportErrors);
public void showQuitWindow(QuitResponse response, boolean forced) {
CompletableFuture.runAsync(() -> quitWindowBuilder.build().showQuitWindow(response,forced), Platform::runLater);
}
public CompletionStage<Void> startUnlockWorkflow(Vault vault, @Nullable Stage owner) {
@@ -9,14 +9,12 @@ import javax.inject.Inject;
import javax.inject.Named;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Worker;
import javafx.concurrent.WorkerStateEvent;
import javafx.util.Duration;
import java.util.Comparator;
import java.util.Optional;
@FxApplicationScoped
public class UpdateChecker {
@@ -25,8 +23,7 @@ public class UpdateChecker {
private static final Duration AUTOCHECK_DELAY = Duration.seconds(5);
private final Settings settings;
private final Optional<String> applicationVersion;
private final StringProperty currentVersionProperty;
private final String currentVersion;
private final StringProperty latestVersionProperty;
private final Comparator<String> semVerComparator;
private final ScheduledService<String> updateCheckerService;
@@ -34,11 +31,10 @@ public class UpdateChecker {
@Inject
UpdateChecker(Settings settings, Environment env, @Named("latestVersion") StringProperty latestVersionProperty, @Named("SemVer") Comparator<String> semVerComparator, ScheduledService<String> updateCheckerService) {
this.settings = settings;
this.applicationVersion = env.getAppVersion();
this.latestVersionProperty = latestVersionProperty;
this.semVerComparator = semVerComparator;
this.updateCheckerService = updateCheckerService;
this.currentVersionProperty = new SimpleStringProperty(applicationVersion.orElse("SNAPSHOT"));
this.currentVersion = env.getAppVersion();
}
public void automaticallyCheckForUpdatesIfEnabled() {
@@ -66,11 +62,10 @@ public class UpdateChecker {
}
private void checkSucceeded(WorkerStateEvent event) {
String currentVersion = applicationVersion.orElse(null);
String latestVersion = updateCheckerService.getValue();
LOG.info("Current version: {}, lastest version: {}", currentVersion, latestVersion);
if (currentVersion == null || semVerComparator.compare(currentVersion, latestVersion) < 0) {
if (semVerComparator.compare(currentVersion, latestVersion) < 0) {
// update is available
latestVersionProperty.set(latestVersion);
} else {
@@ -92,8 +87,8 @@ public class UpdateChecker {
return latestVersionProperty;
}
public ReadOnlyStringProperty currentVersionProperty() {
return currentVersionProperty;
public String getCurrentVersion() {
return currentVersion;
}
}
@@ -56,7 +56,7 @@ public abstract class UpdateCheckerModule {
@FxApplicationScoped
static HttpRequest provideCheckForUpdatesRequest(Environment env) {
String userAgent = String.format("Cryptomator VersionChecker/%s %s %s (%s)", //
env.getAppVersion().orElse("SNAPSHOT"), //
env.getAppVersion(), //
SystemUtils.OS_NAME, //
SystemUtils.OS_VERSION, //
SystemUtils.OS_ARCH); //
@@ -3,12 +3,12 @@ package org.cryptomator.ui.health;
import com.tobiasdiez.easybind.EasyBind;
import com.tobiasdiez.easybind.EasyObservableList;
import com.tobiasdiez.easybind.Subscription;
import com.tobiasdiez.easybind.optional.OptionalBinding;
import org.cryptomator.cryptofs.health.api.DiagnosticResult;
import org.cryptomator.ui.common.FxController;
import javax.inject.Inject;
import javafx.beans.binding.Binding;
import javafx.beans.binding.BooleanExpression;
import javafx.beans.property.ObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
@@ -22,15 +22,15 @@ public class CheckDetailController implements FxController {
private final EasyObservableList<Result> results;
private final ObjectProperty<Check> check;
private final OptionalBinding<Check.CheckState> checkState;
private final Binding<String> checkName;
private final Binding<Boolean> checkRunning;
private final Binding<Boolean> checkScheduled;
private final Binding<Boolean> checkFinished;
private final Binding<Boolean> checkSkipped;
private final Binding<Boolean> checkSucceeded;
private final Binding<Boolean> checkFailed;
private final Binding<Boolean> checkCancelled;
private final ObservableValue<Check.CheckState> checkState;
private final ObservableValue<String> checkName;
private final BooleanExpression checkRunning;
private final BooleanExpression checkScheduled;
private final BooleanExpression checkFinished;
private final BooleanExpression checkSkipped;
private final BooleanExpression checkSucceeded;
private final BooleanExpression checkFailed;
private final BooleanExpression checkCancelled;
private final Binding<Number> countOfWarnSeverity;
private final Binding<Number> countOfCritSeverity;
private final Binding<Boolean> warnOrCritsExist;
@@ -44,15 +44,15 @@ public class CheckDetailController implements FxController {
this.resultListCellFactory = resultListCellFactory;
this.results = EasyBind.wrapList(FXCollections.observableArrayList());
this.check = selectedTask;
this.checkState = EasyBind.wrapNullable(selectedTask).mapObservable(Check::stateProperty);
this.checkName = EasyBind.wrapNullable(selectedTask).map(Check::getName).orElse("");
this.checkRunning = checkState.map(Check.CheckState.RUNNING::equals).orElse(false);
this.checkScheduled = checkState.map(Check.CheckState.SCHEDULED::equals).orElse(false);
this.checkSkipped = checkState.map(Check.CheckState.SKIPPED::equals).orElse(false);
this.checkSucceeded = checkState.map(Check.CheckState.SUCCEEDED::equals).orElse(false);
this.checkFailed = checkState.map(Check.CheckState.ERROR::equals).orElse(false);
this.checkCancelled = checkState.map(Check.CheckState.CANCELLED::equals).orElse(false);
this.checkFinished = EasyBind.combine(checkSucceeded, checkFailed, checkCancelled, (a, b, c) -> a || b || c);
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.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) );
@@ -84,7 +84,7 @@ public class CheckDetailController implements FxController {
return checkName.getValue();
}
public Binding<String> checkNameProperty() {
public ObservableValue<String> checkNameProperty() {
return checkName;
}
@@ -108,7 +108,7 @@ public class CheckDetailController implements FxController {
return checkRunning.getValue();
}
public Binding<Boolean> checkRunningProperty() {
public BooleanExpression checkRunningProperty() {
return checkRunning;
}
@@ -116,7 +116,7 @@ public class CheckDetailController implements FxController {
return checkFinished.getValue();
}
public Binding<Boolean> checkFinishedProperty() {
public BooleanExpression checkFinishedProperty() {
return checkFinished;
}
@@ -124,7 +124,7 @@ public class CheckDetailController implements FxController {
return checkScheduled.getValue();
}
public Binding<Boolean> checkScheduledProperty() {
public BooleanExpression checkScheduledProperty() {
return checkScheduled;
}
@@ -132,7 +132,7 @@ public class CheckDetailController implements FxController {
return checkSkipped.getValue();
}
public Binding<Boolean> checkSkippedProperty() {
public BooleanExpression checkSkippedProperty() {
return checkSkipped;
}
@@ -140,7 +140,7 @@ public class CheckDetailController implements FxController {
return checkSucceeded.getValue();
}
public Binding<Boolean> checkSucceededProperty() {
public BooleanExpression checkSucceededProperty() {
return checkSucceeded;
}
@@ -148,7 +148,7 @@ public class CheckDetailController implements FxController {
return checkFailed.getValue();
}
public Binding<Boolean> checkFailedProperty() {
public BooleanExpression checkFailedProperty() {
return checkFailed;
}
@@ -164,7 +164,7 @@ public class CheckDetailController implements FxController {
return warnOrCritsExist.getValue();
}
public Binding<Boolean> checkCancelledProperty() {
public BooleanExpression checkCancelledProperty() {
return checkCancelled;
}
@@ -1,21 +1,20 @@
package org.cryptomator.ui.health;
import com.tobiasdiez.easybind.EasyBind;
import org.cryptomator.ui.common.FxController;
import javax.inject.Inject;
import javafx.beans.binding.Binding;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.CheckBox;
public class CheckListCellController implements FxController {
private final ObjectProperty<Check> check;
private final Binding<String> checkName;
private final Binding<Boolean> checkRunnable;
private final ObservableValue<Boolean> checkRunnable;
private final ObservableValue<String> checkName;
/* FXML */
public CheckBox checkbox;
@@ -23,8 +22,8 @@ public class CheckListCellController implements FxController {
@Inject
public CheckListCellController() {
check = new SimpleObjectProperty<>();
checkRunnable = EasyBind.wrapNullable(check).mapObservable(Check::stateProperty).map(Check.CheckState.RUNNABLE::equals).orElse(false);
checkName = EasyBind.wrapNullable(check).map(Check::getName).orElse("");
checkRunnable = check.flatMap(Check::stateProperty).map(Check.CheckState.RUNNABLE::equals).orElse(false);
checkName = check.map(Check::getName).orElse("");
}
public void initialize() {
@@ -50,7 +49,7 @@ public class CheckListCellController implements FxController {
check.set(c);
}
public Binding<String> checkNameProperty() {
public ObservableValue<String> checkNameProperty() {
return checkName;
}
@@ -58,7 +57,7 @@ public class CheckListCellController implements FxController {
return checkName.getValue();
}
public Binding<Boolean> checkRunnableProperty() {
public ObservableValue<Boolean> checkRunnableProperty() {
return checkRunnable;
}
@@ -13,13 +13,12 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.application.Platform;
import javafx.beans.binding.Binding;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableObjectValue;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.control.Tooltip;
import javafx.util.Duration;
@@ -38,10 +37,10 @@ public class ResultListCellController implements FxController {
private final Logger LOG = LoggerFactory.getLogger(ResultListCellController.class);
private final ObjectProperty<Result> result;
private final ObservableObjectValue<DiagnosticResult.Severity> severity;
private final Binding<String> description;
private final ObservableValue<DiagnosticResult.Severity> severity;
private final ObservableValue<String> description;
private final ResultFixApplier fixApplier;
private final ObservableObjectValue<Result.FixState> fixState;
private final ObservableValue<Result.FixState> fixState;
private final ObjectBinding<FontAwesome5Icon> severityGlyph;
private final ObjectBinding<FontAwesome5Icon> fixGlyph;
private final BooleanBinding fixable;
@@ -62,10 +61,10 @@ public class ResultListCellController implements FxController {
@Inject
public ResultListCellController(ResultFixApplier fixApplier, ResourceBundle resourceBundle) {
this.result = new SimpleObjectProperty<>(null);
this.severity = EasyBind.wrapNullable(result).map(r -> r.diagnosis().getSeverity()).asOrdinary();
this.description = EasyBind.wrapNullable(result).map(Result::getDescription).orElse("");
this.severity = result.map(Result::diagnosis).map(DiagnosticResult::getSeverity);
this.description = result.map(Result::getDescription).orElse("");
this.fixApplier = fixApplier;
this.fixState = EasyBind.wrapNullable(result).mapObservable(Result::fixState).asOrdinary();
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);
@@ -83,14 +82,15 @@ public class ResultListCellController implements FxController {
@FXML
public void initialize() {
// see getGlyph() for relevant glyphs:
subscriptions.addAll(List.of(EasyBind.includeWhen(severityView.getStyleClass(), "glyph-icon-muted", Bindings.equal(severity, DiagnosticResult.Severity.INFO)), //
EasyBind.includeWhen(severityView.getStyleClass(), "glyph-icon-primary", Bindings.equal(severity, DiagnosticResult.Severity.GOOD)), //
EasyBind.includeWhen(severityView.getStyleClass(), "glyph-icon-orange", Bindings.equal(severity, DiagnosticResult.Severity.WARN)), //
EasyBind.includeWhen(severityView.getStyleClass(), "glyph-icon-red", Bindings.equal(severity, DiagnosticResult.Severity.CRITICAL)) //
subscriptions.addAll(List.of(EasyBind.includeWhen(severityView.getStyleClass(), "glyph-icon-muted", severity.map(DiagnosticResult.Severity.INFO::equals).orElse(false)), //
EasyBind.includeWhen(severityView.getStyleClass(), "glyph-icon-primary", severity.map(DiagnosticResult.Severity.GOOD::equals).orElse(false)), //
EasyBind.includeWhen(severityView.getStyleClass(), "glyph-icon-orange", severity.map(DiagnosticResult.Severity.WARN::equals).orElse(false)), //
EasyBind.includeWhen(severityView.getStyleClass(), "glyph-icon-red", severity.map(DiagnosticResult.Severity.CRITICAL::equals).orElse(false)) //
));
var animation = Animations.createDiscrete360Rotation(fixView);
this.fixRunningRotator = AutoAnimator.animate(animation) //
.onCondition(Bindings.equal(fixState, Result.FixState.FIXING)) //
.onCondition(fixing) //
.afterStop(() -> fixView.setRotate(0)) //
.build();
}
@@ -127,7 +127,7 @@ public class ResultListCellController implements FxController {
return result;
}
public Binding<String> descriptionProperty() {
public ObservableValue<String> descriptionProperty() {
return description;
}
@@ -173,7 +173,7 @@ public class ResultListCellController implements FxController {
}
public boolean isFixable() {
return Result.FixState.FIXABLE.equals(fixState.get());
return Result.FixState.FIXABLE.equals(fixState.getValue());
}
public BooleanBinding fixingProperty() {
@@ -181,7 +181,7 @@ public class ResultListCellController implements FxController {
}
public boolean isFixing() {
return Result.FixState.FIXING.equals(fixState.get());
return Result.FixState.FIXING.equals(fixState.getValue());
}
public BooleanBinding fixedProperty() {
@@ -189,7 +189,7 @@ public class ResultListCellController implements FxController {
}
public boolean isFixed() {
return Result.FixState.FIXED.equals(fixState.get());
return Result.FixState.FIXED.equals(fixState.getValue());
}
public BooleanBinding fixFailedProperty() {
@@ -197,7 +197,7 @@ public class ResultListCellController implements FxController {
}
public Boolean isFixFailed() {
return Result.FixState.FIX_FAILED.equals(fixState.get());
return Result.FixState.FIX_FAILED.equals(fixState.getValue());
}
public BooleanBinding fixRunningOrDoneProperty() {
@@ -6,6 +6,7 @@ import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.DefaultSceneFactory;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlLoaderFactory;
import org.cryptomator.ui.keyloading.hub.HubKeyLoadingModule;
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingModule;
import javax.inject.Provider;
@@ -13,7 +14,7 @@ import java.io.IOException;
import java.util.Map;
import java.util.ResourceBundle;
@Module(includes = {MasterkeyFileLoadingModule.class})
@Module(includes = {MasterkeyFileLoadingModule.class, HubKeyLoadingModule.class})
abstract class KeyLoadingModule {
@Provides
@@ -0,0 +1,5 @@
package org.cryptomator.ui.keyloading.hub;
record AuthFlowContext(String deviceId) {
}
@@ -0,0 +1,101 @@
package org.cryptomator.ui.keyloading.hub;
import com.nimbusds.jose.JWEObject;
import dagger.Lazy;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.keyloading.KeyLoading;
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.concurrent.WorkerStateEvent;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import java.net.URI;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicReference;
@KeyLoadingScoped
public class AuthFlowController implements FxController {
private final Application application;
private final Stage window;
private final ExecutorService executor;
private final String deviceId;
private final HubConfig hubConfig;
private final AtomicReference<String> tokenRef;
private final CompletableFuture<JWEObject> result;
private final Lazy<Scene> receiveKeyScene;
private final ObjectProperty<URI> authUri;
private AuthFlowTask task;
@Inject
public AuthFlowController(Application application, @KeyLoading Stage window, ExecutorService executor, @Named("deviceId") String deviceId, HubConfig hubConfig, @Named("bearerToken") AtomicReference<String> tokenRef, CompletableFuture<JWEObject> result, @FxmlScene(FxmlFile.HUB_RECEIVE_KEY) Lazy<Scene> receiveKeyScene) {
this.application = application;
this.window = window;
this.executor = executor;
this.deviceId = deviceId;
this.hubConfig = hubConfig;
this.tokenRef = tokenRef;
this.result = result;
this.receiveKeyScene = receiveKeyScene;
this.authUri = new SimpleObjectProperty<>();
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
}
@FXML
public void initialize() {
assert task == null;
task = new AuthFlowTask(hubConfig, new AuthFlowContext(deviceId), this::setAuthUri);
task.setOnFailed(this::authFailed);
task.setOnSucceeded(this::authSucceeded);
executor.submit(task);
}
@FXML
public void browse() {
application.getHostServices().showDocument(authUri.get().toString());
}
@FXML
public void cancel() {
window.close();
}
private void setAuthUri(URI uri) {
Platform.runLater(() -> {
authUri.set(uri);
browse();
});
}
private void windowClosed(WindowEvent windowEvent) {
// stop server, if it is still running
task.cancel();
result.cancel(true);
}
private void authSucceeded(WorkerStateEvent workerStateEvent) {
tokenRef.set(task.getValue());
window.requestFocus();
window.setScene(receiveKeyScene.get());
}
private void authFailed(WorkerStateEvent workerStateEvent) {
window.requestFocus();
var exception = workerStateEvent.getSource().getException();
result.completeExceptionally(exception);
}
}
@@ -0,0 +1,53 @@
package org.cryptomator.ui.keyloading.hub;
import com.google.gson.JsonParser;
import io.github.coffeelibs.tinyoauth2client.AuthFlow;
import io.github.coffeelibs.tinyoauth2client.TinyOAuth2;
import io.github.coffeelibs.tinyoauth2client.http.response.Response;
import javafx.concurrent.Task;
import java.io.IOException;
import java.net.URI;
import java.util.function.Consumer;
class AuthFlowTask extends Task<String> {
private final HubConfig hubConfig;
private final AuthFlowContext authFlowContext;
private final Consumer<URI> redirectUriConsumer;
/**
* Spawns a server and waits for the redirectUri to be called.
*
* @param hubConfig Configuration object holding parameters required by {@link AuthFlow}
* @param redirectUriConsumer A callback invoked with the redirectUri, as soon as the server has started
*/
public AuthFlowTask(HubConfig hubConfig, AuthFlowContext authFlowContext, Consumer<URI> redirectUriConsumer) {
this.hubConfig = hubConfig;
this.authFlowContext = authFlowContext;
this.redirectUriConsumer = redirectUriConsumer;
}
@Override
protected String call() throws IOException, InterruptedException {
var response = TinyOAuth2.client(hubConfig.clientId) //
.withTokenEndpoint(URI.create(hubConfig.tokenEndpoint)) //
.authFlow(URI.create(hubConfig.authEndpoint)) //
.setSuccessResponse(Response.redirect(URI.create(hubConfig.authSuccessUrl + "&device=" + authFlowContext.deviceId()))) //
.setErrorResponse(Response.redirect(URI.create(hubConfig.authErrorUrl + "&device=" + authFlowContext.deviceId()))) //
.authorize(redirectUriConsumer);
if (response.statusCode() != 200) {
throw new NotOkResponseException("Authorization returned status code " + response.statusCode());
}
var json = JsonParser.parseString(response.body());
return json.getAsJsonObject().get("access_token").getAsString();
}
public static class NotOkResponseException extends RuntimeException {
NotOkResponseException(String msg) {
super(msg);
}
}
}
@@ -0,0 +1,9 @@
package org.cryptomator.ui.keyloading.hub;
class CreateDeviceDto {
public String id;
public String name;
public String publicKey;
}
@@ -0,0 +1,23 @@
package org.cryptomator.ui.keyloading.hub;
import com.google.common.io.CharStreams;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
class HttpHelper {
public static String readBody(HttpResponse<InputStream> response) throws IOException {
try (var in = response.body(); var reader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
return CharStreams.toString(reader);
}
}
}

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