Compare commits

..

316 Commits
1.5.2 ... 1.5.9

Author SHA1 Message Date
Sebastian Stenzel
41492a951a Merge branch 'release/1.5.9' 2020-11-17 17:02:14 +01:00
Sebastian Stenzel
cbbfec5531 Merge branch 'develop' into release/1.5.9 2020-11-16 17:16:54 +01:00
Sebastian Stenzel
d7a4dff080 New Crowdin updates (#1401)
New translations strings.properties
[ci skip]
2020-11-16 17:15:37 +01:00
Armin Schrenk
8e56218fbb Update launcher-linux.sh
drastically reducing the allowed stacksize to 2m
2020-11-11 17:13:11 +01:00
Sebastian Stenzel
9c40386106 fix CI release build 2020-11-10 12:54:01 +01:00
Sebastian Stenzel
c865805f55 Preparing 1.5.9 2020-11-10 12:41:10 +01:00
Sebastian Stenzel
8e352301cd New Crowdin updates (#1392)
[ci skip]
2020-11-10 12:39:15 +01:00
Armin Schrenk
52db1e8bdd Merge pull request #1396 from cryptomator/feature/#1013-#1061-cleanupAndInformation 2020-11-10 12:33:23 +01:00
Armin Schrenk
1947623be8 final cleanup:
* renamed methods
* apply code style
2020-11-10 12:24:49 +01:00
Armin Schrenk
4f8602684d Merge branch 'develop' into feature/#1013-#1061-cleanupAndInformation 2020-11-10 12:08:23 +01:00
Armin Schrenk
75f66e40bf Add additional mountName crunching to be urlconform when WebDAV is used 2020-11-10 12:05:30 +01:00
Sebastian Stenzel
73587437cb fixes #1400 2020-11-10 08:27:47 +01:00
Sebastian Stenzel
18f6e70bba Merge branch 'develop' of https://github.com/cryptomator/cryptomator into develop
# Conflicts:
#	main/ui/src/main/resources/i18n/strings.properties
2020-11-09 17:53:18 +01:00
Sebastian Stenzel
e067b94aca fixed labels 2020-11-09 17:52:48 +01:00
Armin Schrenk
b04d5669fb Format lables the same way 2020-11-09 17:40:57 +01:00
Armin Schrenk
ad44af338d Merge pull request #1399 from cryptomator/feature/io-stats
closes #1070
2020-11-09 17:20:10 +01:00
Sebastian Stenzel
4f4ddbc3e0 allow GCing of Stats Window while remaining reusability and preventing opening multiple windows per vault 2020-11-09 13:49:28 +01:00
Sebastian Stenzel
4e9af1c713 cleanup 2020-11-09 13:12:42 +01:00
Sebastian Stenzel
9a94b6f95e fixed typo 2020-11-09 13:10:35 +01:00
Sebastian Stenzel
371b826aa0 Merge branch 'develop' into feature/io-stats 2020-11-09 13:05:44 +01:00
Sebastian Stenzel
d3fb90bf6d fixed NPE if winDriveLetter is null (on Linux/Mac) 2020-11-09 13:05:06 +01:00
Sebastian Stenzel
d24c478faa more dependency updates 2020-11-09 12:01:38 +01:00
Sebastian Stenzel
48d0aa423c fixed getter name 2020-11-09 11:54:15 +01:00
Sebastian Stenzel
5695fa2916 Merge branch 'develop' into feature/io-stats 2020-11-09 11:48:17 +01:00
Armin Schrenk
bcea1a4b32 Bumping dependencies:
* javafx to 15
* webdav-nio-server to 1.0.13
2020-11-09 11:17:52 +01:00
Armin Schrenk
2f8d4568fc Updating CryptoFS Dependency.
Closes #1293
2020-11-09 10:33:27 +01:00
Armin Schrenk
5b55318e52 Bumping integrations-linux dependency, removing unused legacy property
closes #1308
2020-11-09 10:28:07 +01:00
Armin Schrenk
459f2ea2c3 Updating Dokany-nio-adapter dependency
References #1228
2020-11-09 10:27:52 +01:00
Martin Beyer
d26147a34a bumped up depencency of cryptofs to 1.9.13 2020-11-08 20:51:47 +01:00
JaniruTEC
afb9d4f010 Fixed exception on startup if mount folder doesn't exist
IrregularUnmountCleaner should not be called if there is no folder that could contain mountpoints.
2020-11-06 18:44:47 +01:00
JaniruTEC
db3bf9723e Merge branch 'feature/additional-error-messages' into develop 2020-11-06 17:34:23 +01:00
JaniruTEC
3a5937b35f Added runLater to implementation of ErrorComponent#showErrorScene() 2020-11-06 17:09:44 +01:00
Sebastian Stenzel
f5cddda7a8 Merge branch 'develop' into feature/#1013-#1061-cleanupAndInformation 2020-11-06 15:17:45 +01:00
Sebastian Stenzel
e9fa1d6476 removed old jni dependency 2020-11-06 14:53:45 +01:00
Sebastian Stenzel
084c1881ba removed unused imports
[ci skip]
2020-11-06 14:47:47 +01:00
Sebastian Stenzel
ce131e4653 Implemented AutoStart integration for macOS 2020-11-06 14:46:32 +01:00
Tobias Hagemann
a121c7d0fe added tooltip to path in vault list cell 2020-11-06 14:42:21 +01:00
Sebastian Stenzel
603bf99b25 Replaced MacFunctionsUiState using new integrations api 2020-11-06 14:18:23 +01:00
Sebastian Stenzel
9076fe5b46 Reintroduced trimming of vault display name 2020-11-06 11:07:45 +01:00
Sebastian Stenzel
6848f1a38e Renamed IrregularUnmountCleaner and relayouted code 2020-11-06 11:07:22 +01:00
Sebastian Stenzel
f4103fc917 reducing complexity of normalizedDisplayName 2020-11-06 09:54:25 +01:00
Sebastian Stenzel
c838da9df4 reducing complexity for "vault name max length" 2020-11-06 09:52:31 +01:00
Sebastian Stenzel
78df4e24b3 keep iconography consistent by reusing already existing icons 2020-11-06 09:51:51 +01:00
Sebastian Stenzel
c0e2d01297 Migrated from deprecated MacFunctions to UiAppearanceProvider for UI theme switching 2020-11-05 17:49:39 +01:00
JaniruTEC
313f8b36d2 Replaced dependency with call to getter 2020-11-05 17:11:35 +01:00
JaniruTEC
343794c1fe Redo "Added UI error messages"
This reverts commit d331c302
2020-11-05 17:04:31 +01:00
Armin Schrenk
813c01aaed Use mountName instead of display name 2020-11-05 13:23:30 +01:00
Armin Schrenk
510ea8a6f6 refined normalize Method, fxied Tests for it:
* all unicode spaces are now replaced with \u0020
* if the end string only contains whitspaces, "_" will be returned
2020-11-05 12:44:51 +01:00
Armin Schrenk
a22fbea467 The display/vault name is now constrained:
* should not be empty
* is stripped of leading & trailing whitespaces
* is truncated to 50 characters
2020-11-05 11:55:25 +01:00
Armin Schrenk
f64144d1da Revert 1554437884 2020-11-05 11:52:38 +01:00
Armin Schrenk
c01dd225c9 Refactor normalizedMountname to only exclude certain, filesystem reserved characters or Unicode control sequences 2020-11-05 11:47:18 +01:00
Sebastian Stenzel
18b408a83a Adjusted keychain display names
[ci skip]
2020-11-05 11:15:39 +01:00
Sebastian Stenzel
efaf107b20 Merge pull request #1393 from cryptomator/feature/modular-keychains
Modularized Keychain Access, references #1301
2020-11-05 11:10:15 +01:00
Sebastian Stenzel
86cba5e8a7 partial revert of 6ccd2486 2020-11-04 16:11:16 +01:00
Sebastian Stenzel
f4cee6d161 made KeychainManager immutable
[ci skip]
2020-11-04 14:15:28 +01:00
Sebastian Stenzel
28c40b2277 WHY? 2020-11-04 14:13:53 +01:00
Armin Schrenk
8853054ed4 fixing test 2020-11-03 14:53:51 +01:00
Armin Schrenk
3eb44b06af Fixes #1061:
* temp mount point is first tried without any addition
* then with id Suffix in brackets
* then with underscores, id suffix and count
2020-11-03 12:50:52 +01:00
Armin Schrenk
1554437884 Use regular display name and only normalize if webdav is used:
* rename StringBinding mountName to normalizedDisplayName
* replace in all other occurences mountName with DisplayName
2020-11-03 12:44:09 +01:00
Armin Schrenk
268bae8879 Change Invalid mount point error window:
* Add heading
* change icon to times_with_circle
* reformat text
2020-11-03 12:13:22 +01:00
Sebastian Stenzel
b382e436e2 minor CI fixes 2020-11-03 11:27:10 +01:00
Sebastian Stenzel
6ccd248675 use maven profiles to build different buildkits 2020-11-03 11:21:08 +01:00
Armin Schrenk
c760e63995 only use supported keychainAccessProviders 2020-11-03 10:54:32 +01:00
Armin Schrenk
959dee153e fixing bug where stored password is not used 2020-11-03 10:03:14 +01:00
Armin Schrenk
aa22f656e8 Moving the code for unregular unmount leftovers from mountpoint chooser to an own class and execute it at each application start. 2020-11-02 17:34:34 +01:00
Armin Schrenk
d24734e16f Revert "Adding method to clean up _our_ tmp mounting dirs to mitigate #1061 and #1013"
This reverts commit 01522e8c97.
2020-11-02 17:25:43 +01:00
Armin Schrenk
01522e8c97 Adding method to clean up _our_ tmp mounting dirs to mitigate #1061 and #1013 2020-11-02 14:49:57 +01:00
Sebastian Stenzel
14e056551d no longer track coverage for deleted keychain module 2020-11-02 14:07:21 +01:00
Sebastian Stenzel
82a42ea183 Removed keychain module. Implemented new KeychainManager in various UI controller classes 2020-11-02 14:03:39 +01:00
Sebastian Stenzel
bfde26be79 Added new keychain module and refactored UI parts 2020-11-02 13:09:30 +01:00
Sebastian Stenzel
51e43a0567 include system integration lib automatically depending on current OS 2020-11-02 13:08:50 +01:00
Sebastian Stenzel
9a375cb3f6 updated label
[ci skip]
2020-11-02 09:43:54 +01:00
Sebastian Stenzel
2b8f39a149 ignored some derived files 2020-11-02 09:41:07 +01:00
JaniruTEC
2720a999d1 Updated codestyle (#1388)
Co-authored-by: Sebastian Stenzel <overheadhunter@users.noreply.github.com>
2020-11-02 08:45:41 +01:00
Ralph Plawetzki
f675bd5017 Feature/change password backend 2 (#1375)
* Add option to choose from available password backends on Linux
Implements #1301
2020-11-02 08:19:26 +01:00
JaniruTEC
16be3e7a97 Merge pull request #1384 from cryptomator/fix/missing-exceptions
Fix/missing exceptions
2020-10-29 16:58:51 +01:00
JaniruTEC
551d9d1af1 Added default Errorhandler to all Tasks and Services 2020-10-28 16:21:14 +01:00
JaniruTEC
0acc8ddb0a Fixed UnlockWorkflow
Partly reverted 253abd4930
2020-10-28 15:50:20 +01:00
JaniruTEC
f0e1bcb899 Moved call to #setOnFailed() to UpdateStatsService implementation 2020-10-28 15:40:59 +01:00
JaniruTEC
d331c30224 Revert "Added UI error messages"
This reverts commit eb8f5f523d.
2020-10-28 15:21:59 +01:00
JaniruTEC
d0d39a1c72 Merge branch 'develop' into fix/missing-exceptions 2020-10-27 17:04:32 +01:00
JaniruTEC
8904df91b4 Reverted accidental changes to Project_Default.xml [Skip CI] 2020-10-27 16:18:55 +01:00
JaniruTEC
786365c095 Added ability to skip CI
Added check for "Skip CI", added comment
Added filter for skipping CI
2020-10-27 16:12:35 +01:00
JaniruTEC
eb8f5f523d Added UI error messages
Added UI error message to GeneralPreferencesController for ToggleAutoStartTask
Added UI error message to RecoveryKeyCreationController
2020-10-26 21:19:24 +01:00
JaniruTEC
9c22adc97d Added/Fixed error logging
Added error logging to VaultStats and GeneralPreferencesController
Fixed possible missing error logging in VaultService
2020-10-26 21:17:22 +01:00
Sebastian Stenzel
265212cc0f New Crowdin updates (#1302)
* New translations strings.properties [ci skip]
2020-10-26 10:00:58 +01:00
Sebastian Stenzel
9c860cc17c Merge pull request #1380 from cryptomator/snyk-fix-bc2c8d6dd6d679e9ec6736de3d13592d
[Snyk] Security upgrade com.google.guava:guava from 29.0-jre to 30.0-jre
2020-10-24 09:55:05 +02:00
Sebastian Stenzel
7f63153bc1 Update main/pom.xml 2020-10-24 09:47:09 +02:00
snyk-bot
9531d44ab5 fix: main/pom.xml to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JAVA-COMGOOGLEGUAVA-1015415
2020-10-24 01:50:58 +00:00
JaniruTEC
253abd4930 Refactored UnlockWorkflow
Replaced protected state methods (scheduled, etc.) with lambdas in the constructor
Replaced try-multi-catch with if-instanceof in onFailed-lambda
2020-10-22 18:58:46 +02:00
JaniruTEC
022ba81f2f Reformatted classes ErrorComponent and Tasks 2020-10-22 18:33:20 +02:00
Armin Schrenk
26160e9bbc Merge pull request #1346 from purejava/fix-isSupported
Evade DBusException
2020-10-21 10:56:16 +02:00
JaniruTEC
0a32de2c92 Addded dev run configs, removed fuse-setting from win prod config 2020-10-16 22:38:39 +02:00
JaniruTEC
60aeb6a04b Merge pull request #1307 from cryptomator/feature/fuse-on-win
Added (experimental) support for using FUSE on Windows (using WinFSP)
2020-10-14 15:29:37 +02:00
JaniruTEC
0206fc0b8f Bumped required version of fuse-nio-adapter to 1.2.5 2020-10-14 15:26:28 +02:00
JaniruTEC
06b3343a6d Partly Revert "Finished feature: Fuse on Win"
Revert changes to TemporaryMountPointChooser introduced by 598bec3050
2020-10-13 18:30:00 +02:00
JaniruTEC
598bec3050 Finished feature: Fuse on Win
Bumped required version of fuse-nio-adapter to 1.2.4
Added comments
Changed log messages to be more descriptive
2020-10-12 21:34:56 +02:00
Reid Rankin
0e5d72c22a shorten FS_TYPE_NAME to "CryptomatorFS" (#1371)
This fixes part of #1033 by shortening the FS_TYPE_NAME so that it can be returned inside a 48-byte-long FILE_FS_ATTRIBUTE_INFORMATION buffer, which only has room for up to 17 UTF-16 characters in the file system name field.
2020-10-12 14:12:09 +02:00
JaniruTEC
21f4fab9ed Fixed bug: Strange error message if no MPC was applicable 2020-10-09 18:40:50 +02:00
JaniruTEC
58e2ec4742 Changed contract of priorities in MPC, added null-check
See: ab538cd215 (original commit)
See: 6b6867b826 (reverting commit)
2020-10-08 19:14:03 +02:00
JaniruTEC
6b6867b826 Reverted ab538cd215 2020-10-08 18:51:03 +02:00
Ralph Plawetzki
92321a7569 Remove unneeded access modifier 2020-10-08 12:57:50 +02:00
JaniruTEC
7781572b6d Replaced call to Validate with call to Preconditions
See: https://github.com/cryptomator/cryptomator/pull/1307#discussion_r500760560
2020-10-07 23:27:08 +02:00
JaniruTEC
b5efe39eb8 Refactored Vault, MPCs, Unlock to integrate better with latest changes
Removed delegate to Volume#getMountPointRequirement() from Vault
Added getter for the Vault's Volume (#getVolume())

Changed CustomMountPointChooser to use VaultSettings instead of Vault for the constructor/field declaration
Updated CustomMountPointChooser#isApplicable() to be disabled when using FUSE on Windows (without useExperimentalFuse)
Updated CustomMountPointChooser to call Volume#getMountPointRequirement() directly

Replaced OS-Check in TemporaryMountPointChooser with MPR-Check

Replaced call to Vault#getMountPointRequirement() with call to Vault#getVolume() (and Volume#getMountPointRequirement()) in UnlockInvalidMountPointController and UnlockWorkflow
Cleaned up UnlockWorkflow
2020-10-07 23:19:24 +02:00
Ralph Plawetzki
cae86cd71f Get more information logged in case something goes wrong 2020-10-07 18:55:18 +02:00
Ralph Plawetzki
667617b480 Update kdewallet to latest 1.1.1 2020-10-07 18:32:16 +02:00
JaniruTEC
9329311491 Merge branch 'feature/volume-type-getter' into feature/fuse-on-win 2020-10-07 16:59:54 +02:00
JaniruTEC
22438d1eba Added api for getting the implemented VolumeImpl (type) of a Volume 2020-10-07 16:46:58 +02:00
JaniruTEC
d295084312 Changed contracts in MPC interface
Added Volume parameter to the method signature of all MPC methods and updated implementations to use the change.

Changed contract of the MPC interface: Validation of the MPC (call to #isApplicable()) moved from the Module to the Volume.
Updated docs to reflect those changes. Fixed typos.
Updated method/qualifier names.
Updated implementation of #provideOrdered(Valid)MountPointChoosers() to reflect this change and to build the set differently.
Updated #determineMountPoint() to work with the new set.
2020-10-06 19:52:59 +02:00
JaniruTEC
ab538cd215 Fixed bug: Dropped MPCs if priorities were equal
Fixed bug that caused MPCs with equal priorities to be dropped (even if they weren't equal).
Added additional criteria (hashCode) for cases were priorities are equal.

See the docs of ImmutableSortedSet for further information.
2020-10-06 19:40:03 +02:00
Armin Schrenk
f9cb438a0b Closes #1065 for real by adding missing style in dark theme for debug mode 2020-10-01 18:37:32 +02:00
JaniruTEC
354de225ed Reverted changes to WebDavVolume#getMountPoint()
See: https://github.com/cryptomator/cryptomator/pull/1307#discussion_r472888268
2020-09-30 14:09:01 +02:00
Armin Schrenk
0b7f8d71ed closes #1065 2020-09-29 12:12:59 +02:00
JaniruTEC
9657a13912 Updated MPCModule to return a SortedSet and switched collector
See: https://github.com/cryptomator/cryptomator/pull/1307#discussion_r474452898
2020-09-28 23:34:47 +02:00
Ralph Plawetzki
c5a3b29554 Make logger static 2020-09-16 18:56:54 +02:00
Ralph Plawetzki
ae957c2588 Print error message instead of stacktrace
Fixes #1345
2020-09-16 06:55:37 +02:00
Tobias Hagemann
da84133732 allows running launcher-mac.sh from any path 2020-09-15 16:23:31 +02:00
Tobias Hagemann
10275eb80f Merge pull request #1326 from TheShellLand/linux-libs
allows running launcher-linux.sh from any path
2020-09-15 09:28:04 +02:00
Sebastian Stenzel
29a9c7dffe auto-close bugs violating the issue template
[ci skip]
2020-09-13 11:50:26 +02:00
JaniruTEC
d5b8996a39 Fixed violation of method contract
See: https://github.com/cryptomator/cryptomator/pull/1307#discussion_r472857043
2020-09-11 22:23:48 +02:00
JaniruTEC
f012977aac Fixed spelling
See: https://github.com/cryptomator/cryptomator/pull/1307#discussion_r472825411
2020-09-11 21:47:30 +02:00
JaniruTEC
8680bd1dee Updated MountPointChooserModule and MPCs
Updated MountPointChooserModule and MPCs to use bindings (@'Binds) instead of providers (@'Provides)
Updated CustomDriveLetterChooser and TemporaryMountPointChooser  to use VaultSettings instead of Vaults

See: https://github.com/cryptomator/cryptomator/pull/1307#discussion_r472872166
2020-09-07 22:25:01 +02:00
JaniruTEC
528510eb53 Renamed binding fuseAndWindows to restrictToStableFuseOnWindows
Renamed binding fuseAndWindows to restrictToStableFuseOnWindows in MountOptionsController

See: https://github.com/cryptomator/cryptomator/pull/1307#discussion_r477135521
2020-09-07 22:24:59 +02:00
ainiml
e8d17cfb7b allows running launcher-linux.sh from any path 2020-09-06 19:25:50 -04:00
JaniruTEC
cdbcd20241 Fixed faulty access introduced by parallel release of 1.5.8 2020-09-05 22:15:02 +02:00
JaniruTEC
fef3ade5c6 Merge branch 'develop' into feature/fuse-on-win
Fixed conflicts in WebDavVolume
2020-09-04 22:28:53 +02:00
Tobias Hagemann
194d7f0da9 Merge branch 'hotfix/1.5.8' into develop 2020-08-28 16:01:35 +02:00
Tobias Hagemann
8ad20c27da Merge branch 'hotfix/1.5.8' 2020-08-28 15:57:54 +02:00
Tobias Hagemann
f891a9f1a7 refactored that fix from 2a335eb7af 2020-08-28 14:25:26 +02:00
Tobias Hagemann
2a335eb7af fixed test 2020-08-28 14:06:09 +02:00
Tobias Hagemann
6578ac0121 use mountName in FUSE on macOS instead of displayName 2020-08-28 13:55:17 +02:00
Tobias Hagemann
83f9514a42 fixed icon views of labels in new password screen that could only appear once
(cherry picked from commit 4723416d45)
2020-08-28 13:41:43 +02:00
Tobias Hagemann
c92f1aa655 updated third-party licenses 2020-08-28 13:34:41 +02:00
Tobias Hagemann
688993ba37 preparing 1.5.8 2020-08-28 12:58:50 +02:00
Armin Schrenk
ddfc8d5bc3 Merge branch 'feature/#1289-introduceVaultname' into hotfix/1.5.8 2020-08-28 11:59:56 +02:00
Armin Schrenk
c3141e3601 minor adjustments 2020-08-28 11:58:46 +02:00
Armin Schrenk
f49974180b Fixing test 2020-08-28 11:19:56 +02:00
Armin Schrenk
eb6ade49f6 refactor to use parameterized test 2020-08-27 16:18:20 +02:00
Armin Schrenk
6553c04256 add code to migrate legacy settings.json files to new property. 2020-08-27 15:53:20 +02:00
Armin Schrenk
a8cb015a06 rename property and getters in vault class for display name. 2020-08-27 15:52:16 +02:00
Martin Beyer
ee81dca71c Nice implementation of only one stats window per vault 2020-08-26 17:23:39 +02:00
Sebastian Stenzel
ce06ddf312 updated webdav dependencies
(cherry picked from commit 8c4d35d3db)
2020-08-26 12:51:28 +02:00
Armin Schrenk
fee2d4bc0c Merge pull request #1311 from cryptomator/feature/alternativeAutostartWin
Feature/alternative autostart win
2020-08-26 11:44:37 +02:00
Armin Schrenk
a2a990bd0c Correct spelling 2020-08-26 11:36:46 +02:00
Armin Schrenk
cedbe6523d Merge pull request #1294 from purejava/access-kde-wallets
This adds the missing part to linux system keychain access: use KDE wallets
2020-08-26 10:03:55 +02:00
JaniruTEC
ec64055d93 Renamed property "position" (defined by MPC) to "priority" 2020-08-21 22:41:15 +02:00
JaniruTEC
6f542ac8b8 Replaced "assert" with IllegalStateException 2020-08-21 21:57:12 +02:00
Ralph Plawetzki
8a3c101764 Merge remote branch 'cryptomator/develop' into access-kde-wallets 2020-08-21 19:30:46 +02:00
infeo
49a8689090 Remove legacy concepts:
* in vaultSettings, displayName does not listen on path
* the caller/creater of vault settings must set the name explicitly
* add localizable default vault name
2020-08-21 13:30:05 +02:00
infeo
8f5c4997b1 closes #1300 2020-08-21 11:32:51 +02:00
infeo
900fdd7f6f minor changes
* removing unused imports
* exchanging addListener() by Easybind.subscribe()
2020-08-21 11:17:55 +02:00
Tobias Hagemann
4723416d45 fixed icon views of labels in new password screen that could only appear once 2020-08-21 10:13:45 +02:00
JaniruTEC
8483f71f57 Refactored CustomMountPointChooser
See: https://github.com/cryptomator/cryptomator/pull/1307#discussion_r472832435
2020-08-21 00:55:33 +02:00
JaniruTEC
5dac594cc9 Removed Phasesystem, specified docs, removed default #isApplicable()
See: https://github.com/cryptomator/cryptomator/pull/1307#discussion_r472835069 https://github.com/cryptomator/cryptomator/pull/1307#discussion_r472839837 https://github.com/cryptomator/cryptomator/pull/1307#discussion_r472820689 https://github.com/cryptomator/cryptomator/pull/1307#discussion_r472872901
2020-08-21 00:00:13 +02:00
infeo
06f4e160d6 Migrate to easybind fork 2020-08-20 21:25:54 +02:00
infeo
1d38ee2fcb Further improvements:
* corrected bad english
* improved documentation
* restricted visibility of specific exception
2020-08-20 13:49:31 +02:00
Sebastian Stenzel
921b70ebaa updated webdav dependencies
(cherry picked from commit 8c4d35d3db)
2020-08-20 13:38:40 +02:00
infeo
0f56d424da Refactored AutoStartStrategy:
* prevented on best effort basis inconsistent states
* extracted the registry setting as an own strategy (by methods)
* refactored the overriden methods to call the strategies (registry or folder) depending on the different variables
* removed Powershell specific ToggleException
* added documentation
2020-08-20 12:18:57 +02:00
JaniruTEC
4cd43a2b1c Merge branch 'develop' into feature/fuse-on-win 2020-08-19 15:53:26 +02:00
JaniruTEC
1ef359e68b Added messages to rethrown IOExceptions 2020-08-19 15:47:09 +02:00
JaniruTEC
58ce1e45b8 Updated Codestyle to not use any arrangements 2020-08-19 15:34:08 +02:00
Martin Beyer
bd854e7f7a updates secret-service for linux keychains and fixes #1268 2020-08-19 15:31:42 +02:00
JaniruTEC
3ecc0cf906 Cleaned up MountPointRequirement
See: https://github.com/cryptomator/cryptomator/pull/1307#discussion_r472827299
See: https://github.com/cryptomator/cryptomator/pull/1307#discussion_r472826920
2020-08-19 14:45:10 +02:00
JaniruTEC
ea6925f905 Replaced #wrapAsIMPE() with explicit call to IMPExc
See: https://github.com/cryptomator/cryptomator/pull/1307#discussion_r472872373 https://github.com/cryptomator/cryptomator/pull/1307#discussion_r472859488 https://github.com/cryptomator/cryptomator/pull/1307#discussion_r472827776
2020-08-19 11:44:18 +02:00
JaniruTEC
f45b78d8c0 Removed accidental debugcode, updated lookup for env-var
See: https://github.com/cryptomator/cryptomator/pull/1307#discussion_r472848604
See: https://github.com/cryptomator/cryptomator/pull/1307#discussion_r472851003
2020-08-19 11:44:01 +02:00
JaniruTEC
75ca8a91f8 Merge remote-tracking branch 'upstream/develop' into feature/fuse-on-win 2020-08-19 11:42:55 +02:00
JaniruTEC
16462d05c8 Refactored changes, cleaned up
Changed visibility of methods
Removed Logger from AbstractVolume
Moved mountPoint and #getMountPoint()
Renamed fuseMnt to mount in FuseVol (as in DokanyVol)
Removed unnecessary mountPoint-field from WebDavVolume
2020-08-18 21:27:13 +02:00
JaniruTEC
cbef54f05c Added Logger, moved cleanup, moved choosing 2020-08-18 21:27:03 +02:00
JaniruTEC
f601ff4ce3 Moved #isSupported() in the file, moved #determineMountPoint() 2020-08-18 21:26:52 +02:00
JaniruTEC
84fbf7392b Updated usage of FuseNotSupportedException 2020-08-18 16:57:56 +02:00
JaniruTEC
bbf9379f56 Changed DokanyVolumes and FuseVolumes to use mountnames
Changed DokanyVolumes and FuseVolumes to use mountnames for automatic, folder-based mounting instead of ids.
2020-08-18 16:57:55 +02:00
JaniruTEC
6aa14fcfb1 Updated Errorhandling of UnlockWorkflow 2020-08-18 16:57:55 +02:00
Tobias Hagemann
710c7d0da7 added button to main window title if debug mode is enabled, fixes #1065 2020-08-18 16:08:23 +02:00
Tobias Hagemann
841d4eb9c7 updated third-party licenses 2020-08-18 13:59:37 +02:00
Sebastian Stenzel
8c4d35d3db updated webdav dependencies 2020-08-18 10:36:55 +02:00
JaniruTEC
4110057fa1 Refactored multiple classes
Refactored UnlockWorkflow, CustomMountPointChooser, DokanyVolume and UnlockInvalidMountPointController

Changed UnlockWorkflow#handleGenericError() to accept Throwables

Added check for parent to CustomMountPointChooser (when using FUSE on Win)

Removed legacy constant from DokanyVolume

Added asserts to UnlockInvalidMountPointController and UnlockWorkflow
2020-08-16 18:40:11 +02:00
Tobias Hagemann
9b1d6d2f2f Merge branch 'release/1.5.7' into develop 2020-08-14 12:59:07 +02:00
Tobias Hagemann
1c45455cb2 Merge branch 'release/1.5.7' 2020-08-14 12:58:26 +02:00
Tobias Hagemann
411aca8695 preparing 1.5.7 2020-08-14 12:55:46 +02:00
Sebastian Stenzel
3de69021df New Crowdin updates (#1255)
[ci skip]
2020-08-14 12:49:55 +02:00
Tobias Hagemann
8418038736 dependency update 2020-08-14 12:47:11 +02:00
Ralph Plawetzki
3bf2b499a7 Reverse order to initialize backend
The GNOME keyring feature was implemented first and we don't want to confuse users who used it before
2020-08-14 07:40:46 +02:00
JaniruTEC
430da2b78d Implemented MPC-System in FuseVolume and DokanyVolume, did refactoring
Implemented MPC-System in FuseVolume and DokanyVolume
Removed methods from FuseVol and DokanyVol
Renamed methods
Added chooser logic instead

Added imports for VolumeException to multiple classes
Added support for throwing and handling of InvalidMountPointException from Volume up to UnlockWorkflow (changed method signatures, added try-catchs, etc.)
2020-08-12 23:42:38 +02:00
JaniruTEC
51047dcbce Added additional debug messages 2020-08-12 23:42:38 +02:00
JaniruTEC
708bcaa630 Updated CustomMountPointChooser to use MPR instead of OS-Checks
Updated CustomMountPointChooser to use MountPointRequirements instead of OS-Checks.

This led to the discovery of the bug that was fixed in 724b20c826.
2020-08-12 23:42:37 +02:00
JaniruTEC
724b20c826 Fixed possible racecondition at Vault initialization
Fixed possible racecondition at Vault initialization: VaulSettings were read before the Vault was fully initialized resulting in breakage of the code if the excact order was to be changed.
2020-08-12 21:43:24 +02:00
Tobias Hagemann
69f8c46f15 ui adjustments in vault detail missing 2020-08-12 17:35:11 +02:00
JaniruTEC
b7f2fb0bdf Added cleanup to TemporaryMountPointChooser 2020-08-11 23:35:43 +02:00
JaniruTEC
222ffd8c53 Implemented MountPointChooserModule 2020-08-11 23:35:42 +02:00
JaniruTEC
00e0a0f79a Fixed doc for MountPointChooser 2020-08-11 23:35:42 +02:00
JaniruTEC
d6bbc4383b Added implementations for MountPointChooser
Added AvailableDriveLetterChooser for automatic DriveLetter-choice
Added CustomDriveLetterChooser for a chosen DriveLetter
Added CustomMountPointChooser for a chosen directory
Added TemporaryMountPointChooser for automatic directory-choice
2020-08-11 23:03:12 +02:00
JaniruTEC
b51a2602a9 Documented MountPointChooser 2020-08-11 23:03:12 +02:00
JaniruTEC
c3fbddc671 Refactored PerVault, Vault and VaultSettings
Made @`PerVault public
Added import for VaultException to Vault
Added convenience method to VaultSettings
2020-08-11 22:26:15 +02:00
JaniruTEC
ce262b691a Added base classes for MountPointChoosers and added module to component 2020-08-11 22:26:15 +02:00
Martin Beyer
65de6fef9b Allowing only one io-stats window per vault to be open at the same time. 2020-08-11 17:32:39 +02:00
Armin Schrenk
31d95ae5b0 Remove restrictions of vault name filed to only contain alpha numeric characters 2020-08-11 12:04:02 +02:00
Armin Schrenk
429098733c Improve the vault/mount name system:
* fix bug where the default name is not replaced by the actual folder name
* introduce new field in settings "mountName" as binding on displayName
* change volumes to use mountName instead of displayName
2020-08-11 11:19:54 +02:00
JaniruTEC
597899d2bf Fixed random set order and updated #getAvailableDriveLetter()
Fixed bug introduced by 32a810fe1d:
The Set of existing DriveLetters was in random order because Collectors#toUnmodifiableSet is an unordered collector.

Changed #getAvailableDriveLetter() to use Stream#findFirst() instead of #findAny()
2020-08-11 02:24:44 +02:00
Ralph Plawetzki
b1c66b181d Depend on kdewallet 1.0.1, hence dbus-java 3.2.3
that fixes issues when dealing with multiple signals of the same name but different signatures
2020-08-09 11:18:17 +02:00
Ralph Plawetzki
100b836979 Adjust buildkit to exclude kdewallet for Windows and macOS 2020-08-07 18:49:07 +02:00
Ralph Plawetzki
817907c25a Add access functionality for KDE kwallets 2020-08-07 18:32:54 +02:00
Tobias Hagemann
25195fffe2 added icon to password strength label in new password controller 2020-08-07 17:47:49 +02:00
Tobias Hagemann
0e784a6ffc also disable finish button in change password if old password field is empty 2020-08-07 17:09:55 +02:00
Tobias Hagemann
351f96fa8b switch to light theme if donation key has changed and is invalid 2020-08-07 09:30:22 +02:00
Tobias Hagemann
703fb4fb51 fixed 0091c401df (r40816364) 2020-08-06 18:32:57 +02:00
Tobias Hagemann
a975df6f8c added "Automatic" theme on macOS 2020-08-06 18:30:34 +02:00
Armin Schrenk
fb90128b63 Move option to change display name of a vault from mount options to general options 2020-08-06 15:33:28 +02:00
Armin Schrenk
ea4c91fb37 change meaning of mount name setting:
* it is now considered the gerenal vault name
* methods (except parsing) are renamed accordingly
2020-08-06 15:20:32 +02:00
Armin Schrenk
7c1d6973ba fixes #1289 by decoupling storage path from vault name 2020-08-06 15:18:30 +02:00
JaniruTEC
4f2120b729 Added support for folderbased backup-mountpoint to DokanyVol and FuseVol 2020-08-03 22:20:20 +02:00
JaniruTEC
51f7d74e4a Changed cryptomator.mountPointsDir-property in Windows Configs
Changed cryptomator.mountPointsDir-property in Windows Configs from "~/Mount" to "~/Cryptomator
2020-08-03 21:22:22 +02:00
JaniruTEC
32a810fe1d Changed FuseVolume to choose a DriveLetter instead of a folder
Changed FuseVolume to choose a DriveLetter instead of a folder if the the mountpoint is chosen by the app.
Reworked priorities when choosing
Changed DokanyVolume to use the new methods supplied by WindowsDriveLetters
2020-07-29 22:15:12 +02:00
JaniruTEC
570482521a Delegated choice of DriveLetter to WDL and made internal Set immutable
Delegated choice of DriveLetter to WindowsDriveLetters
Made internal Set ("C_TO_Z") immutable, that is returned by getAvailableDriveLetters()
Changed direct reference to C_TO_Z to use getAvailableDriveLetters() instead
2020-07-29 22:14:51 +02:00
Martin Beyer
3bd2a2f911 Fixes #1262 by adding new buttons, if a vault cannot be found 2020-07-29 10:21:16 +02:00
JaniruTEC
1ac6d086bd Disabled some experimental FUSE Features on Windows
Disabled some experimental FUSE Features on Windows unless they are explicitly enabled by using the flag '-Dfuse.experimental="true"'

They are enabled in the default IntelliJ-Config (disabled on Build)
2020-07-28 19:15:56 +02:00
JaniruTEC
8374599183 Modified creation of temporary mountpoint, fixed bugs, added comments
Changed FuseVolume#prepareTemporaryMountPoint to handle FUSE on Windows correctly (WinFSP requires the folder to NOT exist...)
See: https://github.com/billziss-gh/winfsp/issues/320

Added comments
Fixed bug: Message showing a malformed path
2020-07-26 23:03:36 +02:00
Martin Beyer
0091c401df Fixes #1267 by binding the settings.preferredVolumeImpl to VolumeImpl.WEBDAV, if preferredVolimeImpl isn't available 2020-07-22 15:26:21 +02:00
Martin Beyer
00dd67e345 Refactoring 2020-07-21 15:16:53 +02:00
Martin Beyer
158677bd54 Using new methods from CryptoFs to get correct statistics 2020-07-21 11:10:05 +02:00
JaniruTEC
a1b375316c Specified the messages of failed UnlockWorkflows
Specified the messages of failed UnlockWorkflows by adding the ability to distinguish between the different MountPointRequirements

Translation for all languages but German and Englisch required
2020-07-20 22:06:15 +02:00
JaniruTEC
dae2814b0f Added MountPointRequirement-Enum
Added MountPointRequirement-Enum and added method getMountPointRequirement() to Volume (and all implementing classes) to query the requirment for the specific VolumeProvider.
2020-07-20 22:01:10 +02:00
JaniruTEC
341a98848d Modified check of provided mountpoint
Changed FuseVolume#checkProvidedMountPoint to handle FUSE on Windows correctly (WinFSP requires the folder to NOT exist...)
2020-07-20 22:00:35 +02:00
JaniruTEC
902e66906a Refactored method FuseVolume#prepareTemporaryMountPoint
Refactored method FuseVolume#prepareTemporaryMountPoint to be more readable (removed duplicated code)
2020-07-20 21:55:49 +02:00
JaniruTEC
c59d3cd03f Removed check for OS from FuseVolume#isSupportedStatic 2020-07-20 21:23:35 +02:00
Martin Beyer
ed9f16e012 Displaying additional stats with a new DataLabel 2020-07-20 16:09:45 +02:00
Martin Beyer
ed77a4aacd Added basic counting of additional total stats (not correct) 2020-07-20 16:08:25 +02:00
Martin Beyer
ff2fa70bd6 preperation for displaying en-de-crypted bytes per second 2020-07-20 11:00:59 +02:00
Martin Beyer
3519346df9 cleaned up unlock window 2020-07-20 10:59:48 +02:00
JaniruTEC
45f6caa7bd Added basic support for FUSE-Mounting on Windows 2020-07-18 19:05:18 +02:00
JaniruTEC
e2e7755af2 Added cryptomator.mountPointsDir-property to Windows Configs 2020-07-18 18:57:11 +02:00
Armin Schrenk
4ab7bfaa20 reformat 2020-07-17 11:58:11 +02:00
Armin Schrenk
7e0ffb43a6 fixing bug where the mountName of the vault is initially set as the default one 2020-07-17 11:57:53 +02:00
Martin Beyer
526c8328f6 Refactoring - Removing unnecessary environment variable 2020-07-13 15:21:24 +02:00
JaniruTEC
81bad4eb25 Fixed bug: Some exceptions caused by unlock were not shown
If an unmatched exception occurred during unlocking it was neither logged nor shown to the user.
2020-07-09 22:34:57 +02:00
Martin Beyer
dcaa6e81a3 Improving Exception calling and logging 2020-07-08 14:53:15 +02:00
Martin Beyer
e78155396d Implementing #1251 2020-07-08 14:01:10 +02:00
Sebastian Stenzel
d3063c8117 Make sure not to launch the FX app more than once. 2020-07-07 16:01:32 +02:00
Armin Schrenk
cd1ed088c3 Update bug.md
Change bullet point "Drive" to "Volume type" such that users are less confused by this field. Also added where this info can be found.
2020-07-03 10:37:02 +02:00
Martin Beyer
e831debb94 fixes #1244 by renaming the refresh button and adding a method call 2020-06-30 09:36:46 +02:00
Martin
8d15a6612f Implements #1244 by adding a small button to regain focus 2020-06-28 11:05:27 +02:00
Sebastian Stenzel
9c034f3be6 split up read and write charts and added cache efficiency chart
(because we will add further read/write-specific stats like total number of files read/written, bytes encrypted/decrypted, etc)
2020-06-25 17:16:42 +02:00
Sebastian Stenzel
60dd9349a8 Merge branch 'develop' into feature/io-stats 2020-06-25 17:15:22 +02:00
Sebastian Stenzel
330b1cbf7d derived resources after dependency update
[ci skip]
2020-06-25 17:15:07 +02:00
Sebastian Stenzel
9ffa38d54e Merge branch 'develop' into feature/io-stats 2020-06-22 17:24:18 +02:00
Sebastian Stenzel
c925727269 Merge branch 'master' into develop 2020-06-22 17:23:58 +02:00
Sebastian Stenzel
ac2dc06bd6 Merge branch 'release/1.5.6' 2020-06-22 17:23:27 +02:00
Sebastian Stenzel
80b83adb53 Preparing 1.5.6 2020-06-22 17:08:48 +02:00
Sebastian Stenzel
ddc31ac2bb New Crowdin translations (#1227)
[ci skip]
2020-06-22 17:08:09 +02:00
Sebastian Stenzel
eb7c5d0b2b Dependency update 2020-06-22 10:21:44 +02:00
Armin Schrenk
e5ce3cb62d closes #1254 2020-06-19 15:57:12 +02:00
Martin Beyer
7a69692999 Prettied up the statistics window for both Ui Themes 2020-06-17 11:04:36 +02:00
Sebastian Stenzel
d00d9d98dc make sure to release all strong referenced to the I/O chart when closing the window 2020-06-16 15:35:12 +02:00
Sebastian Stenzel
1846c5c14c formatting 2020-06-16 15:05:24 +02:00
Sebastian Stenzel
0d9db783f7 Merge branch 'develop' into feature/io-stats 2020-06-16 15:04:47 +02:00
Sebastian Stenzel
d3a6964d8f fixes #1249 2020-06-16 15:02:38 +02:00
Martin Beyer
55e9cabf51 Updated Graph to use Timeline + minor UI changes + wording 2020-06-16 14:13:17 +02:00
Sebastian Stenzel
5180f2061a add series only once (in scene initialization instead of on every tick) - thus fixing IAE 2020-06-10 13:58:02 +02:00
Martin Beyer
05154cf6aa Added Graph to statistics window 2020-06-10 13:38:58 +02:00
Martin Beyer
f61e955945 First Dummy window to show statistics 2020-06-10 13:38:57 +02:00
Armin Schrenk
d80605532f closes #1141 2020-05-28 16:26:22 +02:00
Sebastian Stenzel
8d5fb14b50 Merge branch 'master' into develop 2020-05-27 12:43:56 +02:00
Sebastian Stenzel
570286c7df Merge branch 'release/1.5.5' 2020-05-27 12:43:35 +02:00
Sebastian Stenzel
11e3ee8d90 formatted code
[ci skip]
2020-05-27 12:43:18 +02:00
Sebastian Stenzel
0664c670f7 Merge branch 'develop' into release/1.5.5 2020-05-27 12:33:55 +02:00
Martin Beyer
f36f9d412c Fixes #1203 2020-05-27 12:04:34 +02:00
Sebastian Stenzel
bd734f1960 preparing 1.5.5 2020-05-27 11:23:09 +02:00
Sebastian Stenzel
675a0b1a73 New Crowdin translations (#1194)
[ci skip]
2020-05-26 16:50:01 +02:00
Martin Beyer
a2816277bf Refactor unlock Window in preparation of #1203 2020-05-26 14:00:25 +02:00
Tobias Hagemann
e37f1f914b some layout adjustments in vault detail 2020-05-26 13:20:47 +02:00
Sebastian Stenzel
adf7694308 fixes #1021, fixes #1170, fixes #1180 2020-05-26 10:04:32 +02:00
Sebastian Stenzel
c13449c6ad fixes #1214 2020-05-26 09:21:18 +02:00
Armin Schrenk
f72035210c fixing error where vault stays in processing state if unlock is canceld via system close button (window decoration bar) 2020-05-25 13:51:14 +02:00
Martin Beyer
89ef5238ea Fixes #1218 by setting the maxWidth of each button to Infinity 2020-05-24 12:41:45 +02:00
Armin Schrenk
8198f66c1f fixes #1208 2020-05-22 12:39:41 +02:00
Armin Schrenk
eb5aa4ee44 remove unnecessary binding 2020-05-22 11:14:35 +02:00
Tobias Hagemann
adb09a0efe proofread strings 2020-05-20 17:45:54 +02:00
Martin Beyer
241eb8bed5 Implements #1182 By adding a button to forget the saved password 2020-05-20 15:04:33 +02:00
Martin Beyer
842a0d6ff3 Implements #1183 Saved Passwords are indicated 2020-05-20 15:02:53 +02:00
Martin Beyer
3200917df2 Merge remote-tracking branch 'origin/develop' into develop 2020-05-20 14:51:57 +02:00
Martin Beyer
12dcf0647d Reverted small error in 22859c9ffa to fix#1082 2020-05-20 14:49:12 +02:00
Martin Beyer
22859c9ffa Fixes #1082 Illegal mount options cannot be selected (WebDav + Windows)
By adding the visible and managed flags to the corresponding fxml tags, it is no longer possible to select illegal combinations.
2020-05-20 13:02:14 +02:00
Sebastian Stenzel
29182156df fixes #1196 2020-05-19 11:21:20 +02:00
Sebastian Stenzel
357d63f398 Added animation to unlock dialog 2020-05-14 10:37:51 +02:00
Sebastian Stenzel
e594bf208d moved images to subdir 2020-05-14 07:53:51 +02:00
Sebastian Stenzel
2f0de3520a Refactored KeychainAccess in preparation of #1183, #1182 2020-05-13 19:59:32 +02:00
Sebastian Stenzel
8def68eb02 Merge branch 'master' into develop 2020-05-12 16:27:14 +02:00
Sebastian Stenzel
aef33dc864 Merge branch 'release/1.5.4' 2020-05-12 16:26:44 +02:00
Sebastian Stenzel
06d2f2d9e9 Preparing 1.5.4 2020-05-12 16:21:50 +02:00
Sebastian Stenzel
dad0ad76fb New Crowdin translations (#1166)
[ci skip]
2020-05-12 16:20:49 +02:00
Sebastian Stenzel
99fa8e7c8e fixes #1171 (or rather applying a workaround until we get an upstream fix) 2020-05-12 16:18:11 +02:00
Martin Beyer
ab0f175edf Implemented hyperlink in preferences to reveal log files (#1184) 2020-05-12 11:59:22 +02:00
Ralph Plawetzki
8cb9728565 Implement changePassphrase from the secret-service API (#1191)
Fixes #1189
2020-05-12 07:41:10 +02:00
Ralph Plawetzki
d91d27f2a4 Close unlockScene after entering the phrase and unlocking the vault (#1186) 2020-05-12 07:40:38 +02:00
Sebastian Stenzel
b2a6e038ae Updated secret-service lib to 1.0.0
references #1169
2020-05-11 17:34:12 +02:00
Sebastian Stenzel
75f360903c recheck vault state when focusing window
fixes #1190
fixes #1110
fixes #1139
2020-05-11 08:08:15 +02:00
Sebastian Stenzel
79c3137b90 no need to be application-scoped 2020-05-11 07:58:29 +02:00
Sebastian Stenzel
d2189d379c Using switch expressions 2020-05-11 07:47:15 +02:00
Sebastian Stenzel
49aead7323 Merge branch 'feature/refactored-unlock' into develop 2020-05-08 15:08:46 +02:00
Sebastian Stenzel
7bd610563f exception not thrown here
[ci skip]
2020-05-08 09:30:21 +02:00
Sebastian Stenzel
5c1a1ad162 respect choice made in #1083 2020-05-07 16:57:57 +02:00
Sebastian Stenzel
86906d0049 fixes #1083 2020-05-07 16:45:24 +02:00
Sebastian Stenzel
93011dc754 cleanup 2020-05-07 16:33:05 +02:00
Sebastian Stenzel
b084b651af wipe memory when setting a new password 2020-05-07 15:57:56 +02:00
Sebastian Stenzel
fecf9c0423 Fixes #1088 2020-05-07 14:18:09 +02:00
Sebastian Stenzel
117fe78a4a Refactored stage creation 2020-05-07 12:35:01 +02:00
Tobias Hagemann
153d43573a Merge branch 'master' into develop 2020-04-30 16:51:48 +02:00
Tobias Hagemann
9145f5d2f8 Merge branch 'release/1.5.3' 2020-04-30 16:51:01 +02:00
Tobias Hagemann
835ea3b640 preparing 1.5.3 2020-04-30 16:50:16 +02:00
Sebastian Stenzel
1e7eb23d1b New Crowdin translations (#1164)
[ci skip]
2020-04-30 16:48:22 +02:00
Sebastian Stenzel
cfe25d0bf5 updating siv-mode for twice as fast filename encryption/decryption 2020-04-30 16:37:07 +02:00
Sebastian Stenzel
b14939bd77 New Crowdin translations (#1159)
[ci skip]
2020-04-30 16:33:33 +02:00
Tobias Hagemann
26e140ee22 fixed checkmark color if it's selected and disabled 2020-04-30 16:31:25 +02:00
Tobias Hagemann
1c5ecf8c01 centered main.vaultlist.emptyList.onboardingInstruction 2020-04-30 14:52:16 +02:00
Tobias Hagemann
9b528a05b5 localized display name of ui themes, now actually use vaultOptions.mount.winDriveLetterOccupied localizations 2020-04-30 14:42:51 +02:00
Sebastian Stenzel
3a7aa6d64f fixes #1163, fixes #1131 2020-04-30 13:41:18 +02:00
Sebastian Stenzel
55820e47f9 Merge branch 'master' into develop 2020-04-29 16:47:55 +02:00
338 changed files with 7579 additions and 3161 deletions

View File

@@ -5,6 +5,7 @@ labels: type:bug
---
<!--
⚠️⚠️⚠️ READ CAREFULLY ⚠️⚠️⚠️
Do you want to ask a QUESTION? Are you looking for SUPPORT?
We're happy to help you via our support channels! Please read: https://github.com/cryptomator/cryptomator/blob/develop/SUPPORT.md
@@ -13,7 +14,9 @@ By filing an issue, you are expected to comply with our code of conduct: https:/
Of course, we also expect you to search for existing similar issues first! ;) https://github.com/cryptomator/cryptomator/issues?q=
⚠️ IMPORTANT: If you don't stick to this template, the issue will get closed. To proof that you read this, please remove the X from the following line:
-->
<!-- oooXooo -->
### Description
@@ -23,7 +26,7 @@ Of course, we also expect you to search for existing similar issues first! ;) ht
* Operating system and version: [Windows/macOS/Linux + Version]
* Cryptomator version: [Shown in the settings]
* Drive: [Dokany/FUSE/WebDAV]
* Volume type: [Dokany/FUSE/WebDAV, shown in the settings]
### Steps to Reproduce
@@ -58,4 +61,4 @@ Log file location:
- macOS: ~/Library/Logs/Cryptomator
- Linux: ~/.local/share/Cryptomator/logs
-->
-->

View File

@@ -7,6 +7,7 @@ jobs:
build:
name: Build 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@v1
@@ -27,24 +28,27 @@ jobs:
run: |
curl -o ~/codacy-coverage-reporter.jar https://repo.maven.apache.org/maven2/com/codacy/codacy-coverage-reporter/7.1.0/codacy-coverage-reporter-7.1.0-assembly.jar
$JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/commons/target/site/jacoco/jacoco.xml --partial
$JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/keychain/target/site/jacoco/jacoco.xml --partial
$JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/ui/target/site/jacoco/jacoco.xml --partial
$JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/launcher/target/site/jacoco/jacoco.xml --partial
$JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar final
env:
CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
- name: Assemble Buildkit
run: mvn -B package -DskipTests --file main/pom.xml --resume-from=buildkit -Prelease
- name: Assemble buildkit-linux.zip
run: mvn -B clean package -DskipTests --file main/pom.xml --resume-from=buildkit -Prelease,linux
- name: Upload buildkit-linux.zip
uses: actions/upload-artifact@v1
with:
name: buildkit-linux.zip
path: main/buildkit/target/buildkit-linux.zip
- name: Assemble buildkit-mac.zip
run: mvn -B clean package -DskipTests --file main/pom.xml --resume-from=buildkit -Prelease,mac
- name: Upload buildkit-mac.zip
uses: actions/upload-artifact@v1
with:
name: buildkit-mac.zip
path: main/buildkit/target/buildkit-mac.zip
- name: Assemble buildkit-win.zip
run: mvn -B clean package -DskipTests --file main/pom.xml --resume-from=buildkit -Prelease,windows
- name: Upload buildkit-win.zip
uses: actions/upload-artifact@v1
with:

25
.github/workflows/triageBugs.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: Bug Report Triage
on:
issues:
types: [opened]
jobs:
closeTemplateViolation:
name: Close bug reports that violate the issue template
runs-on: ubuntu-latest
steps:
- if: |
contains(github.event.issue.labels.*.name, 'type:bug')
&& (
!contains(github.event.issue.body, '<!-- oooooo -->')
|| !contains(github.event.issue.body, '### Description')
)
name: Close Issue
uses: peter-evans/close-issue@v1
with:
comment: |
This bug report did ignore our issue template. 😞
Auto-closing this issue, since it is most likely not useful.
_This decision was made by a bot. If you think the bot is wrong, let us know and we'll reopen this issue._

3
.gitignore vendored
View File

@@ -18,5 +18,8 @@ pom.xml.versionsBackup
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/dictionaries
.idea/compiler.xml
.idea/encodings.xml
.idea/jarRepositories.xml
.idea/**/libraries/
*.iml

View File

@@ -9,14 +9,36 @@
<option name="RIGHT_MARGIN" value="220" />
<option name="FORMATTER_TAGS_ENABLED" value="true" />
<JavaCodeStyleSettings>
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="30" />
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="10" />
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
<value />
</option>
<option name="IMPORT_LAYOUT_TABLE">
<value>
<package name="" withSubpackages="true" static="false" />
<emptyLine />
<package name="javax" withSubpackages="true" static="false" />
<package name="javafx" withSubpackages="true" static="false" />
<package name="java" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="true" />
</value>
</option>
<option name="JD_ALIGN_PARAM_COMMENTS" value="false" />
<option name="JD_ALIGN_EXCEPTION_COMMENTS" value="false" />
</JavaCodeStyleSettings>
<Properties>
<option name="KEEP_BLANK_LINES" value="true" />
</Properties>
<XML>
<option name="XML_ATTRIBUTE_WRAP" value="0" />
</XML>
<codeStyleSettings language="CSS">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Groovy">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
@@ -30,12 +52,17 @@
<codeStyleSettings language="JAVA">
<option name="KEEP_LINE_BREAKS" value="false" />
<option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" />
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
<option name="KEEP_SIMPLE_BLOCKS_IN_ONE_LINE" value="true" />
<option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
<option name="KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE" value="true" />
<option name="ENUM_CONSTANTS_WRAP" value="2" />
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
</indentOptions>
<arrangement>
<rules />
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="JSON">
<indentOptions>

41
.idea/compiler.xml generated
View File

@@ -1,41 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile name="Annotation profile for Cryptomator" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<processorPath useClasspath="false">
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.27/dagger-compiler-2.27.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.27/dagger-2.27.jar" />
<entry name="$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-producers/2.27/dagger-producers-2.27.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/27.1-jre/guava-27.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" />
<entry name="$MAVEN_REPOSITORY$/com/google/code/findbugs/jsr305/3.0.1/jsr305-3.0.1.jar" />
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-qual/2.5.2/checker-qual-2.5.2.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.2.0/error_prone_annotations-2.2.0.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/j2objc/j2objc-annotations/1.1/j2objc-annotations-1.1.jar" />
<entry name="$MAVEN_REPOSITORY$/org/codehaus/mojo/animal-sniffer-annotations/1.17/animal-sniffer-annotations-1.17.jar" />
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-compat-qual/2.5.3/checker-compat-qual-2.5.3.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.27/dagger-spi-2.27.jar" />
<entry name="$MAVEN_REPOSITORY$/com/squareup/javapoet/1.11.1/javapoet-1.11.1.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$/javax/annotation/jsr250-api/1.0/jsr250-api-1.0.jar" />
<entry name="$MAVEN_REPOSITORY$/net/ltgt/gradle/incap/incap/0.2/incap-0.2.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.3.50/kotlin-stdlib-1.3.50.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.3.50/kotlin-stdlib-common-1.3.50.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-metadata-jvm/0.1.0/kotlinx-metadata-jvm-0.1.0.jar" />
</processorPath>
<module name="keychain" />
<module name="launcher" />
<module name="commons" />
<module name="ui" />
</profile>
</annotationProcessing>
</component>
</project>

13
.idea/encodings.xml generated
View File

@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8">
<file url="file://$PROJECT_DIR$/main" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/main/commons/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/main/keychain/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/main/keychain/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/main/launcher/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/main/ui/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/main/ui/src/main/resources" charset="UTF-8" />
<file url="PROJECT" charset="UTF-8" />
</component>
</project>

View File

@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="bintray" />
<option name="name" value="Bintray JCenter" />
<option name="url" value="https://jcenter.bintray.com" />
</remote-repository>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central Repository" />
<option name="url" value="https://repo.maven.apache.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jcenter" />
<option name="name" value="jcenter" />
<option name="url" value="https://jcenter.bintray.com" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
</component>
</project>

View File

@@ -0,0 +1,10 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Cryptomator Linux Dev" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="launcher" />
<option name="VM_PARAMETERS" value="-Djdk.gtk.version=2 -Duser.language=en -Dcryptomator.settingsPath=&quot;~/.config/Cryptomator-Dev/settings.json&quot; -Dcryptomator.ipcPortPath=&quot;~/.config/Cryptomator-Dev/ipcPort.bin&quot; -Dcryptomator.logDir=&quot;~/.local/share/Cryptomator-Dev/logs&quot; -Dcryptomator.mountPointsDir=&quot;~/.local/share/Cryptomator-Dev/mnt&quot; -Dfuse.experimental=&quot;true&quot; -Xss20m -Xmx512m" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

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="launcher" />
<option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath=&quot;~/AppData/Roaming/Cryptomator/settings.json&quot; -Dcryptomator.ipcPortPath=&quot;~/AppData/Roaming/Cryptomator/ipcPort.bin&quot; -Dcryptomator.logDir=&quot;~/AppData/Roaming/Cryptomator&quot; -Dcryptomator.keychainPath=&quot;~/AppData/Roaming/Cryptomator/keychain.json&quot; -Xss2m -Xmx512m" />
<option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath=&quot;~/AppData/Roaming/Cryptomator/settings.json&quot; -Dcryptomator.ipcPortPath=&quot;~/AppData/Roaming/Cryptomator/ipcPort.bin&quot; -Dcryptomator.logDir=&quot;~/AppData/Roaming/Cryptomator&quot; -Dcryptomator.keychainPath=&quot;~/AppData/Roaming/Cryptomator/keychain.json&quot; -Dcryptomator.mountPointsDir=&quot;~/Cryptomator&quot; -Xss2m -Xmx512m" />
<method v="2">
<option name="Make" enabled="true" />
</method>

View File

@@ -0,0 +1,10 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Cryptomator Windows Dev" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="launcher" />
<option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath=&quot;~/AppData/Roaming/Cryptomator-Dev/settings.json&quot; -Dcryptomator.ipcPortPath=&quot;~/AppData/Roaming/Cryptomator-Dev/ipcPort.bin&quot; -Dcryptomator.logDir=&quot;~/AppData/Roaming/Cryptomator-Dev&quot; -Dcryptomator.keychainPath=&quot;~/AppData/Roaming/Cryptomator-Dev/keychain.json&quot; -Dcryptomator.mountPointsDir=&quot;~/Cryptomator-Dev&quot; -Dfuse.experimental=&quot;true&quot; -Xss2m -Xmx512m" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View File

@@ -0,0 +1,13 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Cryptomator macOS Dev" type="Application" factoryName="Application">
<envs>
<env name="LD_LIBRARY_PATH" value="/usr/local/lib" />
</envs>
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="launcher" />
<option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath=&quot;~/Library/Application Support/Cryptomator-Dev/settings.json&quot; -Dcryptomator.ipcPortPath=&quot;~/Library/Application Support/Cryptomator-Dev/ipcPort.bin&quot; -Dcryptomator.logDir=&quot;~/Library/Logs/Cryptomator-Dev&quot; -Dcryptomator.mountPointsDir=&quot;/Volumes/&quot; -Dfuse.experimental=&quot;true&quot; -Xss2m -Xmx512m -ea" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View File

@@ -73,7 +73,9 @@ For more information on the security details visit [cryptomator.org](https://doc
```
cd main
mvn clean install -Prelease
mvn clean install -Prelease,windows
# or mvn clean install -Prelease,mac
# or mvn clean install -Prelease,linux
```
This will build all the jars and bundle them together with their OS-specific dependencies under `main/buildkit/target`. This can now be used to build native packages.

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<assembly xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
<id>tarball</id>
<includeBaseDirectory>false</includeBaseDirectory>
@@ -14,13 +14,6 @@
</includes>
<outputDirectory>libs</outputDirectory>
</fileSet>
<fileSet>
<directory>target/</directory>
<includes>
<include>ffi-version.txt</include>
</includes>
<outputDirectory>libs</outputDirectory>
</fileSet>
<fileSet>
<directory>target/</directory>
<includes>
@@ -43,12 +36,5 @@
</includes>
<outputDirectory>libs</outputDirectory>
</fileSet>
<fileSet>
<directory>target/linux-libs</directory>
<includes>
<include>*.jar</include>
</includes>
<outputDirectory>libs</outputDirectory>
</fileSet>
</fileSets>
</assembly>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<assembly xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
<id>tarball</id>
<includeBaseDirectory>false</includeBaseDirectory>
@@ -14,13 +14,6 @@
</includes>
<outputDirectory>libs</outputDirectory>
</fileSet>
<fileSet>
<directory>target/</directory>
<includes>
<include>ffi-version.txt</include>
</includes>
<outputDirectory>libs</outputDirectory>
</fileSet>
<fileSet>
<directory>target/</directory>
<includes>
@@ -43,12 +36,5 @@
</includes>
<outputDirectory>libs</outputDirectory>
</fileSet>
<fileSet>
<directory>target/mac-libs</directory>
<includes>
<include>*.jar</include>
</includes>
<outputDirectory>libs</outputDirectory>
</fileSet>
</fileSets>
</assembly>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<assembly xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
<id>tarball</id>
<includeBaseDirectory>false</includeBaseDirectory>
@@ -14,13 +14,6 @@
</includes>
<outputDirectory>libs</outputDirectory>
</fileSet>
<fileSet>
<directory>target/</directory>
<includes>
<include>ffi-version.txt</include>
</includes>
<outputDirectory>libs</outputDirectory>
</fileSet>
<fileSet>
<directory>target/</directory>
<includes>
@@ -43,12 +36,5 @@
</includes>
<outputDirectory>libs</outputDirectory>
</fileSet>
<fileSet>
<directory>target/win-libs</directory>
<includes>
<include>*.jar</include>
</includes>
<outputDirectory>libs</outputDirectory>
</fileSet>
</fileSets>
</assembly>

View File

@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.5.2</version>
<version>1.5.9</version>
</parent>
<artifactId>buildkit</artifactId>
<packaging>pom</packaging>
@@ -24,7 +24,6 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>copy-resources</id>
@@ -55,8 +54,8 @@
<!-- copy libraries to target/libs/: -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.1.1</version>
<executions>
<execution>
<id>copy-libs</id>
@@ -65,110 +64,153 @@
<goal>copy-dependencies</goal>
</goals>
<configuration>
<includeScope>runtime</includeScope>
<outputDirectory>${project.build.directory}/libs</outputDirectory>
<excludeClassifiers>linux,mac,win</excludeClassifiers>
<excludeArtifactIds>dbus-java,secret-service,hkdf,java-utils</excludeArtifactIds>
</configuration>
</execution>
<execution>
<id>copy-linux-libs</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/linux-libs</outputDirectory>
<includeGroupIds>org.openjfx</includeGroupIds>
<classifier>linux</classifier>
</configuration>
</execution>
<execution>
<id>copy-linux-secret-service</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/linux-libs</outputDirectory>
<includeArtifactIds>dbus-java,secret-service,hkdf,java-utils</includeArtifactIds>
</configuration>
</execution>
<execution>
<id>copy-mac-libs</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/mac-libs</outputDirectory>
<includeGroupIds>org.openjfx</includeGroupIds>
<classifier>mac</classifier>
</configuration>
</execution>
<execution>
<id>copy-win-libs</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/win-libs</outputDirectory>
<includeGroupIds>org.openjfx</includeGroupIds>
<classifier>win</classifier>
</configuration>
</execution>
</executions>
</plugin>
<!-- create buildkit.zip: -->
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.1</version>
<executions>
<execution>
<id>assemble-linux</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptors>
<descriptor>assembly-linux.xml</descriptor>
</descriptors>
<appendAssemblyId>false</appendAssemblyId>
<finalName>buildkit-linux</finalName>
</configuration>
</execution>
<execution>
<id>assemble-mac</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptors>
<descriptor>assembly-mac.xml</descriptor>
</descriptors>
<appendAssemblyId>false</appendAssemblyId>
<finalName>buildkit-mac</finalName>
</configuration>
</execution>
<execution>
<id>assemble-win</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptors>
<descriptor>assembly-win.xml</descriptor>
</descriptors>
<appendAssemblyId>false</appendAssemblyId>
<finalName>buildkit-win</finalName>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>linux</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>assemble-linux</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptors>
<descriptor>assembly-linux.xml</descriptor>
</descriptors>
<appendAssemblyId>false</appendAssemblyId>
<finalName>buildkit-linux</finalName>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-linux-libs</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/libs</outputDirectory>
<includeGroupIds>org.openjfx</includeGroupIds>
<classifier>linux</classifier>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>mac</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>assemble-mac</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptors>
<descriptor>assembly-mac.xml</descriptor>
</descriptors>
<appendAssemblyId>false</appendAssemblyId>
<finalName>buildkit-mac</finalName>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-mac-libs</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/libs</outputDirectory>
<includeGroupIds>org.openjfx</includeGroupIds>
<classifier>mac</classifier>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>windows</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>assemble-win</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptors>
<descriptor>assembly-win.xml</descriptor>
</descriptors>
<appendAssemblyId>false</appendAssemblyId>
<finalName>buildkit-win</finalName>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-win-libs</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/libs</outputDirectory>
<includeGroupIds>org.openjfx</includeGroupIds>
<classifier>win</classifier>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@@ -1 +0,0 @@
${cryptomator.jni.version}

View File

@@ -1,4 +1,5 @@
#!/bin/sh
cd $(dirname $0)
java \
-cp "libs/*" \
-Dcryptomator.settingsPath="~/.config/Cryptomator/settings.json" \
@@ -6,6 +7,6 @@ java \
-Dcryptomator.logDir="~/.local/share/Cryptomator/logs" \
-Dcryptomator.mountPointsDir="~/.local/share/Cryptomator/mnt" \
-Djdk.gtk.version=2 \
-Xss20m \
-Xss2m \
-Xmx512m \
org.cryptomator.launcher.Cryptomator
org.cryptomator.launcher.Cryptomator

View File

@@ -1,4 +1,5 @@
#!/bin/sh
cd $(dirname $0)
java \
-cp "libs/*" \
-Dcryptomator.settingsPath="~/Library/Application Support/Cryptomator/settings.json" \

View File

@@ -4,6 +4,7 @@ java ^
-Dcryptomator.settingsPath="~/AppData/Roaming/Cryptomator/settings.json" ^
-Dcryptomator.ipcPortPath="~/AppData/Roaming/Cryptomator/ipcPort.bin" ^
-Dcryptomator.logDir="~/AppData/Roaming/Cryptomator" ^
-Dcryptomator.mountPointsDir="~/Cryptomator" ^
-Dcryptomator.keychainPath="~/AppData/Roaming/Cryptomator/keychain.json" ^
-Xss20m ^
-Xmx512m ^

View File

@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.5.2</version>
<version>1.5.9</version>
</parent>
<artifactId>commons</artifactId>
<name>Cryptomator Commons</name>
@@ -29,7 +29,7 @@
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>jni</artifactId>
<artifactId>integrations-api</artifactId>
</dependency>
<!-- JavaFx -->
@@ -44,7 +44,7 @@
<!-- EasyBind -->
<dependency>
<groupId>org.fxmisc.easybind</groupId>
<groupId>com.tobiasdiez</groupId>
<artifactId>easybind</artifactId>
</dependency>

View File

@@ -5,24 +5,25 @@
*******************************************************************************/
package org.cryptomator.common;
import com.tobiasdiez.easybind.EasyBind;
import dagger.Module;
import dagger.Provides;
import javafx.beans.binding.Binding;
import javafx.beans.binding.Bindings;
import javafx.collections.ObservableList;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.keychain.KeychainModule;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.SettingsProvider;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultComponent;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.frontend.webdav.WebDavServer;
import org.fxmisc.easybind.EasyBind;
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.collections.ObservableList;
import java.net.InetSocketAddress;
import java.util.Comparator;
import java.util.concurrent.ExecutorService;
@@ -33,7 +34,7 @@ import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@Module(subcomponents = {VaultComponent.class})
@Module(subcomponents = {VaultComponent.class}, includes = {KeychainModule.class})
public abstract class CommonsModule {
private static final Logger LOG = LoggerFactory.getLogger(CommonsModule.class);

View File

@@ -40,6 +40,7 @@ public class Environment {
LOG.debug("cryptomator.mountPointsDir: {}", System.getProperty("cryptomator.mountPointsDir"));
LOG.debug("cryptomator.minPwLength: {}", System.getProperty("cryptomator.minPwLength"));
LOG.debug("cryptomator.buildNumber: {}", System.getProperty("cryptomator.buildNumber"));
LOG.debug("fuse.experimental: {}", Boolean.getBoolean("fuse.experimental"));
}
public boolean useCustomLogbackConfig() {
@@ -74,6 +75,10 @@ public class Environment {
return getInt("cryptomator.minPwLength", DEFAULT_MIN_PW_LENGTH);
}
public boolean useExperimentalFuse() {
return Boolean.getBoolean("fuse.experimental");
}
private int getInt(String propertyName, int defaultValue) {
String value = System.getProperty(propertyName);
try {

View File

@@ -1,32 +0,0 @@
/*******************************************************************************
* Copyright (c) 2019 Skymatic GmbH.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.common;
import dagger.Module;
import dagger.Provides;
import org.cryptomator.jni.JniFunctions;
import org.cryptomator.jni.MacFunctions;
import org.cryptomator.jni.WinFunctions;
import javax.inject.Singleton;
import java.util.Optional;
@Module
public class JniModule {
@Provides
@Singleton
Optional<MacFunctions> provideOptionalMacFunctions() {
return JniFunctions.macFunctions();
}
@Provides
@Singleton
Optional<WinFunctions> provideOptionalWinFunctions() {
return JniFunctions.winFunctions();
}
}

View File

@@ -5,12 +5,12 @@
*******************************************************************************/
package org.cryptomator.common;
import com.google.common.base.Throwables;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import com.google.common.base.Throwables;
public final class LazyInitializer {
private LazyInitializer() {
@@ -18,7 +18,7 @@ public final class LazyInitializer {
/**
* Same as {@link #initializeLazily(AtomicReference, SupplierThrowingException, Class)} except that no checked exception may be thrown by the factory function.
*
*
* @param <T> Type of the value
* @param reference A reference to a maybe not yet initialized value.
* @param factory A factory providing a value for the reference, if it doesn't exist yet. The factory may be invoked multiple times, but only one result will survive.
@@ -31,7 +31,7 @@ public final class LazyInitializer {
/**
* Threadsafe lazy initialization pattern as proposed on http://stackoverflow.com/a/30247202/4014509
*
*
* @param <T> Type of the value
* @param <E> Type of the any expected exception that may occur during initialization
* @param reference A reference to a maybe not yet initialized value.

View File

@@ -1,15 +1,15 @@
package org.cryptomator.common;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.cryptomator.common.settings.Settings;
import javax.inject.Inject;
import javax.inject.Singleton;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import org.cryptomator.common.settings.Settings;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Optional;
@Singleton

View File

@@ -8,10 +8,10 @@
*******************************************************************************/
package org.cryptomator.common;
import java.util.Comparator;
import org.apache.commons.lang3.StringUtils;
import java.util.Comparator;
/**
* Compares version strings according to <a href="http://semver.org/spec/v2.0.0.html">SemVer 2.0.0</a>.
*/

View File

@@ -0,0 +1,126 @@
package org.cryptomator.common.keychain;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import org.cryptomator.integrations.keychain.KeychainAccessException;
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
import javax.inject.Inject;
import javax.inject.Singleton;
import javafx.application.Platform;
import javafx.beans.binding.ObjectExpression;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import java.util.Arrays;
@Singleton
public class KeychainManager implements KeychainAccessProvider {
private final ObjectExpression<KeychainAccessProvider> keychain;
private final LoadingCache<String, BooleanProperty> passphraseStoredProperties;
@Inject
KeychainManager(ObjectExpression<KeychainAccessProvider> selectedKeychain) {
this.keychain = selectedKeychain;
this.passphraseStoredProperties = CacheBuilder.newBuilder() //
.weakValues() //
.build(CacheLoader.from(this::createStoredPassphraseProperty));
keychain.addListener(ignored -> passphraseStoredProperties.invalidateAll());
}
private KeychainAccessProvider getKeychainOrFail() throws KeychainAccessException {
var result = keychain.getValue();
if (result == null) {
throw new NoKeychainAccessProviderException();
}
return result;
}
@Override
public void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
getKeychainOrFail().storePassphrase(key, passphrase);
setPassphraseStored(key, true);
}
@Override
public char[] loadPassphrase(String key) throws KeychainAccessException {
char[] passphrase = getKeychainOrFail().loadPassphrase(key);
setPassphraseStored(key, passphrase != null);
return passphrase;
}
@Override
public void deletePassphrase(String key) throws KeychainAccessException {
getKeychainOrFail().deletePassphrase(key);
setPassphraseStored(key, false);
}
@Override
public void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
getKeychainOrFail().changePassphrase(key, passphrase);
setPassphraseStored(key, true);
}
@Override
public boolean isSupported() {
return keychain.getValue() != null;
}
/**
* Checks if the keychain knows a passphrase for the given key.
* <p>
* Expensive operation. If possible, use {@link #getPassphraseStoredProperty(String)} instead.
*
* @param key The key to look up
* @return <code>true</code> if a password for <code>key</code> is stored.
* @throws KeychainAccessException
*/
public boolean isPassphraseStored(String key) throws KeychainAccessException {
char[] storedPw = null;
try {
storedPw = getKeychainOrFail().loadPassphrase(key);
return storedPw != null;
} finally {
if (storedPw != null) {
Arrays.fill(storedPw, ' ');
}
}
}
private void setPassphraseStored(String key, boolean value) {
BooleanProperty property = passphraseStoredProperties.getIfPresent(key);
if (property != null) {
if (Platform.isFxApplicationThread()) {
property.set(value);
} else {
Platform.runLater(() -> property.set(value));
}
}
}
/**
* Returns an observable property for use in the UI that tells whether a passphrase is stored for the given key.
* <p>
* Assuming that this process is the only process modifying Cryptomator-related items in the system keychain, this
* property stays in memory in an attempt to avoid unnecessary calls to the system keychain. Note that due to this
* fact the value stored in the returned property is not 100% reliable. Code defensively!
*
* @param key The key to look up
* @return An observable property which is <code>true</code> when it almost certain that a password for <code>key</code> is stored.
* @see #isPassphraseStored(String)
*/
public ReadOnlyBooleanProperty getPassphraseStoredProperty(String key) {
return passphraseStoredProperties.getUnchecked(key);
}
private BooleanProperty createStoredPassphraseProperty(String key) {
try {
return new SimpleBooleanProperty(isPassphraseStored(key));
} catch (KeychainAccessException e) {
return new SimpleBooleanProperty(false);
}
}
}

View File

@@ -0,0 +1,44 @@
package org.cryptomator.common.keychain;
import dagger.Module;
import dagger.Provides;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
import javax.inject.Singleton;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectExpression;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.stream.Collectors;
@Module
public class KeychainModule {
@Provides
@Singleton
static Set<ServiceLoader.Provider<KeychainAccessProvider>> provideAvailableKeychainAccessProviderFactories() {
return ServiceLoader.load(KeychainAccessProvider.class).stream().collect(Collectors.toUnmodifiableSet());
}
@Provides
@Singleton
static Set<KeychainAccessProvider> provideSupportedKeychainAccessProviders(Set<ServiceLoader.Provider<KeychainAccessProvider>> availableFactories) {
return availableFactories.stream() //
.map(ServiceLoader.Provider::get) //
.filter(KeychainAccessProvider::isSupported) //
.collect(Collectors.toUnmodifiableSet());
}
@Provides
@Singleton
static ObjectExpression<KeychainAccessProvider> provideKeychainAccessProvider(Settings settings, Set<KeychainAccessProvider> providers) {
return Bindings.createObjectBinding(() -> {
var selectedProviderClass = settings.keychainBackend().get().getProviderClass();
var selectedProvider = providers.stream().filter(provider -> provider.getClass().getName().equals(selectedProviderClass)).findAny();
var fallbackProvider = providers.stream().findAny().orElse(null);
return selectedProvider.orElse(fallbackProvider);
}, settings.keychainBackend());
}
}

View File

@@ -0,0 +1,13 @@
package org.cryptomator.common.keychain;
import org.cryptomator.integrations.keychain.KeychainAccessException;
/**
* Thrown by {@link KeychainManager} if attempted to access a keychain despite no supported keychain access provider being available.
*/
public class NoKeychainAccessProviderException extends KeychainAccessException {
public NoKeychainAccessProviderException() {
super("Did not find any supported keychain access provider.");
}
}

View File

@@ -0,0 +1,36 @@
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;
public class AvailableDriveLetterChooser implements MountPointChooser {
public static final int PRIORITY = 200;
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.getAvailableDriveLetterPath();
}
@Override
public int getPriority() {
return PRIORITY;
}
}

View File

@@ -0,0 +1,37 @@
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.nio.file.Paths;
import java.util.Optional;
public class CustomDriveLetterChooser implements MountPointChooser {
public static final int PRIORITY = 100;
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 int getPriority() {
return PRIORITY;
}
}

View File

@@ -0,0 +1,101 @@
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.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.DirectoryStream;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.NotDirectoryException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
public class CustomMountPointChooser implements MountPointChooser {
public static final int PRIORITY = 0;
private static final Logger LOG = LoggerFactory.getLogger(CustomMountPointChooser.class);
private final VaultSettings vaultSettings;
private final Environment environment;
@Inject
public CustomMountPointChooser(VaultSettings vaultSettings, Environment environment) {
this.vaultSettings = vaultSettings;
this.environment = environment;
}
@Override
public boolean isApplicable(Volume caller) {
//Disable if useExperimentalFuse is required (Win + Fuse), but set to false
return caller.getImplementationType() != VolumeImpl.FUSE || !SystemUtils.IS_OS_WINDOWS || environment.useExperimentalFuse();
}
@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 {
switch (caller.getMountPointRequirement()) {
case PARENT_NO_MOUNT_POINT -> prepareParentNoMountPoint(mountPoint);
case EMPTY_MOUNT_POINT -> prepareEmptyMountPoint(mountPoint);
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 "PARENT_OPT_MOUNT_POINT"
throw new InvalidMountPointException(new IllegalStateException("Not implemented"));
}
}
LOG.debug("Successfully checked custom mount point: {}", mountPoint);
return false;
}
private void prepareParentNoMountPoint(Path mountPoint) throws InvalidMountPointException {
//This the case on Windows when using FUSE
//See https://github.com/billziss-gh/winfsp/issues/320
Path parent = mountPoint.getParent();
if (!Files.isDirectory(parent)) {
throw new InvalidMountPointException(new NotDirectoryException(parent.toString()));
}
//We must use #notExists() here because notExists =/= !exists (see docs)
if (!Files.notExists(mountPoint, LinkOption.NOFOLLOW_LINKS)) {
//File exists OR can't be determined
throw new InvalidMountPointException(new FileAlreadyExistsException(mountPoint.toString()));
}
}
private void prepareEmptyMountPoint(Path mountPoint) throws InvalidMountPointException {
//This is the case for Windows when using Dokany and for Linux and Mac
if (!Files.isDirectory(mountPoint)) {
throw new InvalidMountPointException(new NotDirectoryException(mountPoint.toString()));
}
try (DirectoryStream<Path> ds = Files.newDirectoryStream(mountPoint)) {
if (ds.iterator().hasNext()) {
throw new InvalidMountPointException(new DirectoryNotEmptyException(mountPoint.toString()));
}
} catch (IOException exception) {
throw new InvalidMountPointException("IOException while checking folder content", exception);
}
}
@Override
public int getPriority() {
return PRIORITY;
}
}

View File

@@ -0,0 +1,16 @@
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);
}
}

View File

@@ -0,0 +1,64 @@
package org.cryptomator.common.mountpoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;
public class IrregularUnmountCleaner {
public static Logger LOG = LoggerFactory.getLogger(IrregularUnmountCleaner.class);
public static void removeIrregularUnmountDebris(Path dirContainingMountPoints) {
IOException cleanupFailed = new IOException("Cleanup failed");
try {
LOG.debug("Performing cleanup of mountpoint dir {}.", dirContainingMountPoints);
for (Path p : Files.newDirectoryStream(dirContainingMountPoints)) {
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);
}
}
private static void deleteEmptyDir(Path dir) throws IOException {
assert Files.isDirectory(dir, LinkOption.NOFOLLOW_LINKS);
try {
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 static void deleteDeadLink(Path symlink) throws IOException {
assert Files.isSymbolicLink(symlink);
if (Files.notExists(symlink)) { // following link: target does not exist
Files.delete(symlink);
}
}
}

View File

@@ -0,0 +1,169 @@
package org.cryptomator.common.mountpoint;
import com.google.common.base.Preconditions;
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 #getPriority() 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 ascending order of their priority
* (smaller 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 extends Comparable<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
}
/**
* Called by the {@link MountPointChooserModule} to sort the available MPCs
* and determine their execution order.
* The priority must be defined by the developer to reflect a useful execution order.
* MPCs with lower priorities will be placed at lower indices in the resulting
* {@link SortedSet} and will be executed with higher probability.<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.
*
* @return the priority of this MPC.
*/
int getPriority();
/**
* Called by the {@link Volume} to determine the execution order of the registered MPCs.
* <b>Implementations usually may not override this method.</b> This default implementation
* sorts the MPCs in ascending order of their {@link #getPriority() priority.}<br>
* <br>
* <b>Original description:</b>
* <p>{@inheritDoc}
*
* @implNote This default implementation sorts the MPCs in ascending order
* of their {@link #getPriority() priority.}
*/
@Override
default int compareTo(MountPointChooser other) {
Preconditions.checkNotNull(other, "Other must not be null!");
//Sort by priority (ascending order)
return Integer.compare(this.getPriority(), other.getPriority());
}
}

View File

@@ -0,0 +1,50 @@
package org.cryptomator.common.mountpoint;
import com.google.common.collect.ImmutableSortedSet;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoSet;
import org.cryptomator.common.vaults.PerVault;
import javax.inject.Named;
import java.util.Set;
import java.util.SortedSet;
/**
* Dagger-Module for {@link MountPointChooser MountPointChoosers.}<br>
* See there for additional information.
*
* @see MountPointChooser
*/
@Module
public abstract class MountPointChooserModule {
@Binds
@IntoSet
@PerVault
public abstract MountPointChooser bindCustomMountPointChooser(CustomMountPointChooser chooser);
@Binds
@IntoSet
@PerVault
public abstract MountPointChooser bindCustomDriveLetterChooser(CustomDriveLetterChooser chooser);
@Binds
@IntoSet
@PerVault
public abstract MountPointChooser bindAvailableDriveLetterChooser(AvailableDriveLetterChooser chooser);
@Binds
@IntoSet
@PerVault
public abstract MountPointChooser bindTemporaryMountPointChooser(TemporaryMountPointChooser chooser);
@Provides
@PerVault
@Named("orderedMountPointChoosers")
public static SortedSet<MountPointChooser> provideOrderedMountPointChoosers(Set<MountPointChooser> choosers) {
//Sort by natural order. The natural order is defined by MountPointChooser#compareTo
return ImmutableSortedSet.copyOf(choosers);
}
}

View File

@@ -0,0 +1,121 @@
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.vaults.Volume;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
public class TemporaryMountPointChooser implements MountPointChooser {
public static final int PRIORITY = 300;
private static final Logger LOG = LoggerFactory.getLogger(TemporaryMountPointChooser.class);
private static final int MAX_TMPMOUNTPOINT_CREATION_RETRIES = 10;
private final VaultSettings vaultSettings;
private final Environment environment;
@Inject
public TemporaryMountPointChooser(VaultSettings vaultSettings, Environment environment) {
this.vaultSettings = vaultSettings;
this.environment = environment;
}
@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) {
return this.environment.getMountPointsDir().map(this::choose);
}
private Path choose(Path parent) {
String basename = this.vaultSettings.mountName().get();
//regular
Path mountPoint = parent.resolve(basename);
if (Files.notExists(mountPoint)) {
return mountPoint;
}
//with id
mountPoint = parent.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 = parent.resolve(basename + "_(" +vaultSettings.getId() + ")_"+i);
if (Files.notExists(mountPoint)) {
return mountPoint;
}
}
LOG.error("Failed to find feasible mountpoint at {}{}{}_x. Giving up after {} attempts.", parent, File.separator, basename, MAX_TMPMOUNTPOINT_CREATION_RETRIES);
return null;
}
@Override
public boolean prepare(Volume caller, Path mountPoint) throws InvalidMountPointException {
// 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.
if (SystemUtils.IS_OS_MAC && mountPoint.getParent().equals(Paths.get("/Volumes"))) {
return false;
}
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 "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());
}
}
@Override
public int getPriority() {
return PRIORITY;
}
}

View File

@@ -0,0 +1,19 @@
package org.cryptomator.common.settings;
public enum KeychainBackend {
GNOME("org.cryptomator.linux.keychain.SecretServiceKeychainAccess"),
KDE("org.cryptomator.linux.keychain.KDEWalletKeychainAccess"),
MAC_SYSTEM_KEYCHAIN("org.cryptomator.macos.keychain.MacSystemKeychainAccess"),
WIN_SYSTEM_KEYCHAIN("org.cryptomator.windows.keychain.WindowsProtectedKeychainAccess");
private final String providerClass;
KeychainBackend(String providerClass) {
this.providerClass = providerClass;
}
public String getProviderClass() {
return providerClass;
}
}

View File

@@ -8,6 +8,8 @@
******************************************************************************/
package org.cryptomator.common.settings;
import org.apache.commons.lang3.SystemUtils;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
@@ -20,7 +22,6 @@ import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.NodeOrientation;
import java.util.function.Consumer;
public class Settings {
@@ -34,8 +35,9 @@ public class Settings {
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 = System.getProperty("os.name").toLowerCase().contains("windows") ? VolumeImpl.DOKANY : VolumeImpl.FUSE;
public static final VolumeImpl DEFAULT_PREFERRED_VOLUME_IMPL = SystemUtils.IS_OS_WINDOWS ? VolumeImpl.DOKANY : VolumeImpl.FUSE;
public static final UiTheme DEFAULT_THEME = UiTheme.LIGHT;
public static final KeychainBackend DEFAULT_KEYCHAIN_BACKEND = SystemUtils.IS_OS_WINDOWS ? KeychainBackend.WIN_SYSTEM_KEYCHAIN : SystemUtils.IS_OS_MAC ? KeychainBackend.MAC_SYSTEM_KEYCHAIN : KeychainBackend.GNOME;
public static final NodeOrientation DEFAULT_USER_INTERFACE_ORIENTATION = NodeOrientation.LEFT_TO_RIGHT;
private static final String DEFAULT_LICENSE_KEY = "";
@@ -49,6 +51,7 @@ public class Settings {
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<KeychainBackend> keychainBackend = new SimpleObjectProperty<>(DEFAULT_KEYCHAIN_BACKEND);
private final ObjectProperty<NodeOrientation> userInterfaceOrientation = new SimpleObjectProperty<>(DEFAULT_USER_INTERFACE_ORIENTATION);
private final StringProperty licenseKey = new SimpleStringProperty(DEFAULT_LICENSE_KEY);
@@ -68,6 +71,7 @@ public class Settings {
debugMode.addListener(this::somethingChanged);
preferredVolumeImpl.addListener(this::somethingChanged);
theme.addListener(this::somethingChanged);
keychainBackend.addListener(this::somethingChanged);
userInterfaceOrientation.addListener(this::somethingChanged);
licenseKey.addListener(this::somethingChanged);
}
@@ -75,7 +79,7 @@ public class Settings {
void setSaveCmd(Consumer<Settings> saveCmd) {
this.saveCmd = saveCmd;
}
private void somethingChanged(@SuppressWarnings("unused") Observable observable) {
this.save();
}
@@ -99,7 +103,7 @@ public class Settings {
public BooleanProperty checkForUpdates() {
return checkForUpdates;
}
public BooleanProperty startHidden() {
return startHidden;
}
@@ -128,6 +132,8 @@ public class Settings {
return theme;
}
public ObjectProperty<KeychainBackend> keychainBackend() { return keychainBackend; }
public ObjectProperty<NodeOrientation> userInterfaceOrientation() {
return userInterfaceOrientation;
}

View File

@@ -9,10 +9,10 @@ import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import javafx.geometry.NodeOrientation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javafx.geometry.NodeOrientation;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -38,6 +38,7 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
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("keychainBackend").value(value.keychainBackend().get().name());
out.name("licenseKey").value(value.licenseKey().get());
out.endObject();
}
@@ -58,46 +59,23 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
while (in.hasNext()) {
String name = in.nextName();
switch (name) {
case "directories":
settings.getDirectories().addAll(readVaultSettingsArray(in));
break;
case "askedForUpdateCheck":
settings.askedForUpdateCheck().set(in.nextBoolean());
break;
case "checkForUpdatesEnabled":
settings.checkForUpdates().set(in.nextBoolean());
break;
case "startHidden":
settings.startHidden().set(in.nextBoolean());
break;
case "port":
settings.port().set(in.nextInt());
break;
case "numTrayNotifications":
settings.numTrayNotifications().set(in.nextInt());
break;
case "preferredGvfsScheme":
settings.preferredGvfsScheme().set(parseWebDavUrlSchemePrefix(in.nextString()));
break;
case "debugMode":
settings.debugMode().set(in.nextBoolean());
break;
case "preferredVolumeImpl":
settings.preferredVolumeImpl().set(parsePreferredVolumeImplName(in.nextString()));
break;
case "theme":
settings.theme().set(parseUiTheme(in.nextString()));
break;
case "uiOrientation":
settings.userInterfaceOrientation().set(parseUiOrientation(in.nextString()));
break;
case "licenseKey":
settings.licenseKey().set(in.nextString());
break;
default:
case "directories" -> settings.getDirectories().addAll(readVaultSettingsArray(in));
case "askedForUpdateCheck" -> settings.askedForUpdateCheck().set(in.nextBoolean());
case "checkForUpdatesEnabled" -> settings.checkForUpdates().set(in.nextBoolean());
case "startHidden" -> settings.startHidden().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 "keychainBackend" -> settings.keychainBackend().set(parseKeychainBackend(in.nextString()));
case "licenseKey" -> settings.licenseKey().set(in.nextString());
default -> {
LOG.warn("Unsupported vault setting found in JSON: " + name);
in.skipValue();
break;
}
}
}
in.endObject();
@@ -132,6 +110,15 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
}
}
private KeychainBackend parseKeychainBackend(String backendName) {
try {
return KeychainBackend.valueOf(backendName.toUpperCase());
} catch (IllegalArgumentException e) {
LOG.warn("Invalid keychain backend {}. Defaulting to {}.", backendName, Settings.DEFAULT_KEYCHAIN_BACKEND);
return Settings.DEFAULT_KEYCHAIN_BACKEND;
}
}
private NodeOrientation parseUiOrientation(String uiOrientationName) {
try {
return NodeOrientation.valueOf(uiOrientationName.toUpperCase());

View File

@@ -1,11 +1,21 @@
package org.cryptomator.common.settings;
public enum UiTheme {
LIGHT("Light"),
DARK("Dark");
// CUSTOM("Custom (%s)");
import org.apache.commons.lang3.SystemUtils;
private String displayName;
public enum UiTheme {
LIGHT("preferences.general.theme.light"), //
DARK("preferences.general.theme.dark"), //
AUTOMATIC("preferences.general.theme.automatic");
public static UiTheme[] applicableValues() {
if (SystemUtils.IS_OS_MAC) {
return values();
} else {
return new UiTheme[]{LIGHT, DARK};
}
}
private final String displayName;
UiTheme(String displayName) {
this.displayName = displayName;

View File

@@ -5,9 +5,13 @@
*******************************************************************************/
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.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
@@ -16,17 +20,15 @@ import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import org.apache.commons.lang3.StringUtils;
import org.fxmisc.easybind.EasyBind;
import java.nio.file.Path;
import java.util.Objects;
import java.util.Optional;
import java.util.Random;
import java.util.Set;
import java.util.stream.Collectors;
/**
* The settings specific to a single vault.
* TODO: Change the name of individualMountPath and its derivatives to customMountPath
*/
public class VaultSettings {
@@ -36,35 +38,32 @@ public class VaultSettings {
public static final boolean DEFAULT_USES_READONLY_MODE = false;
public static final String DEFAULT_MOUNT_FLAGS = "";
public static final int DEFAULT_FILENAME_LENGTH_LIMIT = -1;
private static final Random RNG = new Random();
public static final WhenUnlocked DEFAULT_ACTION_AFTER_UNLOCK = WhenUnlocked.ASK;
private static final Random RNG = new Random();
private final String id;
private final ObjectProperty<Path> path = new SimpleObjectProperty();
private final StringProperty mountName = new SimpleStringProperty();
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_REAVEAL_AFTER_MOUNT);
private final BooleanProperty usesIndividualMountPath = new SimpleBooleanProperty(DEFAULT_USES_INDIVIDUAL_MOUNTPATH);
private final StringProperty individualMountPath = new SimpleStringProperty();
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 filenameLengthLimit = new SimpleIntegerProperty(DEFAULT_FILENAME_LENGTH_LIMIT);
private final ObjectProperty<WhenUnlocked> actionAfterUnlock = new SimpleObjectProperty<>(DEFAULT_ACTION_AFTER_UNLOCK);
private final StringBinding mountName;
public VaultSettings(String id) {
this.id = Objects.requireNonNull(id);
EasyBind.subscribe(path, this::deriveMountNameFromPath);
this.mountName = Bindings.createStringBinding(this::normalizeDisplayName, displayName);
}
Observable[] observables() {
return new Observable[]{path, mountName, winDriveLetter, unlockAfterStartup, revealAfterMount, usesIndividualMountPath, individualMountPath, usesReadOnlyMode, mountFlags, filenameLengthLimit};
}
private void deriveMountNameFromPath(Path path) {
if (path != null && StringUtils.isBlank(mountName.get())) {
mountName.set(normalizeMountName(path.getFileName().toString()));
}
return new Observable[]{path, displayName, winDriveLetter, unlockAfterStartup, revealAfterMount, useCustomMountPath, customMountPath, usesReadOnlyMode, mountFlags, filenameLengthLimit, actionAfterUnlock};
}
public static VaultSettings withRandomId() {
@@ -77,23 +76,18 @@ public class VaultSettings {
return BaseEncoding.base64Url().encode(randomBytes);
}
public static String normalizeMountName(String mountName) {
String normalizedMountName = StringUtils.stripAccents(mountName);
StringBuilder builder = new StringBuilder();
for (char c : normalizedMountName.toCharArray()) {
if (Character.isWhitespace(c)) {
if (builder.length() == 0 || builder.charAt(builder.length() - 1) != '_') {
builder.append('_');
}
} else if (c < 127 && Character.isLetterOrDigit(c)) {
builder.append(c);
} else {
if (builder.length() == 0 || builder.charAt(builder.length() - 1) != '_') {
builder.append('_');
}
}
//visible for testing
String normalizeDisplayName() {
var original = displayName.getValueSafe();
if (original.isBlank() || ".".equals(original) || "..".equals(original)) {
return "_";
}
return builder.toString();
// replace whitespaces (tabs, linebreaks, ...) by simple space (0x20)
var withoutFancyWhitespaces = CharMatcher.whitespace().collapseFrom(original, ' ');
// replace control chars as well as chars that aren't allowed in file names on standard file systems by underscore
return CharMatcher.anyOf("<>:\"/\\|?*").or(CharMatcher.javaIsoControl()).collapseFrom(withoutFancyWhitespaces, '_');
}
/* Getter/Setter */
@@ -106,7 +100,11 @@ public class VaultSettings {
return path;
}
public StringProperty mountName() {
public StringProperty displayName() {
return displayName;
}
public StringBinding mountName() {
return mountName;
}
@@ -114,6 +112,14 @@ public class VaultSettings {
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;
}
@@ -122,17 +128,17 @@ public class VaultSettings {
return revealAfterMount;
}
public BooleanProperty usesIndividualMountPath() {
return usesIndividualMountPath;
public BooleanProperty useCustomMountPath() {
return useCustomMountPath;
}
public StringProperty individualMountPath() {
return individualMountPath;
public StringProperty customMountPath() {
return customMountPath;
}
public Optional<String> getIndividualMountPath() {
if (usesIndividualMountPath.get()) {
return Optional.ofNullable(Strings.emptyToNull(individualMountPath.get()));
public Optional<String> getCustomMountPath() {
if (useCustomMountPath.get()) {
return Optional.ofNullable(Strings.emptyToNull(customMountPath.get()));
} else {
return Optional.empty();
}
@@ -145,11 +151,19 @@ public class VaultSettings {
public StringProperty mountFlags() {
return mountFlags;
}
public IntegerProperty filenameLengthLimit() {
return filenameLengthLimit;
}
public ObjectProperty<WhenUnlocked> actionAfterUnlock() {
return actionAfterUnlock;
}
public WhenUnlocked getActionAfterUnlock() {
return actionAfterUnlock.get();
}
/* Hashcode/Equals */
@Override

View File

@@ -21,88 +21,85 @@ class VaultSettingsJsonAdapter {
out.beginObject();
out.name("id").value(value.getId());
out.name("path").value(value.path().get().toString());
out.name("mountName").value(value.mountName().get());
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("usesIndividualMountPath").value(value.usesIndividualMountPath().get());
out.name("individualMountPath").value(value.individualMountPath().get());
out.name("useCustomMountPath").value(value.useCustomMountPath().get());
out.name("customMountPath").value(value.customMountPath().get());
out.name("usesReadOnlyMode").value(value.usesReadOnlyMode().get());
out.name("mountFlags").value(value.mountFlags().get());
out.name("filenameLengthLimit").value(value.filenameLengthLimit().get());
out.name("actionAfterUnlock").value(value.actionAfterUnlock().get().name());
out.endObject();
}
public VaultSettings read(JsonReader in) throws IOException {
String id = null;
String path = null;
String mountName = null;
String individualMountPath = 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_REAVEAL_AFTER_MOUNT;
boolean usesIndividualMountPath = VaultSettings.DEFAULT_USES_INDIVIDUAL_MOUNTPATH;
boolean useCustomMountPath = VaultSettings.DEFAULT_USES_INDIVIDUAL_MOUNTPATH;
boolean usesReadOnlyMode = VaultSettings.DEFAULT_USES_READONLY_MODE;
String mountFlags = VaultSettings.DEFAULT_MOUNT_FLAGS;
int filenameLengthLimit = VaultSettings.DEFAULT_FILENAME_LENGTH_LIMIT;
WhenUnlocked actionAfterUnlock = VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK;
in.beginObject();
while (in.hasNext()) {
String name = in.nextName();
switch (name) {
case "id":
id = in.nextString();
break;
case "path":
path = in.nextString();
break;
case "mountName":
mountName = in.nextString();
break;
case "winDriveLetter":
winDriveLetter = in.nextString();
break;
case "unlockAfterStartup":
unlockAfterStartup = in.nextBoolean();
break;
case "revealAfterMount":
revealAfterMount = in.nextBoolean();
break;
case "usesIndividualMountPath":
usesIndividualMountPath = in.nextBoolean();
break;
case "individualMountPath":
individualMountPath = in.nextString();
break;
case "usesReadOnlyMode":
usesReadOnlyMode = in.nextBoolean();
break;
case "mountFlags":
mountFlags = in.nextString();
break;
case "filenameLengthLimit":
filenameLengthLimit = in.nextInt();
break;
default:
case "id" -> id = in.nextString();
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 "filenameLengthLimit" -> filenameLengthLimit = in.nextInt();
case "actionAfterUnlock" -> actionAfterUnlock = parseActionAfterUnlock(in.nextString());
default -> {
LOG.warn("Unsupported vault setting found in JSON: " + name);
in.skipValue();
break;
}
}
}
in.endObject();
VaultSettings vaultSettings = (id == null) ? VaultSettings.withRandomId() : new VaultSettings(id);
vaultSettings.mountName().set(mountName);
if (displayName != null) { //see https://github.com/cryptomator/cryptomator/pull/1318
vaultSettings.displayName().set(displayName);
} else {
vaultSettings.displayName().set(mountName);
}
vaultSettings.path().set(Paths.get(path));
vaultSettings.winDriveLetter().set(winDriveLetter);
vaultSettings.unlockAfterStartup().set(unlockAfterStartup);
vaultSettings.revealAfterMount().set(revealAfterMount);
vaultSettings.usesIndividualMountPath().set(usesIndividualMountPath);
vaultSettings.individualMountPath().set(individualMountPath);
vaultSettings.useCustomMountPath().set(useCustomMountPath);
vaultSettings.customMountPath().set(customMountPath);
vaultSettings.usesReadOnlyMode().set(usesReadOnlyMode);
vaultSettings.mountFlags().set(mountFlags);
vaultSettings.filenameLengthLimit().set(filenameLengthLimit);
vaultSettings.actionAfterUnlock().set(actionAfterUnlock);
return vaultSettings;
}
private WhenUnlocked parseActionAfterUnlock(String actionAfterUnlockName) {
try {
return WhenUnlocked.valueOf(actionAfterUnlockName.toUpperCase());
} catch (IllegalArgumentException e) {
LOG.warn("Invalid action after unlock {}. Defaulting to {}.", actionAfterUnlockName, VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK);
return VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK;
}
}
}

View File

@@ -1,7 +1,5 @@
package org.cryptomator.common.settings;
import java.util.Arrays;
public enum VolumeImpl {
WEBDAV("WebDAV"),
FUSE("FUSE"),
@@ -17,18 +15,4 @@ public enum VolumeImpl {
return displayName;
}
/**
* Finds a VolumeImpl by display name.
*
* @param displayName Display name of the VolumeImpl
* @return VolumeImpl with the given <code>displayName</code>.
* @throws IllegalArgumentException if not volumeImpl with the given <code>displayName</code> was found.
*/
public static VolumeImpl forDisplayName(String displayName) throws IllegalArgumentException {
return Arrays.stream(values()) //
.filter(impl -> impl.displayName.equals(displayName)) //
.findAny() //
.orElseThrow(IllegalArgumentException::new);
}
}

View File

@@ -1,7 +1,5 @@
package org.cryptomator.common.settings;
import java.util.Arrays;
public enum WebDavUrlScheme {
DAV("dav", "dav:// (Gnome, Nautilus, ...)"),
WEBDAV("webdav", "webdav:// (KDE, Dolphin, ...)");
@@ -9,7 +7,8 @@ public enum WebDavUrlScheme {
private final String prefix;
private final String displayName;
WebDavUrlScheme(String prefix, String displayName) {this.prefix = prefix;
WebDavUrlScheme(String prefix, String displayName) {
this.prefix = prefix;
this.displayName = displayName;
}
@@ -20,18 +19,4 @@ public enum WebDavUrlScheme {
public String getDisplayName() {
return displayName;
}
/**
* Finds a WebDavUrlScheme by prefix.
*
* @param prefix Prefix of the WebDavUrlScheme
* @return WebDavUrlScheme with the given <code>prefix</code>.
* @throws IllegalArgumentException if not WebDavUrlScheme with the given <code>prefix</code> was found.
*/
public static WebDavUrlScheme forPrefix(String prefix) throws IllegalArgumentException {
return Arrays.stream(values()) //
.filter(impl -> impl.prefix.equals(prefix)) //
.findAny() //
.orElseThrow(IllegalArgumentException::new);
}
}

View File

@@ -0,0 +1,17 @@
package org.cryptomator.common.settings;
public enum WhenUnlocked {
IGNORE("vaultOptions.general.actionAfterUnlock.ignore"),
REVEAL("vaultOptions.general.actionAfterUnlock.reveal"),
ASK("vaultOptions.general.actionAfterUnlock.ask");
private String displayName;
WhenUnlocked(String displayName) {
this.displayName = displayName;
}
public String getDisplayName() {
return displayName;
}
}

View File

@@ -0,0 +1,60 @@
package org.cryptomator.common.vaults;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSet;
import org.cryptomator.common.mountpoint.InvalidMountPointException;
import org.cryptomator.common.mountpoint.MountPointChooser;
import java.nio.file.Path;
import java.util.Optional;
import java.util.SortedSet;
import java.util.TreeSet;
public abstract class AbstractVolume implements Volume {
private final SortedSet<MountPointChooser> choosers;
protected Path mountPoint;
//Cleanup
private boolean cleanupRequired;
private MountPointChooser usedChooser;
public AbstractVolume(SortedSet<MountPointChooser> choosers) {
this.choosers = choosers;
}
protected Path determineMountPoint() throws InvalidMountPointException {
SortedSet<MountPointChooser> checkedChoosers = new TreeSet<>(); //Natural order
for (MountPointChooser chooser : this.choosers) {
if (!chooser.isApplicable(this)) {
continue;
}
Optional<Path> chosenPath = chooser.chooseMountPoint(this);
checkedChoosers.add(chooser); //Consider a chooser checked if it's #chooseMountPoint() method was called
if (chosenPath.isEmpty()) {
//Chooser was applicable, but couldn't find a feasible mountpoint
continue;
}
this.cleanupRequired = chooser.prepare(this, chosenPath.get()); //Fail entirely if an Exception occurs
this.usedChooser = chooser;
return chosenPath.get();
}
//SortedSet#stream() should return a sorted stream (that's what it's docs and the docs of #spliterator() say, even if they are not 100% clear for me.)
//We want to keep that order, that's why we use ImmutableSet#toImmutableSet() to collect (even if it doesn't implement SortedSet, it's docs promise use encounter ordering.)
String checked = Joiner.on(", ").join(checkedChoosers.stream().map((mpc) -> mpc.getClass().getTypeName()).collect(ImmutableSet.toImmutableSet()));
throw new InvalidMountPointException(String.format("No feasible MountPoint found! Checked %s", checked.isBlank() ? "<No applicable MPC>" : checked));
}
protected void cleanupMountPoint() {
if (this.cleanupRequired) {
this.usedChooser.cleanup(this, this.mountPoint);
}
}
@Override
public Optional<Path> getMountPoint() {
return Optional.ofNullable(mountPoint);
}
}

View File

@@ -10,4 +10,5 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Documented
@Retention(RUNTIME)
public @interface DefaultMountFlags {
}

View File

@@ -1,7 +1,9 @@
package org.cryptomator.common.vaults;
import com.google.common.base.Strings;
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.Mount;
import org.cryptomator.frontend.dokany.MountFactory;
@@ -10,84 +12,47 @@ 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.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.NotDirectoryException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import javax.inject.Named;
import java.util.SortedSet;
import java.util.concurrent.ExecutorService;
public class DokanyVolume implements Volume {
public class DokanyVolume extends AbstractVolume {
private static final Logger LOG = LoggerFactory.getLogger(DokanyVolume.class);
private static final String FS_TYPE_NAME = "Cryptomator File System";
private static final String FS_TYPE_NAME = "CryptomatorFS";
private final VaultSettings vaultSettings;
private final MountFactory mountFactory;
private final WindowsDriveLetters windowsDriveLetters;
private Mount mount;
private Path mountPoint;
@Inject
public DokanyVolume(VaultSettings vaultSettings, ExecutorService executorService, WindowsDriveLetters windowsDriveLetters) {
public DokanyVolume(VaultSettings vaultSettings, ExecutorService executorService, @Named("orderedMountPointChoosers") SortedSet<MountPointChooser> choosers) {
super(choosers);
this.vaultSettings = vaultSettings;
this.mountFactory = new MountFactory(executorService);
this.windowsDriveLetters = windowsDriveLetters;
}
@Override
public boolean isSupported() {
return DokanyVolume.isSupportedStatic();
public VolumeImpl getImplementationType() {
return VolumeImpl.DOKANY;
}
@Override
public void mount(CryptoFileSystem fs, String mountFlags) throws VolumeException, IOException {
public void mount(CryptoFileSystem fs, String mountFlags) throws InvalidMountPointException, VolumeException {
this.mountPoint = determineMountPoint();
String mountName = vaultSettings.mountName().get();
try {
this.mount = mountFactory.mount(fs.getPath("/"), mountPoint, mountName, FS_TYPE_NAME, mountFlags.strip());
this.mount = mountFactory.mount(fs.getPath("/"), mountPoint, vaultSettings.mountName().get(), FS_TYPE_NAME, mountFlags.strip());
} catch (MountFailedException e) {
if (vaultSettings.getIndividualMountPath().isPresent()) {
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);
}
}
private Path determineMountPoint() throws VolumeException, IOException {
Optional<String> optionalCustomMountPoint = vaultSettings.getIndividualMountPath();
if (optionalCustomMountPoint.isPresent()) {
Path customMountPoint = Paths.get(optionalCustomMountPoint.get());
checkProvidedMountPoint(customMountPoint);
return customMountPoint;
} else if (!Strings.isNullOrEmpty(vaultSettings.winDriveLetter().get())) {
return Path.of(vaultSettings.winDriveLetter().get().charAt(0) + ":\\");
} else {
//auto assign drive letter
if (!windowsDriveLetters.getAvailableDriveLetters().isEmpty()) {
return Path.of(windowsDriveLetters.getAvailableDriveLetters().iterator().next() + ":\\");
} else {
//TODO: Error Handling
throw new VolumeException("No free drive letter available.");
}
}
}
private void checkProvidedMountPoint(Path mountPoint) throws IOException {
if (!Files.isDirectory(mountPoint)) {
throw new NotDirectoryException(mountPoint.toString());
}
try (DirectoryStream<Path> ds = Files.newDirectoryStream(mountPoint)) {
if (ds.iterator().hasNext()) {
throw new DirectoryNotEmptyException(mountPoint.toString());
}
}
}
@Override
public void reveal() throws VolumeException {
boolean success = mount.reveal();
@@ -99,11 +64,17 @@ public class DokanyVolume implements Volume {
@Override
public void unmount() {
mount.close();
cleanupMountPoint();
}
@Override
public Optional<Path> getMountPoint() {
return Optional.ofNullable(mountPoint);
public boolean isSupported() {
return DokanyVolume.isSupportedStatic();
}
@Override
public MountPointRequirement getMountPointRequirement() {
return MountPointRequirement.EMPTY_MOUNT_POINT;
}
public static boolean isSupportedStatic() {

View File

@@ -2,8 +2,9 @@ package org.cryptomator.common.vaults;
import com.google.common.base.Splitter;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Environment;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.common.mountpoint.InvalidMountPointException;
import org.cryptomator.common.mountpoint.MountPointChooser;
import org.cryptomator.common.settings.VolumeImpl;
import org.cryptomator.cryptofs.CryptoFileSystem;
import org.cryptomator.frontend.fuse.mount.CommandFailedException;
import org.cryptomator.frontend.fuse.mount.EnvironmentVariables;
@@ -15,95 +16,36 @@ 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.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.NotDirectoryException;
import javax.inject.Named;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.SortedSet;
public class FuseVolume implements Volume {
public class FuseVolume extends AbstractVolume {
private static final Logger LOG = LoggerFactory.getLogger(FuseVolume.class);
private static final int MAX_TMPMOUNTPOINT_CREATION_RETRIES = 10;
private static final boolean IS_MAC = System.getProperty("os.name").toLowerCase().contains("mac");
private final VaultSettings vaultSettings;
private final Environment environment;
private Mount fuseMnt;
private Path mountPoint;
private boolean createdTemporaryMountPoint;
private Mount mount;
@Inject
public FuseVolume(VaultSettings vaultSettings, Environment environment) {
this.vaultSettings = vaultSettings;
this.environment = environment;
public FuseVolume(@Named("orderedMountPointChoosers") SortedSet<MountPointChooser> choosers) {
super(choosers);
}
@Override
public void mount(CryptoFileSystem fs, String mountFlags) throws IOException, FuseNotSupportedException, VolumeException {
Optional<String> optionalCustomMountPoint = vaultSettings.getIndividualMountPath();
if (optionalCustomMountPoint.isPresent()) {
Path customMountPoint = Paths.get(optionalCustomMountPoint.get());
checkProvidedMountPoint(customMountPoint);
this.mountPoint = customMountPoint;
LOG.debug("Successfully checked custom mount point: {}", mountPoint);
} else {
this.mountPoint = prepareTemporaryMountPoint();
LOG.debug("Successfully created mount point: {}", mountPoint);
}
public void mount(CryptoFileSystem fs, String mountFlags) throws InvalidMountPointException, VolumeException {
this.mountPoint = determineMountPoint();
mount(fs.getPath("/"), mountFlags);
}
private void checkProvidedMountPoint(Path mountPoint) throws IOException {
if (!Files.isDirectory(mountPoint)) {
throw new NotDirectoryException(mountPoint.toString());
}
try (DirectoryStream<Path> ds = Files.newDirectoryStream(mountPoint)) {
if (ds.iterator().hasNext()) {
throw new DirectoryNotEmptyException(mountPoint.toString());
}
}
}
private Path prepareTemporaryMountPoint() throws IOException, VolumeException {
Path mountPoint = chooseNonExistingTemporaryMountPoint();
// 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.
if (IS_MAC && mountPoint.getParent().equals(Paths.get("/Volumes"))) {
return mountPoint;
} else {
Files.createDirectories(mountPoint);
this.createdTemporaryMountPoint = true;
return mountPoint;
}
}
private Path chooseNonExistingTemporaryMountPoint() throws VolumeException {
Path parent = environment.getMountPointsDir().orElseThrow();
String basename = vaultSettings.getId();
for (int i = 0; i < MAX_TMPMOUNTPOINT_CREATION_RETRIES; i++) {
Path mountPoint = parent.resolve(basename + "_" + i);
if (Files.notExists(mountPoint)) {
return mountPoint;
}
}
LOG.error("Failed to find feasible mountpoint at {}/{}_x. Giving up after {} attempts.", parent, basename, MAX_TMPMOUNTPOINT_CREATION_RETRIES);
throw new VolumeException("Did not find feasible mount point.");
}
private void mount(Path root, String mountFlags) throws VolumeException {
try {
Mounter mounter = FuseMountFactory.getMounter();
EnvironmentVariables envVars = EnvironmentVariables.create() //
.withFlags(splitFlags(mountFlags)).withMountPoint(mountPoint) //
.build();
this.fuseMnt = mounter.mount(root, envVars);
} catch (CommandFailedException e) {
this.mount = mounter.mount(root, envVars);
} catch (CommandFailedException | FuseNotSupportedException e) {
throw new VolumeException("Unable to mount Filesystem", e);
}
}
@@ -115,7 +57,7 @@ public class FuseVolume implements Volume {
@Override
public void reveal() throws VolumeException {
try {
fuseMnt.revealInFileManager();
mount.revealInFileManager();
} catch (CommandFailedException e) {
LOG.debug("Revealing the vault in file manger failed: " + e.getMessage());
throw new VolumeException(e);
@@ -130,34 +72,23 @@ public class FuseVolume implements Volume {
@Override
public synchronized void unmountForced() throws VolumeException {
try {
fuseMnt.unmountForced();
fuseMnt.close();
mount.unmountForced();
mount.close();
} catch (CommandFailedException e) {
throw new VolumeException(e);
}
cleanupTemporaryMountPoint();
cleanupMountPoint();
}
@Override
public synchronized void unmount() throws VolumeException {
try {
fuseMnt.unmount();
fuseMnt.close();
mount.unmount();
mount.close();
} catch (CommandFailedException e) {
throw new VolumeException(e);
}
cleanupTemporaryMountPoint();
}
private void cleanupTemporaryMountPoint() {
if (createdTemporaryMountPoint) {
try {
Files.delete(mountPoint);
LOG.debug("Successfully deleted mount point: {}", mountPoint);
} catch (IOException e) {
LOG.warn("Could not delete mount point: {}", e.getMessage());
}
}
cleanupMountPoint();
}
@Override
@@ -166,12 +97,17 @@ public class FuseVolume implements Volume {
}
@Override
public Optional<Path> getMountPoint() {
return Optional.ofNullable(mountPoint);
public VolumeImpl getImplementationType() {
return VolumeImpl.FUSE;
}
@Override
public MountPointRequirement getMountPointRequirement() {
return SystemUtils.IS_OS_WINDOWS ? MountPointRequirement.PARENT_NO_MOUNT_POINT : MountPointRequirement.EMPTY_MOUNT_POINT;
}
public static boolean isSupportedStatic() {
return (SystemUtils.IS_OS_MAC_OSX || SystemUtils.IS_OS_LINUX) && FuseMountFactory.isFuseSupported();
return FuseMountFactory.isFuseSupported();
}
}

View File

@@ -0,0 +1,28 @@
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 {
/**
* 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;
}

View File

@@ -8,6 +8,6 @@ import java.lang.annotation.RetentionPolicy;
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
@interface PerVault {
public @interface PerVault {
}

View File

@@ -9,14 +9,11 @@
package org.cryptomator.common.vaults;
import com.google.common.base.Strings;
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 org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.LazyInitializer;
import org.cryptomator.common.mountpoint.InvalidMountPointException;
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;
@@ -31,13 +28,20 @@ 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.StringBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import java.io.IOException;
import java.nio.file.NoSuchFileException;
import java.nio.file.NotDirectoryException;
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.atomic.AtomicReference;
@@ -56,7 +60,7 @@ public class Vault {
private final ObjectProperty<VaultState> state;
private final ObjectProperty<Exception> lastKnownException;
private final VaultStats stats;
private final StringBinding displayableName;
private final StringBinding displayName;
private final StringBinding displayablePath;
private final BooleanBinding locked;
private final BooleanBinding processing;
@@ -66,6 +70,7 @@ public class Vault {
private final BooleanBinding unknownError;
private final StringBinding accessPoint;
private final BooleanBinding accessPointPresent;
private final BooleanProperty showingStats;
private volatile Volume volume;
@@ -78,7 +83,7 @@ public class Vault {
this.state = state;
this.lastKnownException = lastKnownException;
this.stats = stats;
this.displayableName = Bindings.createStringBinding(this::getDisplayableName, vaultSettings.path());
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);
@@ -88,6 +93,7 @@ public class Vault {
this.unknownError = Bindings.createBooleanBinding(this::isUnknownError, state);
this.accessPoint = Bindings.createStringBinding(this::getAccessPoint, state);
this.accessPointPresent = this.accessPoint.isNotEmpty();
this.showingStats = new SimpleBooleanProperty(false);
}
// ******************************************************************************
@@ -120,16 +126,13 @@ public class Vault {
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
}
public synchronized void unlock(CharSequence passphrase) throws CryptoException, IOException, Volume.VolumeException {
if (vaultSettings.usesIndividualMountPath().get() && Strings.isNullOrEmpty(vaultSettings.individualMountPath().get())) {
throw new NotDirectoryException("");
}
public synchronized void unlock(CharSequence passphrase) throws CryptoException, IOException, VolumeException, InvalidMountPointException {
CryptoFileSystem fs = getCryptoFileSystem(passphrase);
volume = volumeProvider.get();
volume.mount(fs, getEffectiveMountFlags());
}
public synchronized void lock(boolean forced) throws Volume.VolumeException {
public synchronized void lock(boolean forced) throws VolumeException {
if (forced && volume.supportsForcedUnmount()) {
volume.unmountForced();
} else {
@@ -145,7 +148,7 @@ public class Vault {
}
}
public void reveal() throws Volume.VolumeException {
public void reveal() throws VolumeException {
volume.reveal();
}
@@ -225,13 +228,12 @@ public class Vault {
return state.get() == VaultState.ERROR;
}
public StringBinding displayableNameProperty() {
return displayableName;
public StringBinding displayNameProperty() {
return displayName;
}
public String getDisplayableName() {
Path p = vaultSettings.path().get();
return p.getFileName().toString();
public String getDisplayName() {
return vaultSettings.displayName().get();
}
public StringBinding accessPointProperty() {
@@ -270,6 +272,15 @@ public class Vault {
}
}
public BooleanProperty showingStatsProperty() {
return showingStats;
}
public boolean isShowingStats() {
return accessPointPresent.get();
}
// ******************************************************************************
// Getter/Setter
// *******************************************************************************/
@@ -319,6 +330,10 @@ public class Vault {
return vaultSettings.getId();
}
public Optional<Volume> getVolume() {
return Optional.ofNullable(this.volume);
}
// ******************************************************************************
// Hashcode / Equals
// *******************************************************************************/

View File

@@ -6,15 +6,15 @@
package org.cryptomator.common.vaults;
import dagger.BindsInstance;
import org.cryptomator.common.settings.VaultSettings;
import dagger.Subcomponent;
import org.cryptomator.common.mountpoint.MountPointChooserModule;
import org.cryptomator.common.settings.VaultSettings;
import javax.annotation.Nullable;
import javax.inject.Named;
@PerVault
@Subcomponent(modules = {VaultModule.class})
@Subcomponent(modules = {VaultModule.class, MountPointChooserModule.class})
public interface VaultComponent {
Vault vault();

View File

@@ -1,9 +1,9 @@
package org.cryptomator.common.vaults;
import org.cryptomator.common.settings.VaultSettings;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import org.cryptomator.common.settings.VaultSettings;
import java.util.List;
import java.util.stream.Collectors;
@@ -20,7 +20,7 @@ class VaultListChangeListener implements ListChangeListener<Vault> {
@Override
public void onChanged(Change<? extends Vault> c) {
while(c.next()) {
while (c.next()) {
if (c.wasAdded()) {
List<VaultSettings> addedSettings = c.getAddedSubList().stream().map(Vault::getVaultSettings).collect(Collectors.toList());
vaultSettingsList.addAll(c.getFrom(), addedSettings);

View File

@@ -8,8 +8,6 @@
*******************************************************************************/
package org.cryptomator.common.vaults;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
@@ -19,12 +17,14 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.stream.Collectors;
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
@@ -36,10 +36,12 @@ public class VaultListManager {
private final VaultComponent.Builder vaultComponentBuilder;
private final ObservableList<Vault> vaultList;
private final String defaultVaultName;
@Inject
public VaultListManager(VaultComponent.Builder vaultComponentBuilder, Settings settings) {
public VaultListManager(VaultComponent.Builder vaultComponentBuilder, ResourceBundle resourceBundle, Settings settings) {
this.vaultComponentBuilder = vaultComponentBuilder;
this.defaultVaultName = resourceBundle.getString("defaults.vault.vaultName");
this.vaultList = FXCollections.observableArrayList(Vault::observables);
addAll(settings.getDirectories());
@@ -59,14 +61,23 @@ public class VaultListManager {
if (alreadyExistingVault.isPresent()) {
return alreadyExistingVault.get();
} else {
VaultSettings vaultSettings = VaultSettings.withRandomId();
vaultSettings.path().set(normalizedPathToVault);
Vault newVault = create(vaultSettings);
Vault newVault = create(newVaultSettings(normalizedPathToVault));
vaultList.add(newVault);
return newVault;
}
}
private VaultSettings newVaultSettings(Path path) {
VaultSettings vaultSettings = VaultSettings.withRandomId();
vaultSettings.path().set(path);
if (path.getFileName() != null) {
vaultSettings.displayName().set(path.getFileName().toString());
} else {
vaultSettings.displayName().set(defaultVaultName);
}
return vaultSettings;
}
private void addAll(Collection<VaultSettings> vaultSettings) {
Collection<Vault> vaults = vaultSettings.stream().map(this::create).collect(Collectors.toList());
vaultList.addAll(vaults);
@@ -93,7 +104,26 @@ public class VaultListManager {
return compBuilder.build().vault();
}
public static VaultState determineVaultState(Path pathToVault) throws IOException {
public static VaultState redetermineVaultState(Vault vault) {
VaultState previousState = vault.getState();
return switch (previousState) {
case LOCKED, NEEDS_MIGRATION, MISSING -> {
try {
VaultState determinedState = determineVaultState(vault.getPath());
vault.setState(determinedState);
yield determinedState;
} catch (IOException e) {
LOG.warn("Failed to determine vault state for " + vault.getPath(), e);
vault.setState(VaultState.ERROR);
vault.setLastKnownException(e);
yield VaultState.ERROR;
}
}
case ERROR, UNLOCKED, PROCESSING -> previousState;
};
}
private static VaultState determineVaultState(Path pathToVault) throws IOException {
if (!CryptoFileSystemProvider.containsVault(pathToVault, MASTERKEY_FILENAME)) {
return VaultState.MISSING;
} else if (Migrators.get().needsMigration(pathToVault, MASTERKEY_FILENAME)) {

View File

@@ -7,14 +7,6 @@ package org.cryptomator.common.vaults;
import dagger.Module;
import dagger.Provides;
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.ReadOnlyStringProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.StringProperty;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
@@ -25,6 +17,12 @@ import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
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;
@@ -77,7 +75,7 @@ public class VaultModule {
@DefaultMountFlags
public StringBinding provideDefaultMountFlags(Settings settings, VaultSettings vaultSettings) {
ObjectProperty<VolumeImpl> preferredVolumeImpl = settings.preferredVolumeImpl();
StringProperty mountName = vaultSettings.mountName();
StringBinding mountName = vaultSettings.mountName();
BooleanProperty readOnly = vaultSettings.usesReadOnlyMode();
return Bindings.createStringBinding(() -> {
@@ -86,6 +84,8 @@ public class VaultModule {
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 {
@@ -95,7 +95,7 @@ public class VaultModule {
}
// see: https://github.com/osxfuse/osxfuse/wiki/Mount-options
private String getMacFuseDefaultMountFlags(ReadOnlyStringProperty mountName, ReadOnlyBooleanProperty readOnly) {
private String getMacFuseDefaultMountFlags(StringBinding mountName, ReadOnlyBooleanProperty readOnly) {
assert SystemUtils.IS_OS_MAC_OSX;
StringBuilder flags = new StringBuilder();
if (readOnly.get()) {
@@ -144,6 +144,28 @@ public class VaultModule {
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 choosen
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=-1");
}
flags.append(" -ovolname=").append(mountName.get());
//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;

View File

@@ -2,7 +2,7 @@ package org.cryptomator.common.vaults;
public enum VaultState {
/**
* No vault found at the provided path
* No vault found at the provided path
*/
MISSING,

View File

@@ -1,19 +1,21 @@
package org.cryptomator.common.vaults;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.beans.property.LongProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import javafx.util.Duration;
import org.cryptomator.cryptofs.CryptoFileSystem;
import org.cryptomator.cryptofs.CryptoFileSystemStats;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.LongProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import javafx.util.Duration;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicReference;
@@ -28,6 +30,15 @@ public class VaultStats {
private final ScheduledService<Optional<CryptoFileSystemStats>> updateService;
private final LongProperty bytesPerSecondRead = new SimpleLongProperty();
private final LongProperty bytesPerSecondWritten = new SimpleLongProperty();
private final LongProperty bytesPerSecondEncrypted = new SimpleLongProperty();
private final LongProperty bytesPerSecondDecrypted = new SimpleLongProperty();
private final DoubleProperty cacheHitRate = new SimpleDoubleProperty();
private final LongProperty toalBytesRead = new SimpleLongProperty();
private final LongProperty toalBytesWritten = new SimpleLongProperty();
private final LongProperty totalBytesEncrypted = new SimpleLongProperty();
private final LongProperty totalBytesDecrypted = new SimpleLongProperty();
private final LongProperty filesRead = new SimpleLongProperty();
private final LongProperty filesWritten = new SimpleLongProperty();
@Inject
VaultStats(AtomicReference<CryptoFileSystem> fs, ObjectProperty<VaultState> state, ExecutorService executor) {
@@ -53,12 +64,36 @@ public class VaultStats {
private void updateStats(Optional<CryptoFileSystemStats> stats) {
assert Platform.isFxApplicationThread();
bytesPerSecondRead.set(stats.map(CryptoFileSystemStats::pollBytesRead).orElse(0l));
bytesPerSecondWritten.set(stats.map(CryptoFileSystemStats::pollBytesWritten).orElse(0l));
bytesPerSecondRead.set(stats.map(CryptoFileSystemStats::pollBytesRead).orElse(0L));
bytesPerSecondWritten.set(stats.map(CryptoFileSystemStats::pollBytesWritten).orElse(0L));
cacheHitRate.set(stats.map(this::getCacheHitRate).orElse(0.0));
bytesPerSecondDecrypted.set(stats.map(CryptoFileSystemStats::pollBytesDecrypted).orElse(0L));
bytesPerSecondEncrypted.set(stats.map(CryptoFileSystemStats::pollBytesEncrypted).orElse(0L));
toalBytesRead.set(stats.map(CryptoFileSystemStats::pollTotalBytesRead).orElse(0L));
toalBytesWritten.set(stats.map(CryptoFileSystemStats::pollTotalBytesWritten).orElse(0L));
totalBytesEncrypted.set(stats.map(CryptoFileSystemStats::pollTotalBytesEncrypted).orElse(0L));
totalBytesDecrypted.set(stats.map(CryptoFileSystemStats::pollTotalBytesDecrypted).orElse(0L));
filesRead.set(stats.map(CryptoFileSystemStats::pollAmountOfAccessesRead).orElse(0L));
filesWritten.set(stats.map(CryptoFileSystemStats::pollAmountOfAccessesWritten).orElse(0L));
}
private double getCacheHitRate(CryptoFileSystemStats stats) {
long accesses = stats.pollChunkCacheAccesses();
long hits = stats.pollChunkCacheHits();
if (accesses == 0) {
return 0.0;
} else {
return hits / (double) accesses;
}
}
private class UpdateStatsService extends ScheduledService<Optional<CryptoFileSystemStats>> {
private UpdateStatsService() {
setOnFailed(event -> LOG.error("Error in UpdateStateService.", getException()));
}
@Override
protected Task<Optional<CryptoFileSystemStats>> createTask() {
return new Task<>() {
@@ -94,4 +129,50 @@ public class VaultStats {
public long getBytesPerSecondWritten() {
return bytesPerSecondWritten.get();
}
public LongProperty bytesPerSecondEncryptedProperty() {
return bytesPerSecondEncrypted;
}
public long getBytesPerSecondEnrypted() {
return bytesPerSecondEncrypted.get();
}
public LongProperty bytesPerSecondDecryptedProperty() {
return bytesPerSecondDecrypted;
}
public long getBytesPerSecondDecrypted() {
return bytesPerSecondDecrypted.get();
}
public DoubleProperty cacheHitRateProperty() { return cacheHitRate; }
public double getCacheHitRate() {
return cacheHitRate.get();
}
public LongProperty toalBytesReadProperty() {return toalBytesRead;}
public long getTotalBytesRead() { return toalBytesRead.get();}
public LongProperty toalBytesWrittenProperty() {return toalBytesWritten;}
public long getTotalBytesWritten() { return toalBytesWritten.get();}
public LongProperty totalBytesEncryptedProperty() {return totalBytesEncrypted;}
public long getTotalBytesEncrypted() { return totalBytesEncrypted.get();}
public LongProperty totalBytesDecryptedProperty() {return totalBytesDecrypted;}
public long getTotalBytesDecrypted() { return totalBytesDecrypted.get();}
public LongProperty filesRead() { return filesRead;}
public long getFilesRead() { return filesRead.get();}
public LongProperty filesWritten() {return filesWritten;}
public long getFilesWritten() {return filesWritten.get();}
}

View File

@@ -1,5 +1,6 @@
package org.cryptomator.common.vaults;
import org.cryptomator.common.mountpoint.InvalidMountPointException;
import org.cryptomator.common.settings.VolumeImpl;
import org.cryptomator.cryptofs.CryptoFileSystem;
@@ -20,11 +21,18 @@ public interface Volume {
*/
boolean isSupported();
/**
* Gets the coresponding 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) throws IOException, VolumeException;
void mount(CryptoFileSystem fs, String mountFlags) throws IOException, VolumeException, InvalidMountPointException;
void reveal() throws VolumeException;
@@ -32,6 +40,8 @@ public interface Volume {
Optional<Path> getMountPoint();
MountPointRequirement getMountPointRequirement();
// optional forced unmounting:
default boolean supportsForcedUnmount() {

View File

@@ -1,8 +1,10 @@
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;
@@ -44,7 +46,9 @@ public class WebDavVolume implements Volume {
if (!server.isRunning()) {
server.start();
}
servlet = server.createWebDavServlet(fs.getPath("/"), vaultSettings.getId() + "/" + vaultSettings.mountName().get());
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();
mount();
}
@@ -98,7 +102,12 @@ public class WebDavVolume implements Volume {
@Override
public Optional<Path> getMountPoint() {
return Optional.ofNullable(mountPoint);
return Optional.ofNullable(mountPoint); //TODO
}
@Override
public MountPointRequirement getMountPointRequirement() {
return MountPointRequirement.NONE;
}
private String getLocalhostAliasOrNull() {
@@ -126,6 +135,11 @@ public class WebDavVolume implements Volume {
return WebDavVolume.isSupportedStatic();
}
@Override
public VolumeImpl getImplementationType() {
return VolumeImpl.WEBDAV;
}
@Override
public boolean supportsForcedUnmount() {
return mount != null && mount.forced().isPresent();

View File

@@ -5,6 +5,7 @@
*******************************************************************************/
package org.cryptomator.common.vaults;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import org.apache.commons.lang3.SystemUtils;
@@ -12,6 +13,7 @@ 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;
@@ -24,7 +26,7 @@ public final class WindowsDriveLetters {
static {
try (IntStream stream = IntStream.rangeClosed('C', 'Z')) {
C_TO_Z = stream.mapToObj(i -> String.valueOf((char) i)).collect(Collectors.toSet());
C_TO_Z = stream.mapToObj(i -> String.valueOf((char) i)).collect(ImmutableSet.toImmutableSet());
}
}
@@ -46,7 +48,18 @@ public final class WindowsDriveLetters {
}
public Set<String> getAvailableDriveLetters() {
return Sets.difference(C_TO_Z, getOccupiedDriveLetters());
return Sets.difference(getAllDriveLetters(), getOccupiedDriveLetters());
}
public Optional<String> getAvailableDriveLetter() {
return getAvailableDriveLetters().stream().findFirst();
}
public Optional<Path> getAvailableDriveLetterPath() {
return getAvailableDriveLetter().map(this::toPath);
}
public Path toPath(String driveLetter) {
return Path.of(driveLetter + ":\\");
}
}

View File

@@ -37,7 +37,7 @@ class EnvironmentTest {
List<Path> result = env.getSettingsPath().collect(Collectors.toList());
MatcherAssert.assertThat(result, Matchers.hasSize(2));
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/settings.json"),
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/settings.json"), //
Paths.get("/home/testuser/.Cryptomator/settings.json")));
}
@@ -48,7 +48,7 @@ class EnvironmentTest {
List<Path> result = env.getIpcPortPath().collect(Collectors.toList());
MatcherAssert.assertThat(result, Matchers.hasSize(2));
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/ipcPort.bin"),
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/ipcPort.bin"), //
Paths.get("/home/testuser/.Cryptomator/ipcPort.bin")));
}
@@ -123,8 +123,8 @@ class EnvironmentTest {
List<Path> result = env.getPaths("test.path.property").collect(Collectors.toList());
MatcherAssert.assertThat(result, Matchers.hasSize(3));
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/test"),
Paths.get("/home/testuser/test2"),
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/test"), //
Paths.get("/home/testuser/test2"), //
Paths.get("/foo/bar/test")));
}

View File

@@ -1,7 +1,6 @@
package org.cryptomator.common;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.cryptomator.common.LicenseChecker;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -14,7 +13,7 @@ class LicenseCheckerTest {
+ "PDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47" //
+ "6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM" //
+ "Al8G7CqwoJOsW7Kddns=";
private LicenseChecker licenseChecker;
@BeforeEach
@@ -25,9 +24,9 @@ class LicenseCheckerTest {
@Test
public void testCheckValidLicense() {
String license = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InhaRGZacHJ5NFA5dlpQWnlHMmZOQlJqLTdMejVvbVZkbTd0SG9DZ1NOZlkifQ.eyJzdWIiOiJjcnlwdG9ib3RAZXhhbXBsZS5jb20iLCJpYXQiOjE1MTYyMzkwMjJ9.AQaBIKQdNCxmRJi2wLOcbagTgi39WhdWwgdpKTYSPicg-aPr_tst_RjmnqMemx3cBe0Blr4nEbj_lAtSKHz_i61fAUyI1xCIAZYbK9Q3ICHIHQl3AiuCpBwFl-k81OB4QDYiKpEc9gLN5dhW_VymJMsgOvyiC0UjC91f2AM7s46byDNj";
Optional<DecodedJWT> decoded = licenseChecker.check(license);
Assertions.assertTrue(decoded.isPresent());
Assertions.assertEquals("cryptobot@example.com", decoded.get().getSubject());
}

View File

@@ -0,0 +1,58 @@
package org.cryptomator.common.keychain;
import org.cryptomator.integrations.keychain.KeychainAccessException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class KeychainManagerTest {
@Test
public void testStoreAndLoad() throws KeychainAccessException {
KeychainManager keychainManager = new KeychainManager(new SimpleObjectProperty<>(new MapKeychainAccess()));
keychainManager.storePassphrase("test", "asd");
Assertions.assertArrayEquals("asd".toCharArray(), keychainManager.loadPassphrase("test"));
}
@Nested
public static class WhenObservingProperties {
@BeforeAll
public static void startup() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
Platform.startup(latch::countDown);
latch.await(5, TimeUnit.SECONDS);
}
@Test
public void testPropertyChangesWhenStoringPassword() throws KeychainAccessException, InterruptedException {
KeychainManager keychainManager = new KeychainManager(new SimpleObjectProperty<>(new MapKeychainAccess()));
ReadOnlyBooleanProperty property = keychainManager.getPassphraseStoredProperty("test");
Assertions.assertEquals(false, property.get());
keychainManager.storePassphrase("test", "bar");
AtomicBoolean result = new AtomicBoolean(false);
CountDownLatch latch = new CountDownLatch(1);
Platform.runLater(() -> {
result.set(property.get());
latch.countDown();
});
latch.await(1, TimeUnit.SECONDS);
Assertions.assertEquals(true, result.get());
}
}
}

View File

@@ -3,12 +3,14 @@
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.keychain;
package org.cryptomator.common.keychain;
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
import java.util.HashMap;
import java.util.Map;
class MapKeychainAccess implements KeychainAccessStrategy {
class MapKeychainAccess implements KeychainAccessProvider {
private final Map<String, char[]> map = new HashMap<>();
@@ -31,6 +33,12 @@ class MapKeychainAccess implements KeychainAccessStrategy {
map.remove(key);
}
@Override
public void changePassphrase(String key, CharSequence passphrase) {
map.get(key);
storePassphrase(key, passphrase);
}
@Override
public boolean isSupported() {
return true;

View File

@@ -37,10 +37,10 @@ public class SettingsJsonAdapterTest {
}
@ParameterizedTest(name = "fromJson() should throw IOException for input: {0}")
@ValueSource(strings = {
"",
"<html>",
"{invalidjson}"
@ValueSource(strings = { //
"", //
"<html>", //
"{invalidjson}" //
})
public void testDeserializeMalformed(String input) {
Assertions.assertThrows(IOException.class, () -> {

View File

@@ -29,7 +29,7 @@ public class SettingsTest {
Mockito.verify(changeListener, Mockito.times(2)).accept(settings);
// third change (to property of list item):
vaultSettings.mountName().set("asd");
vaultSettings.displayName().set("asd");
Mockito.verify(changeListener, Mockito.times(3)).accept(settings);
}

View File

@@ -16,7 +16,6 @@ import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.file.Paths;
import java.util.Arrays;
public class VaultSettingsJsonAdapterTest {
@@ -24,15 +23,15 @@ public class VaultSettingsJsonAdapterTest {
@Test
public void testDeserialize() throws IOException {
String json = "{\"id\": \"foo\", \"path\": \"/foo/bar\", \"mountName\": \"test\", \"winDriveLetter\": \"X\", \"shouldBeIgnored\": true, \"individualMountPath\": \"/home/test/crypto\", \"mountFlags\":\"--foo --bar\"}";
String json = "{\"id\": \"foo\", \"path\": \"/foo/bar\", \"displayName\": \"test\", \"winDriveLetter\": \"X\", \"shouldBeIgnored\": true, \"individualMountPath\": \"/home/test/crypto\", \"mountFlags\":\"--foo --bar\"}";
JsonReader jsonReader = new JsonReader(new StringReader(json));
VaultSettings vaultSettings = adapter.read(jsonReader);
Assertions.assertEquals("foo", vaultSettings.getId());
Assertions.assertEquals(Paths.get("/foo/bar"), vaultSettings.path().get());
Assertions.assertEquals("test", vaultSettings.mountName().get());
Assertions.assertEquals("test", vaultSettings.displayName().get());
Assertions.assertEquals("X", vaultSettings.winDriveLetter().get());
Assertions.assertEquals("/home/test/crypto", vaultSettings.individualMountPath().get());
Assertions.assertEquals("/home/test/crypto", vaultSettings.customMountPath().get());
Assertions.assertEquals("--foo --bar", vaultSettings.mountFlags().get());
@@ -42,7 +41,7 @@ public class VaultSettingsJsonAdapterTest {
public void testSerialize() throws IOException {
VaultSettings vaultSettings = new VaultSettings("test");
vaultSettings.path().set(Paths.get("/foo/bar"));
vaultSettings.mountName().set("mountyMcMountFace");
vaultSettings.displayName().set("mountyMcMountFace");
vaultSettings.mountFlags().set("--foo --bar");
StringWriter buf = new StringWriter();
@@ -51,12 +50,12 @@ public class VaultSettingsJsonAdapterTest {
String result = buf.toString();
MatcherAssert.assertThat(result, CoreMatchers.containsString("\"id\":\"test\""));
if(System.getProperty("os.name").contains("Windows")){
if (System.getProperty("os.name").contains("Windows")) {
MatcherAssert.assertThat(result, CoreMatchers.containsString("\"path\":\"\\\\foo\\\\bar\""));
} else {
MatcherAssert.assertThat(result, CoreMatchers.containsString("\"path\":\"/foo/bar\""));
}
MatcherAssert.assertThat(result, CoreMatchers.containsString("\"mountName\":\"mountyMcMountFace\""));
MatcherAssert.assertThat(result, CoreMatchers.containsString("\"displayName\":\"mountyMcMountFace\""));
MatcherAssert.assertThat(result, CoreMatchers.containsString("\"mountFlags\":\"--foo --bar\""));
}

View File

@@ -8,19 +8,19 @@
*******************************************************************************/
package org.cryptomator.common.settings;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class VaultSettingsTest {
@Test
public void testNormalize() throws Exception {
assertEquals("_", VaultSettings.normalizeMountName(" "));
assertEquals("a", VaultSettings.normalizeMountName("ä"));
assertEquals("C", VaultSettings.normalizeMountName("Ĉ"));
assertEquals("_", VaultSettings.normalizeMountName(":"));
assertEquals("_", VaultSettings.normalizeMountName("汉语"));
@ParameterizedTest
@CsvSource({"a\u000Fa,a_a", ": \\,_ _", "汉语,汉语", "..,_", "a\ta,a\u0020a", "\t\n\r,_"})
public void testNormalize(String test, String expected) {
VaultSettings settings = new VaultSettings("id");
settings.displayName().setValue(test);
assertEquals(expected, settings.normalizeDisplayName());
}
}

View File

@@ -1,9 +1,5 @@
package org.cryptomator.common.vaults;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.common.settings.VolumeImpl;
@@ -17,6 +13,11 @@ import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.Mockito;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import java.nio.file.Path;
public class VaultModuleTest {
@@ -28,8 +29,9 @@ public class VaultModuleTest {
@BeforeEach
public void setup(@TempDir Path tmpDir) {
Mockito.when(vaultSettings.mountName()).thenReturn(new SimpleStringProperty("TEST"));
Mockito.when(vaultSettings.mountName()).thenReturn(Bindings.createStringBinding(() -> "TEST"));
Mockito.when(vaultSettings.usesReadOnlyMode()).thenReturn(new SimpleBooleanProperty(true));
Mockito.when(vaultSettings.displayName()).thenReturn(new SimpleStringProperty("Vault"));
System.setProperty("user.home", tmpDir.toString());
}

View File

@@ -1,53 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.5.2</version>
</parent>
<artifactId>keychain</artifactId>
<name>System Keychain Access</name>
<dependencies>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>commons</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<!-- Google -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<!-- DI -->
<dependency>
<groupId>com.google.dagger</groupId>
<artifactId>dagger</artifactId>
</dependency>
<!-- secret-service lib -->
<dependency>
<groupId>de.swiesend</groupId>
<artifactId>secret-service</artifactId>
<version>1.0.0-RC.3</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -1,31 +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.keychain;
public interface KeychainAccess {
/**
* Associates a passphrase with a given key.
*
* @param key Key used to retrieve the passphrase via {@link #loadPassphrase(String)}.
* @param passphrase The secret to store in this keychain.
*/
void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException;
/**
* @param key Unique key previously used while {@link #storePassphrase(String, CharSequence) storing a passphrase}.
* @return The stored passphrase for the given key or <code>null</code> if no value for the given key could be found.
*/
char[] loadPassphrase(String key) throws KeychainAccessException;
/**
* Deletes a passphrase with a given key.
*
* @param key Unique key previously used while {@link #storePassphrase(String, CharSequence) storing a passphrase}.
*/
void deletePassphrase(String key) throws KeychainAccessException;
}

View File

@@ -1,12 +0,0 @@
package org.cryptomator.keychain;
/**
* Indicates an error during communication with the operating system's keychain.
*/
public class KeychainAccessException extends Exception {
KeychainAccessException(Throwable cause) {
super(cause);
}
}

View File

@@ -1,17 +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.keychain;
interface KeychainAccessStrategy extends KeychainAccess {
/**
* @return <code>true</code> if this KeychainAccessStrategy works on the current machine.
* @implNote This method must not throw any exceptions and should fail fast
* returning <code>false</code> if it can't determine availability of the checked strategy
*/
boolean isSupported();
}

View File

@@ -1,33 +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.keychain;
import com.google.common.collect.Sets;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.ElementsIntoSet;
import org.cryptomator.common.JniModule;
import javax.inject.Singleton;
import java.util.Optional;
import java.util.Set;
@Module(includes = {JniModule.class})
public class KeychainModule {
@Provides
@ElementsIntoSet
Set<KeychainAccessStrategy> provideKeychainAccessStrategies(MacSystemKeychainAccess macKeychain, WindowsProtectedKeychainAccess winKeychain, LinuxSecretServiceKeychainAccess linKeychain) {
return Sets.newHashSet(macKeychain, winKeychain, linKeychain);
}
@Provides
@Singleton
public Optional<KeychainAccess> provideSupportedKeychain(Set<KeychainAccessStrategy> keychainAccessStrategies) {
return keychainAccessStrategies.stream().filter(KeychainAccessStrategy::isSupported).map(KeychainAccess.class::cast).findFirst();
}
}

View File

@@ -1,51 +0,0 @@
package org.cryptomator.keychain;
import org.apache.commons.lang3.SystemUtils;
import javax.inject.Inject;
import java.util.Optional;
/**
* A facade to LinuxSecretServiceKeychainAccessImpl that doesn't depend on libraries that are unavailable on Mac and Windows.
*/
public class LinuxSecretServiceKeychainAccess implements KeychainAccessStrategy {
// the actual implementation is hidden in this delegate object which is loaded via reflection,
// as it depends on libraries that aren't necessarily available:
private final Optional<KeychainAccessStrategy> delegate;
@Inject
public LinuxSecretServiceKeychainAccess() {
this.delegate = constructGnomeKeyringKeychainAccess();
}
private static Optional<KeychainAccessStrategy> constructGnomeKeyringKeychainAccess() {
try {
Class<?> clazz = Class.forName("org.cryptomator.keychain.LinuxSecretServiceKeychainAccessImpl");
KeychainAccessStrategy instance = (KeychainAccessStrategy) clazz.getDeclaredConstructor().newInstance();
return Optional.of(instance);
} catch (Exception e) {
return Optional.empty();
}
}
@Override
public boolean isSupported() {
return SystemUtils.IS_OS_LINUX && delegate.map(KeychainAccessStrategy::isSupported).orElse(false);
}
@Override
public void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
delegate.orElseThrow(IllegalStateException::new).storePassphrase(key, passphrase);
}
@Override
public char[] loadPassphrase(String key) throws KeychainAccessException {
return delegate.orElseThrow(IllegalStateException::new).loadPassphrase(key);
}
@Override
public void deletePassphrase(String key) throws KeychainAccessException {
delegate.orElseThrow(IllegalStateException::new).deletePassphrase(key);
}
}

View File

@@ -1,65 +0,0 @@
package org.cryptomator.keychain;
import org.freedesktop.secret.simple.SimpleCollection;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class LinuxSecretServiceKeychainAccessImpl implements KeychainAccessStrategy {
@Override
public boolean isSupported() {
try (@SuppressWarnings("unused") SimpleCollection keyring = new SimpleCollection()) {
// seems like we're able to access the keyring.
return true;
} catch (IOException | RuntimeException e) {
return false;
}
}
@Override
public void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
try (SimpleCollection keyring = new SimpleCollection()) {
List<String> list = keyring.getItems(createAttributes(key));
if (list == null) {
keyring.createItem("Cryptomator", passphrase, createAttributes(key));
}
} catch (IOException e) {
throw new KeychainAccessException(e);
}
}
@Override
public char[] loadPassphrase(String key) throws KeychainAccessException {
try (SimpleCollection keyring = new SimpleCollection()) {
List<String> list = keyring.getItems(createAttributes(key));
if (list != null) {
return keyring.getSecret(list.get(0));
} else {
return null;
}
} catch (IOException e) {
throw new KeychainAccessException(e);
}
}
@Override
public void deletePassphrase(String key) throws KeychainAccessException {
try (SimpleCollection keyring = new SimpleCollection()) {
List<String> list = keyring.getItems(createAttributes(key));
if (list != null) {
keyring.deleteItem(list.get(0));
}
} catch (IOException e) {
throw new KeychainAccessException(e);
}
}
private Map<String, String> createAttributes(String key) {
Map<String, String> attributes = new HashMap();
attributes.put("Vault", key);
return attributes;
}
}

View File

@@ -1,49 +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.keychain;
import java.util.Optional;
import javax.inject.Inject;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.jni.MacFunctions;
import org.cryptomator.jni.MacKeychainAccess;
class MacSystemKeychainAccess implements KeychainAccessStrategy {
private final Optional<MacFunctions> macFunctions;
@Inject
public MacSystemKeychainAccess(Optional<MacFunctions> macFunctions) {
this.macFunctions = macFunctions;
}
private MacKeychainAccess keychain() {
return macFunctions.orElseThrow(IllegalStateException::new).keychainAccess();
}
@Override
public void storePassphrase(String key, CharSequence passphrase) {
keychain().storePassword(key, passphrase);
}
@Override
public char[] loadPassphrase(String key) {
return keychain().loadPassword(key);
}
@Override
public boolean isSupported() {
return SystemUtils.IS_OS_MAC_OSX && macFunctions.isPresent();
}
@Override
public void deletePassphrase(String key) {
keychain().deletePassword(key);
}
}

View File

@@ -1,199 +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.keychain;
import com.google.common.io.BaseEncoding;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.annotations.SerializedName;
import com.google.gson.reflect.TypeToken;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Environment;
import org.cryptomator.jni.WinDataProtection;
import org.cryptomator.jni.WinFunctions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.UncheckedIOException;
import java.io.Writer;
import java.lang.reflect.Type;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import static java.nio.charset.StandardCharsets.UTF_8;
class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
private static final Logger LOG = LoggerFactory.getLogger(WindowsProtectedKeychainAccess.class);
private static final Gson GSON = new GsonBuilder().setPrettyPrinting() //
.registerTypeHierarchyAdapter(byte[].class, new ByteArrayJsonAdapter()) //
.disableHtmlEscaping().create();
private final Optional<WinFunctions> winFunctions;
private final List<Path> keychainPaths;
private Map<String, KeychainEntry> keychainEntries;
@Inject
public WindowsProtectedKeychainAccess(Optional<WinFunctions> winFunctions, Environment environment) {
this.winFunctions = winFunctions;
this.keychainPaths = environment.getKeychainPath().collect(Collectors.toList());
}
private WinDataProtection dataProtection() {
return winFunctions.orElseThrow(IllegalStateException::new).dataProtection();
}
@Override
public void storePassphrase(String key, CharSequence passphrase) {
loadKeychainEntriesIfNeeded();
ByteBuffer buf = UTF_8.encode(CharBuffer.wrap(passphrase));
byte[] cleartext = new byte[buf.remaining()];
buf.get(cleartext);
KeychainEntry entry = new KeychainEntry();
entry.salt = generateSalt();
entry.ciphertext = dataProtection().protect(cleartext, entry.salt);
Arrays.fill(buf.array(), (byte) 0x00);
Arrays.fill(cleartext, (byte) 0x00);
keychainEntries.put(key, entry);
saveKeychainEntries();
}
@Override
public char[] loadPassphrase(String key) {
loadKeychainEntriesIfNeeded();
KeychainEntry entry = keychainEntries.get(key);
if (entry == null) {
return null;
}
byte[] cleartext = dataProtection().unprotect(entry.ciphertext, entry.salt);
if (cleartext == null) {
return null;
}
CharBuffer buf = UTF_8.decode(ByteBuffer.wrap(cleartext));
char[] passphrase = new char[buf.remaining()];
buf.get(passphrase);
Arrays.fill(cleartext, (byte) 0x00);
Arrays.fill(buf.array(), (char) 0x00);
return passphrase;
}
@Override
public void deletePassphrase(String key) {
loadKeychainEntriesIfNeeded();
keychainEntries.remove(key);
saveKeychainEntries();
}
@Override
public boolean isSupported() {
return SystemUtils.IS_OS_WINDOWS && winFunctions.isPresent() && !keychainPaths.isEmpty();
}
private byte[] generateSalt() {
byte[] result = new byte[2 * Long.BYTES];
UUID uuid = UUID.randomUUID();
ByteBuffer buf = ByteBuffer.wrap(result);
buf.putLong(uuid.getMostSignificantBits());
buf.putLong(uuid.getLeastSignificantBits());
return result;
}
private void loadKeychainEntriesIfNeeded() {
if (keychainEntries == null) {
for (Path keychainPath : keychainPaths) {
Optional<Map<String, KeychainEntry>> keychain = loadKeychainEntries(keychainPath);
if (keychain.isPresent()) {
keychainEntries = keychain.get();
break;
}
}
}
if (keychainEntries == null) {
LOG.info("Unable to load existing keychain file, creating new keychain.");
keychainEntries = new HashMap<>();
}
}
private Optional<Map<String, KeychainEntry>> loadKeychainEntries(Path keychainPath) {
LOG.debug("Attempting to load keychain from {}", keychainPath);
Type type = new TypeToken<Map<String, KeychainEntry>>() {
}.getType();
try (InputStream in = Files.newInputStream(keychainPath, StandardOpenOption.READ); //
Reader reader = new InputStreamReader(in, UTF_8)) {
return Optional.of(GSON.fromJson(reader, type));
} catch (NoSuchFileException | JsonParseException e) {
return Optional.empty();
} catch (IOException e) {
throw new UncheckedIOException("Could not read keychain from path " + keychainPath, e);
}
}
private void saveKeychainEntries() {
if (keychainPaths.isEmpty()) {
throw new IllegalStateException("Can't save keychain if no keychain path is specified.");
}
saveKeychainEntries(keychainPaths.get(0));
}
private void saveKeychainEntries(Path keychainPath) {
try (OutputStream out = Files.newOutputStream(keychainPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); //
Writer writer = new OutputStreamWriter(out, UTF_8)) {
GSON.toJson(keychainEntries, writer);
} catch (IOException e) {
throw new UncheckedIOException("Could not read keychain from path " + keychainPath, e);
}
}
private static class KeychainEntry {
@SerializedName("ciphertext")
byte[] ciphertext;
@SerializedName("salt")
byte[] salt;
}
private static class ByteArrayJsonAdapter implements JsonSerializer<byte[]>, JsonDeserializer<byte[]> {
private static final BaseEncoding BASE64 = BaseEncoding.base64();
@Override
public byte[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return BASE64.decode(json.getAsString());
}
@Override
public JsonElement serialize(byte[] src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(BASE64.encode(src));
}
}
}

View File

@@ -1,24 +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.keychain;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.Optional;
public class KeychainModuleTest {
@Test
public void testGetKeychain() throws KeychainAccessException {
Optional<KeychainAccess> keychainAccess = DaggerTestKeychainComponent.builder().keychainModule(new TestKeychainModule()).build().keychainAccess();
Assertions.assertTrue(keychainAccess.isPresent());
Assertions.assertTrue(keychainAccess.get() instanceof MapKeychainAccess);
keychainAccess.get().storePassphrase("test", "asd");
Assertions.assertArrayEquals("asd".toCharArray(), keychainAccess.get().loadPassphrase("test"));
}
}

View File

@@ -1,19 +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.keychain;
import dagger.Component;
import javax.inject.Singleton;
import java.util.Optional;
@Singleton
@Component(modules = KeychainModule.class)
interface TestKeychainComponent {
Optional<KeychainAccess> keychainAccess();
}

View File

@@ -1,17 +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.keychain;
import java.util.Set;
public class TestKeychainModule extends KeychainModule {
@Override
Set<KeychainAccessStrategy> provideKeychainAccessStrategies(MacSystemKeychainAccess macKeychain, WindowsProtectedKeychainAccess winKeychain, LinuxSecretServiceKeychainAccess linKeychain) {
return Set.of(new MapKeychainAccess());
}
}

View File

@@ -1,56 +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.keychain;
import org.cryptomator.common.Environment;
import org.cryptomator.jni.WinDataProtection;
import org.cryptomator.jni.WinFunctions;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.Mockito;
import org.mockito.stubbing.Answer;
import java.nio.file.Path;
import java.util.Optional;
import java.util.stream.Stream;
public class WindowsProtectedKeychainAccessTest {
private WindowsProtectedKeychainAccess keychain;
@BeforeEach
public void setup(@TempDir Path tempDir) {
Path keychainPath = tempDir.resolve("keychainfile.tmp");
Environment env = Mockito.mock(Environment.class);
Mockito.when(env.getKeychainPath()).thenReturn(Stream.of(keychainPath));
WinFunctions winFunctions = Mockito.mock(WinFunctions.class);
WinDataProtection winDataProtection = Mockito.mock(WinDataProtection.class);
Mockito.when(winFunctions.dataProtection()).thenReturn(winDataProtection);
Answer<byte[]> answerReturningFirstArg = invocation -> ((byte[]) invocation.getArgument(0)).clone();
Mockito.when(winDataProtection.protect(Mockito.any(), Mockito.any())).thenAnswer(answerReturningFirstArg);
Mockito.when(winDataProtection.unprotect(Mockito.any(), Mockito.any())).thenAnswer(answerReturningFirstArg);
keychain = new WindowsProtectedKeychainAccess(Optional.of(winFunctions), env);
}
@Test
public void testStoreAndLoad() {
String storedPw1 = "topSecret";
String storedPw2 = "bottomSecret";
keychain.storePassphrase("myPassword", storedPw1);
keychain.storePassphrase("myOtherPassword", storedPw2);
String loadedPw1 = new String(keychain.loadPassphrase("myPassword"));
String loadedPw2 = new String(keychain.loadPassphrase("myOtherPassword"));
Assertions.assertEquals(storedPw1, loadedPw1);
Assertions.assertEquals(storedPw2, loadedPw2);
keychain.deletePassphrase("myPassword");
Assertions.assertNull(keychain.loadPassphrase("myPassword"));
Assertions.assertNotNull(keychain.loadPassphrase("myOtherPassword"));
Assertions.assertNull(keychain.loadPassphrase("nonExistingPassword"));
}
}

View File

@@ -1,33 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Copyright (c) 2014 Markus Kreusch
This file is licensed under the terms of the MIT license.
See the LICENSE.txt file for more info.
Contributors:
Sebastian Stenzel - log4j config for WebDAV unit tests
-->
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%16d %-5p [%c{1}:%L] %m%n" />
<ThresholdFilter level="WARN" onMatch="DENY" onMismatch="ACCEPT" />
</Console>
<Console name="StdErr" target="SYSTEM_ERR">
<PatternLayout pattern="%16d %-5p [%c{1}:%L] %m%n" />
<ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY" />
</Console>
</Appenders>
<Loggers>
<!-- show our own debug messages: -->
<Logger name="org.cryptomator" level="DEBUG" />
<!-- mute dependencies: -->
<Root level="INFO">
<AppenderRef ref="Console" />
<AppenderRef ref="StdErr" />
</Root>
</Loggers>
</Configuration>

View File

@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.5.2</version>
<version>1.5.9</version>
</parent>
<artifactId>launcher</artifactId>
<name>Cryptomator Launcher</name>

View File

@@ -10,7 +10,7 @@ import java.util.concurrent.CountDownLatch;
@Module
class CryptomatorModule {
@Provides
@Singleton
@Named("shutdownLatch")

View File

@@ -1,7 +1,7 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved.
*
*
* This class is licensed under the LGPL 3.0 (https://www.gnu.org/licenses/lgpl-3.0.de.html).
*******************************************************************************/
package org.cryptomator.launcher;
@@ -25,7 +25,6 @@ import java.util.Collection;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Singleton
class FileOpenRequestHandler {

View File

@@ -10,7 +10,6 @@ import javax.inject.Singleton;
import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.BlockingQueue;
import java.util.stream.Stream;
@Singleton
class IpcProtocolImpl implements IpcProtocol {

View File

@@ -8,12 +8,12 @@ package org.cryptomator.logging;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import javafx.beans.value.ObservableValue;
import org.cryptomator.common.settings.Settings;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import javafx.beans.value.ObservableValue;
import java.util.Map;
@Singleton

View File

@@ -0,0 +1,30 @@
package org.cryptomator.logging;
import ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy;
import ch.qos.logback.core.rolling.TriggeringPolicyBase;
import ch.qos.logback.core.util.FileSize;
import java.io.File;
/**
* Triggers a roll-over either on the first log event or if watched log file reaches a certain size
*
* @param <E> Event type the policy possibly reacts to
*/
public class LaunchAndSizeBasedTriggerinPolicy<E> extends TriggeringPolicyBase<E> {
LaunchBasedTriggeringPolicy<E> launchBasedTriggeringPolicy;
SizeBasedTriggeringPolicy<E> sizeBasedTriggeringPolicy;
public LaunchAndSizeBasedTriggerinPolicy(FileSize threshold) {
this.launchBasedTriggeringPolicy = new LaunchBasedTriggeringPolicy<>();
this.sizeBasedTriggeringPolicy = new SizeBasedTriggeringPolicy<>();
sizeBasedTriggeringPolicy.setMaxFileSize(threshold);
}
@Override
public boolean isTriggeringEvent(File activeFile, E event) {
return launchBasedTriggeringPolicy.isTriggeringEvent(activeFile, event) || sizeBasedTriggeringPolicy.isTriggeringEvent(activeFile, event);
}
}

View File

@@ -5,11 +5,11 @@
*******************************************************************************/
package org.cryptomator.logging;
import ch.qos.logback.core.rolling.TriggeringPolicyBase;
import java.io.File;
import java.util.concurrent.atomic.AtomicBoolean;
import ch.qos.logback.core.rolling.TriggeringPolicyBase;
/**
* Triggers a roll-over on the first log event, so each launched application instance will rotate the log.
*/

View File

@@ -15,7 +15,7 @@ import java.util.Map;
@Singleton
public class LoggerConfiguration {
private final LoggerContext context;
private final Environment environment;
private final Appender<ILoggingEvent> stdout;

View File

@@ -9,10 +9,9 @@ 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.hook.DelayingShutdownHook;
import ch.qos.logback.core.rolling.FixedWindowRollingPolicy;
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.util.Duration;
import ch.qos.logback.core.util.FileSize;
import dagger.Module;
import dagger.Provides;
import org.cryptomator.common.Environment;
@@ -33,6 +32,8 @@ public class LoggerModule {
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 //
@@ -84,7 +85,7 @@ public class LoggerModule {
appender.setContext(context);
appender.setFile(logDir.resolve(LOGFILE_NAME).toString());
appender.setEncoder(encoder);
LaunchBasedTriggeringPolicy triggeringPolicy = new LaunchBasedTriggeringPolicy();
LaunchAndSizeBasedTriggerinPolicy triggeringPolicy = new LaunchAndSizeBasedTriggerinPolicy(FileSize.valueOf(LOG_MAX_SIZE));
triggeringPolicy.setContext(context);
triggeringPolicy.start();
appender.setTriggeringPolicy(triggeringPolicy);

View File

@@ -62,8 +62,8 @@ public class IpcFactoryTest {
Assertions.assertTrue(endpoint2.isConnectedToRemote());
Assertions.assertNotSame(protocolHandler, endpoint2.getRemote());
Mockito.verifyZeroInteractions(protocolHandler);
endpoint2.getRemote().handleLaunchArgs(new String[] {"foo"});
Mockito.verify(protocolHandler).handleLaunchArgs(new String[] {"foo"});
endpoint2.getRemote().handleLaunchArgs(new String[]{"foo"});
Mockito.verify(protocolHandler).handleLaunchArgs(new String[]{"foo"});
}
}
}

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.5.2</version>
<version>1.5.9</version>
<packaging>pom</packaging>
<name>Cryptomator</name>
@@ -24,26 +24,29 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- cryptomator dependencies -->
<cryptomator.cryptofs.version>1.9.9</cryptomator.cryptofs.version>
<cryptomator.jni.version>2.2.2</cryptomator.jni.version>
<cryptomator.fuse.version>1.2.3</cryptomator.fuse.version>
<cryptomator.dokany.version>1.1.14</cryptomator.dokany.version>
<cryptomator.webdav.version>1.0.11</cryptomator.webdav.version>
<cryptomator.cryptofs.version>1.9.13</cryptomator.cryptofs.version>
<cryptomator.integrations.version>0.1.6</cryptomator.integrations.version>
<cryptomator.integrations.win.version>0.1.0-beta1</cryptomator.integrations.win.version>
<cryptomator.integrations.mac.version>0.1.0-beta3</cryptomator.integrations.mac.version>
<cryptomator.integrations.linux.version>0.1.0-beta2</cryptomator.integrations.linux.version>
<cryptomator.fuse.version>1.2.5</cryptomator.fuse.version>
<cryptomator.dokany.version>1.2.0</cryptomator.dokany.version>
<cryptomator.webdav.version>1.0.13</cryptomator.webdav.version>
<!-- 3rd party dependencies -->
<javafx.version>14</javafx.version>
<commons-lang3.version>3.9</commons-lang3.version>
<jwt.version>3.10.2</jwt.version>
<easybind.version>1.0.3</easybind.version>
<guava.version>28.2-jre</guava.version>
<dagger.version>2.27</dagger.version>
<javafx.version>15</javafx.version>
<commons-lang3.version>3.11</commons-lang3.version>
<jwt.version>3.11.0</jwt.version>
<easybind.version>2.1.0</easybind.version>
<guava.version>30.0-jre</guava.version>
<dagger.version>2.29.1</dagger.version>
<gson.version>2.8.6</gson.version>
<slf4j.version>1.7.30</slf4j.version>
<logback.version>1.2.3</logback.version>
<!-- test dependencies -->
<junit.jupiter.version>5.6.1</junit.jupiter.version>
<mockito.version>3.3.3</mockito.version>
<junit.jupiter.version>5.7.0</junit.jupiter.version>
<mockito.version>3.6.0</mockito.version>
<hamcrest.version>2.2</hamcrest.version>
</properties>
@@ -62,11 +65,6 @@
<artifactId>commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>keychain</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>ui</artifactId>
@@ -101,8 +99,23 @@
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>jni</artifactId>
<version>${cryptomator.jni.version}</version>
<artifactId>integrations-api</artifactId>
<version>${cryptomator.integrations.version}</version>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>integrations-win</artifactId>
<version>${cryptomator.integrations.win.version}</version>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>integrations-mac</artifactId>
<version>${cryptomator.integrations.mac.version}</version>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>integrations-linux</artifactId>
<version>${cryptomator.integrations.linux.version}</version>
</dependency>
<!-- JavaFX -->
@@ -155,7 +168,7 @@
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<!-- JWT -->
<dependency>
<groupId>com.auth0</groupId>
@@ -165,7 +178,7 @@
<!-- EasyBind -->
<dependency>
<groupId>org.fxmisc.easybind</groupId>
<groupId>com.tobiasdiez</groupId>
<artifactId>easybind</artifactId>
<version>${easybind.version}</version>
</dependency>
@@ -222,6 +235,7 @@
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
@@ -237,7 +251,6 @@
<modules>
<module>commons</module>
<module>keychain</module>
<module>ui</module>
<module>launcher</module>
</modules>
@@ -260,26 +273,87 @@
</plugins>
</build>
</profile>
<profile>
<id>mac</id>
<activation>
<os>
<family>mac</family>
</os>
<property>
<name>idea.version</name>
</property>
</activation>
<dependencies>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>integrations-mac</artifactId>
</dependency>
</dependencies>
</profile>
<profile>
<id>linux</id>
<activation>
<os>
<family>unix</family>
<name>Linux</name>
</os>
<property>
<name>idea.version</name>
</property>
</activation>
<dependencies>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>integrations-linux</artifactId>
</dependency>
</dependencies>
</profile>
<profile>
<id>windows</id>
<activation>
<os>
<family>windows</family>
</os>
<property>
<name>idea.version</name>
</property>
</activation>
<dependencies>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>integrations-win</artifactId>
</dependency>
</dependencies>
</profile>
</profiles>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.2.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.1.2</version>
<executions>
<execution>
<id>copy-libs</id>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/libs</outputDirectory>
<includeScope>runtime</includeScope>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
@@ -302,7 +376,7 @@
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.5</version>
<version>0.8.6</version>
<executions>
<execution>
<id>prepare-agent</id>
@@ -329,7 +403,6 @@
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<release>14</release>
<annotationProcessorPaths>
@@ -344,7 +417,6 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
</plugins>
</build>

View File

@@ -1,28 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.5.2</version>
<version>1.5.9</version>
</parent>
<artifactId>ui</artifactId>
<name>Cryptomator GUI</name>
<dependencies>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>keychain</artifactId>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>commons</artifactId>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>jni</artifactId>
</dependency>
<!-- JavaFx -->
<dependency>
<groupId>org.openjfx</groupId>
@@ -35,7 +27,7 @@
<!-- EasyBind -->
<dependency>
<groupId>org.fxmisc.easybind</groupId>
<groupId>com.tobiasdiez</groupId>
<artifactId>easybind</artifactId>
</dependency>
@@ -67,14 +59,14 @@
<artifactId>zxcvbn</artifactId>
<version>1.3.0</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<scope>test</scope>
</dependency>
<!-- Testing -->
<dependency>
<groupId>com.google.jimfs</groupId>
@@ -88,7 +80,7 @@
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>

View File

@@ -20,16 +20,16 @@ along with this program. If not, see http://www.gnu.org/licenses/.
Cryptomator uses ${dependencyMap?size} third-party dependencies under the following licenses:
<#list licenseMap as e>
<#assign license = e.getKey()/>
<#assign projects = e.getValue()/>
<#if projects?size &gt; 0>
${license}:
<#list projects as project>
- ${artifactFormat(project)}
</#list>
</#if>
<#assign license = e.getKey()/>
<#assign projects = e.getValue()/>
<#if projects?size &gt; 0>
${license}:
<#list projects as project>
- ${artifactFormat(project)}
</#list>
</#if>
</#list>
Cryptomator uses other third-party assets under the following licenses:
SIL OFL 1.1 License:
- Font Awesome 5.12.0 (https://fontawesome.com/)
SIL OFL 1.1 License:
- Font Awesome 5.12.0 (https://fontawesome.com/)

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