Compare commits

...
117 Commits
Author SHA1 Message Date
Armin Schrenk 6efb839fd0 prepare 1.10.0 2023-07-24 15:00:28 +02:00
CryptobotandGitHub ca71bb207f New Crowdin updates (#2945)
New translations strings.properties

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

[ci skip]
2023-07-24 14:58:36 +02:00
JaniruTEC ca3a11de90 Made AtomicReference to mountHandle final
[ci skip]
2023-07-21 16:37:08 +02:00
mindmonkandGitHub 91ece74f57 Merge pull request #2998 from cryptomator/feature/update-reminder
Update Reminder
2023-07-21 09:46:48 +02:00
Jan-Peter Klein b9f77cb1d6 Merge branch 'develop' into feature/update-reminder 2023-07-20 11:32:12 +02:00
Jan-Peter Klein b6d09016cd rearranged layout elements and changed window width 2023-07-20 11:31:52 +02:00
Jan-Peter Klein 0a07103a4f moved logic of update checking from FxApplication class to UpdateReminderComponent 2023-07-20 11:06:55 +02:00
Jan-Peter Klein 96253f636a removed unused provide method 2023-07-20 11:05:50 +02:00
Armin SchrenkandGitHub bd932b1067 Fixes #3021 2023-07-18 17:06:03 +02:00
Jan-Peter Klein ade277daf4 changed naming pattern and wording 2023-07-18 10:29:14 +02:00
Jan-Peter Klein 14776fc571 refactored UpdateReminderComponent.Builder to Factory 2023-07-17 15:40:11 +02:00
Jan-Peter Klein c18f489e9d Merge branch 'develop' into feature/update-reminder 2023-07-17 15:32:25 +02:00
Jan-Peter Klein bfc1e25335 some code cleanup 2023-07-17 12:23:23 +02:00
Jan-Peter Klein 4c2e6088a9 reformat code and optimized imports 2023-07-17 11:26:59 +02:00
Armin Schrenk f1d4bd4cd4 Closes #2989 2023-07-13 12:22:46 +02:00
mindmonkandGitHub 2a0e0738be Merge pull request #2987 from cryptomator/feature/custom-shortening-threshold
Feature/custom shortening threshold
2023-07-13 12:15:07 +02:00
Jan-Peter Klein 1fec9781bf Merge branch 'develop' into feature/custom-shortening-threshold 2023-07-12 16:07:03 +02:00
Jan-Peter Klein 886c69f298 reordered ui elements and added expert settings section to strings.properties 2023-07-12 16:04:29 +02:00
JaniruTEC 58c7774e0d Added arg3 to FormattedLabel 2023-07-11 21:50:52 +02:00
Jan-Peter Klein df31b214f5 changed button wording from "Not now" to "Ask Me Later" 2023-07-11 15:23:50 +02:00
Jan-Peter Klein 25474ce014 changed wording and window size 2023-07-11 14:53:36 +02:00
Jan-Peter Klein 5f62f038d5 removed message below the input field and inserted in the invalid message 2023-07-11 14:41:43 +02:00
Jan-Peter Klein eebba45909 added comment to explain code that sets shorteningThreshold to 0
removed unnecessary introduction text
2023-07-11 14:10:26 +02:00
Jan-Peter Klein 982e88d1c5 changed icon to question mark 2023-07-10 15:47:12 +02:00
Jan-Peter Klein 632d39bc9f added lastUpdateCheck to settings and integrated check in FXApplication start 2023-07-10 14:50:06 +02:00
Jan-Peter Klein f25afb720f optimized isValidShorteningThreshold method and renamed DEFAULT_SHORTENING_THRESHOLD to MAX_SHORTENING_THRESHOLD 2023-07-10 13:14:58 +02:00
Jan-Peter Klein bc361ea592 added ui elements for update reminder 2023-07-10 13:04:07 +02:00
Jan-Peter Klein 2202c91564 rearranged view
added pencil icon
optimized code
cleaned up strings.properties
2023-07-07 16:22:53 +02:00
Jan-Peter Klein f817bc5378 implement requested changes from PR review 2023-07-06 11:03:34 +02:00
JaniruTECandGitHub c30c90d2c3 Merge pull request #2985 from cryptomator/feature/2981-differ-options-preferences
Fix 2981
2023-07-05 16:27:55 +02:00
Jan-Peter Klein 09b638eafa pr mentioned changes implemented 2023-07-05 16:19:11 +02:00
JaniruTEC 91de0c1813 Added called to #showMainWindow
See: https://github.com/cryptomator/cryptomator/pull/2985#discussion_r1252863161
2023-07-05 15:50:35 +02:00
JaniruTEC a3c953d437 Refactored decision model
See: https://github.com/cryptomator/cryptomator/pull/2985#pullrequestreview-1514141146
2023-07-05 15:49:43 +02:00
JaniruTEC 743da019f9 Cleaned up UnlockInvalidMountPointController
See:
https://github.com/cryptomator/cryptomator/pull/2985#discussion_r1252868655
https://github.com/cryptomator/cryptomator/pull/2985#discussion_r1252874468
2023-07-05 15:26:19 +02:00
Jan-Peter Klein 2c506fcc97 rearranged view elements an renamed advancedSettings to expertSettings 2023-07-05 13:17:10 +02:00
Jan-Peter Klein dd71df8382 Merge branch 'develop' into feature/custom-shortening-threshold 2023-07-05 11:55:51 +02:00
Armin Schrenk 2c984ad405 use system property path.separator and fix failing unit test on windows 2023-07-04 16:31:28 +02:00
JaniruTEC 171b0e61ad Finished implementation 2023-07-04 16:14:08 +02:00
JaniruTEC e5047c242c Moved subcomponent-bindings
Moved bindings for VaultOptionsComponent and HealthCheckComponent from MainWindowModule to FxApplicationModule
2023-07-04 15:35:23 +02:00
JaniruTEC 131145e8b8 Merge branch 'develop' into feature/2981-differ-options-preferences 2023-07-04 14:00:44 +02:00
JaniruTEC 11a0136cb9 Fixed run configurations
See: #2982
2023-07-04 13:55:42 +02:00
Armin Schrenk e92b80bdf4 fix local win build script to download jfxmods 2023-07-04 12:42:55 +02:00
Armin Schrenk 9d81b2312f fix local build script to parse two digit verison nums correctly 2023-07-04 12:41:58 +02:00
Armin Schrenk 511dbe355a Fixes #2982 2023-07-04 12:41:06 +02:00
JaniruTEC cca6d475b3 Updated UI 2023-07-03 21:14:12 +02:00
JaniruTEC 88c6246ceb Prepared UnlockInvalidMountPointController for new feature 2023-07-03 21:08:49 +02:00
Jan-Peter Klein 7bf2de6339 Merge branch 'develop' into feature/custom-shortening-threshold 2023-07-03 16:22:43 +02:00
Jan-Peter Klein 894d0528df refactoring 2023-07-03 15:34:01 +02:00
Armin Schrenk 168b9ef096 changed default folder mount location on macOS 2023-07-03 13:11:38 +02:00
Sebastian Stenzel 6c8005e1b2 use github.ref instead of inputs.ref
[ci skip]
2023-07-03 10:59:32 +02:00
Sebastian Stenzel ed8457ff36 remove dead test 2023-06-30 13:53:25 +02:00
Sebastian Stenzel a50bc8e05c reduce api surface 2023-06-30 13:53:06 +02:00
Sebastian Stenzel e382a3bab9 make settings' fields public, remove accessor methods 2023-06-30 13:52:15 +02:00
Sebastian StenzelandGitHub 9cd50e8ae4 Merge pull request #2974 from cryptomator/feature/replace-gson
Replace GSON with Jackson
2023-06-30 13:48:19 +02:00
Sebastian Stenzel 1a6836dac6 remove unnecessary annotation
[ci skip]
2023-06-30 12:39:48 +02:00
Armin SchrenkandGitHub 067814d5db Merge pull request #2957 from cryptomator/feature/preprocess-properties
Feature: Preprocess JVM properties at app start to adjust to OS environment.

Closes #2838
2023-06-30 12:38:55 +02:00
Armin Schrenk 29d63a0a83 final clean up 2023-06-30 11:57:09 +02:00
Armin Schrenk 8c3ede0d2a clean up 2023-06-30 11:20:28 +02:00
Armin Schrenk ee82965ef5 adding "integration tests" 2023-06-30 11:20:07 +02:00
Armin Schrenk ab048508a8 change getProperty(key, default) and add unit test 2023-06-30 10:57:36 +02:00
Sebastian Stenzel 47ae271126 cleanup
[ci skip]
2023-06-29 23:00:19 +02:00
Sebastian Stenzel ef22928e01 undo formatter-induced changes
[ci skip]
2023-06-29 22:53:19 +02:00
Sebastian Stenzel 287584429b update code style
remove line break from `new Object() {}`

[ci skip]
2023-06-29 22:45:57 +02:00
Sebastian Stenzel e23c0c23c9 remove GSON from dependencies 2023-06-29 22:44:22 +02:00
Sebastian Stenzel e9bb3195dd cleanup 2023-06-29 22:44:05 +02:00
Sebastian Stenzel b1ff94bdd6 replace GSON from settings 2023-06-29 22:41:19 +02:00
Armin Schrenk 79eea4814e renamed class 2023-06-29 18:56:32 +02:00
Armin Schrenk cee0486d71 adding unit tests for getProperty 2023-06-29 18:55:49 +02:00
Armin Schrenk 12e990a8b3 cleanup 2023-06-29 18:24:48 +02:00
Armin Schrenk 24fc2888ef more unit tests 2023-06-29 18:20:25 +02:00
Armin Schrenk 9b0c940195 readd unit tests for Environment.java 2023-06-29 18:13:54 +02:00
Armin Schrenk ce596698e3 stick to a unified path separator for cryptomator properties 2023-06-29 17:58:52 +02:00
Armin Schrenk 9383abbdbd let child method match parent 2023-06-29 15:55:07 +02:00
Armin Schrenk a31d3185dc move boilerplate code to own class 2023-06-29 15:51:46 +02:00
Armin Schrenk 3098628db1 Fix problem of circular class init 2023-06-29 15:32:15 +02:00
Sebastian Stenzel 3c53968dd2 replace GSON from update checker 2023-06-29 13:20:39 +02:00
Armin Schrenk 6c18e3a929 apply suggestion from sonarCloud 2023-06-29 13:04:00 +02:00
Sebastian Stenzel 99862869bc replace GSON from error dialog 2023-06-29 13:03:27 +02:00
Armin Schrenk c0368f2a50 Merge branch 'develop' into feature/preprocess-properties 2023-06-29 12:39:31 +02:00
Armin Schrenk c6c292b505 bump google guava to 32.0.1 2023-06-29 12:39:19 +02:00
Armin Schrenk ebc60c4fb3 make process function reliable testable and adjust unit test 2023-06-29 12:35:59 +02:00
Sebastian Stenzel 5f2497de90 replace GSON in "hub" 2023-06-29 12:26:45 +02:00
Sebastian Stenzel b2a184bdf0 ignore unknown fields during JWT deserialization 2023-06-29 12:02:33 +02:00
Armin Schrenk 01da51e6e7 Refactor properties preprocessing:
* decorate Properties class
* set the system properties to decorator
* for logging setup, skip enviroment and access props over decorator
2023-06-29 11:37:35 +02:00
Armin Schrenk 8417618615 Revert "fix missing logDir path resolution"
This reverts commit b3d8df0da0.
2023-06-28 16:04:10 +02:00
Jan-Peter Klein 386bdd3490 added new AdvancedSettings scene in AddVaultWizard 2023-06-28 12:24:14 +02:00
Tobias Hagemann fe3abcaaa8 added leitzcloud location preset 2023-06-27 09:36:14 +02:00
Tobias Hagemann 56ba12fe56 fixed location presets of google drive and pcloud on macos 2023-06-27 09:31:43 +02:00
Armin Schrenk 5e52f715ce Merge branch 'develop' into feature/preprocess-properties 2023-06-21 10:31:43 +02:00
Armin SchrenkandGitHub 2c2606d6ad Merge pull request #2961 from sschuberth/win-drive-occupied
Show a dedicated message when mounting to an occupied Windows drive

Fixes #2309
2023-06-20 18:25:00 +02:00
Armin Schrenk b3d8df0da0 fix missing logDir path resolution 2023-06-20 18:08:31 +02:00
Armin Schrenk ebea8ef7e5 adjust windows buildscripts to use appdata instead of userhome/hard/coded/path 2023-06-20 17:32:23 +02:00
Armin Schrenk ec645a4bb9 replace ~ by @{userhome} on unix systems 2023-06-20 16:37:32 +02:00
Armin Schrenk 2c0474ec46 add test 2023-06-20 12:37:07 +02:00
Sebastian Schuberth 4d09728880 Suppress a CVE false-positive for jackson-databind 2.14.2
Also see https://github.com/cryptomator/cryptomator/pull/2961#issuecomment-1597652134.
2023-06-19 22:31:50 +02:00
Sebastian Schuberth 173b1e8386 Show a dedicated message when mounting to an occupied Windows drive
The error message was restored from 6395f17. Fixes #2309.
2023-06-16 19:17:30 +02:00
mindmonkandGitHub 9b18a179c2 Merge pull request #2953 from cryptomator/feature/error-dialog
Improved Error Dialog
2023-06-16 15:55:43 +02:00
Jan-Peter Klein 12b38ad8c8 removed extra tabs generated by reformat code 2023-06-16 15:25:22 +02:00
Armin SchrenkandSebastian Stenzel 4065e15aa1 Apply code suggestion from review
Co-authored-by: Sebastian Stenzel <overheadhunter@users.noreply.github.com>
2023-06-16 13:12:00 +02:00
Jan-Peter Klein 6fda206b9e just one little change 2023-06-16 12:57:21 +02:00
Jan-Peter Klein 638a6d1195 optimized code 2023-06-16 12:56:07 +02:00
Jan-Peter Klein 27bc62e602 Merge branch 'develop' into feature/error-dialog 2023-06-16 12:48:28 +02:00
Tobias HagemannandGitHub 2e7af0affa Update SECURITY.md
[ci skip]
2023-06-16 11:41:33 +02:00
Tobias HagemannandGitHub 19c41a3cca Update SECURITY.md
[ci skip]
2023-06-16 11:20:26 +02:00
Jan-Peter Klein 3bf4473f9d implemented changes mentioned by overheadhunter 2023-06-16 11:13:24 +02:00
Jan-Peter Klein f92c436339 reformat code and optimize imports 2023-06-15 13:12:10 +02:00
Jan-Peter Klein f054a1af03 optimized tests and some code docu 2023-06-15 10:54:44 +02:00
Armin Schrenk c46eeb0401 first impl draft 2023-06-15 10:38:19 +02:00
Jan-Peter Klein d680c5812d cleaned up code and using parameterized tests 2023-06-14 16:10:11 +02:00
Jan-Peter Klein 3120751d3e resolved sonarclouds mentioned issues 2023-06-13 12:48:35 +02:00
Jan-Peter Klein 3c623b7ed1 cleaned up code 2023-06-12 21:04:54 +02:00
Jan-Peter Klein 150dd3e542 Merge branch 'develop' of https://github.com/cryptomator/cryptomator into feature/error-dialog 2023-06-12 13:52:23 +02:00
Jan-Peter Klein 8f3f23939c created some tests for ErrorController 2023-06-12 13:50:43 +02:00
Armin Schrenk 5d796efe30 Merge branch 'main' into develop 2023-06-07 12:56:23 +02:00
Jan-Peter Klein 0e7a27b2e1 Merge branch 'develop' of https://github.com/cryptomator/cryptomator into feature/error-dialog 2023-05-30 12:06:50 +02:00
Jan-Peter Klein d82d11feb7 optimized implementation 2023-05-30 12:03:56 +02:00
Jan-Peter Klein b10523ea6c init commit 2023-05-09 09:00:56 +02:00
158 changed files with 3190 additions and 1384 deletions
+19 -117
View File
@@ -2,123 +2,25 @@
## Reporting a Vulnerability ## Reporting a Vulnerability
For reporting security-related vulnerabilities or exploits that [haven't been reported yet](https://github.com/cryptomator/cryptomator/labels/type%3Asecurity-issue), contact us at: security@cryptomator.org We take security seriously at Cryptomator. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.
<details> To report a security vulnerability, please use the [GitHub Security Advisory feature](https://github.com/cryptomator/cryptomator/security/advisories). This feature allows you to privately discuss, fix, and publish information about security vulnerabilities.
<summary>PGP Key</summary>
``` If you prefer to report the vulnerability via email, please send an email to security@cryptomator.org.
-----BEGIN PGP PUBLIC KEY BLOCK-----
Comment: GPGTools - https://gpgtools.org
mQINBFbgeicBEADM9AcU6DTgM5KZnBaJc6x9DBLr+TCMHntTt7YM9GLTlO2Z43Jt PGP key fingerprint: `3647 9903 B23A E0A5 9359  9A3E 23B5 DBEF 94D4 D81D` ([public key](https://gist.github.com/cryptobot/864300b6b44ae2d2a15abedfe14bd040))
oYoyqdRWAY28veqpLEFgRvvVD3fdBj/KUOxF1cr2JsErwXqbjwaLq0o/0KIXz7UK
a6pQSemZKfpOtJrfacofOTwvG6AuG9uakBYNMyxuojyOkoh3xsYS1KZ7TwPgCdET ## Expectations
t8/zva41Pa5kh5+GeSZJdCuygG6ynPBJEpmK5V7Qizvics5fziXecF+QaFZijafv
YahfxokvF9pXCQTmV4m57NQma9uK0w83U9nJCPjEd+x3wK0Hxrc1ojy8ZFTA1YND When reporting a vulnerability, please provide us with a detailed report that includes:
AQg/MTABgHbQQkXDQhjS/TloOObqtbMBqNSbcSXpaR4teaCWKBl1MSq00nJLj8db
vPJGqfg7UbXhlALggp029/kskYlR5SmbxWquLbl0Xre3fDHuHEiWcJL6MS3454Wt - A description of the vulnerability
Mno13/4UhOlRFh5g0pLmPz7seOTJjDqc9abn/RXOLq0+3qX0gC0bDm5aCE5dQ2MV - Steps to reproduce the vulnerability
FMbrrlw/dZESNLZvtB3gOsramSry1R3HVZ0QJ2vMaF2cxewebqcYbuecUNj6bxpv - Possible impact of the vulnerability
5LEhEmqz6dG1meLLWDsvQLPEUWEIJnfpBiDSm342yxJq4pXnVF+aqAQsCL3FpmvZ - Any additional information that may be helpful
2j0FgFOs7iXOcFUJIiR0xUmWPk1NWYcUowqmRW8pMM9nFUzFF99iggPznwARAQAB
tC1DcnlwdG9tYXRvciBTdXBwb3J0IDxzdXBwb3J0QGNyeXB0b21hdG9yLm9yZz6J We ask that you do not publicly disclose the vulnerability until we have had a chance to address it.
AkAEEwEKACoCGwMFCQcrKAAFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AFAleu2cQC
GQEACgkQI7Xb75TU2B3+7A/7BKRWdo5/moCCEbBzYQ7vRMLFdwmjFFlSZ7aGC0fP ## Thank You
YHdeUwxPbO0cATwmNpGMma7rBn1FDg3Vto6/wottGxm+XIRwlyY84CD1VZAihZ/e
WvjOO28/7VgRy6PGKzlhpDSoT8GwFOgO69e7bEff1Zj562RZe7nXc4tDivILMB++ We appreciate your help in keeping Cryptomator secure. Thank you for your contributions to the security of our project.
KgmmSgtddygmNQCS3RD3KssGo+l+cSjsg09F5WAJ6nQe8Jq2hICq+o/P6UXPI5lX
bhvWYDn4/8sRHsIlGpQYYDDe0fz7IQKuSLAHpF5upNDxj6dYb05F8PPVrk6MW6nL
/kf1fZ27DlLN5/NFvhhBRuwxxoAFqPS7Iel3z7L0JkRUYmGLVB5m9Cqiw6FK8JRv
OtvakdDoKb5lVAoN5NeBfNBSqEcXVF/EdfTfIyyo7hZRA6xFMEVbmYbzt0sj0djV
ZOey2TOFrTCpkHfUUDgKvk5sn+F3u8mmPIbqquEzlFJSFjcyiYYDv22rg1In+zKV
Xmw4BFZRDS6IVSQRGlskRGJBixCaGyDYxHXXT2cg4Rk9uiCX11+0E9qlAsg6xPe6
rnaYDT8dU0AFyVpDpshflXH3kVQSpiqZS3jkAk1/54ODO8pE80Zrnd5m5AMuNcmX
+9MkZKE+h0882UskDs1dyt26GU2hoy4lAeRUaut7zIK/WO6nnuLaTvGWT95RDz+q
kD2JAiIEEwEKAAwFAleu2iYFgweGH4AACgkQZnuGbqgkCgnmCA//U22uhyEC/Tp3
Cbt5lctQmqbgMbjRBaHQyW52tPFMaq8vXMbo/5TTtVC6xsp2PJT84cxAd8KX8hWq
cPtF4wWCJGng/AzyxQ5dWfGvA/ll32ygjtJN3P/AvA9KlhG+6XYmS8cPkBkJBi6B
2yCdZT1cXc/TPAFzjgAwz7K9g3awG0OeOc/CXymH0DD/snkiwKQoucStolYywZGc
GszjMQgeT4zOc1wtEz24uL3dMNDlDcQMAh56YvK2oB0iMYmAFyX/IS+f2bM9paXi
HX+mg/z53iwgf5ZXbslNDbMTJ5GNksjEGjCFfDHAdNdgT+lcW4l2U7q4PYUaN4LA
DE9j2OlOlQ9qjucOgoCStirnTP7XHd4p31lgdz8+THOQowB5Ji95OkiNQAFCfxBt
mcA/bWnJZQDm7L8RVzHovBpAaK6vUjxEvR+DXdESSzyZwkpsZwGZcyqGRT26R1/L
JE5WvjKufNc5v3Cat320MjyrLZwVGRgvEpDMoCw3nTWl9AtOj5vgaakEWr7AnqET
xk7UFbYmdTlQqkWuLKubz9Rx/FbrBmvd6vwTHy1Dfl6QyMWNCClatgN00Hxped/6
CErg+R/RXd8apGxnOuWDqoujPn5LOHzgJolp1Ox16nTiZe2G+LbDr3hqRFi1wW6w
ioMB4KpkdA03uyxJSWmDEMiR1l3Oxom0KUNyeXB0b21hdG9yIFByZXNzIDxwcmVz
c0BjcnlwdG9tYXRvci5vcmc+iQI9BBMBCgAnBQJXrtnDAhsDBQkHKygABQsJCAcD
BRUKCQgLBRYCAwEAAh4BAheAAAoJECO12++U1NgdQYMQAKCIzNJF8rURQcFLSv3J
sPBjRy2HCzCWm21MuhU+bsaZx7U9M9dgEjzLfxN9s19VsBH3WKLgok2FgiYSGka3
6Oy/P8VFLFmHs7dS9i2fro2eF7i4zj/ZD/9t0jM4ZIgLpbzr5sTBld292nsfXGob
xOJeOx3oWYyR2FO9VQxXjC3JvJyZkFgoy0tauS4Mvii4cF56wJGcxDTbe1s7UaRC
a/fh4zgISZSBE3rYhCawkN4mqMDM5RDjrdtjKUPWk345HcjjQ4Wos8xw4YbGbNr9
Pc7m2URYJJ0jFM4tnoRF6cmA3bT9tm8pcOFg+K/ycVrltVEy+A8Wj8UGjyP1uI1t
EqWHN3LZpIGfW0w9AGrw7OUI9czXcukfngj/DsOU3WMBDIM8pW9+zBpr75yIS6lz
C0IqksLXSqX0b/Rby4O+wb6UZ1ZFkaim2GGtAZV+nGXtdnEXSNFiP7ykzjZ02m/1
7CKyj3VmdAgT56zEIypFSfxm9gOWsJPmfhSyuE8bFyoitgNxpheZk6xZy4upVMPR
WK3hutScU0yDv2HVCiA3o3Ggy42nmz9HpGF6W2DmBx4bhMaVs6I2VFyKdQzmJD/3
FCWjwz8PiEgVGHGPnD+WdPFLhrc/44gF4h/VuLjkubtULGuTVvgjeTIJ5LR1Gmwc
YOk6eD7MAJPzJVj5/PYFtIbKiQIiBBMBCgAMBQJXrtonBYMHhh+AAAoJEGZ7hm6o
JAoJBh4P/1w88YMTKUHpFTfJEwH2hK36BZN96Bf/k+vP7n1Xxp3NheInJblHFOt/
ccsup6am+APrk8gGtlIVmtVc3nO8WMsWxfJxGDecyRsNbessnODv/llyg3tzVU/H
tLk7gLiK0TcIsOLfeNXGTxRRSKWjVFsNfuixNCzzHa7tFq6ddVn9VRZ8fqJB2p21
OogWSDqUo9q9Wfb4RkYHguDx+8Jzoo/MxR1TSt8gUO2xDvEbqgeQiMCLF8R0lO3Y
zz0FrpyOsFU1CxVp+wo55bWv1UdwgQKQt4o0m5/zDJ2RAtscXpd4YcTE+XxKeK+4
qhihhkhLGpKsxzK5m9/qwMbodHwoBCBzfalkUR9xOq9yQIeEoC8XYL62NqB3BCSU
KfWFIHxUkE9WH5zHWaV+bhrlNgk7nz3xBfPf1P2mNIc1VUHoNqOZOmWwz2VaKLSW
f3GIqx9wGythFbLdXmUoC3W//DDYgQnvImvkncMqQ5nRHPf8uHcLQK5WZyIxpgWT
eKon5G/cj0BTptcBhapMwSIyfaC5FV7so0/CkOA6R9Fyq2VpGoHy7XPhFS+6ieLi
KUWhCvbuf2deWbSaJ0peMdzy1p72UXwrsEM0M3Fz+Jd8zvCaFzf5Fx27+pAAdlfg
4bT3/2gSf7S+cU3+DnYOH0NeRt2Z2mjEKg9OwttTO/oDboQHdZlrtDRDcnlwdG9t
YXRvciBTZWN1cml0eS1UZWFtIDxzZWN1cml0eUBjcnlwdG9tYXRvci5vcmc+iQI9
BBMBCgAnBQJXrtnWAhsDBQkHKygABQsJCAcDBRUKCQgLBRYCAwEAAh4BAheAAAoJ
ECO12++U1NgddzoQAI78+Nvm6VvNuptXJjEmrpHRyKCnHF9wH5kxvF8WZCgpOkJ4
vONmyS+9ZlepnT83MpGm/MzdIMCnDJmDmqmA5ISBRcD7k9Gjzz5rPKwE2zDyo0M0
wF1L2UEUqAlcvE0e4twZcP2DGoNqdSf6IaWsXhQMb1a/rTMsoGZLuTB8kCbv6Ntl
ULahcRToTB2shsbZjzE896P6X5hDCfGWl0Jhcbf53pnXX1dOsEw3et9AGru1IUMs
UGM+wpgTwagRj+XB/WY1x9IznKtiHTq83Fvt+3bkg0+NIcV3GDqXDIUtqIwy8gDd
4KgBU+LkyxXFDa4OxLc53n6b+Iy1nDosM+SiqSzdCCgEs/dY1tQBn/7P1GT18dEe
tFgeH/c6wLvEpDIc9urAsYXf8H+1uy7glWpWTq8DE0yhCr4adjCqlIsVHQQO4UUW
NfqMGEFpJ+3HjSSwnvDGY78lLQh5d4vqWV435aNaMqZg0gJIA0FtiP1fRtmT73BG
N/tBNiBxretFR4B+x/TWqPd5iJV7/MAn/pa1WSOcaxzJrVUsjXdgLQCqcHWd4/w1
f4DU9cJjl3sxZlMdAlg8Q1bF+pmjQQ4WKZkqMtwpoUilfVXmL42ay1LBCgW68/uJ
OTyGfp8ntUsbbm5raGsny3TLqnacyG9hxcPGNTzD1+MrbUvfsc7+4U0dCZTuiQIi
BBMBCgAMBQJXrtonBYMHhh+AAAoJEGZ7hm6oJAoJ1DQP/R+1drZiZQU45ChMbfTb
XQjJRsUOGZp3PTWtx4KrVFvE8ea0PF+DZX5gLJYIU+iZmPXRpzFu6dKPbcZ7RfRt
5RRH102zDZzijt2CQd7YLO8wxUFoWX9X7DGgxXEcNjl9kFVmnyHgiTwTzuZ0Zy4y
PvoiwrhcZmXEYbOeV40gLFie6wuzz5IIcs01e30xIs+1/1gwmgI5UnG3jveUgmcj
f/lvg3POKiwrY5Uzw1FSruJx21X06wTpDcfOACID4L7aY9eg2B/qL2Xj8nuhejqG
+1AVTMk2o6pxkvevHmxYQfEpuWGCw0RCBn9ObWwz6Zn5J9pjGbMrM+b1/M2Ouv3N
cpoGgCSahKNsRMKO7RMrBG0jtLcasPSgZFYPJSZAAb+YhxKUbpPHzDIwTEjgM7CL
gKSyRTKyp5IoFK53bpXL/ZIjkAhMvyDult6+BL6vI0+h3BBA9I0FF2Qhe139xLv/
DS7aDiYAE9vGMGoeCBfxJPwUsDU3hrGe/wgL7fR6nmN7R2QffisBHKHsklORy9t3
w3YFRd5sBAxv+EOcdkgXEmqKOfVQ8KU9adQcxPDGMAK/esjVwxUxsaf2PF5noxxW
3zL2ureUO/mMoH5Cwr0BuM3HFb82t1JJd4IXlLEyNvDMFMwD2d7h37bGK7Y5hEsl
zL7Dm+wQRY8sxg4QOZHbJjQXuQINBFbgeicBEADnkxGSEL1zwACaiVqADKC6/pgO
MMWjxoENBT6r8Vnp1D5hfNDkEi9iXUpCEO6nzywBf3/4c4Yk1wBOBZ7YWyWXMf4v
2g1evxELO5z1UlAwna6HSl7G0omIBqzz1Er5IS7C9WEZM8ZggwcuswCrbxfz4+fN
t7cCL5QyOvuxez+vrn+VIgLQzKm+LV4Wc+OFbHIys+0saQUhItKO0/CsXGc8R314
jdN5UsZk/MUdPPAs+6OCr8d3PpJvR6IST76TtN8aDjSS9T6em7dwdGFEwCGww3Jc
xrAkvvUmSlscz+rnvHA5DYQGK6NXLenB40sVQVfch1r1VqwvlzA0u7OovjwM8+7u
+DaBQ0YejbdnC7yfeE91LmZkG6jRKfvTJkv18tjNsgZsVmM13xzP67fCFIB9M+lN
t9zEldGKHVwm+06FHIWJsBDRgrquNb9xd1vgHHeIbJvKf+LqZhVrbKVEneG34Km+
ndtb+mvcGc0fOoMU9lYrFaxAWl8oU9BchC9IyjcPZB445R+AhfTuoHSUViSCo6IO
TG0hQsJuNoKmDAU8l5sTsiFXuXBOo1wK8gTkRnhZHduZrZIjJXvT7efz1knLQ6eG
prZHf4CtbgHyAe2XZabetWtCsFcPbOjC7ezNK57UvVH98h2GkckxOM00BESMCTee
kYy7uG0v0rrajzHY1wARAQABiQIlBBgBCgAPBQJW4HonAhsMBQkHKygAAAoJECO1
2++U1NgdyAsQAKZUVA6pY225BASkeNiW31L7K4VeRYpAdFkiRex2zQFtj9Vovfi1
JeTs0fRm35dUsQraf1bkhsjEdPVZ3gD324/baauFO04KX+soyQvK/tUq8KO+5ALt
Ul5aAljuSwxfJWFpApv+Mbf7gOjm+77jirs7pgG/gCow/mkRlmKTwAmn2DXjkckC
2EH0mqmh5pdoNWKO7WeTFFbUmESsPcnB2FwTpEjHFvgHll+rmKpXZTgFYN4dDhhm
HsL/SCf/Nw+YIsuvErQ9TJVdJDLG8ZYatruk7dZZMPtFxvxM1Q36gDIpPEOKPkvm
dMXg6jHaIdYIaoMpzXFaXsQMdRuMtzbcA+CdwXVY55qGLtfmM/QuEiIJdDeeh7iB
+VAMyEFOOpi8IFhixaeMoZAmrKDqOkzPcMJVklLYq8N+b9p5JszYNwZEbpyWCACM
6K+iJzlWzW/OPZttGLJBgYuSYIJIuG80Cx5m5m1e5RAgQ1iT8nbfrS+gYttwP48J
V7SXQg7QugxG9l1vlK4VjnXiOFulJ7V0e/VyUBpJp3qHcCxFq3RnxVwlIqKZh+jm
Q1bk0H0Xodd27nQITfDP5ullByGW2Jrjs6SsXeR3jl9+t0XQfInU1L9d/wSOkMjL
9IMUt06lV4vB/WP2xioqLZiZ4eAi0E+lWkFxjZsgNs2xbOAYRThMB8a5
=W1Ri
-----END PGP PUBLIC KEY BLOCK-----
```
</details>
+7 -6
View File
@@ -85,12 +85,13 @@ jobs:
--java-options "-Xmx256m" --java-options "-Xmx256m"
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\"" --java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
--java-options "-Dfile.encoding=\"utf-8\"" --java-options "-Dfile.encoding=\"utf-8\""
--java-options "-Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\"" --java-options "-Djava.net.useSystemProxies=true"
--java-options "-Dcryptomator.pluginDir=\"~/.local/share/Cryptomator/plugins\"" --java-options "-Dcryptomator.logDir=\"@{userhome}/.local/share/Cryptomator/logs\""
--java-options "-Dcryptomator.settingsPath=\"~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json\"" --java-options "-Dcryptomator.pluginDir=\"@{userhome}/.local/share/Cryptomator/plugins\""
--java-options "-Dcryptomator.p12Path=\"~/.config/Cryptomator/key.p12\"" --java-options "-Dcryptomator.settingsPath=\"@{userhome}/.config/Cryptomator/settings.json:@{userhome}/.Cryptomator/settings.json\""
--java-options "-Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\"" --java-options "-Dcryptomator.p12Path=\"@{userhome}/.config/Cryptomator/key.p12\""
--java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\"" --java-options "-Dcryptomator.ipcSocketPath=\"@{userhome}/.config/Cryptomator/ipc.socket\""
--java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/.local/share/Cryptomator/mnt\""
--java-options "-Dcryptomator.showTrayIcon=false" --java-options "-Dcryptomator.showTrayIcon=false"
--java-options "-Dcryptomator.buildNumber=\"appimage-${{ needs.get-version.outputs.revNum }}\"" --java-options "-Dcryptomator.buildNumber=\"appimage-${{ needs.get-version.outputs.revNum }}\""
--add-launcher Cryptomator-gtk2=launcher-gtk2.properties --add-launcher Cryptomator-gtk2=launcher-gtk2.properties
+2 -13
View File
@@ -3,9 +3,6 @@ name: Build Debian Package
on: on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
ref:
description: 'GitHub Ref (e.g. refs/tags/1.6.16)'
required: true
semver: semver:
description: 'SemVer String (e.g. 1.7.0-beta1)' description: 'SemVer String (e.g. 1.7.0-beta1)'
required: true required: true
@@ -29,9 +26,6 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with:
ref: ${{ inputs.ref }}
fetch-depth: 0
- id: versions - id: versions
name: Get version information name: Get version information
run: | run: |
@@ -141,15 +135,10 @@ jobs:
run: dput ppa:sebastian-stenzel/cryptomator-beta cryptomator_*_source.changes run: dput ppa:sebastian-stenzel/cryptomator-beta cryptomator_*_source.changes
# If ref is a tag, also upload to GitHub Releases: # If ref is a tag, also upload to GitHub Releases:
- name: Determine tag name
if: startsWith(inputs.ref, 'refs/tags/')
run: |
REF=${{ inputs.ref }}
echo "TAG_NAME=${REF##*/}" >> $GITHUB_ENV
- name: Publish Debian package on GitHub Releases - name: Publish Debian package on GitHub Releases
if: startsWith(inputs.ref, 'refs/tags/') if: startsWith(github.ref, 'refs/tags/')
env: env:
GITHUB_TOKEN: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} GITHUB_TOKEN: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
run: | run: |
artifacts=$(ls | grep cryptomator*.deb) artifacts=$(ls | grep cryptomator*.deb)
gh release upload ${{ env.TAG_NAME }} $artifacts gh release upload ${{ github.ref_name }} $artifacts
+7 -6
View File
@@ -95,16 +95,17 @@ jobs:
--java-options "-Xss5m" --java-options "-Xss5m"
--java-options "-Xmx256m" --java-options "-Xmx256m"
--java-options "-Dfile.encoding=\"utf-8\"" --java-options "-Dfile.encoding=\"utf-8\""
--java-options "-Djava.net.useSystemProxies=true"
--java-options "-Dapple.awt.enableTemplateImages=true" --java-options "-Dapple.awt.enableTemplateImages=true"
--java-options "-Dsun.java2d.metal=true" --java-options "-Dsun.java2d.metal=true"
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\"" --java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
--java-options "-Dcryptomator.logDir=\"~/Library/Logs/Cryptomator\"" --java-options "-Dcryptomator.logDir=\"@{userhome}/Library/Logs/Cryptomator\""
--java-options "-Dcryptomator.pluginDir=\"~/Library/Application Support/Cryptomator/Plugins\"" --java-options "-Dcryptomator.pluginDir=\"@{userhome}/Library/Application Support/Cryptomator/Plugins\""
--java-options "-Dcryptomator.settingsPath=\"~/Library/Application Support/Cryptomator/settings.json\"" --java-options "-Dcryptomator.settingsPath=\"@{userhome}/Library/Application Support/Cryptomator/settings.json\""
--java-options "-Dcryptomator.p12Path=\"~/Library/Application Support/Cryptomator/key.p12\"" --java-options "-Dcryptomator.p12Path=\"@{userhome}/Library/Application Support/Cryptomator/key.p12\""
--java-options "-Dcryptomator.ipcSocketPath=\"~/Library/Application Support/Cryptomator/ipc.socket\"" --java-options "-Dcryptomator.ipcSocketPath=\"@{userhome}/Library/Application Support/Cryptomator/ipc.socket\""
--java-options "-Dcryptomator.integrationsMac.keychainServiceName=\"Cryptomator\"" --java-options "-Dcryptomator.integrationsMac.keychainServiceName=\"Cryptomator\""
--java-options "-Dcryptomator.mountPointsDir=\"~/Cryptomator\"" --java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/Library/Application Support/Cryptomator/mnt\""
--java-options "-Dcryptomator.showTrayIcon=true" --java-options "-Dcryptomator.showTrayIcon=true"
--java-options "-Dcryptomator.buildNumber=\"dmg-${{ needs.get-version.outputs.revNum }}\"" --java-options "-Dcryptomator.buildNumber=\"dmg-${{ needs.get-version.outputs.revNum }}\""
--mac-package-identifier org.cryptomator --mac-package-identifier org.cryptomator
+8 -7
View File
@@ -112,17 +112,18 @@ jobs:
--java-options "-Xmx256m" --java-options "-Xmx256m"
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\"" --java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
--java-options "-Dfile.encoding=\"utf-8\"" --java-options "-Dfile.encoding=\"utf-8\""
--java-options "-Dcryptomator.logDir=\"~/AppData/Roaming/Cryptomator\"" --java-options "-Djava.net.useSystemProxies=true"
--java-options "-Dcryptomator.pluginDir=\"~/AppData/Roaming/Cryptomator/Plugins\"" --java-options "-Dcryptomator.logDir=\"@{localappdata}/Cryptomator\""
--java-options "-Dcryptomator.settingsPath=\"~/AppData/Roaming/Cryptomator/settings.json\"" --java-options "-Dcryptomator.pluginDir=\"@{appdata}/Cryptomator/Plugins\""
--java-options "-Dcryptomator.p12Path=\"~/AppData/Roaming/Cryptomator/key.p12\"" --java-options "-Dcryptomator.settingsPath=\"@{appdata}/Cryptomator/settings.json;@{userhome}/AppData/Roaming/Cryptomator/settings.json\""
--java-options "-Dcryptomator.ipcSocketPath=\"~/AppData/Roaming/Cryptomator/ipc.socket\"" --java-options "-Dcryptomator.p12Path=\"@{appdata}/Cryptomator/key.p12;@{userhome}/AppData/Roaming/Cryptomator/key.p12\""
--java-options "-Dcryptomator.mountPointsDir=\"~/Cryptomator\"" --java-options "-Dcryptomator.ipcSocketPath=\"@{localappdata}/Cryptomator/ipc.socket\""
--java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/Cryptomator\""
--java-options "-Dcryptomator.loopbackAlias=\"${{ env.LOOPBACK_ALIAS }}\"" --java-options "-Dcryptomator.loopbackAlias=\"${{ env.LOOPBACK_ALIAS }}\""
--java-options "-Dcryptomator.showTrayIcon=true" --java-options "-Dcryptomator.showTrayIcon=true"
--java-options "-Dcryptomator.buildNumber=\"msi-${{ needs.get-version.outputs.revNum }}\"" --java-options "-Dcryptomator.buildNumber=\"msi-${{ needs.get-version.outputs.revNum }}\""
--java-options "-Dcryptomator.integrationsWin.autoStartShellLinkName=\"Cryptomator\"" --java-options "-Dcryptomator.integrationsWin.autoStartShellLinkName=\"Cryptomator\""
--java-options "-Dcryptomator.integrationsWin.keychainPaths=\"~/AppData/Roaming/Cryptomator/keychain.json\"" --java-options "-Dcryptomator.integrationsWin.keychainPaths=\"@{appdata}/Cryptomator/keychain.json;@{userhome}/AppData/Roaming/Cryptomator/keychain.json\""
--java-options "-Djavafx.verbose=${{ inputs.isDebug }}" --java-options "-Djavafx.verbose=${{ inputs.isDebug }}"
--resource-dir dist/win/resources --resource-dir dist/win/resources
--icon dist/win/resources/Cryptomator.ico --icon dist/win/resources/Cryptomator.ico
+2 -1
View File
@@ -53,9 +53,10 @@
<option name="KEEP_LINE_BREAKS" value="false" /> <option name="KEEP_LINE_BREAKS" value="false" />
<option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" /> <option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" />
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" /> <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_METHODS_IN_ONE_LINE" value="true" />
<option name="KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE" value="true" /> <option name="KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE" value="true" />
<option name="KEEP_SIMPLE_CLASSES_IN_ONE_LINE" value="true" />
<option name="IF_BRACE_FORCE" value="3" />
<option name="ENUM_CONSTANTS_WRAP" value="2" /> <option name="ENUM_CONSTANTS_WRAP" value="2" />
<indentOptions> <indentOptions>
<option name="USE_TAB_CHARACTER" value="true" /> <option name="USE_TAB_CHARACTER" value="true" />
+1 -1
View File
@@ -2,7 +2,7 @@
<configuration default="false" name="Cryptomator Linux" type="Application" factoryName="Application"> <configuration default="false" name="Cryptomator Linux" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" /> <option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" /> <module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;~/.config/Cryptomator/settings.json&quot; -Dcryptomator.p12Path=&quot;~/.config/Cryptomator/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;~/.config/Cryptomator/ipc.socket&quot; -Dcryptomator.logDir=&quot;~/.local/share/Cryptomator/logs&quot; -Dcryptomator.pluginDir=&quot;~/.local/share/Cryptomator/plugins&quot; -Dcryptomator.mountPointsDir=&quot;~/.local/share/Cryptomator/mnt&quot; -Dcryptomator.showTrayIcon=true -Xss20m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64" /> <option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;@{userhome}/.config/Cryptomator/settings.json&quot; -Dcryptomator.p12Path=&quot;@{userhome}/.config/Cryptomator/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;@{userhome}/.config/Cryptomator/ipc.socket&quot; -Dcryptomator.logDir=&quot;@{userhome}/.local/share/Cryptomator/logs&quot; -Dcryptomator.pluginDir=&quot;@{userhome}/.local/share/Cryptomator/plugins&quot; -Dcryptomator.mountPointsDir=&quot;@{userhome}/.local/share/Cryptomator/mnt&quot; -Dcryptomator.showTrayIcon=true -Xss20m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64" />
<method v="2"> <method v="2">
<option name="Make" enabled="true" /> <option name="Make" enabled="true" />
</method> </method>
+1 -1
View File
@@ -2,7 +2,7 @@
<configuration default="false" name="Cryptomator Linux Dev" type="Application" factoryName="Application"> <configuration default="false" name="Cryptomator Linux Dev" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" /> <option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" /> <module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;~/.config/Cryptomator-Dev/settings.json&quot; -Dcryptomator.p12Path=&quot;~/.config/Cryptomator-Dev/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;~/.config/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;~/.local/share/Cryptomator-Dev/logs&quot; -Dcryptomator.pluginDir=&quot;~/.local/share/Cryptomator-Dev/plugins&quot; -Dcryptomator.mountPointsDir=&quot;~/.local/share/Cryptomator-Dev/mnt&quot; -Dcryptomator.showTrayIcon=true -Dfuse.experimental=&quot;true&quot; -Xss20m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64" /> <option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;@{userhome}/.config/Cryptomator-Dev/settings.json&quot; -Dcryptomator.p12Path=&quot;@{userhome}/.config/Cryptomator-Dev/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;@{userhome}/.config/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;@{userhome}/.local/share/Cryptomator-Dev/logs&quot; -Dcryptomator.pluginDir=&quot;@{userhome}/.local/share/Cryptomator-Dev/plugins&quot; -Dcryptomator.mountPointsDir=&quot;@{userhome}/.local/share/Cryptomator-Dev/mnt&quot; -Dcryptomator.showTrayIcon=true -Dfuse.experimental=&quot;true&quot; -Xss20m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64" />
<method v="2"> <method v="2">
<option name="Make" enabled="true" /> <option name="Make" enabled="true" />
</method> </method>
+1 -1
View File
@@ -2,7 +2,7 @@
<configuration default="false" name="Cryptomator Windows" type="Application" factoryName="Application"> <configuration default="false" name="Cryptomator Windows" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" /> <option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" /> <module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;~/AppData/Roaming/Cryptomator/settings.json&quot; -Dcryptomator.ipcSocketPath=&quot;~/AppData/Roaming/Cryptomator/ipc.socket&quot; -Dcryptomator.logDir=&quot;~/AppData/Roaming/Cryptomator&quot; -Dcryptomator.pluginDir=&quot;~/AppData/Roaming/Cryptomator/Plugins&quot; -Dcryptomator.integrationsWin.keychainPaths=&quot;~/AppData/Roaming/Cryptomator/keychain.json&quot; -Dcryptomator.p12Path=&quot;~/AppData/Roaming/Cryptomator/key.p12&quot; -Dcryptomator.mountPointsDir=&quot;~/Cryptomator&quot; -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.win" /> <option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;@{appdata}/Cryptomator/settings.json;@{userhome}/AppData/Roaming/Cryptomator/settings.json&quot; -Dcryptomator.ipcSocketPath=&quot;@{localappdata}/Cryptomator/ipc.socket&quot; -Dcryptomator.logDir=&quot;@{localappdata}/Cryptomator&quot; -Dcryptomator.pluginDir=&quot;@{appdata}/Cryptomator/Plugins&quot; -Dcryptomator.integrationsWin.keychainPaths=&quot;@{appdata}/Cryptomator/keychain.json;@{userhome}/AppData/Roaming/Cryptomator/keychain.json&quot; -Dcryptomator.p12Path=&quot;@{appdata}/Cryptomator/key.p12;@{userhome}/AppData/Roaming/Cryptomator/key.p12&quot; -Dcryptomator.mountPointsDir=&quot;@{userhome}/Cryptomator&quot; -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.win" />
<method v="2"> <method v="2">
<option name="Make" enabled="true" /> <option name="Make" enabled="true" />
</method> </method>
+1 -1
View File
@@ -2,7 +2,7 @@
<configuration default="false" name="Cryptomator Windows Dev" type="Application" factoryName="Application"> <configuration default="false" name="Cryptomator Windows Dev" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" /> <option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" /> <module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;~/AppData/Roaming/Cryptomator-Dev/settings.json&quot; -Dcryptomator.ipcSocketPath=&quot;~/AppData/Roaming/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;~/AppData/Roaming/Cryptomator-Dev&quot; -Dcryptomator.pluginDir=&quot;~/AppData/Roaming/Cryptomator-Dev/Plugins&quot; -Dcryptomator.integrationsWin.keychainPaths=&quot;~/AppData/Roaming/Cryptomator-Dev/keychain.json&quot; -Dcryptomator.p12Path=&quot;~/AppData/Roaming/Cryptomator-Dev/key.p12&quot; -Dcryptomator.mountPointsDir=&quot;~/Cryptomator-Dev&quot; -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.win" /> <option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;@{appdata}/Cryptomator-Dev/settings.json;@{userhome}/AppData/Roaming/Cryptomator-Dev/settings.json&quot; -Dcryptomator.ipcSocketPath=&quot;@{localappdata}/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;@{localappdata}/Cryptomator-Dev&quot; -Dcryptomator.pluginDir=&quot;@{appdata}/Cryptomator-Dev/Plugins&quot; -Dcryptomator.integrationsWin.keychainPaths=&quot;@{appdata}/Cryptomator-Dev/keychain.json;@{userhome}/AppData/Roaming/Cryptomator-Dev/keychain.json&quot; -Dcryptomator.p12Path=&quot;@{appdata}/Cryptomator-Dev/key.p12;@{userhome}/AppData/Roaming/Cryptomator-Dev/key.p12&quot; -Dcryptomator.mountPointsDir=&quot;@{userhome}/Cryptomator-Dev&quot; -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.win" />
<method v="2"> <method v="2">
<option name="Make" enabled="true" /> <option name="Make" enabled="true" />
</method> </method>
+1 -1
View File
@@ -5,7 +5,7 @@
</envs> </envs>
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" /> <option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" /> <module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dapple.awt.enableTemplateImages=true -Dcryptomator.settingsPath=&quot;~/Library/Application Support/Cryptomator/settings.json&quot; -Dcryptomator.p12Path=&quot;~/Library/Application Support/Cryptomator/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;~/Library/Application Support/Cryptomator/ipc.socket&quot; -Dcryptomator.logDir=&quot;~/Library/Logs/Cryptomator&quot; -Dcryptomator.pluginDir=&quot;~/Library/Application Support/Cryptomator/Plugins&quot; -Dcryptomator.mountPointsDir=&quot;~/Cryptomator&quot; -Dcryptomator.showTrayIcon=true -Dcryptomator.integrationsMac.keychainServiceName=Cryptomator -Xss2m -Xmx512m -ea --enable-preview --enable-native-access=org.cryptomator.jfuse.mac" /> <option name="VM_PARAMETERS" value="-Dapple.awt.enableTemplateImages=true -Dcryptomator.settingsPath=&quot;@{userhome}/Library/Application Support/Cryptomator/settings.json&quot; -Dcryptomator.p12Path=&quot;@{userhome}/Library/Application Support/Cryptomator/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;@{userhome}/Library/Application Support/Cryptomator/ipc.socket&quot; -Dcryptomator.logDir=&quot;@{userhome}/Library/Logs/Cryptomator&quot; -Dcryptomator.pluginDir=&quot;@{userhome}/Library/Application Support/Cryptomator/Plugins&quot; -Dcryptomator.mountPointsDir=&quot;@{userhome}/Cryptomator&quot; -Dcryptomator.showTrayIcon=true -Dcryptomator.integrationsMac.keychainServiceName=Cryptomator -Xss2m -Xmx512m -ea --enable-preview --enable-native-access=org.cryptomator.jfuse.mac" />
<method v="2"> <method v="2">
<option name="Make" enabled="true" /> <option name="Make" enabled="true" />
</method> </method>
+1 -1
View File
@@ -5,7 +5,7 @@
</envs> </envs>
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" /> <option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" /> <module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dapple.awt.enableTemplateImages=true -Dcryptomator.settingsPath=&quot;~/Library/Application Support/Cryptomator-Dev/settings.json&quot; -Dcryptomator.p12Path=&quot;~/Library/Application Support/Cryptomator-Dev/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;~/Library/Application Support/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;~/Library/Logs/Cryptomator-Dev&quot; -Dcryptomator.pluginDir=&quot;~/Library/Application Support/Cryptomator-Dev/Plugins&quot; -Dcryptomator.mountPointsDir=&quot;~/Cryptomator&quot; -Dcryptomator.showTrayIcon=true -Dcryptomator.integrationsMac.keychainServiceName=Cryptomator -Xss2m -Xmx512m -ea --enable-preview --enable-native-access=org.cryptomator.jfuse.mac" /> <option name="VM_PARAMETERS" value="-Dapple.awt.enableTemplateImages=true -Dcryptomator.settingsPath=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/settings.json&quot; -Dcryptomator.p12Path=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;@{userhome}/Library/Logs/Cryptomator-Dev&quot; -Dcryptomator.pluginDir=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/Plugins&quot; -Dcryptomator.mountPointsDir=&quot;@{userhome}/Cryptomator&quot; -Dcryptomator.showTrayIcon=true -Dcryptomator.integrationsMac.keychainServiceName=Cryptomator -Xss2m -Xmx512m -ea --enable-preview --enable-native-access=org.cryptomator.jfuse.mac" />
<method v="2"> <method v="2">
<option name="Make" enabled="true" /> <option name="Make" enabled="true" />
</method> </method>
+7 -6
View File
@@ -50,12 +50,13 @@ ${JAVA_HOME}/bin/jpackage \
--java-options "-Xmx256m" \ --java-options "-Xmx256m" \
--app-version "${VERSION}.${REVISION_NO}" \ --app-version "${VERSION}.${REVISION_NO}" \
--java-options "-Dfile.encoding=\"utf-8\"" \ --java-options "-Dfile.encoding=\"utf-8\"" \
--java-options "-Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\"" \ --java-options "-Djava.net.useSystemProxies=true" \
--java-options "-Dcryptomator.pluginDir=\"~/.local/share/Cryptomator/plugins\"" \ --java-options "-Dcryptomator.logDir=\"@{userhome}/.local/share/Cryptomator/logs\"" \
--java-options "-Dcryptomator.settingsPath=\"~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json\"" \ --java-options "-Dcryptomator.pluginDir=\"@{userhome}/.local/share/Cryptomator/plugins\"" \
--java-options "-Dcryptomator.p12Path=\"~/.config/Cryptomator/key.p12\"" \ --java-options "-Dcryptomator.settingsPath=\"@{userhome}/.config/Cryptomator/settings.json:@{userhome}/.Cryptomator/settings.json\"" \
--java-options "-Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\"" \ --java-options "-Dcryptomator.p12Path=\"@{userhome}/.config/Cryptomator/key.p12\"" \
--java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\"" \ --java-options "-Dcryptomator.ipcSocketPath=\"@{userhome}/.config/Cryptomator/ipc.socket\"" \
--java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/.local/share/Cryptomator/mnt\"" \
--java-options "-Dcryptomator.showTrayIcon=false" \ --java-options "-Dcryptomator.showTrayIcon=false" \
--java-options "-Dcryptomator.buildNumber=\"appimage-${REVISION_NO}\"" \ --java-options "-Dcryptomator.buildNumber=\"appimage-${REVISION_NO}\"" \
--add-launcher cryptomator-gtk2=launcher-gtk2.properties \ --add-launcher cryptomator-gtk2=launcher-gtk2.properties \
+7 -6
View File
@@ -48,12 +48,13 @@ override_dh_auto_build:
--java-options "-Xss5m" \ --java-options "-Xss5m" \
--java-options "-Xmx256m" \ --java-options "-Xmx256m" \
--java-options "-Dfile.encoding=\"utf-8\"" \ --java-options "-Dfile.encoding=\"utf-8\"" \
--java-options "-Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\"" \ --java-options "-Djava.net.useSystemProxies=true" \
--java-options "-Dcryptomator.pluginDir=\"~/.local/share/Cryptomator/plugins\"" \ --java-options "-Dcryptomator.logDir=\"@{userhome}/.local/share/Cryptomator/logs\"" \
--java-options "-Dcryptomator.settingsPath=\"~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json\"" \ --java-options "-Dcryptomator.pluginDir=\"@{userhome}/.local/share/Cryptomator/plugins\"" \
--java-options "-Dcryptomator.p12Path=\"~/.config/Cryptomator/key.p12\"" \ --java-options "-Dcryptomator.settingsPath=\"@{userhome}/.config/Cryptomator/settings.json:@{userhome}/.Cryptomator/settings.json\"" \
--java-options "-Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\"" \ --java-options "-Dcryptomator.p12Path=\"@{userhome}/.config/Cryptomator/key.p12\"" \
--java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\"" \ --java-options "-Dcryptomator.ipcSocketPath=\"@{userhome}/.config/Cryptomator/ipc.socket\"" \
--java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/.local/share/Cryptomator/mnt\"" \
--java-options "-Dcryptomator.showTrayIcon=false" \ --java-options "-Dcryptomator.showTrayIcon=false" \
--java-options "-Dcryptomator.buildNumber=\"deb-${REVISION_NUM}\"" \ --java-options "-Dcryptomator.buildNumber=\"deb-${REVISION_NUM}\"" \
--java-options "-Dcryptomator.appVersion=\"${SEMVER_STR}\"" \ --java-options "-Dcryptomator.appVersion=\"${SEMVER_STR}\"" \
+7 -6
View File
@@ -71,16 +71,17 @@ ${JAVA_HOME}/bin/jpackage \
--java-options "-Xss5m" \ --java-options "-Xss5m" \
--java-options "-Xmx256m" \ --java-options "-Xmx256m" \
--java-options "-Dfile.encoding=\"utf-8\"" \ --java-options "-Dfile.encoding=\"utf-8\"" \
--java-options "-Djava.net.useSystemProxies=true" \
--java-options "-Dapple.awt.enableTemplateImages=true" \ --java-options "-Dapple.awt.enableTemplateImages=true" \
--java-options "-Dsun.java2d.metal=true" \ --java-options "-Dsun.java2d.metal=true" \
--java-options "-Dcryptomator.appVersion=\"${VERSION_NO}\"" \ --java-options "-Dcryptomator.appVersion=\"${VERSION_NO}\"" \
--java-options "-Dcryptomator.logDir=\"~/Library/Logs/${APP_NAME}\"" \ --java-options "-Dcryptomator.logDir=\"@{userhome}/Library/Logs/${APP_NAME}\"" \
--java-options "-Dcryptomator.pluginDir=\"~/Library/Application Support/${APP_NAME}/Plugins\"" \ --java-options "-Dcryptomator.pluginDir=\"@{userhome}/Library/Application Support/${APP_NAME}/Plugins\"" \
--java-options "-Dcryptomator.settingsPath=\"~/Library/Application Support/${APP_NAME}/settings.json\"" \ --java-options "-Dcryptomator.settingsPath=\"@{userhome}/Library/Application Support/${APP_NAME}/settings.json\"" \
--java-options "-Dcryptomator.ipcSocketPath=\"~/Library/Application Support/${APP_NAME}/ipc.socket\"" \ --java-options "-Dcryptomator.ipcSocketPath=\"@{userhome}/Library/Application Support/${APP_NAME}/ipc.socket\"" \
--java-options "-Dcryptomator.p12Path=\"~/Library/Application Support/${APP_NAME}/key.p12\"" \ --java-options "-Dcryptomator.p12Path=\"@{userhome}/Library/Application Support/${APP_NAME}/key.p12\"" \
--java-options "-Dcryptomator.integrationsMac.keychainServiceName=\"${APP_NAME}\"" \ --java-options "-Dcryptomator.integrationsMac.keychainServiceName=\"${APP_NAME}\"" \
--java-options "-Dcryptomator.mountPointsDir=\"~/${APP_NAME}\"" \ --java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/Library/Application Support${APP_NAME}/mnt\"" \
--java-options "-Dcryptomator.showTrayIcon=true" \ --java-options "-Dcryptomator.showTrayIcon=true" \
--java-options "-Dcryptomator.buildNumber=\"dmg-${REVISION_NO}\"" \ --java-options "-Dcryptomator.buildNumber=\"dmg-${REVISION_NO}\"" \
--mac-package-identifier ${PACKAGE_IDENTIFIER} \ --mac-package-identifier ${PACKAGE_IDENTIFIER} \
+31 -12
View File
@@ -12,6 +12,9 @@ Param(
[bool] $clean [bool] $clean
) )
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$ProgressPreference = 'SilentlyContinue' # disables Invoke-WebRequest's progress bar, which slows down downloads to a few bytes/s
# check preconditions # check preconditions
if ((Get-Command "git" -ErrorAction SilentlyContinue) -eq $null) if ((Get-Command "git" -ErrorAction SilentlyContinue) -eq $null)
{ {
@@ -26,7 +29,7 @@ if ((Get-Command "mvn" -ErrorAction SilentlyContinue) -eq $null)
$buildDir = Split-Path -Parent $PSCommandPath $buildDir = Split-Path -Parent $PSCommandPath
$version = $(mvn -f $buildDir/../../pom.xml help:evaluate -Dexpression="project.version" -q -DforceStdout) $version = $(mvn -f $buildDir/../../pom.xml help:evaluate -Dexpression="project.version" -q -DforceStdout)
$semVerNo = $version -replace '(\d\.\d\.\d).*','$1' $semVerNo = $version -replace '(\d+\.\d+\.\d+).*','$1'
$revisionNo = $(git rev-list --count HEAD) $revisionNo = $(git rev-list --count HEAD)
Write-Output "`$version=$version" Write-Output "`$version=$version"
@@ -47,11 +50,28 @@ if ($clean -and (Test-Path -Path $runtimeImagePath)) {
Remove-Item -Path $runtimeImagePath -Force -Recurse Remove-Item -Path $runtimeImagePath -Force -Recurse
} }
## download jfx jmods
$jfxJmodsChecksum = 'd00767334c43b8832b5cf10267d34ca8f563d187c4655b73eb6020dd79c054b5'
$jfxJmodsZip = '.\resources\jfxJmods.zip'
if( !(Test-Path -Path $jfxJmodsZip) ) {
$jmodsUrl = "https://download2.gluonhq.com/openjfx/20.0.1/openjfx-20.0.1_windows-x64_bin-jmods.zip"
Write-Output "Downloading ${jmodsUrl}..."
Invoke-WebRequest $jmodsUrl -OutFile $jfxJmodsZip # redirects are followed by default
}
$jmodsChecksumActual = $(Get-FileHash -Path $jfxJmodsZip -Algorithm SHA256).Hash
if( $jmodsChecksumActual -ne $jfxJmodsChecksum ) {
Write-Error "Checksum mismatch for jfxJmods.zip. Expected: $jfxJmodsChecksum, actual: $jmodsChecksumActual"
exit 1;
}
Expand-Archive -Force -Path $jfxJmodsZip -DestinationPath ".\resources\"
& "$Env:JAVA_HOME\bin\jlink" ` & "$Env:JAVA_HOME\bin\jlink" `
--verbose ` --verbose `
--output runtime ` --output runtime `
--module-path "$Env:JAVA_HOME/jmods" ` --module-path "$Env:JAVA_HOME/jmods;$buildDir/resources/javafx-jmods-20.0.1" `
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr ` --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr,javafx.base,javafx.graphics,javafx.controls,javafx.fxml `
--strip-native-commands ` --strip-native-commands `
--no-header-files ` --no-header-files `
--no-man-pages ` --no-man-pages `
@@ -82,15 +102,16 @@ if ($clean -and (Test-Path -Path $appPath)) {
--java-options "-Dcryptomator.appVersion=`"$semVerNo`"" ` --java-options "-Dcryptomator.appVersion=`"$semVerNo`"" `
--app-version "$semVerNo.$revisionNo" ` --app-version "$semVerNo.$revisionNo" `
--java-options "-Dfile.encoding=`"utf-8`"" ` --java-options "-Dfile.encoding=`"utf-8`"" `
--java-options "-Dcryptomator.logDir=`"~/AppData/Roaming/$AppName`"" ` --java-options "-Djava.net.useSystemProxies=true" `
--java-options "-Dcryptomator.pluginDir=`"~/AppData/Roaming/$AppName/Plugins`"" ` --java-options "-Dcryptomator.logDir=`"@{localappdata}/$AppName`"" `
--java-options "-Dcryptomator.settingsPath=`"~/AppData/Roaming/$AppName/settings.json`"" ` --java-options "-Dcryptomator.pluginDir=`"@{appdata}/$AppName/Plugins`"" `
--java-options "-Dcryptomator.ipcSocketPath=`"~/AppData/Roaming/$AppName/ipc.socket`"" ` --java-options "-Dcryptomator.settingsPath=`"@{appdata}/$AppName/settings.json;@{userhome}/AppData/Roaming/$AppName/settings.json`"" `
--java-options "-Dcryptomator.p12Path=`"~/AppData/Roaming/$AppName/key.p12`"" ` --java-options "-Dcryptomator.ipcSocketPath=`"@{localappdata}/$AppName/ipc.socket`"" `
--java-options "-Dcryptomator.mountPointsDir=`"~/$AppName`"" ` --java-options "-Dcryptomator.p12Path=`"@{appdata}/$AppName/key.p12;@{userhome}/AppData/Roaming/$AppName/key.p12`"" `
--java-options "-Dcryptomator.mountPointsDir=`"@{userhome}/$AppName`"" `
--java-options "-Dcryptomator.loopbackAlias=`"$LoopbackAlias`"" ` --java-options "-Dcryptomator.loopbackAlias=`"$LoopbackAlias`"" `
--java-options "-Dcryptomator.integrationsWin.autoStartShellLinkName=`"$AppName`"" ` --java-options "-Dcryptomator.integrationsWin.autoStartShellLinkName=`"$AppName`"" `
--java-options "-Dcryptomator.integrationsWin.keychainPaths=`"~/AppData/Roaming/$AppName/keychain.json`"" ` --java-options "-Dcryptomator.integrationsWin.keychainPaths=`"@{appdata}/$AppName/keychain.json;@{userhome}/AppData/Roaming/$AppName/keychain.json`"" `
--java-options "-Dcryptomator.showTrayIcon=true" ` --java-options "-Dcryptomator.showTrayIcon=true" `
--java-options "-Dcryptomator.buildNumber=`"msi-$revisionNo`"" ` --java-options "-Dcryptomator.buildNumber=`"msi-$revisionNo`"" `
--resource-dir resources ` --resource-dir resources `
@@ -151,8 +172,6 @@ $Env:JP_WIXWIZARD_RESOURCES = "$buildDir\resources"
"-Dlicense.licenseMergesUrl=file:///$buildDir/../../license/merges" "-Dlicense.licenseMergesUrl=file:///$buildDir/../../license/merges"
# download Winfsp # download Winfsp
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$ProgressPreference = 'SilentlyContinue' # disables Invoke-WebRequest's progress bar, which slows down downloads to a few bytes/s
$winfspMsiUrl= (Select-String -Path ".\bundle\resources\winFspMetaData.wxi" -Pattern '<\?define BundledWinFspDownloadLink="(.+)".*?>').Matches.Groups[1].Value $winfspMsiUrl= (Select-String -Path ".\bundle\resources\winFspMetaData.wxi" -Pattern '<\?define BundledWinFspDownloadLink="(.+)".*?>').Matches.Groups[1].Value
Write-Output "Downloading ${winfspMsiUrl}..." Write-Output "Downloading ${winfspMsiUrl}..."
Invoke-WebRequest $winfspMsiUrl -OutFile ".\bundle\resources\winfsp.msi" # redirects are followed by default Invoke-WebRequest $winfspMsiUrl -OutFile ".\bundle\resources\winfsp.msi" # redirects are followed by default
+9 -9
View File
@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId> <groupId>org.cryptomator</groupId>
<artifactId>cryptomator</artifactId> <artifactId>cryptomator</artifactId>
<version>1.9.1</version> <version>1.10.0</version>
<name>Cryptomator Desktop App</name> <name>Cryptomator Desktop App</name>
<organization> <organization>
@@ -33,7 +33,7 @@
<nonModularGroupIds>org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents,de.swiesend,org.purejava,com.github.hypfvieh</nonModularGroupIds> <nonModularGroupIds>org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents,de.swiesend,org.purejava,com.github.hypfvieh</nonModularGroupIds>
<!-- cryptomator dependencies --> <!-- cryptomator dependencies -->
<cryptomator.cryptofs.version>2.6.5</cryptomator.cryptofs.version> <cryptomator.cryptofs.version>2.6.6</cryptomator.cryptofs.version>
<cryptomator.integrations.version>1.2.0</cryptomator.integrations.version> <cryptomator.integrations.version>1.2.0</cryptomator.integrations.version>
<cryptomator.integrations.win.version>1.2.0</cryptomator.integrations.win.version> <cryptomator.integrations.win.version>1.2.0</cryptomator.integrations.win.version>
<cryptomator.integrations.mac.version>1.2.0</cryptomator.integrations.mac.version> <cryptomator.integrations.mac.version>1.2.0</cryptomator.integrations.mac.version>
@@ -46,8 +46,8 @@
<commons-lang3.version>3.12.0</commons-lang3.version> <commons-lang3.version>3.12.0</commons-lang3.version>
<dagger.version>2.45</dagger.version> <dagger.version>2.45</dagger.version>
<easybind.version>2.2</easybind.version> <easybind.version>2.2</easybind.version>
<guava.version>32.0.0-jre</guava.version> <guava.version>32.0.1-jre</guava.version>
<gson.version>2.10.1</gson.version> <jackson.version>2.15.2</jackson.version>
<javafx.version>20.0.1</javafx.version> <javafx.version>20.0.1</javafx.version>
<jwt.version>4.4.0</jwt.version> <jwt.version>4.4.0</jwt.version>
<nimbus-jose.version>9.31</nimbus-jose.version> <nimbus-jose.version>9.31</nimbus-jose.version>
@@ -157,6 +157,11 @@
<artifactId>nimbus-jose-jwt</artifactId> <artifactId>nimbus-jose-jwt</artifactId>
<version>${nimbus-jose.version}</version> <version>${nimbus-jose.version}</version>
</dependency> </dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- EasyBind --> <!-- EasyBind -->
<dependency> <dependency>
@@ -206,11 +211,6 @@
<artifactId>dagger</artifactId> <artifactId>dagger</artifactId>
<version>${dagger.version}</version> <version>${dagger.version}</version>
</dependency> </dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>${gson.version}</version>
</dependency>
<!-- JUnit / Mockito / Hamcrest --> <!-- JUnit / Mockito / Hamcrest -->
<dependency> <dependency>
+9 -7
View File
@@ -5,6 +5,7 @@ import org.cryptomator.common.locationpresets.DropboxWindowsLocationPresetsProvi
import org.cryptomator.common.locationpresets.GoogleDriveLocationPresetsProvider; import org.cryptomator.common.locationpresets.GoogleDriveLocationPresetsProvider;
import org.cryptomator.common.locationpresets.ICloudMacLocationPresetsProvider; import org.cryptomator.common.locationpresets.ICloudMacLocationPresetsProvider;
import org.cryptomator.common.locationpresets.ICloudWindowsLocationPresetsProvider; import org.cryptomator.common.locationpresets.ICloudWindowsLocationPresetsProvider;
import org.cryptomator.common.locationpresets.LeitzcloudLocationPresetsProvider;
import org.cryptomator.common.locationpresets.LocationPresetsProvider; import org.cryptomator.common.locationpresets.LocationPresetsProvider;
import org.cryptomator.common.locationpresets.MegaLocationPresetsProvider; import org.cryptomator.common.locationpresets.MegaLocationPresetsProvider;
import org.cryptomator.common.locationpresets.OneDriveLinuxLocationPresetsProvider; import org.cryptomator.common.locationpresets.OneDriveLinuxLocationPresetsProvider;
@@ -37,7 +38,7 @@ open module org.cryptomator.desktop {
requires ch.qos.logback.core; requires ch.qos.logback.core;
requires com.auth0.jwt; requires com.auth0.jwt;
requires com.google.common; requires com.google.common;
requires com.google.gson; requires com.fasterxml.jackson.databind;
requires com.nimbusds.jose.jwt; requires com.nimbusds.jose.jwt;
requires com.nulabinc.zxcvbn; requires com.nulabinc.zxcvbn;
requires com.tobiasdiez.easybind; requires com.tobiasdiez.easybind;
@@ -53,11 +54,12 @@ open module org.cryptomator.desktop {
provides TrayMenuController with AwtTrayMenuController; provides TrayMenuController with AwtTrayMenuController;
provides Configurator with LogbackConfiguratorFactory; provides Configurator with LogbackConfiguratorFactory;
provides LocationPresetsProvider with DropboxMacLocationPresetsProvider, // provides LocationPresetsProvider with //
DropboxWindowsLocationPresetsProvider, DropboxLinuxLocationPresetsProvider, // DropboxWindowsLocationPresetsProvider, DropboxMacLocationPresetsProvider, DropboxLinuxLocationPresetsProvider, //
ICloudMacLocationPresetsProvider, ICloudWindowsLocationPresetsProvider, //
GoogleDriveLocationPresetsProvider, // GoogleDriveLocationPresetsProvider, //
PCloudLocationPresetsProvider, MegaLocationPresetsProvider, // ICloudWindowsLocationPresetsProvider, ICloudMacLocationPresetsProvider, //
OneDriveLinuxLocationPresetsProvider, OneDriveWindowsLocationPresetsProvider, // LeitzcloudLocationPresetsProvider, //
OneDriveMacLocationPresetsProvider; MegaLocationPresetsProvider, //
OneDriveWindowsLocationPresetsProvider, OneDriveMacLocationPresetsProvider, OneDriveLinuxLocationPresetsProvider, //
PCloudLocationPresetsProvider;
} }
@@ -139,9 +139,9 @@ public abstract class CommonsModule {
@Provides @Provides
@Singleton @Singleton
static ObservableValue<InetSocketAddress> provideServerSocketAddressBinding(Settings settings) { static ObservableValue<InetSocketAddress> provideServerSocketAddressBinding(Settings settings) {
return settings.port().map(port -> { return settings.port.map(port -> {
String host = SystemUtils.IS_OS_WINDOWS ? "127.0.0.1" : "localhost"; String host = SystemUtils.IS_OS_WINDOWS ? "127.0.0.1" : "localhost";
return InetSocketAddress.createUnresolved(host, settings.port().intValue()); return InetSocketAddress.createUnresolved(host, settings.port.intValue());
}); });
} }
@@ -18,8 +18,6 @@ import java.util.stream.StreamSupport;
public class Environment { public class Environment {
private static final Logger LOG = LoggerFactory.getLogger(Environment.class); private static final Logger LOG = LoggerFactory.getLogger(Environment.class);
private static final Path RELATIVE_HOME_DIR = Paths.get("~");
private static final char PATH_LIST_SEP = ':';
private static final int DEFAULT_MIN_PW_LENGTH = 8; private static final int DEFAULT_MIN_PW_LENGTH = 8;
private static final String SETTINGS_PATH_PROP_NAME = "cryptomator.settingsPath"; private static final String SETTINGS_PATH_PROP_NAME = "cryptomator.settingsPath";
private static final String IPC_SOCKET_PATH_PROP_NAME = "cryptomator.ipcSocketPath"; private static final String IPC_SOCKET_PATH_PROP_NAME = "cryptomator.ipcSocketPath";
@@ -80,7 +78,7 @@ public class Environment {
return getPaths(P12_PATH_PROP_NAME); return getPaths(P12_PATH_PROP_NAME);
} }
public Stream<Path> ipcSocketPath() { public Stream<Path> getIpcSocketPath() {
return getPaths(IPC_SOCKET_PATH_PROP_NAME); return getPaths(IPC_SOCKET_PATH_PROP_NAME);
} }
@@ -89,7 +87,7 @@ public class Environment {
} }
public Optional<Path> getLogDir() { public Optional<Path> getLogDir() {
return getPath(LOG_DIR_PROP_NAME).map(this::replaceHomeDir); return getPath(LOG_DIR_PROP_NAME);
} }
public Optional<String> getLoopbackAlias() { public Optional<String> getLoopbackAlias() {
@@ -97,11 +95,11 @@ public class Environment {
} }
public Optional<Path> getPluginDir() { public Optional<Path> getPluginDir() {
return getPath(PLUGIN_DIR_PROP_NAME).map(this::replaceHomeDir); return getPath(PLUGIN_DIR_PROP_NAME);
} }
public Optional<Path> getMountPointsDir() { public Optional<Path> getMountPointsDir() {
return getPath(MOUNTPOINT_DIR_PROP_NAME).map(this::replaceHomeDir); return getPath(MOUNTPOINT_DIR_PROP_NAME);
} }
/** /**
@@ -131,22 +129,9 @@ public class Environment {
} }
// visible for testing // visible for testing
public Path getHomeDir() { Stream<Path> getPaths(String propertyName) {
return getPath("user.home").orElseThrow(); Stream<String> rawSettingsPaths = getRawList(propertyName, System.getProperty("path.separator").charAt(0));
} return rawSettingsPaths.filter(Predicate.not(Strings::isNullOrEmpty)).map(Path::of);
// visible for testing
public Stream<Path> getPaths(String propertyName) {
Stream<String> rawSettingsPaths = getRawList(propertyName, PATH_LIST_SEP);
return rawSettingsPaths.filter(Predicate.not(Strings::isNullOrEmpty)).map(Paths::get).map(this::replaceHomeDir);
}
private Path replaceHomeDir(Path path) {
if (path.startsWith(RELATIVE_HOME_DIR)) {
return getHomeDir().resolve(RELATIVE_HOME_DIR.relativize(path));
} else {
return path;
}
} }
private Stream<String> getRawList(String propertyName, char separator) { private Stream<String> getRawList(String propertyName, char separator) {
@@ -32,18 +32,15 @@ public class ErrorCode {
this.rootCauseSpecificFrames = rootCauseSpecificFrames; this.rootCauseSpecificFrames = rootCauseSpecificFrames;
} }
// visible for testing public String methodCode() {
String methodCode() {
return format(traceCode(rootCause, LATEST_FRAME)); return format(traceCode(rootCause, LATEST_FRAME));
} }
// visible for testing public String rootCauseCode() {
String rootCauseCode() {
return format(traceCode(rootCause, rootCauseSpecificFrames)); return format(traceCode(rootCause, rootCauseSpecificFrames));
} }
// visible for testing public String throwableCode() {
String throwableCode() {
return format(traceCode(throwable, ALL_FRAMES)); return format(traceCode(throwable, ALL_FRAMES));
} }
@@ -30,7 +30,7 @@ public class LicenseHolder {
this.licenseSubject = validJwtClaims.map(DecodedJWT::getSubject); this.licenseSubject = validJwtClaims.map(DecodedJWT::getSubject);
this.validLicenseProperty = validJwtClaims.isNotNull(); this.validLicenseProperty = validJwtClaims.isNotNull();
Optional<DecodedJWT> claims = licenseChecker.check(settings.licenseKey().get()); Optional<DecodedJWT> claims = licenseChecker.check(settings.licenseKey.get());
validJwtClaims.set(claims.orElse(null)); validJwtClaims.set(claims.orElse(null));
} }
@@ -38,7 +38,7 @@ public class LicenseHolder {
Optional<DecodedJWT> claims = licenseChecker.check(licenseKey); Optional<DecodedJWT> claims = licenseChecker.check(licenseKey);
validJwtClaims.set(claims.orElse(null)); validJwtClaims.set(claims.orElse(null));
if (claims.isPresent()) { if (claims.isPresent()) {
settings.licenseKey().set(licenseKey); settings.licenseKey.set(licenseKey);
return true; return true;
} else { } else {
return false; return false;
@@ -0,0 +1,171 @@
package org.cryptomator.common;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.Enumeration;
import java.util.InvalidPropertiesFormatException;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
class PropertiesDecorator extends Properties {
protected final Properties delegate;
PropertiesDecorator(Properties delegate) {
this.delegate = delegate;
}
@Override
public String getProperty(String key) {return delegate.getProperty(key);}
@Override
public String getProperty(String key, String defaultValue) {return delegate.getProperty(key, defaultValue);}
@Override
public synchronized Object setProperty(String key, String value) {
return delegate.setProperty(key, value);
}
@Override
public synchronized void load(Reader reader) throws IOException {delegate.load(reader);}
@Override
public synchronized void load(InputStream inStream) throws IOException {delegate.load(inStream);}
@Override
public void store(Writer writer, String comments) throws IOException {delegate.store(writer, comments);}
@Override
public void store(OutputStream out, @Nullable String comments) throws IOException {delegate.store(out, comments);}
@Override
public synchronized void loadFromXML(InputStream in) throws IOException, InvalidPropertiesFormatException {delegate.loadFromXML(in);}
@Override
public void storeToXML(OutputStream os, String comment) throws IOException {delegate.storeToXML(os, comment);}
@Override
public void storeToXML(OutputStream os, String comment, String encoding) throws IOException {delegate.storeToXML(os, comment, encoding);}
@Override
public void storeToXML(OutputStream os, String comment, Charset charset) throws IOException {delegate.storeToXML(os, comment, charset);}
@Override
public Enumeration<?> propertyNames() {return delegate.propertyNames();}
@Override
public Set<String> stringPropertyNames() {return delegate.stringPropertyNames();}
@Override
public void list(PrintStream out) {delegate.list(out);}
@Override
public void list(PrintWriter out) {delegate.list(out);}
@Override
public int size() {return delegate.size();}
@Override
public boolean isEmpty() {return delegate.isEmpty();}
@Override
public Enumeration<Object> keys() {return delegate.keys();}
@Override
public Enumeration<Object> elements() {return delegate.elements();}
@Override
public boolean contains(Object value) {return delegate.contains(value);}
@Override
public boolean containsValue(Object value) {return delegate.containsValue(value);}
@Override
public boolean containsKey(Object key) {return delegate.containsKey(key);}
@Override
public Object get(Object key) {return delegate.get(key);}
@Override
public synchronized Object put(Object key, Object value) {return delegate.put(key, value);}
@Override
public synchronized Object remove(Object key) {return delegate.remove(key);}
@Override
public synchronized void putAll(Map<?, ?> t) {delegate.putAll(t);}
@Override
public synchronized void clear() {delegate.clear();}
@Override
public synchronized String toString() {return delegate.toString();}
@Override
public Set<Object> keySet() {return delegate.keySet();}
@Override
public Collection<Object> values() {return delegate.values();}
@Override
public Set<Map.Entry<Object, Object>> entrySet() {return delegate.entrySet();}
@Override
public synchronized boolean equals(Object o) {return delegate.equals(o);}
@Override
public synchronized int hashCode() {return delegate.hashCode();}
@Override
public Object getOrDefault(Object key, Object defaultValue) {return delegate.getOrDefault(key, defaultValue);}
@Override
public synchronized void forEach(BiConsumer<? super Object, ? super Object> action) {delegate.forEach(action);}
@Override
public synchronized void replaceAll(BiFunction<? super Object, ? super Object, ?> function) {delegate.replaceAll(function);}
@Override
public synchronized Object putIfAbsent(Object key, Object value) {return delegate.putIfAbsent(key, value);}
@Override
public synchronized boolean remove(Object key, Object value) {return delegate.remove(key, value);}
@Override
public synchronized boolean replace(Object key, Object oldValue, Object newValue) {return delegate.replace(key, oldValue, newValue);}
@Override
public synchronized Object replace(Object key, Object value) {return delegate.replace(key, value);}
@Override
public synchronized Object computeIfAbsent(Object key, Function<? super Object, ?> mappingFunction) {return delegate.computeIfAbsent(key, mappingFunction);}
@Override
public synchronized Object computeIfPresent(Object key, BiFunction<? super Object, ? super Object, ?> remappingFunction) {return delegate.computeIfPresent(key, remappingFunction);}
@Override
public synchronized Object compute(Object key, BiFunction<? super Object, ? super Object, ?> remappingFunction) {return delegate.compute(key, remappingFunction);}
@Override
public synchronized Object merge(Object key, Object value, BiFunction<? super Object, ? super Object, ?> remappingFunction) {return delegate.merge(key, value, remappingFunction);}
@Override
public synchronized Object clone() {
var delegateClone = (Properties) delegate.clone();
return new PropertiesDecorator(delegateClone);
}
}
@@ -0,0 +1,70 @@
package org.cryptomator.common;
import org.jetbrains.annotations.VisibleForTesting;
import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Pattern;
public class SubstitutingProperties extends PropertiesDecorator {
private static final Pattern TEMPLATE = Pattern.compile("@\\{(\\w+)}");
private final Map<String, String> env;
public SubstitutingProperties(Properties props, Map<String, String> systemEnvironment) {
super(props);
this.env = systemEnvironment;
}
@Override
public String getProperty(String key) {
var value = delegate.getProperty(key);
if (key.startsWith("cryptomator.") && value != null) {
return process(value);
} else {
return value;
}
}
@Override
public String getProperty(String key, String defaultValue) {
var result = getProperty(key);
return result != null ? result : defaultValue;
}
@VisibleForTesting
String process(String value) {
return TEMPLATE.matcher(value).replaceAll(match -> //
switch (match.group(1)) {
case "appdir" -> resolveFrom("APPDIR", Source.ENV);
case "appdata" -> resolveFrom("APPDATA", Source.ENV);
case "localappdata" -> resolveFrom("LOCALAPPDATA", Source.ENV);
case "userhome" -> resolveFrom("user.home", Source.PROPS);
default -> {
LoggerFactory.getLogger(SubstitutingProperties.class).warn("Unknown variable {} in property value {}.", match.group(), value);
yield match.group();
}
});
}
private String resolveFrom(String key, Source src) {
var val = switch (src) {
case ENV -> env.get(key);
case PROPS -> delegate.getProperty(key);
};
if (val == null) {
LoggerFactory.getLogger(SubstitutingProperties.class).warn("Variable {} used for substitution not found in {}. Replaced with empty string.", key, src);
return "";
} else {
return val.replace("\\", "\\\\");
}
}
private enum Source {
ENV,
PROPS;
}
}
@@ -23,14 +23,14 @@ public class KeychainModule {
@Singleton @Singleton
static ObjectExpression<KeychainAccessProvider> provideKeychainAccessProvider(Settings settings, List<KeychainAccessProvider> providers) { static ObjectExpression<KeychainAccessProvider> provideKeychainAccessProvider(Settings settings, List<KeychainAccessProvider> providers) {
return Bindings.createObjectBinding(() -> { return Bindings.createObjectBinding(() -> {
if (!settings.useKeychain().get()) { if (!settings.useKeychain.get()) {
return null; return null;
} }
var selectedProviderClass = settings.keychainProvider().get(); var selectedProviderClass = settings.keychainProvider.get();
var selectedProvider = providers.stream().filter(provider -> provider.getClass().getName().equals(selectedProviderClass)).findAny(); var selectedProvider = providers.stream().filter(provider -> provider.getClass().getName().equals(selectedProviderClass)).findAny();
var fallbackProvider = providers.stream().findFirst().orElse(null); var fallbackProvider = providers.stream().findFirst().orElse(null);
return selectedProvider.orElse(fallbackProvider); return selectedProvider.orElse(fallbackProvider);
}, settings.keychainProvider(), settings.useKeychain()); }, settings.keychainProvider, settings.useKeychain);
} }
} }
@@ -5,6 +5,8 @@ import org.cryptomator.integrations.common.OperatingSystem;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream; import java.util.stream.Stream;
import static org.cryptomator.integrations.common.OperatingSystem.Value.MAC; import static org.cryptomator.integrations.common.OperatingSystem.Value.MAC;
@@ -15,23 +17,25 @@ import static org.cryptomator.integrations.common.OperatingSystem.Value.WINDOWS;
@CheckAvailability @CheckAvailability
public final class GoogleDriveLocationPresetsProvider implements LocationPresetsProvider { public final class GoogleDriveLocationPresetsProvider implements LocationPresetsProvider {
private static final Path LOCATION1 = LocationPresetsProvider.resolveLocation("~/GoogleDrive"); private static final List<Path> LOCATIONS = Arrays.asList( //
private static final Path LOCATION2 = LocationPresetsProvider.resolveLocation("~/GoogleDrive/My Drive"); LocationPresetsProvider.resolveLocation("~/GoogleDrive/My Drive"), //
LocationPresetsProvider.resolveLocation("~/Google Drive/My Drive"), //
LocationPresetsProvider.resolveLocation("~/GoogleDrive"), //
LocationPresetsProvider.resolveLocation("~/Google Drive") //
);
@CheckAvailability @CheckAvailability
public static boolean isPresent() { public static boolean isPresent() {
return Files.isDirectory(LOCATION1) || Files.isDirectory(LOCATION2); return LOCATIONS.stream().anyMatch(Files::isDirectory);
} }
@Override @Override
public Stream<LocationPreset> getLocations() { public Stream<LocationPreset> getLocations() {
if(Files.isDirectory(LOCATION1)) { return LOCATIONS.stream() //
return Stream.of(new LocationPreset("Google Drive", LOCATION1)); .filter(Files::isDirectory) //
} else if(Files.isDirectory(LOCATION2)) { .map(location -> new LocationPreset("Google Drive", location)) //
return Stream.of(new LocationPreset("Google Drive", LOCATION2)); .findFirst() //
} else { .stream();
return Stream.of();
}
} }
} }
@@ -0,0 +1,30 @@
package org.cryptomator.common.locationpresets;
import org.cryptomator.integrations.common.CheckAvailability;
import org.cryptomator.integrations.common.OperatingSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
import static org.cryptomator.integrations.common.OperatingSystem.Value.MAC;
import static org.cryptomator.integrations.common.OperatingSystem.Value.WINDOWS;
@OperatingSystem(WINDOWS)
@OperatingSystem(MAC)
@CheckAvailability
public final class LeitzcloudLocationPresetsProvider implements LocationPresetsProvider {
private static final Path LOCATION = LocationPresetsProvider.resolveLocation("~/leitzcloud");
@CheckAvailability
public static boolean isPresent() {
return Files.isDirectory(LOCATION);
}
@Override
public Stream<LocationPreset> getLocations() {
return Stream.of(new LocationPreset("leitzcloud", LOCATION));
}
}
@@ -5,6 +5,8 @@ import org.cryptomator.integrations.common.OperatingSystem;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream; import java.util.stream.Stream;
import static org.cryptomator.integrations.common.OperatingSystem.Value.MAC; import static org.cryptomator.integrations.common.OperatingSystem.Value.MAC;
@@ -15,16 +17,23 @@ import static org.cryptomator.integrations.common.OperatingSystem.Value.WINDOWS;
@CheckAvailability @CheckAvailability
public final class PCloudLocationPresetsProvider implements LocationPresetsProvider { public final class PCloudLocationPresetsProvider implements LocationPresetsProvider {
private static final List<Path> LOCATIONS = Arrays.asList( //
private static final Path LOCATION = LocationPresetsProvider.resolveLocation("~/pCloudDrive"); LocationPresetsProvider.resolveLocation("~/pCloudDrive"), //
LocationPresetsProvider.resolveLocation("~/pCloud Drive") //
);
@CheckAvailability @CheckAvailability
public static boolean isPresent() { public static boolean isPresent() {
return Files.isDirectory(LOCATION); return LOCATIONS.stream().anyMatch(Files::isDirectory);
} }
@Override @Override
public Stream<LocationPreset> getLocations() { public Stream<LocationPreset> getLocations() {
return Stream.of(new LocationPreset("pCloud", LOCATION)); return LOCATIONS.stream() //
.filter(Files::isDirectory) //
.map(location -> new LocationPreset("pCloud", location)) //
.findFirst() //
.stream();
} }
} }
@@ -37,7 +37,7 @@ public class MountModule {
static ObservableValue<ActualMountService> provideMountService(Settings settings, List<MountService> serviceImpls, @Named("FUPFMS") AtomicReference<MountService> fupfms) { static ObservableValue<ActualMountService> provideMountService(Settings settings, List<MountService> serviceImpls, @Named("FUPFMS") AtomicReference<MountService> fupfms) {
var fallbackProvider = serviceImpls.stream().findFirst().orElse(null); var fallbackProvider = serviceImpls.stream().findFirst().orElse(null);
var observableMountService = ObservableUtil.mapWithDefault(settings.mountService(), // var observableMountService = ObservableUtil.mapWithDefault(settings.mountService, //
desiredServiceImpl -> { // desiredServiceImpl -> { //
var serviceFromSettings = serviceImpls.stream().filter(serviceImpl -> serviceImpl.getClass().getName().equals(desiredServiceImpl)).findAny(); // var serviceFromSettings = serviceImpls.stream().filter(serviceImpl -> serviceImpl.getClass().getName().equals(desiredServiceImpl)).findAny(); //
var targetedService = serviceFromSettings.orElse(fallbackProvider); var targetedService = serviceFromSettings.orElse(fallbackProvider);
@@ -0,0 +1,8 @@
package org.cryptomator.common.mount;
public class MountPointInUseException extends IllegalMountPointException {
public MountPointInUseException(String msg) {
super(msg);
}
}
@@ -54,19 +54,19 @@ public class Mounter {
switch (capability) { switch (capability) {
case FILE_SYSTEM_NAME -> builder.setFileSystemName("cryptoFs"); case FILE_SYSTEM_NAME -> builder.setFileSystemName("cryptoFs");
case LOOPBACK_PORT -> case LOOPBACK_PORT ->
builder.setLoopbackPort(settings.port().get()); //TODO: move port from settings to vaultsettings (see https://github.com/cryptomator/cryptomator/tree/feature/mount-setting-per-vault) builder.setLoopbackPort(settings.port.get()); //TODO: move port from settings to vaultsettings (see https://github.com/cryptomator/cryptomator/tree/feature/mount-setting-per-vault)
case LOOPBACK_HOST_NAME -> env.getLoopbackAlias().ifPresent(builder::setLoopbackHostName); case LOOPBACK_HOST_NAME -> env.getLoopbackAlias().ifPresent(builder::setLoopbackHostName);
case READ_ONLY -> builder.setReadOnly(vaultSettings.usesReadOnlyMode().get()); case READ_ONLY -> builder.setReadOnly(vaultSettings.usesReadOnlyMode.get());
case MOUNT_FLAGS -> { case MOUNT_FLAGS -> {
var mountFlags = vaultSettings.mountFlags().get(); var mountFlags = vaultSettings.mountFlags.get();
if (mountFlags == null || mountFlags.isBlank()) { if (mountFlags == null || mountFlags.isBlank()) {
builder.setMountFlags(service.getDefaultMountFlags()); builder.setMountFlags(service.getDefaultMountFlags());
} else { } else {
builder.setMountFlags(mountFlags); builder.setMountFlags(mountFlags);
} }
} }
case VOLUME_ID -> builder.setVolumeId(vaultSettings.getId()); case VOLUME_ID -> builder.setVolumeId(vaultSettings.id);
case VOLUME_NAME -> builder.setVolumeName(vaultSettings.mountName().get()); case VOLUME_NAME -> builder.setVolumeName(vaultSettings.mountName.get());
} }
} }
@@ -75,7 +75,7 @@ public class Mounter {
private Runnable prepareMountPoint() throws IOException { private Runnable prepareMountPoint() throws IOException {
Runnable cleanup = () -> {}; Runnable cleanup = () -> {};
var userChosenMountPoint = vaultSettings.getMountPoint(); var userChosenMountPoint = vaultSettings.mountPoint.get();
var defaultMountPointBase = env.getMountPointsDir().orElseThrow(); var defaultMountPointBase = env.getMountPointsDir().orElseThrow();
var canMountToDriveLetter = service.hasCapability(MOUNT_AS_DRIVE_LETTER); var canMountToDriveLetter = service.hasCapability(MOUNT_AS_DRIVE_LETTER);
var canMountToParent = service.hasCapability(MOUNT_WITHIN_EXISTING_PARENT); var canMountToParent = service.hasCapability(MOUNT_WITHIN_EXISTING_PARENT);
@@ -91,13 +91,17 @@ public class Mounter {
Files.createDirectories(defaultMountPointBase); Files.createDirectories(defaultMountPointBase);
builder.setMountpoint(defaultMountPointBase); builder.setMountpoint(defaultMountPointBase);
} else if (canMountToDir) { } else if (canMountToDir) {
var mountPoint = defaultMountPointBase.resolve(vaultSettings.mountName().get()); var mountPoint = defaultMountPointBase.resolve(vaultSettings.mountName.get());
Files.createDirectories(mountPoint); Files.createDirectories(mountPoint);
builder.setMountpoint(mountPoint); builder.setMountpoint(mountPoint);
} }
} else { } else {
var mpIsDriveLetter = userChosenMountPoint.toString().matches("[A-Z]:\\\\"); var mpIsDriveLetter = userChosenMountPoint.toString().matches("[A-Z]:\\\\");
if (!mpIsDriveLetter && canMountToParent && !canMountToDir) { if (mpIsDriveLetter) {
if (driveLetters.getOccupied().contains(userChosenMountPoint)) {
throw new MountPointInUseException(userChosenMountPoint.toString());
}
} else if (canMountToParent && !canMountToDir) {
MountWithinParentUtil.prepareParentNoMountPoint(userChosenMountPoint); MountWithinParentUtil.prepareParentNoMountPoint(userChosenMountPoint);
cleanup = () -> { cleanup = () -> {
MountWithinParentUtil.cleanup(userChosenMountPoint); MountWithinParentUtil.cleanup(userChosenMountPoint);
@@ -10,6 +10,8 @@ package org.cryptomator.common.settings;
import org.apache.commons.lang3.SystemUtils; import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Environment; import org.cryptomator.common.Environment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javafx.beans.Observable; import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
@@ -27,59 +29,88 @@ import java.util.function.Consumer;
public class Settings { public class Settings {
public static final int MIN_PORT = 1024; private static final Logger LOG = LoggerFactory.getLogger(Settings.class);
public static final int MAX_PORT = 65535;
public static final boolean DEFAULT_ASKED_FOR_UPDATE_CHECK = false; static final boolean DEFAULT_ASKED_FOR_UPDATE_CHECK = false;
public static final boolean DEFAULT_CHECK_FOR_UPDATES = false; static final boolean DEFAULT_CHECK_FOR_UPDATES = false;
public static final boolean DEFAULT_START_HIDDEN = false; static final boolean DEFAULT_START_HIDDEN = false;
public static final boolean DEFAULT_AUTO_CLOSE_VAULTS = false; static final boolean DEFAULT_AUTO_CLOSE_VAULTS = false;
public static final boolean DEFAULT_USE_KEYCHAIN = true; static final boolean DEFAULT_USE_KEYCHAIN = true;
public static final int DEFAULT_PORT = 42427; static final int DEFAULT_PORT = 42427;
public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3; static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
public static final boolean DEFAULT_DEBUG_MODE = false; static final boolean DEFAULT_DEBUG_MODE = false;
public static final UiTheme DEFAULT_THEME = UiTheme.LIGHT; static final UiTheme DEFAULT_THEME = UiTheme.LIGHT;
@Deprecated // to be changed to "whatever is available" eventually @Deprecated // to be changed to "whatever is available" eventually
public static final String DEFAULT_KEYCHAIN_PROVIDER = SystemUtils.IS_OS_WINDOWS ? "org.cryptomator.windows.keychain.WindowsProtectedKeychainAccess" : SystemUtils.IS_OS_MAC ? "org.cryptomator.macos.keychain.MacSystemKeychainAccess" : "org.cryptomator.linux.keychain.SecretServiceKeychainAccess"; static final String DEFAULT_KEYCHAIN_PROVIDER = SystemUtils.IS_OS_WINDOWS ? "org.cryptomator.windows.keychain.WindowsProtectedKeychainAccess" : SystemUtils.IS_OS_MAC ? "org.cryptomator.macos.keychain.MacSystemKeychainAccess" : "org.cryptomator.linux.keychain.SecretServiceKeychainAccess";
public static final NodeOrientation DEFAULT_USER_INTERFACE_ORIENTATION = NodeOrientation.LEFT_TO_RIGHT; static final String DEFAULT_USER_INTERFACE_ORIENTATION = NodeOrientation.LEFT_TO_RIGHT.name();
public static final String DEFAULT_LICENSE_KEY = ""; static final boolean DEFAULT_SHOW_MINIMIZE_BUTTON = false;
public static final boolean DEFAULT_SHOW_MINIMIZE_BUTTON = false; static final String DEFAULT_LAST_UPDATE_CHECK = "2000-01-01";
public static final String DEFAULT_DISPLAY_CONFIGURATION = "";
public static final String DEFAULT_LANGUAGE = null;
private final ObservableList<VaultSettings> directories = FXCollections.observableArrayList(VaultSettings::observables);
private final BooleanProperty askedForUpdateCheck = new SimpleBooleanProperty(DEFAULT_ASKED_FOR_UPDATE_CHECK);
private final BooleanProperty checkForUpdates = new SimpleBooleanProperty(DEFAULT_CHECK_FOR_UPDATES);
private final BooleanProperty startHidden = new SimpleBooleanProperty(DEFAULT_START_HIDDEN);
private final BooleanProperty autoCloseVaults = new SimpleBooleanProperty(DEFAULT_AUTO_CLOSE_VAULTS);
private final BooleanProperty useKeychain = new SimpleBooleanProperty(DEFAULT_USE_KEYCHAIN);
private final IntegerProperty port = new SimpleIntegerProperty(DEFAULT_PORT);
private final IntegerProperty numTrayNotifications = new SimpleIntegerProperty(DEFAULT_NUM_TRAY_NOTIFICATIONS);
private final BooleanProperty debugMode = new SimpleBooleanProperty(DEFAULT_DEBUG_MODE);
private final ObjectProperty<UiTheme> theme = new SimpleObjectProperty<>(DEFAULT_THEME);
private final ObjectProperty<String> keychainProvider = new SimpleObjectProperty<>(DEFAULT_KEYCHAIN_PROVIDER);
private final ObjectProperty<NodeOrientation> userInterfaceOrientation = new SimpleObjectProperty<>(DEFAULT_USER_INTERFACE_ORIENTATION);
private final StringProperty licenseKey = new SimpleStringProperty(DEFAULT_LICENSE_KEY);
private final BooleanProperty showMinimizeButton = new SimpleBooleanProperty(DEFAULT_SHOW_MINIMIZE_BUTTON);
private final BooleanProperty showTrayIcon;
private final IntegerProperty windowXPosition = new SimpleIntegerProperty();
private final IntegerProperty windowYPosition = new SimpleIntegerProperty();
private final IntegerProperty windowWidth = new SimpleIntegerProperty();
private final IntegerProperty windowHeight = new SimpleIntegerProperty();
private final ObjectProperty<String> displayConfiguration = new SimpleObjectProperty<>(DEFAULT_DISPLAY_CONFIGURATION);
private final StringProperty language = new SimpleStringProperty(DEFAULT_LANGUAGE);
private final StringProperty mountService = new SimpleStringProperty();
public final ObservableList<VaultSettings> directories;
public final BooleanProperty askedForUpdateCheck;
public final BooleanProperty checkForUpdates;
public final BooleanProperty startHidden;
public final BooleanProperty autoCloseVaults;
public final BooleanProperty useKeychain;
public final IntegerProperty port;
public final IntegerProperty numTrayNotifications;
public final BooleanProperty debugMode;
public final ObjectProperty<UiTheme> theme;
public final StringProperty keychainProvider;
public final ObjectProperty<NodeOrientation> userInterfaceOrientation;
public final StringProperty licenseKey;
public final BooleanProperty showMinimizeButton;
public final BooleanProperty showTrayIcon;
public final IntegerProperty windowXPosition;
public final IntegerProperty windowYPosition;
public final IntegerProperty windowWidth;
public final IntegerProperty windowHeight;
public final StringProperty displayConfiguration;
public final StringProperty language;
public final StringProperty mountService;
public final StringProperty lastUpdateCheck;
private Consumer<Settings> saveCmd; private Consumer<Settings> saveCmd;
public static Settings create(Environment env) {
var defaults = new SettingsJson();
defaults.showTrayIcon = env.showTrayIcon();
return new Settings(defaults);
}
/** /**
* Package-private constructor; use {@link SettingsProvider}. * Recreate settings from json
*
* @param json The parsed settings.json
*/ */
Settings(Environment env) { Settings(SettingsJson json) {
this.showTrayIcon = new SimpleBooleanProperty(env.showTrayIcon()); this.directories = FXCollections.observableArrayList(VaultSettings::observables);
this.askedForUpdateCheck = new SimpleBooleanProperty(this, "askedForUpdateCheck", json.askedForUpdateCheck);
this.checkForUpdates = new SimpleBooleanProperty(this, "checkForUpdates", json.checkForUpdatesEnabled);
this.startHidden = new SimpleBooleanProperty(this, "startHidden", json.startHidden);
this.autoCloseVaults = new SimpleBooleanProperty(this, "autoCloseVaults", json.autoCloseVaults);
this.useKeychain = new SimpleBooleanProperty(this, "useKeychain", json.useKeychain);
this.port = new SimpleIntegerProperty(this, "webDavPort", json.port);
this.numTrayNotifications = new SimpleIntegerProperty(this, "numTrayNotifications", json.numTrayNotifications);
this.debugMode = new SimpleBooleanProperty(this, "debugMode", json.debugMode);
this.theme = new SimpleObjectProperty<>(this, "theme", json.theme);
this.keychainProvider = new SimpleStringProperty(this, "keychainProvider", json.keychainProvider);
this.userInterfaceOrientation = new SimpleObjectProperty<>(this, "userInterfaceOrientation", parseEnum(json.uiOrientation, NodeOrientation.class, NodeOrientation.LEFT_TO_RIGHT));
this.licenseKey = new SimpleStringProperty(this, "licenseKey", json.licenseKey);
this.showMinimizeButton = new SimpleBooleanProperty(this, "showMinimizeButton", json.showMinimizeButton);
this.showTrayIcon = new SimpleBooleanProperty(this, "showTrayIcon", json.showTrayIcon);
this.windowXPosition = new SimpleIntegerProperty(this, "windowXPosition", json.windowXPosition);
this.windowYPosition = new SimpleIntegerProperty(this, "windowYPosition", json.windowYPosition);
this.windowWidth = new SimpleIntegerProperty(this, "windowWidth", json.windowWidth);
this.windowHeight = new SimpleIntegerProperty(this, "windowHeight", json.windowHeight);
this.displayConfiguration = new SimpleStringProperty(this, "displayConfiguration", json.displayConfiguration);
this.language = new SimpleStringProperty(this, "language", json.language);
this.mountService = new SimpleStringProperty(this, "mountService", json.mountService);
this.lastUpdateCheck = new SimpleStringProperty(this, "lastUpdateCheck", json.lastUpdateCheck);
this.directories.addAll(json.directories.stream().map(VaultSettings::new).toList());
migrateLegacySettings(json);
directories.addListener(this::somethingChanged); directories.addListener(this::somethingChanged);
askedForUpdateCheck.addListener(this::somethingChanged); askedForUpdateCheck.addListener(this::somethingChanged);
@@ -103,8 +134,76 @@ public class Settings {
displayConfiguration.addListener(this::somethingChanged); displayConfiguration.addListener(this::somethingChanged);
language.addListener(this::somethingChanged); language.addListener(this::somethingChanged);
mountService.addListener(this::somethingChanged); mountService.addListener(this::somethingChanged);
lastUpdateCheck.addListener(this::somethingChanged);
} }
@SuppressWarnings("deprecation")
private void migrateLegacySettings(SettingsJson json) {
// implicit migration of 1.6.x legacy setting "preferredVolumeImpl":
if (this.mountService.get() == null && json.preferredVolumeImpl != null) {
this.mountService.set(switch (json.preferredVolumeImpl) {
case "Dokany" -> "org.cryptomator.frontend.dokany.mount.DokanyMountProvider";
case "FUSE" -> {
if (SystemUtils.IS_OS_WINDOWS) {
yield "org.cryptomator.frontend.fuse.mount.WinFspNetworkMountProvider";
} else if (SystemUtils.IS_OS_MAC) {
yield "org.cryptomator.frontend.fuse.mount.MacFuseMountProvider";
} else {
yield "org.cryptomator.frontend.fuse.mount.LinuxFuseMountProvider";
}
}
default -> {
if (SystemUtils.IS_OS_WINDOWS) {
yield "org.cryptomator.frontend.webdav.mount.WindowsMounter";
} else if (SystemUtils.IS_OS_MAC) {
yield "org.cryptomator.frontend.webdav.mount.MacAppleScriptMounter";
} else {
yield "org.cryptomator.frontend.webdav.mount.LinuxGioMounter";
}
}
});
}
}
SettingsJson serialized() {
var json = new SettingsJson();
json.directories = directories.stream().map(VaultSettings::serialized).toList();
json.askedForUpdateCheck = askedForUpdateCheck.get();
json.checkForUpdatesEnabled = checkForUpdates.get();
json.startHidden = startHidden.get();
json.autoCloseVaults = autoCloseVaults.get();
json.useKeychain = useKeychain.get();
json.port = port.get();
json.numTrayNotifications = numTrayNotifications.get();
json.debugMode = debugMode.get();
json.theme = theme.get();
json.keychainProvider = keychainProvider.get();
json.uiOrientation = userInterfaceOrientation.get().name();
json.licenseKey = licenseKey.get();
json.showMinimizeButton = showMinimizeButton.get();
json.showTrayIcon = showTrayIcon.get();
json.windowXPosition = windowXPosition.get();
json.windowYPosition = windowYPosition.get();
json.windowWidth = windowWidth.get();
json.windowHeight = windowHeight.get();
json.displayConfiguration = displayConfiguration.get();
json.language = language.get();
json.mountService = mountService.get();
json.lastUpdateCheck = lastUpdateCheck.get();
return json;
}
private <E extends Enum<E>> E parseEnum(String value, Class<E> clazz, E defaultValue) {
try {
return Enum.valueOf(clazz, value.toUpperCase());
} catch (IllegalArgumentException e) {
LOG.warn("No value {}.{}. Defaulting to {}.", clazz.getSimpleName(), value, defaultValue);
return defaultValue;
}
}
// TODO rename to setChangeListener
void setSaveCmd(Consumer<Settings> saveCmd) { void setSaveCmd(Consumer<Settings> saveCmd) {
this.saveCmd = saveCmd; this.saveCmd = saveCmd;
} }
@@ -119,90 +218,4 @@ public class Settings {
} }
} }
/* Getter/Setter */
public ObservableList<VaultSettings> getDirectories() {
return directories;
}
public BooleanProperty askedForUpdateCheck() {
return askedForUpdateCheck;
}
public BooleanProperty checkForUpdates() {
return checkForUpdates;
}
public BooleanProperty startHidden() {
return startHidden;
}
public BooleanProperty autoCloseVaults() {
return autoCloseVaults;
}
public BooleanProperty useKeychain() { return useKeychain; }
public IntegerProperty port() {
return port;
}
public IntegerProperty numTrayNotifications() {
return numTrayNotifications;
}
public BooleanProperty debugMode() {
return debugMode;
}
public StringProperty mountService() {
return mountService;
}
public ObjectProperty<UiTheme> theme() {
return theme;
}
public ObjectProperty<String> keychainProvider() {return keychainProvider;}
public ObjectProperty<NodeOrientation> userInterfaceOrientation() {
return userInterfaceOrientation;
}
public StringProperty licenseKey() {
return licenseKey;
}
public BooleanProperty showMinimizeButton() {
return showMinimizeButton;
}
public BooleanProperty showTrayIcon() {
return showTrayIcon;
}
public IntegerProperty windowXPositionProperty() {
return windowXPosition;
}
public IntegerProperty windowYPositionProperty() {
return windowYPosition;
}
public IntegerProperty windowWidthProperty() {
return windowWidth;
}
public IntegerProperty windowHeightProperty() {
return windowHeight;
}
public ObjectProperty<String> displayConfigurationProperty() {
return displayConfiguration;
}
public StringProperty languageProperty() {
return language;
}
} }
@@ -0,0 +1,89 @@
package org.cryptomator.common.settings;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
class SettingsJson {
@JsonProperty("directories")
List<VaultSettingsJson> directories = List.of();
@JsonProperty("writtenByVersion")
String writtenByVersion;
@JsonProperty("askedForUpdateCheck")
boolean askedForUpdateCheck = Settings.DEFAULT_ASKED_FOR_UPDATE_CHECK;
@JsonProperty("autoCloseVaults")
boolean autoCloseVaults = Settings.DEFAULT_AUTO_CLOSE_VAULTS;
@JsonProperty("checkForUpdatesEnabled")
boolean checkForUpdatesEnabled = Settings.DEFAULT_CHECK_FOR_UPDATES;
@JsonProperty("debugMode")
boolean debugMode = Settings.DEFAULT_DEBUG_MODE;
@JsonProperty("theme")
UiTheme theme = Settings.DEFAULT_THEME;
@JsonProperty("displayConfiguration")
String displayConfiguration;
@JsonProperty("keychainProvider")
String keychainProvider = Settings.DEFAULT_KEYCHAIN_PROVIDER;
@JsonProperty("language")
String language;
@JsonProperty("licenseKey")
String licenseKey;
@JsonProperty("mountService")
String mountService;
@JsonProperty("numTrayNotifications")
int numTrayNotifications = Settings.DEFAULT_NUM_TRAY_NOTIFICATIONS;
@JsonProperty("port")
int port = Settings.DEFAULT_PORT;
@JsonProperty("showMinimizeButton")
boolean showMinimizeButton = Settings.DEFAULT_SHOW_MINIMIZE_BUTTON;
@JsonProperty("showTrayIcon")
boolean showTrayIcon;
@JsonProperty("startHidden")
boolean startHidden = Settings.DEFAULT_START_HIDDEN;
@JsonProperty("uiOrientation")
String uiOrientation = Settings.DEFAULT_USER_INTERFACE_ORIENTATION;
@JsonProperty("useKeychain")
boolean useKeychain = Settings.DEFAULT_USE_KEYCHAIN;
@JsonProperty("windowHeight")
int windowHeight;
@JsonProperty("windowWidth")
int windowWidth;
@JsonProperty("windowXPosition")
int windowXPosition;
@JsonProperty("windowYPosition")
int windowYPosition;
@Deprecated(since = "1.7.0")
@JsonProperty(value = "preferredVolumeImpl", access = JsonProperty.Access.WRITE_ONLY) // WRITE_ONLY means value is "written" into the java object during deserialization. Upvote this: https://github.com/FasterXML/jackson-annotations/issues/233
String preferredVolumeImpl;
@JsonProperty("lastUpdateCheck")
String lastUpdateCheck = Settings.DEFAULT_LAST_UPDATE_CHECK;
}
@@ -1,183 +0,0 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.common.settings;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Environment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import javafx.geometry.NodeOrientation;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@Singleton
public class SettingsJsonAdapter extends TypeAdapter<Settings> {
private static final Logger LOG = LoggerFactory.getLogger(SettingsJsonAdapter.class);
private final VaultSettingsJsonAdapter vaultSettingsJsonAdapter = new VaultSettingsJsonAdapter();
private final Environment env;
@Inject
public SettingsJsonAdapter(Environment env) {
this.env = env;
}
@Override
public void write(JsonWriter out, Settings value) throws IOException {
out.beginObject();
out.name("writtenByVersion").value(env.getAppVersion() + env.getBuildNumber().map("-"::concat).orElse(""));
out.name("directories");
writeVaultSettingsArray(out, value.getDirectories());
out.name("askedForUpdateCheck").value(value.askedForUpdateCheck().get());
out.name("autoCloseVaults").value(value.autoCloseVaults().get());
out.name("checkForUpdatesEnabled").value(value.checkForUpdates().get());
out.name("debugMode").value(value.debugMode().get());
out.name("displayConfiguration").value((value.displayConfigurationProperty().get()));
out.name("keychainProvider").value(value.keychainProvider().get());
out.name("language").value((value.languageProperty().get()));
out.name("licenseKey").value(value.licenseKey().get());
out.name("mountService").value(value.mountService().get());
out.name("numTrayNotifications").value(value.numTrayNotifications().get());
out.name("port").value(value.port().get());
out.name("showMinimizeButton").value(value.showMinimizeButton().get());
out.name("showTrayIcon").value(value.showTrayIcon().get());
out.name("startHidden").value(value.startHidden().get());
out.name("theme").value(value.theme().get().name());
out.name("uiOrientation").value(value.userInterfaceOrientation().get().name());
out.name("useKeychain").value(value.useKeychain().get());
out.name("windowHeight").value((value.windowHeightProperty().get()));
out.name("windowWidth").value((value.windowWidthProperty().get()));
out.name("windowXPosition").value((value.windowXPositionProperty().get()));
out.name("windowYPosition").value((value.windowYPositionProperty().get()));
out.endObject();
}
private void writeVaultSettingsArray(JsonWriter out, Iterable<VaultSettings> vaultSettings) throws IOException {
out.beginArray();
for (VaultSettings value : vaultSettings) {
vaultSettingsJsonAdapter.write(out, value);
}
out.endArray();
}
@Override
public Settings read(JsonReader in) throws IOException {
Settings settings = new Settings(env);
//1.6.x legacy
String volumeImpl = null;
//legacy end
in.beginObject();
while (in.hasNext()) {
String name = in.nextName();
switch (name) {
case "writtenByVersion" -> in.skipValue(); //noop
case "directories" -> settings.getDirectories().addAll(readVaultSettingsArray(in));
case "askedForUpdateCheck" -> settings.askedForUpdateCheck().set(in.nextBoolean());
case "autoCloseVaults" -> settings.autoCloseVaults().set(in.nextBoolean());
case "checkForUpdatesEnabled" -> settings.checkForUpdates().set(in.nextBoolean());
case "debugMode" -> settings.debugMode().set(in.nextBoolean());
case "displayConfiguration" -> settings.displayConfigurationProperty().set(in.nextString());
case "keychainProvider" -> settings.keychainProvider().set(in.nextString());
case "language" -> settings.languageProperty().set(in.nextString());
case "licenseKey" -> settings.licenseKey().set(in.nextString());
case "mountService" -> {
var token = in.peek();
if (JsonToken.STRING == token) {
settings.mountService().set(in.nextString());
}
}
case "numTrayNotifications" -> settings.numTrayNotifications().set(in.nextInt());
case "port" -> settings.port().set(in.nextInt());
case "showMinimizeButton" -> settings.showMinimizeButton().set(in.nextBoolean());
case "showTrayIcon" -> settings.showTrayIcon().set(in.nextBoolean());
case "startHidden" -> settings.startHidden().set(in.nextBoolean());
case "theme" -> settings.theme().set(parseUiTheme(in.nextString()));
case "uiOrientation" -> settings.userInterfaceOrientation().set(parseUiOrientation(in.nextString()));
case "useKeychain" -> settings.useKeychain().set(in.nextBoolean());
case "windowHeight" -> settings.windowHeightProperty().set(in.nextInt());
case "windowWidth" -> settings.windowWidthProperty().set(in.nextInt());
case "windowXPosition" -> settings.windowXPositionProperty().set(in.nextInt());
case "windowYPosition" -> settings.windowYPositionProperty().set(in.nextInt());
//1.6.x legacy
case "preferredVolumeImpl" -> volumeImpl = in.nextString();
//legacy end
default -> {
LOG.warn("Unsupported vault setting found in JSON: {}", name);
in.skipValue();
}
}
}
in.endObject();
//1.6.x legacy
if (volumeImpl != null) {
settings.mountService().set(convertLegacyVolumeImplToMountService(volumeImpl));
}
//legacy end
return settings;
}
private String convertLegacyVolumeImplToMountService(String volumeImpl) {
if (volumeImpl.equals("Dokany")) {
return "org.cryptomator.frontend.dokany.mount.DokanyMountProvider";
} else if (volumeImpl.equals("FUSE")) {
if (SystemUtils.IS_OS_WINDOWS) {
return "org.cryptomator.frontend.fuse.mount.WinFspNetworkMountProvider";
} else if (SystemUtils.IS_OS_MAC) {
return "org.cryptomator.frontend.fuse.mount.MacFuseMountProvider";
} else {
return "org.cryptomator.frontend.fuse.mount.LinuxFuseMountProvider";
}
} else {
if (SystemUtils.IS_OS_WINDOWS) {
return "org.cryptomator.frontend.webdav.mount.WindowsMounter";
} else if (SystemUtils.IS_OS_MAC) {
return "org.cryptomator.frontend.webdav.mount.MacAppleScriptMounter";
} else {
return "org.cryptomator.frontend.webdav.mount.LinuxGioMounter";
}
}
}
private UiTheme parseUiTheme(String uiThemeName) {
try {
return UiTheme.valueOf(uiThemeName.toUpperCase());
} catch (IllegalArgumentException e) {
LOG.warn("Invalid ui theme {}. Defaulting to {}.", uiThemeName, Settings.DEFAULT_THEME);
return Settings.DEFAULT_THEME;
}
}
private NodeOrientation parseUiOrientation(String uiOrientationName) {
try {
return NodeOrientation.valueOf(uiOrientationName.toUpperCase());
} catch (IllegalArgumentException e) {
LOG.warn("Invalid ui orientation {}. Defaulting to {}.", uiOrientationName, Settings.DEFAULT_USER_INTERFACE_ORIENTATION);
return Settings.DEFAULT_USER_INTERFACE_ORIENTATION;
}
}
private List<VaultSettings> readVaultSettingsArray(JsonReader in) throws IOException {
List<VaultSettings> result = new ArrayList<>();
in.beginArray();
while (!JsonToken.END_ARRAY.equals(in.peek())) {
result.add(vaultSettingsJsonAdapter.read(in));
}
in.endArray();
return result;
}
}
@@ -8,12 +8,9 @@
*******************************************************************************/ *******************************************************************************/
package org.cryptomator.common.settings; package org.cryptomator.common.settings;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Suppliers; import com.google.common.base.Suppliers;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import org.cryptomator.common.Environment; import org.cryptomator.common.Environment;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -22,12 +19,7 @@ import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.NoSuchFileException; import java.nio.file.NoSuchFileException;
import java.nio.file.Path; import java.nio.file.Path;
@@ -44,6 +36,7 @@ import java.util.stream.Stream;
@Singleton @Singleton
public class SettingsProvider implements Supplier<Settings> { public class SettingsProvider implements Supplier<Settings> {
private static final ObjectMapper JSON = new ObjectMapper().setDefaultLeniency(true);
private static final Logger LOG = LoggerFactory.getLogger(SettingsProvider.class); private static final Logger LOG = LoggerFactory.getLogger(SettingsProvider.class);
private static final long SAVE_DELAY_MS = 1000; private static final long SAVE_DELAY_MS = 1000;
@@ -51,16 +44,11 @@ public class SettingsProvider implements Supplier<Settings> {
private final Supplier<Settings> settings = Suppliers.memoize(this::load); private final Supplier<Settings> settings = Suppliers.memoize(this::load);
private final Environment env; private final Environment env;
private final ScheduledExecutorService scheduler; private final ScheduledExecutorService scheduler;
private final Gson gson;
@Inject @Inject
public SettingsProvider(SettingsJsonAdapter settingsJsonAdapter, Environment env, ScheduledExecutorService scheduler) { public SettingsProvider(Environment env, ScheduledExecutorService scheduler) {
this.env = env; this.env = env;
this.scheduler = scheduler; this.scheduler = scheduler;
this.gson = new GsonBuilder() //
.setPrettyPrinting().setLenient().disableHtmlEscaping() //
.registerTypeAdapter(Settings.class, settingsJsonAdapter) //
.create();
} }
@Override @Override
@@ -69,28 +57,25 @@ public class SettingsProvider implements Supplier<Settings> {
} }
private Settings load() { private Settings load() {
Settings settings = env.getSettingsPath().flatMap(this::tryLoad).findFirst().orElse(new Settings(env)); Settings settings = env.getSettingsPath().flatMap(this::tryLoad).findFirst().orElseGet(() -> Settings.create(env));
settings.setSaveCmd(this::scheduleSave); settings.setSaveCmd(this::scheduleSave);
return settings; return settings;
} }
private Stream<Settings> tryLoad(Path path) { private Stream<Settings> tryLoad(Path path) {
LOG.debug("Attempting to load settings from {}", path); LOG.debug("Attempting to load settings from {}", path);
try (InputStream in = Files.newInputStream(path, StandardOpenOption.READ); // try (InputStream in = Files.newInputStream(path, StandardOpenOption.READ)) {
Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) { var json = JSON.reader().readValue(in, SettingsJson.class);
JsonElement json = JsonParser.parseReader(reader); LOG.info("Settings loaded from {}", path);
if (json.isJsonObject()) { var settings = new Settings(json);
Settings settings = gson.fromJson(json, Settings.class); return Stream.of(settings);
LOG.info("Settings loaded from {}", path); } catch (JacksonException e) {
return Stream.of(settings); LOG.warn("Failed to parse json file {}", path, e);
} else { return Stream.empty();
LOG.warn("Invalid json file {}", path);
return Stream.empty();
}
} catch (NoSuchFileException e) { } catch (NoSuchFileException e) {
return Stream.empty(); return Stream.empty();
} catch (IOException | JsonParseException e) { } catch (IOException e) {
LOG.warn("Exception while loading settings from " + path, e); LOG.warn("Failed to load json file {}", path, e);
return Stream.empty(); return Stream.empty();
} }
} }
@@ -116,13 +101,14 @@ public class SettingsProvider implements Supplier<Settings> {
try { try {
Files.createDirectories(settingsPath.getParent()); Files.createDirectories(settingsPath.getParent());
Path tmpPath = settingsPath.resolveSibling(settingsPath.getFileName().toString() + ".tmp"); Path tmpPath = settingsPath.resolveSibling(settingsPath.getFileName().toString() + ".tmp");
try (OutputStream out = Files.newOutputStream(tmpPath, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE); // try (OutputStream out = Files.newOutputStream(tmpPath, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) {
Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)) { var jsonObj = settings.serialized();
gson.toJson(settings, writer); jsonObj.writtenByVersion = env.getAppVersion() + env.getBuildNumber().map("-"::concat).orElse("");
JSON.writerWithDefaultPrettyPrinter().writeValue(out, jsonObj);
} }
Files.move(tmpPath, settingsPath, StandardCopyOption.REPLACE_EXISTING); Files.move(tmpPath, settingsPath, StandardCopyOption.REPLACE_EXISTING);
LOG.info("Settings saved to {}", settingsPath); LOG.info("Settings saved to {}", settingsPath);
} catch (IOException | JsonParseException e) { } catch (IOException e) {
LOG.error("Failed to save settings.", e); LOG.error("Failed to save settings.", e);
} }
} }
@@ -1,9 +1,12 @@
package org.cryptomator.common.settings; package org.cryptomator.common.settings;
import com.fasterxml.jackson.annotation.JsonEnumDefaultValue;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.apache.commons.lang3.SystemUtils; import org.apache.commons.lang3.SystemUtils;
@JsonFormat(shape = JsonFormat.Shape.STRING)
public enum UiTheme { public enum UiTheme {
LIGHT("preferences.interface.theme.light"), // @JsonEnumDefaultValue LIGHT("preferences.interface.theme.light"), //
DARK("preferences.interface.theme.dark"), // DARK("preferences.interface.theme.dark"), //
AUTOMATIC("preferences.interface.theme.automatic"); AUTOMATIC("preferences.interface.theme.automatic");
@@ -6,7 +6,9 @@
package org.cryptomator.common.settings; package org.cryptomator.common.settings;
import com.google.common.base.CharMatcher; import com.google.common.base.CharMatcher;
import com.google.common.base.Strings;
import com.google.common.io.BaseEncoding; import com.google.common.io.BaseEncoding;
import org.apache.commons.lang3.SystemUtils;
import javafx.beans.Observable; import javafx.beans.Observable;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
@@ -20,6 +22,7 @@ import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty; import javafx.beans.property.StringProperty;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Objects; import java.util.Objects;
import java.util.Random; import java.util.Random;
@@ -28,33 +31,45 @@ import java.util.Random;
*/ */
public class VaultSettings { public class VaultSettings {
public static final boolean DEFAULT_UNLOCK_AFTER_STARTUP = false; static final boolean DEFAULT_UNLOCK_AFTER_STARTUP = false;
public static final boolean DEFAULT_REVEAL_AFTER_MOUNT = true; static final boolean DEFAULT_REVEAL_AFTER_MOUNT = true;
public static final boolean DEFAULT_USES_READONLY_MODE = false; static final boolean DEFAULT_USES_READONLY_MODE = false;
public static final String DEFAULT_MOUNT_FLAGS = ""; static final String DEFAULT_MOUNT_FLAGS = ""; // TODO: remove empty default mount flags and let this property be null if not used
public static final int DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH = -1; static final int DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH = -1;
public static final WhenUnlocked DEFAULT_ACTION_AFTER_UNLOCK = WhenUnlocked.ASK; static final WhenUnlocked DEFAULT_ACTION_AFTER_UNLOCK = WhenUnlocked.ASK;
public static final boolean DEFAULT_AUTOLOCK_WHEN_IDLE = false; static final boolean DEFAULT_AUTOLOCK_WHEN_IDLE = false;
public static final int DEFAULT_AUTOLOCK_IDLE_SECONDS = 30 * 60; static final int DEFAULT_AUTOLOCK_IDLE_SECONDS = 30 * 60;
private static final Random RNG = new Random(); private static final Random RNG = new Random();
private final String id; public final String id;
private final ObjectProperty<Path> path = new SimpleObjectProperty<>(); public final ObjectProperty<Path> path;
private final StringProperty displayName = new SimpleStringProperty(); public final StringProperty displayName;
private final BooleanProperty unlockAfterStartup = new SimpleBooleanProperty(DEFAULT_UNLOCK_AFTER_STARTUP); public final BooleanProperty unlockAfterStartup;
private final BooleanProperty revealAfterMount = new SimpleBooleanProperty(DEFAULT_REVEAL_AFTER_MOUNT); public final BooleanProperty revealAfterMount;
private final BooleanProperty usesReadOnlyMode = new SimpleBooleanProperty(DEFAULT_USES_READONLY_MODE); public final BooleanProperty usesReadOnlyMode;
private final StringProperty mountFlags = new SimpleStringProperty(DEFAULT_MOUNT_FLAGS); //TODO: remove empty default mount flags and let this property be null if not used public final StringProperty mountFlags;
private final IntegerProperty maxCleartextFilenameLength = new SimpleIntegerProperty(DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH); public final IntegerProperty maxCleartextFilenameLength;
private final ObjectProperty<WhenUnlocked> actionAfterUnlock = new SimpleObjectProperty<>(DEFAULT_ACTION_AFTER_UNLOCK); public final ObjectProperty<WhenUnlocked> actionAfterUnlock;
private final BooleanProperty autoLockWhenIdle = new SimpleBooleanProperty(DEFAULT_AUTOLOCK_WHEN_IDLE); public final BooleanProperty autoLockWhenIdle;
private final IntegerProperty autoLockIdleSeconds = new SimpleIntegerProperty(DEFAULT_AUTOLOCK_IDLE_SECONDS); public final IntegerProperty autoLockIdleSeconds;
private final StringExpression mountName; public final ObjectProperty<Path> mountPoint;
private final ObjectProperty<Path> mountPoint = new SimpleObjectProperty<>(); public final StringExpression mountName;
public VaultSettings(String id) { VaultSettings(VaultSettingsJson json) {
this.id = Objects.requireNonNull(id); this.id = json.id;
this.path = new SimpleObjectProperty<>(this, "path", json.path == null ? null : Paths.get(json.path));
this.displayName = new SimpleStringProperty(this, "displayName", json.displayName);
this.unlockAfterStartup = new SimpleBooleanProperty(this, "unlockAfterStartup", json.unlockAfterStartup);
this.revealAfterMount = new SimpleBooleanProperty(this, "revealAfterMount", json.revealAfterMount);
this.usesReadOnlyMode = new SimpleBooleanProperty(this, "usesReadOnlyMode", json.usesReadOnlyMode);
this.mountFlags = new SimpleStringProperty(this, "mountFlags", json.mountFlags);
this.maxCleartextFilenameLength = new SimpleIntegerProperty(this, "maxCleartextFilenameLength", json.maxCleartextFilenameLength);
this.actionAfterUnlock = new SimpleObjectProperty<>(this, "actionAfterUnlock", json.actionAfterUnlock);
this.autoLockWhenIdle = new SimpleBooleanProperty(this, "autoLockWhenIdle", json.autoLockWhenIdle);
this.autoLockIdleSeconds = new SimpleIntegerProperty(this, "autoLockIdleSeconds", json.autoLockIdleSeconds);
this.mountPoint = new SimpleObjectProperty<>(this, "mountPoint", json.mountPoint == null ? null : Path.of(json.mountPoint));
// mount name is no longer an explicit setting, see https://github.com/cryptomator/cryptomator/pull/1318
this.mountName = StringExpression.stringExpression(Bindings.createStringBinding(() -> { this.mountName = StringExpression.stringExpression(Bindings.createStringBinding(() -> {
final String name; final String name;
if (displayName.isEmpty().get()) { if (displayName.isEmpty().get()) {
@@ -64,6 +79,18 @@ public class VaultSettings {
} }
return normalizeDisplayName(name); return normalizeDisplayName(name);
}, displayName, path)); }, displayName, path));
migrateLegacySettings(json);
}
@SuppressWarnings("deprecation")
private void migrateLegacySettings(VaultSettingsJson json) {
// implicit migration of 1.6.x legacy setting "customMountPath" / "winDriveLetter":
if (json.useCustomMountPath && !Strings.isNullOrEmpty(json.customMountPath)) {
this.mountPoint.set(Path.of(json.customMountPath));
} else if (!Strings.isNullOrEmpty(json.winDriveLetter)) {
this.mountPoint.set(Path.of(json.winDriveLetter + ":\\"));
}
} }
Observable[] observables() { Observable[] observables() {
@@ -71,7 +98,9 @@ public class VaultSettings {
} }
public static VaultSettings withRandomId() { public static VaultSettings withRandomId() {
return new VaultSettings(generateId()); var defaults = new VaultSettingsJson();
defaults.id = generateId();
return new VaultSettings(defaults);
} }
private static String generateId() { private static String generateId() {
@@ -80,6 +109,23 @@ public class VaultSettings {
return BaseEncoding.base64Url().encode(randomBytes); return BaseEncoding.base64Url().encode(randomBytes);
} }
VaultSettingsJson serialized() {
var json = new VaultSettingsJson();
json.id = id;
json.path = path.map(Path::toString).getValue();
json.displayName = displayName.get();
json.unlockAfterStartup = unlockAfterStartup.get();
json.revealAfterMount = revealAfterMount.get();
json.usesReadOnlyMode = usesReadOnlyMode.get();
json.mountFlags = mountFlags.get();
json.maxCleartextFilenameLength = maxCleartextFilenameLength.get();
json.actionAfterUnlock = actionAfterUnlock.get();
json.autoLockWhenIdle = autoLockWhenIdle.get();
json.autoLockIdleSeconds = autoLockIdleSeconds.get();
json.mountPoint = mountPoint.map(Path::toString).getValue();
return json;
}
//visible for testing //visible for testing
static String normalizeDisplayName(String original) { static String normalizeDisplayName(String original) {
if (original.isBlank() || ".".equals(original) || "..".equals(original)) { if (original.isBlank() || ".".equals(original) || "..".equals(original)) {
@@ -93,68 +139,6 @@ public class VaultSettings {
return CharMatcher.anyOf("<>:\"/\\|?*").or(CharMatcher.javaIsoControl()).collapseFrom(withoutFancyWhitespaces, '_'); return CharMatcher.anyOf("<>:\"/\\|?*").or(CharMatcher.javaIsoControl()).collapseFrom(withoutFancyWhitespaces, '_');
} }
/* Getter/Setter */
public String getId() {
return id;
}
public ObjectProperty<Path> path() {
return path;
}
public StringProperty displayName() {
return displayName;
}
public StringExpression mountName() {
return mountName;
}
public BooleanProperty unlockAfterStartup() {
return unlockAfterStartup;
}
public BooleanProperty revealAfterMount() {
return revealAfterMount;
}
public Path getMountPoint() {
return mountPoint.get();
}
public ObjectProperty<Path> mountPoint() {
return mountPoint;
}
public BooleanProperty usesReadOnlyMode() {
return usesReadOnlyMode;
}
public StringProperty mountFlags() {
return mountFlags;
}
public IntegerProperty maxCleartextFilenameLength() {
return maxCleartextFilenameLength;
}
public ObjectProperty<WhenUnlocked> actionAfterUnlock() {
return actionAfterUnlock;
}
public WhenUnlocked getActionAfterUnlock() {
return actionAfterUnlock.get();
}
public BooleanProperty autoLockWhenIdle() {
return autoLockWhenIdle;
}
public IntegerProperty autoLockIdleSeconds() {
return autoLockIdleSeconds;
}
/* Hashcode/Equals */ /* Hashcode/Equals */
@Override @Override
@@ -0,0 +1,62 @@
package org.cryptomator.common.settings;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
class VaultSettingsJson {
@JsonProperty(value = "id", required = true)
String id;
@JsonProperty(value = "path")
String path;
@JsonProperty("displayName")
String displayName;
@JsonProperty("unlockAfterStartup")
boolean unlockAfterStartup = VaultSettings.DEFAULT_UNLOCK_AFTER_STARTUP;
@JsonProperty("revealAfterMount")
boolean revealAfterMount = VaultSettings.DEFAULT_REVEAL_AFTER_MOUNT;
@JsonProperty("mountPoint")
String mountPoint;
@JsonProperty("usesReadOnlyMode")
boolean usesReadOnlyMode = VaultSettings.DEFAULT_USES_READONLY_MODE;
@JsonProperty("mountFlags")
String mountFlags = VaultSettings.DEFAULT_MOUNT_FLAGS;
@JsonProperty("maxCleartextFilenameLength")
int maxCleartextFilenameLength = VaultSettings.DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH;
@JsonProperty("actionAfterUnlock")
WhenUnlocked actionAfterUnlock = VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK;
@JsonProperty("autoLockWhenIdle")
boolean autoLockWhenIdle = VaultSettings.DEFAULT_AUTOLOCK_WHEN_IDLE;
@JsonProperty("autoLockIdleSeconds")
int autoLockIdleSeconds = VaultSettings.DEFAULT_AUTOLOCK_IDLE_SECONDS;
@Deprecated(since = "1.7.0")
@JsonProperty(value = "winDriveLetter", access = JsonProperty.Access.WRITE_ONLY) // WRITE_ONLY means value is "written" into the java object during deserialization. Upvote this: https://github.com/FasterXML/jackson-annotations/issues/233
String winDriveLetter;
@Deprecated(since = "1.7.0")
@JsonProperty(value = "useCustomMountPath", access = JsonProperty.Access.WRITE_ONLY) // WRITE_ONLY means value is "written" into the java object during deserialization. Upvote this: https://github.com/FasterXML/jackson-annotations/issues/233
@JsonAlias("usesIndividualMountPath")
boolean useCustomMountPath;
@Deprecated(since = "1.7.0")
@JsonProperty(value = "customMountPath", access = JsonProperty.Access.WRITE_ONLY) // WRITE_ONLY means value is "written" into the java object during deserialization. Upvote this: https://github.com/FasterXML/jackson-annotations/issues/233
@JsonAlias("individualMountPath")
String customMountPath;
}
@@ -1,142 +0,0 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.common.settings;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
class VaultSettingsJsonAdapter {
private static final Logger LOG = LoggerFactory.getLogger(VaultSettingsJsonAdapter.class);
public void write(JsonWriter out, VaultSettings value) throws IOException {
out.beginObject();
out.name("id").value(value.getId());
out.name("path").value(value.path().get().toString());
out.name("displayName").value(value.displayName().get());
out.name("unlockAfterStartup").value(value.unlockAfterStartup().get());
out.name("revealAfterMount").value(value.revealAfterMount().get());
var mountPoint = value.mountPoint().get();
out.name("mountPoint").value(mountPoint != null ? mountPoint.toAbsolutePath().toString() : null);
out.name("usesReadOnlyMode").value(value.usesReadOnlyMode().get());
out.name("mountFlags").value(value.mountFlags().get());
out.name("maxCleartextFilenameLength").value(value.maxCleartextFilenameLength().get());
out.name("actionAfterUnlock").value(value.actionAfterUnlock().get().name());
out.name("autoLockWhenIdle").value(value.autoLockWhenIdle().get());
out.name("autoLockIdleSeconds").value(value.autoLockIdleSeconds().get());
out.endObject();
}
public VaultSettings read(JsonReader in) throws IOException {
String id = null;
String path = null;
String mountName = null; //see https://github.com/cryptomator/cryptomator/pull/1318
String displayName = null;
boolean unlockAfterStartup = VaultSettings.DEFAULT_UNLOCK_AFTER_STARTUP;
boolean revealAfterMount = VaultSettings.DEFAULT_REVEAL_AFTER_MOUNT;
boolean usesReadOnlyMode = VaultSettings.DEFAULT_USES_READONLY_MODE;
String mountFlags = VaultSettings.DEFAULT_MOUNT_FLAGS;
Path mountPoint = null;
int maxCleartextFilenameLength = VaultSettings.DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH;
WhenUnlocked actionAfterUnlock = VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK;
boolean autoLockWhenIdle = VaultSettings.DEFAULT_AUTOLOCK_WHEN_IDLE;
int autoLockIdleSeconds = VaultSettings.DEFAULT_AUTOLOCK_IDLE_SECONDS;
//legacy from 1.6.x
boolean useCustomMountPath = false;
String customMountPath = "";
String winDriveLetter = "";
//legacy end
in.beginObject();
while (in.hasNext()) {
String name = in.nextName();
switch (name) {
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 "unlockAfterStartup" -> unlockAfterStartup = in.nextBoolean();
case "revealAfterMount" -> revealAfterMount = in.nextBoolean();
case "usesReadOnlyMode" -> usesReadOnlyMode = in.nextBoolean();
case "mountFlags" -> mountFlags = in.nextString();
case "mountPoint" -> {
if (JsonToken.NULL == in.peek()) {
in.nextNull();
} else {
mountPoint = parseMountPoint(in.nextString());
}
}
case "maxCleartextFilenameLength" -> maxCleartextFilenameLength = in.nextInt();
case "actionAfterUnlock" -> actionAfterUnlock = parseActionAfterUnlock(in.nextString());
case "autoLockWhenIdle" -> autoLockWhenIdle = in.nextBoolean();
case "autoLockIdleSeconds" -> autoLockIdleSeconds = in.nextInt();
//legacy from 1.6.x
case "winDriveLetter" -> winDriveLetter = in.nextString();
case "usesIndividualMountPath", "useCustomMountPath" -> useCustomMountPath = in.nextBoolean();
case "individualMountPath", "customMountPath" -> customMountPath = in.nextString();
//legacy end
default -> {
LOG.warn("Unsupported vault setting found in JSON: {}", name);
in.skipValue();
}
}
}
in.endObject();
VaultSettings vaultSettings = (id == null) ? VaultSettings.withRandomId() : new VaultSettings(id);
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.unlockAfterStartup().set(unlockAfterStartup);
vaultSettings.revealAfterMount().set(revealAfterMount);
vaultSettings.usesReadOnlyMode().set(usesReadOnlyMode);
vaultSettings.mountFlags().set(mountFlags);
vaultSettings.maxCleartextFilenameLength().set(maxCleartextFilenameLength);
vaultSettings.actionAfterUnlock().set(actionAfterUnlock);
vaultSettings.autoLockWhenIdle().set(autoLockWhenIdle);
vaultSettings.autoLockIdleSeconds().set(autoLockIdleSeconds);
vaultSettings.mountPoint().set(mountPoint);
//legacy from 1.6.x
if(useCustomMountPath && !customMountPath.isBlank()) {
vaultSettings.mountPoint().set(parseMountPoint(customMountPath));
} else if(!winDriveLetter.isBlank() ) {
vaultSettings.mountPoint().set(parseMountPoint(winDriveLetter+":\\"));
}
//legacy end
return vaultSettings;
}
private Path parseMountPoint(String mountPoint) {
try {
return Path.of(mountPoint);
} catch (InvalidPathException e) {
LOG.warn("Invalid string as mount point. Defaulting to null.");
return null;
}
}
private WhenUnlocked parseActionAfterUnlock(String actionAfterUnlockName) {
try {
return WhenUnlocked.valueOf(actionAfterUnlockName.toUpperCase());
} catch (IllegalArgumentException e) {
LOG.warn("Invalid action after unlock {}. Defaulting to {}.", actionAfterUnlockName, VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK);
return VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK;
}
}
}
@@ -1,9 +1,13 @@
package org.cryptomator.common.settings; package org.cryptomator.common.settings;
import com.fasterxml.jackson.annotation.JsonEnumDefaultValue;
import com.fasterxml.jackson.annotation.JsonFormat;
@JsonFormat(shape = JsonFormat.Shape.STRING)
public enum WhenUnlocked { public enum WhenUnlocked {
IGNORE("vaultOptions.general.actionAfterUnlock.ignore"), IGNORE("vaultOptions.general.actionAfterUnlock.ignore"),
REVEAL("vaultOptions.general.actionAfterUnlock.reveal"), REVEAL("vaultOptions.general.actionAfterUnlock.reveal"),
ASK("vaultOptions.general.actionAfterUnlock.ask"); @JsonEnumDefaultValue ASK("vaultOptions.general.actionAfterUnlock.ask");
private String displayName; private String displayName;
@@ -50,8 +50,8 @@ public class AutoLocker {
private boolean exceedsIdleTime(Vault vault) { private boolean exceedsIdleTime(Vault vault) {
assert vault.isUnlocked(); assert vault.isUnlocked();
if (vault.getVaultSettings().autoLockWhenIdle().get()) { if (vault.getVaultSettings().autoLockWhenIdle.get()) {
int maxIdleSeconds = vault.getVaultSettings().autoLockIdleSeconds().get(); int maxIdleSeconds = vault.getVaultSettings().autoLockIdleSeconds.get();
var deadline = vault.getStats().getLastActivity().plusSeconds(maxIdleSeconds); var deadline = vault.getStats().getLastActivity().plusSeconds(maxIdleSeconds);
return deadline.isBefore(Instant.now()); return deadline.isBefore(Instant.now());
} else { } else {
@@ -70,7 +70,7 @@ public class Vault {
private final Mounter mounter; private final Mounter mounter;
private final BooleanProperty showingStats; private final BooleanProperty showingStats;
private AtomicReference<Mounter.MountHandle> mountHandle = new AtomicReference<>(null); private final AtomicReference<Mounter.MountHandle> mountHandle = new AtomicReference<>(null);
@Inject @Inject
Vault(VaultSettings vaultSettings, VaultConfigCache configCache, AtomicReference<CryptoFileSystem> cryptoFileSystem, VaultState state, @Named("lastKnownException") ObjectProperty<Exception> lastKnownException, VaultStats stats, WindowsDriveLetters windowsDriveLetters, Mounter mounter) { Vault(VaultSettings vaultSettings, VaultConfigCache configCache, AtomicReference<CryptoFileSystem> cryptoFileSystem, VaultState state, @Named("lastKnownException") ObjectProperty<Exception> lastKnownException, VaultStats stats, WindowsDriveLetters windowsDriveLetters, Mounter mounter) {
@@ -80,7 +80,7 @@ public class Vault {
this.state = state; this.state = state;
this.lastKnownException = lastKnownException; this.lastKnownException = lastKnownException;
this.stats = stats; this.stats = stats;
this.displayablePath = Bindings.createStringBinding(this::getDisplayablePath, vaultSettings.path()); this.displayablePath = Bindings.createStringBinding(this::getDisplayablePath, vaultSettings.path);
this.locked = Bindings.createBooleanBinding(this::isLocked, state); this.locked = Bindings.createBooleanBinding(this::isLocked, state);
this.processing = Bindings.createBooleanBinding(this::isProcessing, state); this.processing = Bindings.createBooleanBinding(this::isProcessing, state);
this.unlocked = Bindings.createBooleanBinding(this::isUnlocked, state); this.unlocked = Bindings.createBooleanBinding(this::isUnlocked, state);
@@ -98,29 +98,29 @@ public class Vault {
private CryptoFileSystem createCryptoFileSystem(MasterkeyLoader keyLoader) throws IOException, MasterkeyLoadingFailedException { private CryptoFileSystem createCryptoFileSystem(MasterkeyLoader keyLoader) throws IOException, MasterkeyLoadingFailedException {
Set<FileSystemFlags> flags = EnumSet.noneOf(FileSystemFlags.class); Set<FileSystemFlags> flags = EnumSet.noneOf(FileSystemFlags.class);
if (vaultSettings.usesReadOnlyMode().get()) { if (vaultSettings.usesReadOnlyMode.get()) {
flags.add(FileSystemFlags.READONLY); flags.add(FileSystemFlags.READONLY);
} else if (vaultSettings.maxCleartextFilenameLength().get() == -1) { } else if (vaultSettings.maxCleartextFilenameLength.get() == -1) {
LOG.debug("Determining cleartext filename length limitations..."); LOG.debug("Determining cleartext filename length limitations...");
var checker = new FileSystemCapabilityChecker(); var checker = new FileSystemCapabilityChecker();
int shorteningThreshold = configCache.get().allegedShorteningThreshold(); int shorteningThreshold = configCache.get().allegedShorteningThreshold();
int ciphertextLimit = checker.determineSupportedCiphertextFileNameLength(getPath()); int ciphertextLimit = checker.determineSupportedCiphertextFileNameLength(getPath());
if (ciphertextLimit < shorteningThreshold) { if (ciphertextLimit < shorteningThreshold) {
int cleartextLimit = checker.determineSupportedCleartextFileNameLength(getPath()); int cleartextLimit = checker.determineSupportedCleartextFileNameLength(getPath());
vaultSettings.maxCleartextFilenameLength().set(cleartextLimit); vaultSettings.maxCleartextFilenameLength.set(cleartextLimit);
} else { } else {
vaultSettings.maxCleartextFilenameLength().setValue(UNLIMITED_FILENAME_LENGTH); vaultSettings.maxCleartextFilenameLength.setValue(UNLIMITED_FILENAME_LENGTH);
} }
} }
if (vaultSettings.maxCleartextFilenameLength().get() < UNLIMITED_FILENAME_LENGTH) { if (vaultSettings.maxCleartextFilenameLength.get() < UNLIMITED_FILENAME_LENGTH) {
LOG.warn("Limiting cleartext filename length on this device to {}.", vaultSettings.maxCleartextFilenameLength().get()); LOG.warn("Limiting cleartext filename length on this device to {}.", vaultSettings.maxCleartextFilenameLength.get());
} }
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties() // CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties() //
.withKeyLoader(keyLoader) // .withKeyLoader(keyLoader) //
.withFlags(flags) // .withFlags(flags) //
.withMaxCleartextNameLength(vaultSettings.maxCleartextFilenameLength().get()) // .withMaxCleartextNameLength(vaultSettings.maxCleartextFilenameLength.get()) //
.withVaultConfigFilename(Constants.VAULTCONFIG_FILENAME) // .withVaultConfigFilename(Constants.VAULTCONFIG_FILENAME) //
.build(); .build();
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps); return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
@@ -253,11 +253,11 @@ public class Vault {
} }
public ReadOnlyStringProperty displayNameProperty() { public ReadOnlyStringProperty displayNameProperty() {
return vaultSettings.displayName(); return vaultSettings.displayName;
} }
public String getDisplayName() { public String getDisplayName() {
return vaultSettings.displayName().get(); return vaultSettings.displayName.get();
} }
public ObjectBinding<Mountpoint> mountPointProperty() { public ObjectBinding<Mountpoint> mountPointProperty() {
@@ -274,7 +274,7 @@ public class Vault {
} }
public String getDisplayablePath() { public String getDisplayablePath() {
Path p = vaultSettings.path().get(); Path p = vaultSettings.path.get();
if (p.startsWith(HOME_DIR)) { if (p.startsWith(HOME_DIR)) {
Path relativePath = HOME_DIR.relativize(p); Path relativePath = HOME_DIR.relativize(p);
String homePrefix = SystemUtils.IS_OS_WINDOWS ? "~\\" : "~/"; String homePrefix = SystemUtils.IS_OS_WINDOWS ? "~\\" : "~/";
@@ -311,7 +311,7 @@ public class Vault {
} }
public Path getPath() { public Path getPath() {
return vaultSettings.path().getValue(); return vaultSettings.path.get();
} }
/** /**
@@ -346,7 +346,7 @@ public class Vault {
} }
public String getId() { public String getId() {
return vaultSettings.getId(); return vaultSettings.id;
} }
// ****************************************************************************** // ******************************************************************************
@@ -27,7 +27,7 @@ public class VaultConfigCache {
void reloadConfig() throws IOException { void reloadConfig() throws IOException {
try { try {
config.set(readConfigFromStorage(this.settings.path().get())); config.set(readConfigFromStorage(this.settings.path.get()));
} catch (IOException e) { } catch (IOException e) {
config.set(null); config.set(null);
throw e; throw e;
@@ -49,8 +49,8 @@ public class VaultListManager {
this.vaultComponentFactory = vaultComponentFactory; this.vaultComponentFactory = vaultComponentFactory;
this.defaultVaultName = resourceBundle.getString("defaults.vault.vaultName"); this.defaultVaultName = resourceBundle.getString("defaults.vault.vaultName");
addAll(settings.getDirectories()); addAll(settings.directories);
vaultList.addListener(new VaultListChangeListener(settings.getDirectories())); vaultList.addListener(new VaultListChangeListener(settings.directories));
autoLocker.init(); autoLocker.init();
} }
@@ -70,11 +70,11 @@ public class VaultListManager {
private VaultSettings newVaultSettings(Path path) { private VaultSettings newVaultSettings(Path path) {
VaultSettings vaultSettings = VaultSettings.withRandomId(); VaultSettings vaultSettings = VaultSettings.withRandomId();
vaultSettings.path().set(path); vaultSettings.path.set(path);
if (path.getFileName() != null) { if (path.getFileName() != null) {
vaultSettings.displayName().set(path.getFileName().toString()); vaultSettings.displayName.set(path.getFileName().toString());
} else { } else {
vaultSettings.displayName().set(defaultVaultName); vaultSettings.displayName.set(defaultVaultName);
} }
return vaultSettings; return vaultSettings;
} }
@@ -95,13 +95,13 @@ public class VaultListManager {
private Vault create(VaultSettings vaultSettings) { private Vault create(VaultSettings vaultSettings) {
var wrapper = new VaultConfigCache(vaultSettings); var wrapper = new VaultConfigCache(vaultSettings);
try { try {
var vaultState = determineVaultState(vaultSettings.path().get()); var vaultState = determineVaultState(vaultSettings.path.get());
if (vaultState == LOCKED) { //for legacy reasons: pre v8 vault do not have a config, but they are in the NEEDS_MIGRATION state if (vaultState == LOCKED) { //for legacy reasons: pre v8 vault do not have a config, but they are in the NEEDS_MIGRATION state
wrapper.reloadConfig(); wrapper.reloadConfig();
} }
return vaultComponentFactory.create(vaultSettings, wrapper, vaultState, null).vault(); return vaultComponentFactory.create(vaultSettings, wrapper, vaultState, null).vault();
} catch (IOException e) { } catch (IOException e) {
LOG.warn("Failed to determine vault state for " + vaultSettings.path().get(), e); LOG.warn("Failed to determine vault state for " + vaultSettings.path.get(), e);
return vaultComponentFactory.create(vaultSettings, wrapper, ERROR, e).vault(); return vaultComponentFactory.create(vaultSettings, wrapper, ERROR, e).vault();
} }
} }
@@ -9,6 +9,7 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder;
import dagger.Lazy; import dagger.Lazy;
import org.apache.commons.lang3.SystemUtils; import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Environment; import org.cryptomator.common.Environment;
import org.cryptomator.common.SubstitutingProperties;
import org.cryptomator.common.ShutdownHook; import org.cryptomator.common.ShutdownHook;
import org.cryptomator.ipc.IpcCommunicator; import org.cryptomator.ipc.IpcCommunicator;
import org.cryptomator.logging.DebugMode; import org.cryptomator.logging.DebugMode;
@@ -29,10 +30,18 @@ import java.util.concurrent.Executors;
public class Cryptomator { public class Cryptomator {
private static final long STARTUP_TIME = System.currentTimeMillis(); private static final long STARTUP_TIME = System.currentTimeMillis();
static {
var lazyProcessedProps = new SubstitutingProperties(System.getProperties(), System.getenv());
System.setProperties(lazyProcessedProps);
CRYPTOMATOR_COMPONENT = DaggerCryptomatorComponent.factory().create(STARTUP_TIME);
LOG = LoggerFactory.getLogger(Cryptomator.class);
}
// DaggerCryptomatorComponent gets generated by Dagger. // DaggerCryptomatorComponent gets generated by Dagger.
// Run Maven and include target/generated-sources/annotations in your IDE. // Run Maven and include target/generated-sources/annotations in your IDE.
private static final CryptomatorComponent CRYPTOMATOR_COMPONENT = DaggerCryptomatorComponent.factory().create(STARTUP_TIME); private static final CryptomatorComponent CRYPTOMATOR_COMPONENT;
private static final Logger LOG = LoggerFactory.getLogger(Cryptomator.class); private static final Logger LOG;
private final DebugMode debugMode; private final DebugMode debugMode;
private final SupportedLanguages supportedLanguages; private final SupportedLanguages supportedLanguages;
@@ -63,7 +72,6 @@ public class Cryptomator {
System.out.printf("Cryptomator version %s (build %s)%n", appVer, buildNumber); System.out.printf("Cryptomator version %s (build %s)%n", appVer, buildNumber);
return; return;
} }
int exitCode = CRYPTOMATOR_COMPONENT.application().run(args); int exitCode = CRYPTOMATOR_COMPONENT.application().run(args);
LOG.info("Exit {}", exitCode); LOG.info("Exit {}", exitCode);
System.exit(exitCode); // end remaining non-daemon threads. System.exit(exitCode); // end remaining non-daemon threads.
@@ -86,7 +94,7 @@ public class Cryptomator {
* Attempts to create an IPC connection to a running Cryptomator instance and sends it the given args. * Attempts to create an IPC connection to a running Cryptomator instance and sends it the given args.
* If no external process could be reached, the args will be handled by the loopback IPC endpoint. * If no external process could be reached, the args will be handled by the loopback IPC endpoint.
*/ */
try (var communicator = IpcCommunicator.create(env.ipcSocketPath().toList())) { try (var communicator = IpcCommunicator.create(env.getIpcSocketPath().toList())) {
if (communicator.isClient()) { if (communicator.isClient()) {
communicator.sendHandleLaunchargs(List.of(args)); communicator.sendHandleLaunchargs(List.of(args));
communicator.sendRevealRunningApp(); communicator.sendRevealRunningApp();
@@ -29,12 +29,12 @@ public class SupportedLanguages {
@Inject @Inject
public SupportedLanguages(Settings settings) { public SupportedLanguages(Settings settings) {
var preferredLanguage = settings.languageProperty().get(); var preferredLanguage = settings.language.get();
preferredLocale = preferredLanguage == null ? Locale.getDefault() : Locale.forLanguageTag(preferredLanguage); preferredLocale = preferredLanguage == null ? Locale.getDefault() : Locale.forLanguageTag(preferredLanguage);
var collator = Collator.getInstance(preferredLocale); var collator = Collator.getInstance(preferredLocale);
collator.setStrength(Collator.PRIMARY); collator.setStrength(Collator.PRIMARY);
var sorted = new ArrayList<String>(); var sorted = new ArrayList<String>();
sorted.add(0, Settings.DEFAULT_LANGUAGE); sorted.add(0, null);
sorted.add(1, ENGLISH); sorted.add(1, ENGLISH);
LANGUAGE_TAGS.stream() // LANGUAGE_TAGS.stream() //
.sorted((a, b) -> collator.compare(Locale.forLanguageTag(a).getDisplayName(), Locale.forLanguageTag(b).getDisplayName())) // .sorted((a, b) -> collator.compare(Locale.forLanguageTag(a).getDisplayName(), Locale.forLanguageTag(b).getDisplayName())) //
@@ -26,8 +26,8 @@ public class DebugMode {
} }
public void initialize() { public void initialize() {
setLogLevels(settings.debugMode().get()); setLogLevels(settings.debugMode.get());
settings.debugMode().addListener(this::logLevelChanged); settings.debugMode.addListener(this::logLevelChanged);
} }
private void logLevelChanged(@SuppressWarnings("unused") ObservableValue<? extends Boolean> observable, @SuppressWarnings("unused") Boolean oldValue, Boolean newValue) { private void logLevelChanged(@SuppressWarnings("unused") ObservableValue<? extends Boolean> observable, @SuppressWarnings("unused") Boolean oldValue, Boolean newValue) {
@@ -5,21 +5,23 @@ import dagger.Module;
import dagger.Provides; import dagger.Provides;
import dagger.multibindings.IntoMap; import dagger.multibindings.IntoMap;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.changepassword.NewPasswordController;
import org.cryptomator.ui.changepassword.PasswordStrengthUtil;
import org.cryptomator.ui.common.DefaultSceneFactory; import org.cryptomator.ui.common.DefaultSceneFactory;
import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxControllerKey; import org.cryptomator.ui.common.FxControllerKey;
import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlLoaderFactory; import org.cryptomator.ui.common.FxmlLoaderFactory;
import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.changepassword.NewPasswordController;
import org.cryptomator.ui.changepassword.PasswordStrengthUtil;
import org.cryptomator.ui.common.StageFactory; import org.cryptomator.ui.common.StageFactory;
import org.cryptomator.ui.fxapp.PrimaryStage; import org.cryptomator.ui.fxapp.PrimaryStage;
import org.cryptomator.ui.recoverykey.RecoveryKeyDisplayController; import org.cryptomator.ui.recoverykey.RecoveryKeyDisplayController;
import javax.inject.Named; import javax.inject.Named;
import javax.inject.Provider; import javax.inject.Provider;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty; import javafx.beans.property.StringProperty;
@@ -65,6 +67,13 @@ public abstract class AddVaultModule {
return new SimpleStringProperty(""); return new SimpleStringProperty("");
} }
@Provides
@Named("shorteningThreshold")
@AddVaultWizardScoped
static IntegerProperty provideShorteningThreshold() {
return new SimpleIntegerProperty(CreateNewVaultExpertSettingsController.MAX_SHORTENING_THRESHOLD);
}
@Provides @Provides
@AddVaultWizardWindow @AddVaultWizardWindow
@AddVaultWizardScoped @AddVaultWizardScoped
@@ -130,6 +139,13 @@ public abstract class AddVaultModule {
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_SUCCESS); return fxmlLoaders.createScene(FxmlFile.ADDVAULT_SUCCESS);
} }
@Provides
@FxmlScene(FxmlFile.ADDVAULT_NEW_EXPERT_SETTINGS)
@AddVaultWizardScoped
static Scene provideCreateNewVaultExpertSettingsScene(@AddVaultWizardWindow FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_NEW_EXPERT_SETTINGS);
}
// ------------------ // ------------------
@Binds @Binds
@@ -181,4 +197,9 @@ public abstract class AddVaultModule {
@FxControllerKey(AddVaultSuccessController.class) @FxControllerKey(AddVaultSuccessController.class)
abstract FxController bindAddVaultSuccessController(AddVaultSuccessController controller); abstract FxController bindAddVaultSuccessController(AddVaultSuccessController controller);
@Binds
@IntoMap
@FxControllerKey(CreateNewVaultExpertSettingsController.class)
abstract FxController bindCreateNewVaultExpertSettingsController(CreateNewVaultExpertSettingsController controller);
} }
@@ -0,0 +1,118 @@
package org.cryptomator.ui.addvaultwizard;
import dagger.Lazy;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.controls.NumericTextField;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.stage.Stage;
import java.nio.file.Path;
@AddVaultWizardScoped
public class CreateNewVaultExpertSettingsController implements FxController {
public static final int MAX_SHORTENING_THRESHOLD = 220;
public static final int MIN_SHORTENING_THRESHOLD = 36;
private static final String DOCS_NAME_SHORTENING_URL = "https://docs.cryptomator.org/en/1.7/security/architecture/#name-shortening";
private final Stage window;
private final Lazy<Application> application;
private final Lazy<Scene> chooseLocationScene;
private final Lazy<Scene> choosePasswordScene;
private final StringProperty vaultNameProperty;
private final ObjectProperty<Path> vaultPathProperty;
private final IntegerProperty shorteningThreshold;
private final BooleanBinding validShorteningThreshold;
//FXML
public Label vaultNameLabel;
public Label vaultPathLabel;
public CheckBox expertSettingsCheckBox;
public NumericTextField shorteningThresholdTextField;
@Inject
CreateNewVaultExpertSettingsController(@AddVaultWizardWindow Stage window, //
Lazy<Application> application, //
@FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy<Scene> chooseLocationScene, //
@FxmlScene(FxmlFile.ADDVAULT_NEW_PASSWORD) Lazy<Scene> choosePasswordScene, //
@Named("vaultName") StringProperty vaultName, //
ObjectProperty<Path> vaultPath, //
@Named("shorteningThreshold") IntegerProperty shorteningThreshold) {
this.window = window;
this.application = application;
this.chooseLocationScene = chooseLocationScene;
this.choosePasswordScene = choosePasswordScene;
this.vaultNameProperty = vaultName;
this.vaultPathProperty = vaultPath;
this.shorteningThreshold = shorteningThreshold;
this.validShorteningThreshold = Bindings.createBooleanBinding(this::isValidShorteningThreshold, shorteningThreshold);
}
@FXML
public void initialize() {
vaultNameLabel.textProperty().bind(vaultNameProperty);
vaultPathLabel.textProperty().bind(vaultPathProperty.asString());
shorteningThresholdTextField.setPromptText(MIN_SHORTENING_THRESHOLD + "-" + MAX_SHORTENING_THRESHOLD);
shorteningThresholdTextField.setText(Integer.toString(MAX_SHORTENING_THRESHOLD));
shorteningThresholdTextField.textProperty().addListener((observable, oldValue, newValue) -> {
try {
int intValue = Integer.parseInt(newValue);
shorteningThreshold.set(intValue);
} catch (NumberFormatException e) {
shorteningThreshold.set(0); //the value is set to 0 to ensure that an invalid value assignment is detected during a NumberFormatException
}
});
}
@FXML
public void toggleUseExpertSettings() {
if (!expertSettingsCheckBox.isSelected()) {
shorteningThresholdTextField.setText(Integer.toString(MAX_SHORTENING_THRESHOLD));
}
}
@FXML
public void back() {
window.setScene(chooseLocationScene.get());
}
@FXML
public void next() {
window.setScene(choosePasswordScene.get());
}
public BooleanBinding validShorteningThresholdProperty() {
return validShorteningThreshold;
}
public boolean isValidShorteningThreshold() {
var value = shorteningThreshold.get();
return value >= MIN_SHORTENING_THRESHOLD && value <= MAX_SHORTENING_THRESHOLD;
}
public void openDocs() {
application.get().getHostServices().showDocument(DOCS_NAME_SHORTENING_URL);
}
public Path getVaultPath() {
return vaultPathProperty.get();
}
public String getVaultName() {
return vaultNameProperty.get();
}
}
@@ -48,7 +48,7 @@ public class CreateNewVaultLocationController implements FxController {
private final Stage window; private final Stage window;
private final Lazy<Scene> chooseNameScene; private final Lazy<Scene> chooseNameScene;
private final Lazy<Scene> choosePasswordScene; private final Lazy<Scene> chooseExpertSettingsScene;
private final List<RadioButton> locationPresetBtns; private final List<RadioButton> locationPresetBtns;
private final ObjectProperty<Path> vaultPath; private final ObjectProperty<Path> vaultPath;
private final StringProperty vaultName; private final StringProperty vaultName;
@@ -68,10 +68,15 @@ public class CreateNewVaultLocationController implements FxController {
public FontAwesome5IconView badLocation; public FontAwesome5IconView badLocation;
@Inject @Inject
CreateNewVaultLocationController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_NAME) Lazy<Scene> chooseNameScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_PASSWORD) Lazy<Scene> choosePasswordScene, ObjectProperty<Path> vaultPath, @Named("vaultName") StringProperty vaultName, ResourceBundle resourceBundle) { CreateNewVaultLocationController(@AddVaultWizardWindow Stage window, //
@FxmlScene(FxmlFile.ADDVAULT_NEW_NAME) Lazy<Scene> chooseNameScene, //
@FxmlScene(FxmlFile.ADDVAULT_NEW_EXPERT_SETTINGS) Lazy<Scene> chooseExpertSettingsScene, //
ObjectProperty<Path> vaultPath, //
@Named("vaultName") StringProperty vaultName, //
ResourceBundle resourceBundle) {
this.window = window; this.window = window;
this.chooseNameScene = chooseNameScene; this.chooseNameScene = chooseNameScene;
this.choosePasswordScene = choosePasswordScene; this.chooseExpertSettingsScene = chooseExpertSettingsScene;
this.vaultPath = vaultPath; this.vaultPath = vaultPath;
this.vaultName = vaultName; this.vaultName = vaultName;
this.resourceBundle = resourceBundle; this.resourceBundle = resourceBundle;
@@ -151,7 +156,7 @@ public class CreateNewVaultLocationController implements FxController {
@FXML @FXML
public void next() { public void next() {
if (validVaultPath.getValue()) { if (validVaultPath.getValue()) {
window.setScene(choosePasswordScene.get()); window.setScene(chooseExpertSettingsScene.get());
} }
} }
@@ -10,13 +10,12 @@ import org.cryptomator.cryptolib.api.CryptorProvider;
import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.api.MasterkeyLoader; import org.cryptomator.cryptolib.api.MasterkeyLoader;
import org.cryptomator.cryptolib.common.MasterkeyFileAccess; import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
import org.cryptomator.ui.changepassword.NewPasswordController;
import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.changepassword.NewPasswordController;
import org.cryptomator.ui.common.Tasks; import org.cryptomator.ui.common.Tasks;
import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy;
import org.cryptomator.ui.recoverykey.RecoveryKeyFactory; import org.cryptomator.ui.recoverykey.RecoveryKeyFactory;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -26,6 +25,7 @@ import javax.inject.Named;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding; import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.StringProperty; import javafx.beans.property.StringProperty;
@@ -37,7 +37,6 @@ import javafx.scene.control.ToggleGroup;
import javafx.stage.Stage; import javafx.stage.Stage;
import java.io.IOException; import java.io.IOException;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
import java.net.URI;
import java.nio.channels.WritableByteChannel; import java.nio.channels.WritableByteChannel;
import java.nio.file.FileSystem; import java.nio.file.FileSystem;
import java.nio.file.Files; import java.nio.file.Files;
@@ -57,7 +56,7 @@ public class CreateNewVaultPasswordController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(CreateNewVaultPasswordController.class); private static final Logger LOG = LoggerFactory.getLogger(CreateNewVaultPasswordController.class);
private final Stage window; private final Stage window;
private final Lazy<Scene> chooseLocationScene; private final Lazy<Scene> chooseExpertSettingsScene;
private final Lazy<Scene> recoveryKeyScene; private final Lazy<Scene> recoveryKeyScene;
private final Lazy<Scene> successScene; private final Lazy<Scene> successScene;
private final FxApplicationWindows appWindows; private final FxApplicationWindows appWindows;
@@ -75,6 +74,7 @@ public class CreateNewVaultPasswordController implements FxController {
private final BooleanProperty processing; private final BooleanProperty processing;
private final BooleanProperty readyToCreateVault; private final BooleanProperty readyToCreateVault;
private final ObjectBinding<ContentDisplay> createVaultButtonState; private final ObjectBinding<ContentDisplay> createVaultButtonState;
private final IntegerProperty shorteningThreshold;
/* FXML */ /* FXML */
public ToggleGroup recoveryKeyChoice; public ToggleGroup recoveryKeyChoice;
@@ -83,9 +83,25 @@ public class CreateNewVaultPasswordController implements FxController {
public NewPasswordController newPasswordSceneController; public NewPasswordController newPasswordSceneController;
@Inject @Inject
CreateNewVaultPasswordController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy<Scene> chooseLocationScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY) Lazy<Scene> recoveryKeyScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, FxApplicationWindows appWindows, ExecutorService executor, RecoveryKeyFactory recoveryKeyFactory, @Named("vaultName") StringProperty vaultName, ObjectProperty<Path> vaultPath, @AddVaultWizardWindow ObjectProperty<Vault> vault, @Named("recoveryKey") StringProperty recoveryKey, VaultListManager vaultListManager, ResourceBundle resourceBundle, ReadmeGenerator readmeGenerator, SecureRandom csprng, MasterkeyFileAccess masterkeyFileAccess) { CreateNewVaultPasswordController(@AddVaultWizardWindow Stage window, //
@FxmlScene(FxmlFile.ADDVAULT_NEW_EXPERT_SETTINGS) Lazy<Scene> chooseExpertSettingsScene, //
@FxmlScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY) Lazy<Scene> recoveryKeyScene, //
@FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, //
FxApplicationWindows appWindows, //
ExecutorService executor, //
RecoveryKeyFactory recoveryKeyFactory, //
@Named("vaultName") StringProperty vaultName, //
ObjectProperty<Path> vaultPath, //
@AddVaultWizardWindow ObjectProperty<Vault> vault, //
@Named("recoveryKey") StringProperty recoveryKey, //
VaultListManager vaultListManager, //
ResourceBundle resourceBundle, //
@Named("shorteningThreshold") IntegerProperty shorteningThreshold, //
ReadmeGenerator readmeGenerator, //
SecureRandom csprng, //
MasterkeyFileAccess masterkeyFileAccess) {
this.window = window; this.window = window;
this.chooseLocationScene = chooseLocationScene; this.chooseExpertSettingsScene = chooseExpertSettingsScene;
this.recoveryKeyScene = recoveryKeyScene; this.recoveryKeyScene = recoveryKeyScene;
this.successScene = successScene; this.successScene = successScene;
this.appWindows = appWindows; this.appWindows = appWindows;
@@ -103,6 +119,7 @@ public class CreateNewVaultPasswordController implements FxController {
this.processing = new SimpleBooleanProperty(); this.processing = new SimpleBooleanProperty();
this.readyToCreateVault = new SimpleBooleanProperty(); this.readyToCreateVault = new SimpleBooleanProperty();
this.createVaultButtonState = Bindings.when(processing).then(ContentDisplay.LEFT).otherwise(ContentDisplay.TEXT_ONLY); this.createVaultButtonState = Bindings.when(processing).then(ContentDisplay.LEFT).otherwise(ContentDisplay.TEXT_ONLY);
this.shorteningThreshold = shorteningThreshold;
} }
@FXML @FXML
@@ -116,7 +133,7 @@ public class CreateNewVaultPasswordController implements FxController {
@FXML @FXML
public void back() { public void back() {
window.setScene(chooseLocationScene.get()); window.setScene(chooseExpertSettingsScene.get());
} }
@FXML @FXML
@@ -176,7 +193,11 @@ public class CreateNewVaultPasswordController implements FxController {
// 2. initialize vault: // 2. initialize vault:
try { try {
MasterkeyLoader loader = ignored -> masterkey.copy(); MasterkeyLoader loader = ignored -> masterkey.copy();
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties().withCipherCombo(CryptorProvider.Scheme.SIV_GCM).withKeyLoader(loader).build(); CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties() //
.withCipherCombo(CryptorProvider.Scheme.SIV_GCM) //
.withKeyLoader(loader) //
.withShorteningThreshold(shorteningThreshold.get()) //
.build();
CryptoFileSystemProvider.initialize(path, fsProps, DEFAULT_KEY_ID); CryptoFileSystemProvider.initialize(path, fsProps, DEFAULT_KEY_ID);
// 3. write vault-internal readme file: // 3. write vault-internal readme file:
@@ -36,7 +36,7 @@ public class DefaultSceneFactory implements Function<Parent, Scene> {
} }
protected void configureRoot(Parent root) { protected void configureRoot(Parent root) {
root.nodeOrientationProperty().bind(settings.userInterfaceOrientation()); root.nodeOrientationProperty().bind(settings.userInterfaceOrientation);
} }
protected void configureScene(Scene scene) { protected void configureScene(Scene scene) {
@@ -4,6 +4,7 @@ public enum FxmlFile {
ADDVAULT_EXISTING("/fxml/addvault_existing.fxml"), // ADDVAULT_EXISTING("/fxml/addvault_existing.fxml"), //
ADDVAULT_NEW_NAME("/fxml/addvault_new_name.fxml"), // ADDVAULT_NEW_NAME("/fxml/addvault_new_name.fxml"), //
ADDVAULT_NEW_LOCATION("/fxml/addvault_new_location.fxml"), // ADDVAULT_NEW_LOCATION("/fxml/addvault_new_location.fxml"), //
ADDVAULT_NEW_EXPERT_SETTINGS("/fxml/addvault_new_expert_settings.fxml"), //
ADDVAULT_NEW_PASSWORD("/fxml/addvault_new_password.fxml"), // ADDVAULT_NEW_PASSWORD("/fxml/addvault_new_password.fxml"), //
ADDVAULT_NEW_RECOVERYKEY("/fxml/addvault_new_recoverykey.fxml"), // ADDVAULT_NEW_RECOVERYKEY("/fxml/addvault_new_recoverykey.fxml"), //
ADDVAULT_SUCCESS("/fxml/addvault_success.fxml"), // ADDVAULT_SUCCESS("/fxml/addvault_success.fxml"), //
@@ -41,6 +42,7 @@ public enum FxmlFile {
RECOVERYKEY_RESET_PASSWORD_SUCCESS("/fxml/recoverykey_reset_password_success.fxml"), // RECOVERYKEY_RESET_PASSWORD_SUCCESS("/fxml/recoverykey_reset_password_success.fxml"), //
RECOVERYKEY_SUCCESS("/fxml/recoverykey_success.fxml"), // RECOVERYKEY_SUCCESS("/fxml/recoverykey_success.fxml"), //
REMOVE_VAULT("/fxml/remove_vault.fxml"), // REMOVE_VAULT("/fxml/remove_vault.fxml"), //
UPDATE_REMINDER("/fxml/update_reminder.fxml"), //
UNLOCK_ENTER_PASSWORD("/fxml/unlock_enter_password.fxml"), UNLOCK_ENTER_PASSWORD("/fxml/unlock_enter_password.fxml"),
UNLOCK_INVALID_MOUNT_POINT("/fxml/unlock_invalid_mount_point.fxml"), // UNLOCK_INVALID_MOUNT_POINT("/fxml/unlock_invalid_mount_point.fxml"), //
UNLOCK_SELECT_MASTERKEYFILE("/fxml/unlock_select_masterkeyfile.fxml"), // UNLOCK_SELECT_MASTERKEYFILE("/fxml/unlock_select_masterkeyfile.fxml"), //
@@ -40,6 +40,7 @@ public enum FontAwesome5Icon {
LOCK("\uF023"), // LOCK("\uF023"), //
LOCK_OPEN("\uF3C1"), // LOCK_OPEN("\uF3C1"), //
MAGIC("\uF0D0"), // MAGIC("\uF0D0"), //
PENCIL("\uF303"), //
PLUS("\uF067"), // PLUS("\uF067"), //
PRINT("\uF02F"), // PRINT("\uF02F"), //
QUESTION("\uF128"), // QUESTION("\uF128"), //
@@ -13,18 +13,19 @@ public class FormattedLabel extends Label {
private final StringProperty format = new SimpleStringProperty(""); private final StringProperty format = new SimpleStringProperty("");
private final ObjectProperty<Object> arg1 = new SimpleObjectProperty<>(); private final ObjectProperty<Object> arg1 = new SimpleObjectProperty<>();
private final ObjectProperty<Object> arg2 = new SimpleObjectProperty<>(); private final ObjectProperty<Object> arg2 = new SimpleObjectProperty<>();
// add arg2, arg3, ... on demand private final ObjectProperty<Object> arg3 = new SimpleObjectProperty<>();
// add arg4, arg5, ... on demand
public FormattedLabel() { public FormattedLabel() {
textProperty().bind(createStringBinding()); textProperty().bind(createStringBinding());
} }
protected StringBinding createStringBinding() { protected StringBinding createStringBinding() {
return Bindings.createStringBinding(this::updateText, format, arg1, arg2); return Bindings.createStringBinding(this::updateText, format, arg1, arg2, arg3);
} }
private String updateText() { private String updateText() {
return String.format(format.get(), arg1.get(), arg2.get()); return String.format(format.get(), arg1.get(), arg2.get(), arg3.get());
} }
/* Observables */ /* Observables */
@@ -64,4 +65,16 @@ public class FormattedLabel extends Label {
public void setArg2(Object arg2) { public void setArg2(Object arg2) {
this.arg2.set(arg2); this.arg2.set(arg2);
} }
public ObjectProperty<Object> arg3Property() {
return arg3;
}
public Object getArg3() {
return arg3.get();
}
public void setArg3(Object arg3) {
this.arg3.set(arg3);
}
} }
@@ -1,28 +1,48 @@
package org.cryptomator.ui.error; package org.cryptomator.ui.error;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.cryptomator.common.Environment; import org.cryptomator.common.Environment;
import org.cryptomator.common.ErrorCode; import org.cryptomator.common.ErrorCode;
import org.cryptomator.common.Nullable; import org.cryptomator.common.Nullable;
import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import javafx.application.Application; import javafx.application.Application;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.binding.BooleanExpression;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.input.Clipboard; import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent; import javafx.scene.input.ClipboardContent;
import javafx.stage.Stage; import javafx.stage.Stage;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Comparator;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class ErrorController implements FxController { public class ErrorController implements FxController {
private static final ObjectMapper JSON = new ObjectMapper();
private static final Logger LOG = LoggerFactory.getLogger(ErrorController.class);
private static final String ERROR_CODES_URL = "https://gist.githubusercontent.com/cryptobot/accba9fb9555e7192271b85606f97230/raw/errorcodes.json";
private static final String SEARCH_URL_FORMAT = "https://github.com/cryptomator/cryptomator/discussions/categories/errors?discussions_q=category:Errors+%s"; private static final String SEARCH_URL_FORMAT = "https://github.com/cryptomator/cryptomator/discussions/categories/errors?discussions_q=category:Errors+%s";
private static final String REPORT_URL_FORMAT = "https://github.com/cryptomator/cryptomator/discussions/new?category=Errors&title=Error+%s&body=%s"; private static final String REPORT_URL_FORMAT = "https://github.com/cryptomator/cryptomator/discussions/new?category=Errors&title=Error+%s&body=%s";
private static final String SEARCH_ERRORCODE_DELIM = " OR "; private static final String SEARCH_ERRORCODE_DELIM = " OR ";
@@ -46,16 +66,28 @@ public class ErrorController implements FxController {
private final Stage window; private final Stage window;
private final Environment environment; private final Environment environment;
private BooleanProperty copiedDetails = new SimpleBooleanProperty(); private final BooleanProperty copiedDetails = new SimpleBooleanProperty();
private final ObjectProperty<ErrorDiscussion> matchingErrorDiscussion = new SimpleObjectProperty<>();
private final BooleanExpression errorSolutionFound = matchingErrorDiscussion.isNotNull();
private final BooleanProperty isLoadingHttpResponse = new SimpleBooleanProperty();
@Inject @Inject
ErrorController(Application application, @Named("stackTrace") String stackTrace, ErrorCode errorCode, @Nullable Scene previousScene, Stage window, Environment environment) { ErrorController(Application application, @Named("stackTrace") String stackTrace, ErrorCode errorCode, @Nullable Scene previousScene, Stage window, Environment environment, ExecutorService executorService) {
this.application = application; this.application = application;
this.stackTrace = stackTrace; this.stackTrace = stackTrace;
this.errorCode = errorCode; this.errorCode = errorCode;
this.previousScene = previousScene; this.previousScene = previousScene;
this.window = window; this.window = window;
this.environment = environment; this.environment = environment;
isLoadingHttpResponse.set(true);
HttpClient httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
HttpRequest httpRequest = HttpRequest.newBuilder()//
.uri(URI.create(ERROR_CODES_URL))//
.build();
httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofInputStream())//
.thenAcceptAsync(this::loadHttpResponse, executorService)//
.whenCompleteAsync((r, e) -> isLoadingHttpResponse.set(false), Platform::runLater);
} }
@FXML @FXML
@@ -70,6 +102,16 @@ public class ErrorController implements FxController {
window.close(); window.close();
} }
@FXML
public void showSolution() {
if (matchingErrorDiscussion.isNotNull().get()) {
var discussion = matchingErrorDiscussion.get();
if (discussion != null) {
application.getHostServices().showDocument(discussion.url);
}
}
}
@FXML @FXML
public void searchError() { public void searchError() {
var searchTerm = URLEncoder.encode(getErrorCode().replace(ErrorCode.DELIM, SEARCH_ERRORCODE_DELIM), StandardCharsets.UTF_8); var searchTerm = URLEncoder.encode(getErrorCode().replace(ErrorCode.DELIM, SEARCH_ERRORCODE_DELIM), StandardCharsets.UTF_8);
@@ -95,13 +137,122 @@ public class ErrorController implements FxController {
Clipboard.getSystemClipboard().setContent(clipboardContent); Clipboard.getSystemClipboard().setContent(clipboardContent);
copiedDetails.set(true); copiedDetails.set(true);
CompletableFuture.delayedExecutor(2, TimeUnit.SECONDS, Platform::runLater).execute(() -> { CompletableFuture.delayedExecutor(2, TimeUnit.SECONDS, Platform::runLater).execute(() -> copiedDetails.set(false));
copiedDetails.set(false); }
});
private void loadHttpResponse(HttpResponse<InputStream> response) {
if (response.statusCode() != 200) {
LOG.error("Status code {} when trying to load {} ", response.statusCode(), response.uri());
}
try {
var typeRef = new TypeReference<Map<String, ErrorDiscussion>>() {};
Map<String, ErrorDiscussion> errorDiscussionMap = JSON.reader().forType(typeRef).readValue(response.body());
if (errorDiscussionMap.values().stream().anyMatch(this::containsMethodCode)) {
Comparator<ErrorDiscussion> comp = this::compareByFullErrorCode;
Optional<ErrorDiscussion> value = errorDiscussionMap.values().stream().filter(this::containsMethodCode)//
.min(comp//
.thenComparing(this::compareByRootCauseCode)//
.thenComparing(this::compareIsAnswered)//
.thenComparing(this::compareUpvoteCount));
if (value.isPresent()) {
matchingErrorDiscussion.set(value.get());
}
}
} catch (IOException e) {
LOG.error("Failed to load or parse JSON from " + response.uri(), e);
}
}
/**
* Checks if an ErrorDiscussion object's title contains the error code's method code.
*
* @param errorDiscussion The ErrorDiscussion object to be checked.
* @return A boolean value indicating if the ErrorDiscussion object's title contains the error code's method code:
* - true if the title contains the method code,
* - false otherwise.
*/
public boolean containsMethodCode(ErrorDiscussion errorDiscussion) {
return errorDiscussion.title.contains(" " + errorCode.methodCode());
}
/**
* Compares two ErrorDiscussion objects based on their upvote counts and returns the result.
*
* @param ed1 The first ErrorDiscussion object.
* @param ed2 The second ErrorDiscussion object.
* @return An integer indicating which ErrorDiscussion object has a higher upvote count:
* - A positive value if ed2 has a higher upvote count than ed1,
* - A negative value if ed1 has a higher upvote count than ed2,
* - Or 0 if both upvote counts are equal.
*/
public int compareUpvoteCount(ErrorDiscussion ed1, ErrorDiscussion ed2) {
return Integer.compare(ed2.upvoteCount, ed1.upvoteCount);
}
/**
* Compares two ErrorDiscussion objects based on their answered status and returns the result.
*
* @param ed1 The first ErrorDiscussion object.
* @param ed2 The second ErrorDiscussion object.
* @return An integer indicating the answered status of the ErrorDiscussion objects:
* - A negative value (-1) if ed1 is considered answered and ed2 is considered unanswered,
* - A positive value (1) if ed1 is considered unanswered and ed2 is considered answered,
* - Or 0 if both ErrorDiscussion objects are considered answered or unanswered.
*/
public int compareIsAnswered(ErrorDiscussion ed1, ErrorDiscussion ed2) {
if (ed1.answer != null && ed2.answer == null) {
return -1;
} else if (ed1.answer == null && ed2.answer != null) {
return 1;
} else {
return 0;
}
}
/**
* Compares two ErrorDiscussion objects based on the presence of the full error code in their titles and returns the result.
*
* @param ed1 The first ErrorDiscussion object.
* @param ed2 The second ErrorDiscussion object.
* @return An integer indicating the comparison result based on the presence of the full error code in the titles:
* - A negative value (-1) if ed1 contains the full error code in the title and ed2 does not have a match,
* - A positive value (1) if ed1 does not have a match and ed2 contains the full error code in the title,
* - Or 0 if both ErrorDiscussion objects either contain the full error code or do not have a match in the titles.
*/
public int compareByFullErrorCode(ErrorDiscussion ed1, ErrorDiscussion ed2) {
if (ed1.title.contains(getErrorCode()) && !ed2.title.contains(getErrorCode())) {
return -1;
} else if (!ed1.title.contains(getErrorCode()) && ed2.title.contains(getErrorCode())) {
return 1;
} else {
return 0;
}
}
/**
* Compares two ErrorDiscussion objects based on the presence of the root cause code in their titles and returns the result.
*
* @param ed1 The first ErrorDiscussion object.
* @param ed2 The second ErrorDiscussion object.
* @return An integer indicating the comparison result based on the presence of the root cause code in the titles:
* - A negative value (-1) if ed1 contains the root cause code in the title and ed2 does not have a match,
* - A positive value (1) if ed1 does not have a match and ed2 contains the root cause code in the title,
* - Or 0 if both ErrorDiscussion objects either contain the root cause code or do not have a match in the titles.
*/
public int compareByRootCauseCode(ErrorDiscussion ed1, ErrorDiscussion ed2) {
String value = " " + errorCode.methodCode() + ErrorCode.DELIM + errorCode.rootCauseCode();
if (ed1.title.contains(value) && !ed2.title.contains(value)) {
return -1;
} else if (!ed1.title.contains(value) && ed2.title.contains(value)) {
return 1;
} else {
return 0;
}
} }
/* Getter/Setter */ /* Getter/Setter */
public boolean isPreviousScenePresent() { public boolean isPreviousScenePresent() {
return previousScene != null; return previousScene != null;
} }
@@ -125,4 +276,21 @@ public class ErrorController implements FxController {
public boolean getCopiedDetails() { public boolean getCopiedDetails() {
return copiedDetails.get(); return copiedDetails.get();
} }
}
public BooleanExpression errorSolutionFoundProperty() {
return errorSolutionFound;
}
public boolean getErrorSolutionFound() {
return errorSolutionFound.get();
}
public BooleanProperty isLoadingHttpResponseProperty() {
return isLoadingHttpResponse;
}
public boolean getIsLoadingHttpResponse() {
return isLoadingHttpResponse.get();
}
}
@@ -0,0 +1,22 @@
package org.cryptomator.ui.error;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown = true)
public class ErrorDiscussion {
@JsonProperty
int upvoteCount;
@JsonProperty
String title;
@JsonProperty
String url;
@JsonProperty
Answer answer;
@JsonIgnoreProperties(ignoreUnknown = true)
static class Answer {
}
}
@@ -36,7 +36,7 @@ public class AutoUnlocker {
public void tryUnlockForTimespan(int timespan, TimeUnit timeUnit) { public void tryUnlockForTimespan(int timespan, TimeUnit timeUnit) {
// Unlock all available auto unlock vaults // Unlock all available auto unlock vaults
Predicate<Vault> shouldAutoUnlock = v -> v.getVaultSettings().unlockAfterStartup().get(); Predicate<Vault> shouldAutoUnlock = v -> v.getVaultSettings().unlockAfterStartup.get();
unlockSequentially(vaults.stream().filter(shouldAutoUnlock)).thenRun(() -> startUnlockMissing(timespan, timeUnit)); unlockSequentially(vaults.stream().filter(shouldAutoUnlock)).thenRun(() -> startUnlockMissing(timespan, timeUnit));
} }
@@ -80,6 +80,6 @@ public class AutoUnlocker {
private Stream<Vault> getMissingAutoUnlockVaults() { private Stream<Vault> getMissingAutoUnlockVaults() {
return vaults.stream() return vaults.stream()
.filter(Vault::isMissing) .filter(Vault::isMissing)
.filter(v -> v.getVaultSettings().unlockAfterStartup().get()); .filter(v -> v.getVaultSettings().unlockAfterStartup.get());
} }
} }
@@ -45,7 +45,7 @@ public class FxApplication {
// init system tray // init system tray
final boolean hasTrayIcon; final boolean hasTrayIcon;
if (settings.showTrayIcon().get() && trayMenu.get().isSupported()) { if (settings.showTrayIcon.get() && trayMenu.get().isSupported()) {
trayMenu.get().initializeTrayIcon(); trayMenu.get().initializeTrayIcon();
Platform.setImplicitExit(false); // don't quit when closing all windows Platform.setImplicitExit(false); // don't quit when closing all windows
hasTrayIcon = true; hasTrayIcon = true;
@@ -55,7 +55,7 @@ public class FxApplication {
// show main window // show main window
appWindows.showMainWindow().thenAccept(stage -> { appWindows.showMainWindow().thenAccept(stage -> {
if (settings.startHidden().get()) { if (settings.startHidden.get()) {
if (hasTrayIcon) { if (hasTrayIcon) {
stage.hide(); stage.hide();
} else { } else {
@@ -68,6 +68,8 @@ public class FxApplication {
return null; return null;
}); });
appWindows.checkAndShowUpdateReminderWindow();
launchEventHandler.startHandlingLaunchEvents(); launchEventHandler.startHandlingLaunchEvents();
autoUnlocker.tryUnlockForTimespan(2, TimeUnit.MINUTES); autoUnlocker.tryUnlockForTimespan(2, TimeUnit.MINUTES);
} }
@@ -8,19 +8,21 @@ package org.cryptomator.ui.fxapp;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import org.cryptomator.ui.error.ErrorComponent; import org.cryptomator.ui.error.ErrorComponent;
import org.cryptomator.ui.health.HealthCheckComponent;
import org.cryptomator.ui.lock.LockComponent; import org.cryptomator.ui.lock.LockComponent;
import org.cryptomator.ui.mainwindow.MainWindowComponent; import org.cryptomator.ui.mainwindow.MainWindowComponent;
import org.cryptomator.ui.preferences.PreferencesComponent; import org.cryptomator.ui.preferences.PreferencesComponent;
import org.cryptomator.ui.quit.QuitComponent; import org.cryptomator.ui.quit.QuitComponent;
import org.cryptomator.ui.traymenu.TrayMenuComponent; import org.cryptomator.ui.traymenu.TrayMenuComponent;
import org.cryptomator.ui.unlock.UnlockComponent; import org.cryptomator.ui.unlock.UnlockComponent;
import org.cryptomator.ui.updatereminder.UpdateReminderComponent;
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@Module(includes = {UpdateCheckerModule.class}, subcomponents = {TrayMenuComponent.class, MainWindowComponent.class, PreferencesComponent.class, UnlockComponent.class, LockComponent.class, QuitComponent.class, ErrorComponent.class}) @Module(includes = {UpdateCheckerModule.class}, subcomponents = {TrayMenuComponent.class, MainWindowComponent.class, PreferencesComponent.class, VaultOptionsComponent.class, UnlockComponent.class, LockComponent.class, QuitComponent.class, ErrorComponent.class, HealthCheckComponent.class, UpdateReminderComponent.class})
abstract class FxApplicationModule { abstract class FxApplicationModule {
private static Image createImageFromResource(String resourceName) throws IOException { private static Image createImageFromResource(String resourceName) throws IOException {
@@ -52,4 +54,5 @@ abstract class FxApplicationModule {
static QuitComponent provideQuitComponent(QuitComponent.Builder builder) { static QuitComponent provideQuitComponent(QuitComponent.Builder builder) {
return builder.build(); return builder.build();
} }
} }
@@ -36,8 +36,8 @@ public class FxApplicationStyle {
} }
public void initialize() { public void initialize() {
settings.theme().addListener(this::appThemeChanged); settings.theme.addListener(this::appThemeChanged);
loadSelectedStyleSheet(settings.theme().get()); loadSelectedStyleSheet(settings.theme.get());
} }
private void appThemeChanged(@SuppressWarnings("unused") ObservableValue<? extends UiTheme> observable, @SuppressWarnings("unused") UiTheme oldValue, UiTheme newValue) { private void appThemeChanged(@SuppressWarnings("unused") ObservableValue<? extends UiTheme> observable, @SuppressWarnings("unused") UiTheme oldValue, UiTheme newValue) {
@@ -107,7 +107,7 @@ public class FxApplicationTerminator {
if (allowQuitWithoutPrompt.get()) { if (allowQuitWithoutPrompt.get()) {
exitingResponse.performQuit(); exitingResponse.performQuit();
} else if (settings.autoCloseVaults().get() && !preventQuitWithGracefulLock.get()) { } else if (settings.autoCloseVaults.get() && !preventQuitWithGracefulLock.get()) {
var lockAllTask = vaultService.createLockAllTask(vaults.filtered(Vault::isUnlocked), false); var lockAllTask = vaultService.createLockAllTask(vaults.filtered(Vault::isUnlocked), false);
lockAllTask.setOnSucceeded(event -> { lockAllTask.setOnSucceeded(event -> {
LOG.info("Locked remaining vaults was succesful."); LOG.info("Locked remaining vaults was succesful.");
@@ -13,6 +13,9 @@ import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.cryptomator.ui.quit.QuitComponent; import org.cryptomator.ui.quit.QuitComponent;
import org.cryptomator.ui.unlock.UnlockComponent; import org.cryptomator.ui.unlock.UnlockComponent;
import org.cryptomator.ui.unlock.UnlockWorkflow; import org.cryptomator.ui.unlock.UnlockWorkflow;
import org.cryptomator.ui.updatereminder.UpdateReminderComponent;
import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab;
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -43,22 +46,36 @@ public class FxApplicationWindows {
private final Lazy<PreferencesComponent> preferencesWindow; private final Lazy<PreferencesComponent> preferencesWindow;
private final QuitComponent.Builder quitWindowBuilder; private final QuitComponent.Builder quitWindowBuilder;
private final UnlockComponent.Factory unlockWorkflowFactory; private final UnlockComponent.Factory unlockWorkflowFactory;
private final UpdateReminderComponent.Factory updateReminderWindowBuilder;
private final LockComponent.Factory lockWorkflowFactory; private final LockComponent.Factory lockWorkflowFactory;
private final ErrorComponent.Factory errorWindowFactory; private final ErrorComponent.Factory errorWindowFactory;
private final ExecutorService executor; private final ExecutorService executor;
private final VaultOptionsComponent.Factory vaultOptionsWindow;
private final FilteredList<Window> visibleWindows; private final FilteredList<Window> visibleWindows;
@Inject @Inject
public FxApplicationWindows(@PrimaryStage Stage primaryStage, Optional<TrayIntegrationProvider> trayIntegration, Lazy<MainWindowComponent> mainWindow, Lazy<PreferencesComponent> preferencesWindow, QuitComponent.Builder quitWindowBuilder, UnlockComponent.Factory unlockWorkflowFactory, LockComponent.Factory lockWorkflowFactory, ErrorComponent.Factory errorWindowFactory, ExecutorService executor) { public FxApplicationWindows(@PrimaryStage Stage primaryStage,
Optional<TrayIntegrationProvider> trayIntegration, //
Lazy<MainWindowComponent> mainWindow, //
Lazy<PreferencesComponent> preferencesWindow, //
QuitComponent.Builder quitWindowBuilder, //
UnlockComponent.Factory unlockWorkflowFactory, //
UpdateReminderComponent.Factory updateReminderWindowBuilder, //
LockComponent.Factory lockWorkflowFactory, //
ErrorComponent.Factory errorWindowFactory, //
VaultOptionsComponent.Factory vaultOptionsWindow, //
ExecutorService executor) {
this.primaryStage = primaryStage; this.primaryStage = primaryStage;
this.trayIntegration = trayIntegration; this.trayIntegration = trayIntegration;
this.mainWindow = mainWindow; this.mainWindow = mainWindow;
this.preferencesWindow = preferencesWindow; this.preferencesWindow = preferencesWindow;
this.quitWindowBuilder = quitWindowBuilder; this.quitWindowBuilder = quitWindowBuilder;
this.unlockWorkflowFactory = unlockWorkflowFactory; this.unlockWorkflowFactory = unlockWorkflowFactory;
this.updateReminderWindowBuilder = updateReminderWindowBuilder;
this.lockWorkflowFactory = lockWorkflowFactory; this.lockWorkflowFactory = lockWorkflowFactory;
this.errorWindowFactory = errorWindowFactory; this.errorWindowFactory = errorWindowFactory;
this.executor = executor; this.executor = executor;
this.vaultOptionsWindow = vaultOptionsWindow;
this.visibleWindows = Window.getWindows().filtered(Window::isShowing); this.visibleWindows = Window.getWindows().filtered(Window::isShowing);
} }
@@ -105,10 +122,18 @@ public class FxApplicationWindows {
return CompletableFuture.supplyAsync(() -> preferencesWindow.get().showPreferencesWindow(selectedTab), Platform::runLater).whenComplete(this::reportErrors); return CompletableFuture.supplyAsync(() -> preferencesWindow.get().showPreferencesWindow(selectedTab), Platform::runLater).whenComplete(this::reportErrors);
} }
public CompletionStage<Stage> showVaultOptionsWindow(Vault vault, SelectedVaultOptionsTab tab) {
return showMainWindow().thenApplyAsync((window) -> vaultOptionsWindow.create(vault).showVaultOptionsWindow(tab), Platform::runLater).whenComplete(this::reportErrors);
}
public void showQuitWindow(QuitResponse response, boolean forced) { public void showQuitWindow(QuitResponse response, boolean forced) {
CompletableFuture.runAsync(() -> quitWindowBuilder.build().showQuitWindow(response,forced), Platform::runLater); CompletableFuture.runAsync(() -> quitWindowBuilder.build().showQuitWindow(response,forced), Platform::runLater);
} }
public void checkAndShowUpdateReminderWindow() {
CompletableFuture.runAsync(() -> updateReminderWindowBuilder.create().checkAndShowUpdateReminderWindow(), Platform::runLater);
}
public CompletionStage<Void> startUnlockWorkflow(Vault vault, @Nullable Stage owner) { public CompletionStage<Void> startUnlockWorkflow(Vault vault, @Nullable Stage owner) {
return CompletableFuture.supplyAsync(() -> { return CompletableFuture.supplyAsync(() -> {
Preconditions.checkState(vault.stateProperty().transition(VaultState.Value.LOCKED, VaultState.Value.PROCESSING), "Vault not locked."); Preconditions.checkState(vault.stateProperty().transition(VaultState.Value.LOCKED, VaultState.Value.PROCESSING), "Vault not locked.");
@@ -38,7 +38,7 @@ public class UpdateChecker {
} }
public void automaticallyCheckForUpdatesIfEnabled() { public void automaticallyCheckForUpdatesIfEnabled() {
if (settings.checkForUpdates().get()) { if (settings.checkForUpdates.get()) {
startCheckingForUpdates(AUTOCHECK_DELAY); startCheckingForUpdates(AUTOCHECK_DELAY);
} }
} }
@@ -70,7 +70,7 @@ public abstract class UpdateCheckerModule {
@Named("checkForUpdatesInterval") @Named("checkForUpdatesInterval")
@FxApplicationScoped @FxApplicationScoped
static ObjectBinding<Duration> provideCheckForUpdateInterval(Settings settings) { static ObjectBinding<Duration> provideCheckForUpdateInterval(Settings settings) {
return Bindings.when(settings.checkForUpdates()).then(UPDATE_CHECK_INTERVAL).otherwise(DISABLED_UPDATE_CHECK_INTERVAL); return Bindings.when(settings.checkForUpdates).then(UPDATE_CHECK_INTERVAL).otherwise(DISABLED_UPDATE_CHECK_INTERVAL);
} }
@Provides @Provides
@@ -1,9 +1,7 @@
package org.cryptomator.ui.fxapp; package org.cryptomator.ui.fxapp;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import org.apache.commons.lang3.SystemUtils; import org.apache.commons.lang3.SystemUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -11,20 +9,16 @@ import org.slf4j.LoggerFactory;
import javafx.concurrent.Task; import javafx.concurrent.Task;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.http.HttpClient; import java.net.http.HttpClient;
import java.net.http.HttpRequest; import java.net.http.HttpRequest;
import java.net.http.HttpResponse; import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Map;
public class UpdateCheckerTask extends Task<String> { public class UpdateCheckerTask extends Task<String> {
private static final ObjectMapper JSON = new ObjectMapper();
private static final Logger LOG = LoggerFactory.getLogger(UpdateCheckerTask.class); private static final Logger LOG = LoggerFactory.getLogger(UpdateCheckerTask.class);
private static final long MAX_RESPONSE_SIZE = 10L * 1024; // 10kb should be sufficient. protect against flooding private static final long MAX_RESPONSE_SIZE = 10L * 1024; // 10kb should be sufficient. protect against flooding
private static final Gson GSON = new GsonBuilder().setLenient().create();
private final HttpClient httpClient; private final HttpClient httpClient;
private final HttpRequest checkForUpdatesRequest; private final HttpRequest checkForUpdatesRequest;
@@ -48,16 +42,14 @@ public class UpdateCheckerTask extends Task<String> {
private String processBody(HttpResponse<InputStream> response) throws IOException { private String processBody(HttpResponse<InputStream> response) throws IOException {
try (InputStream in = response.body(); // try (InputStream in = response.body(); //
InputStream limitedIn = ByteStreams.limit(in, MAX_RESPONSE_SIZE); // InputStream limitedIn = ByteStreams.limit(in, MAX_RESPONSE_SIZE)) {
Reader reader = new InputStreamReader(limitedIn, StandardCharsets.UTF_8)) { var json = JSON.reader().readTree(limitedIn);
Map<String, String> map = GSON.fromJson(reader, new TypeToken<Map<String, String>>() {
}.getType());
if (SystemUtils.IS_OS_MAC_OSX) { if (SystemUtils.IS_OS_MAC_OSX) {
return map.get("mac"); return json.get("mac").asText();
} else if (SystemUtils.IS_OS_WINDOWS) { } else if (SystemUtils.IS_OS_WINDOWS) {
return map.get("win"); return json.get("win").asText();
} else if (SystemUtils.IS_OS_LINUX) { } else if (SystemUtils.IS_OS_LINUX) {
return map.get("linux"); return json.get("linux").asText();
} else { } else {
throw new IllegalStateException("Unsupported operating system"); throw new IllegalStateException("Unsupported operating system");
} }
@@ -1,6 +1,6 @@
package org.cryptomator.ui.keyloading.hub; package org.cryptomator.ui.keyloading.hub;
import com.google.gson.JsonParser; import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.coffeelibs.tinyoauth2client.AuthFlow; import io.github.coffeelibs.tinyoauth2client.AuthFlow;
import io.github.coffeelibs.tinyoauth2client.TinyOAuth2; import io.github.coffeelibs.tinyoauth2client.TinyOAuth2;
import io.github.coffeelibs.tinyoauth2client.http.response.Response; import io.github.coffeelibs.tinyoauth2client.http.response.Response;
@@ -12,6 +12,8 @@ import java.util.function.Consumer;
class AuthFlowTask extends Task<String> { class AuthFlowTask extends Task<String> {
private static final ObjectMapper JSON = new ObjectMapper();
private final HubConfig hubConfig; private final HubConfig hubConfig;
private final AuthFlowContext authFlowContext; private final AuthFlowContext authFlowContext;
private final Consumer<URI> redirectUriConsumer; private final Consumer<URI> redirectUriConsumer;
@@ -39,8 +41,7 @@ class AuthFlowTask extends Task<String> {
if (response.statusCode() != 200) { if (response.statusCode() != 200) {
throw new NotOkResponseException("Authorization returned status code " + response.statusCode()); throw new NotOkResponseException("Authorization returned status code " + response.statusCode());
} }
var json = JsonParser.parseString(response.body()); return JSON.reader().readTree(response.body()).get("access_token").asText();
return json.getAsJsonObject().get("access_token").getAsString();
} }
public static class NotOkResponseException extends RuntimeException { public static class NotOkResponseException extends RuntimeException {
@@ -1,9 +1,5 @@
package org.cryptomator.ui.keyloading.hub; package org.cryptomator.ui.keyloading.hub;
class CreateDeviceDto { record CreateDeviceDto(String id, String name, String publicKey) {
public String id;
public String name;
public String publicKey;
} }
@@ -1,14 +1,10 @@
package org.cryptomator.ui.keyloading.hub; package org.cryptomator.ui.keyloading.hub;
import com.google.common.io.CharStreams; import com.google.common.io.CharStreams;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.Reader;
import java.net.http.HttpResponse; import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@@ -1,6 +1,9 @@
package org.cryptomator.ui.keyloading.hub; package org.cryptomator.ui.keyloading.hub;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
// needs to be accessible by JSON decoder // needs to be accessible by JSON decoder
@JsonIgnoreProperties(ignoreUnknown = true)
public class HubConfig { public class HubConfig {
public String clientId; public String clientId;
@@ -2,9 +2,9 @@ package org.cryptomator.ui.keyloading.hub;
import com.auth0.jwt.JWT; import com.auth0.jwt.JWT;
import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.BaseEncoding; import com.google.common.io.BaseEncoding;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.nimbusds.jose.JWEObject; import com.nimbusds.jose.JWEObject;
import dagger.Lazy; import dagger.Lazy;
import org.cryptomator.common.settings.DeviceKey; import org.cryptomator.common.settings.DeviceKey;
@@ -46,7 +46,7 @@ import java.util.concurrent.atomic.AtomicReference;
public class RegisterDeviceController implements FxController { public class RegisterDeviceController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(RegisterDeviceController.class); private static final Logger LOG = LoggerFactory.getLogger(RegisterDeviceController.class);
private static final Gson GSON = new GsonBuilder().setLenient().create(); private static final ObjectMapper JSON = new ObjectMapper().setDefaultLeniency(true);
private static final List<Integer> EXPECTED_RESPONSE_CODES = List.of(201, 409); private static final List<Integer> EXPECTED_RESPONSE_CODES = List.of(201, 409);
private final Stage window; private final Stage window;
@@ -101,11 +101,8 @@ public class RegisterDeviceController implements FxController {
var keyUri = URI.create(hubConfig.devicesResourceUrl + deviceId); var keyUri = URI.create(hubConfig.devicesResourceUrl + deviceId);
var deviceKey = keyPair.getPublic().getEncoded(); var deviceKey = keyPair.getPublic().getEncoded();
var dto = new CreateDeviceDto(); var dto = new CreateDeviceDto(deviceId, deviceNameField.getText(), BaseEncoding.base64Url().omitPadding().encode(deviceKey));
dto.id = deviceId; var json = toJson(dto);
dto.name = deviceNameField.getText();
dto.publicKey = BaseEncoding.base64Url().omitPadding().encode(deviceKey);
var json = GSON.toJson(dto); // TODO: do we want to keep GSON? doesn't support records -.-
var request = HttpRequest.newBuilder(keyUri) // var request = HttpRequest.newBuilder(keyUri) //
.header("Authorization", "Bearer " + bearerToken) // .header("Authorization", "Bearer " + bearerToken) //
.header("Content-Type", "application/json").PUT(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8)) // .header("Content-Type", "application/json").PUT(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8)) //
@@ -127,6 +124,14 @@ public class RegisterDeviceController implements FxController {
}, Platform::runLater); }, Platform::runLater);
} }
private String toJson(CreateDeviceDto dto) {
try {
return JSON.writer().writeValueAsString(dto);
} catch (JacksonException e) {
throw new IllegalStateException("Failed to serialize DTO", e);
}
}
private void handleResponse(HttpResponse<Void> voidHttpResponse) { private void handleResponse(HttpResponse<Void> voidHttpResponse) {
assert EXPECTED_RESPONSE_CODES.contains(voidHttpResponse.statusCode()); assert EXPECTED_RESPONSE_CODES.contains(voidHttpResponse.statusCode());
@@ -19,7 +19,6 @@ import org.cryptomator.ui.health.HealthCheckComponent;
import org.cryptomator.ui.migration.MigrationComponent; import org.cryptomator.ui.migration.MigrationComponent;
import org.cryptomator.ui.removevault.RemoveVaultComponent; import org.cryptomator.ui.removevault.RemoveVaultComponent;
import org.cryptomator.ui.stats.VaultStatisticsComponent; import org.cryptomator.ui.stats.VaultStatisticsComponent;
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent; import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
import javax.inject.Named; import javax.inject.Named;
@@ -33,7 +32,7 @@ import javafx.stage.StageStyle;
import java.util.Map; import java.util.Map;
import java.util.ResourceBundle; import java.util.ResourceBundle;
@Module(subcomponents = {AddVaultWizardComponent.class, HealthCheckComponent.class, MigrationComponent.class, RemoveVaultComponent.class, VaultOptionsComponent.class, VaultStatisticsComponent.class, WrongFileAlertComponent.class, ErrorComponent.class}) @Module(subcomponents = {AddVaultWizardComponent.class, MigrationComponent.class, RemoveVaultComponent.class, VaultStatisticsComponent.class, WrongFileAlertComponent.class, ErrorComponent.class})
abstract class MainWindowModule { abstract class MainWindowModule {
@Provides @Provides
@@ -49,7 +49,7 @@ public class MainWindowTitleController implements FxController {
this.updateAvailable = updateChecker.latestVersionProperty().isNotNull(); this.updateAvailable = updateChecker.latestVersionProperty().isNotNull();
this.licenseHolder = licenseHolder; this.licenseHolder = licenseHolder;
this.settings = settings; this.settings = settings;
this.showMinimizeButton = Bindings.createBooleanBinding(this::isShowMinimizeButton, settings.showMinimizeButton(), settings.showTrayIcon()); this.showMinimizeButton = Bindings.createBooleanBinding(this::isShowMinimizeButton, settings.showMinimizeButton, settings.showTrayIcon);
} }
@FXML @FXML
@@ -85,10 +85,10 @@ public class MainWindowTitleController implements FxController {
} }
private void saveWindowSettings() { private void saveWindowSettings() {
settings.windowYPositionProperty().setValue(window.getY()); settings.windowXPosition.setValue(window.getX());
settings.windowXPositionProperty().setValue(window.getX()); settings.windowYPosition.setValue(window.getY());
settings.windowWidthProperty().setValue(window.getWidth()); settings.windowWidth.setValue(window.getWidth());
settings.windowHeightProperty().setValue(window.getHeight()); settings.windowHeight.setValue(window.getHeight());
} }
@FXML @FXML
@@ -139,7 +139,7 @@ public class MainWindowTitleController implements FxController {
} }
public ReadOnlyBooleanProperty debugModeEnabledProperty() { public ReadOnlyBooleanProperty debugModeEnabledProperty() {
return settings.debugMode(); return settings.debugMode;
} }
public boolean isDebugModeEnabled() { public boolean isDebugModeEnabled() {
@@ -152,6 +152,6 @@ public class MainWindowTitleController implements FxController {
public boolean isShowMinimizeButton() { public boolean isShowMinimizeButton() {
// always show the minimize button if no tray icon is present OR it is explicitly enabled // always show the minimize button if no tray icon is present OR it is explicitly enabled
return !trayMenuInitialized || settings.showMinimizeButton().get(); return !trayMenuInitialized || settings.showMinimizeButton.get();
} }
} }
@@ -54,7 +54,7 @@ public class ResizeController implements FxController {
LOG.trace("init ResizeController"); LOG.trace("init ResizeController");
if (neverTouched()) { if (neverTouched()) {
settings.displayConfigurationProperty().setValue(getMonitorSizes()); settings.displayConfiguration.set(getMonitorSizes());
return; return;
} else { } else {
if (didDisplayConfigurationChange()) { if (didDisplayConfigurationChange()) {
@@ -65,24 +65,24 @@ public class ResizeController implements FxController {
window.setWidth(window.getMinWidth()); window.setWidth(window.getMinWidth());
window.setHeight(window.getMinHeight()); window.setHeight(window.getMinHeight());
} else { } else {
window.setHeight(settings.windowHeightProperty().get() > window.getMinHeight() ? settings.windowHeightProperty().get() : window.getMinHeight()); window.setHeight(settings.windowHeight.get() > window.getMinHeight() ? settings.windowHeight.get() : window.getMinHeight());
window.setWidth(settings.windowWidthProperty().get() > window.getMinWidth() ? settings.windowWidthProperty().get() : window.getMinWidth()); window.setWidth(settings.windowWidth.get() > window.getMinWidth() ? settings.windowWidth.get() : window.getMinWidth());
window.setX(settings.windowXPositionProperty().get()); window.setX(settings.windowXPosition.get());
window.setY(settings.windowYPositionProperty().get()); window.setY(settings.windowYPosition.get());
} }
} }
savePositionalSettings(); savePositionalSettings();
} }
private boolean neverTouched() { private boolean neverTouched() {
return (settings.windowHeightProperty().get() == 0) && (settings.windowWidthProperty().get() == 0) && (settings.windowXPositionProperty().get() == 0) && (settings.windowYPositionProperty().get() == 0); return (settings.windowHeight.get() == 0) && (settings.windowWidth.get() == 0) && (settings.windowXPosition.get() == 0) && (settings.windowYPosition.get() == 0);
} }
private boolean didDisplayConfigurationChange() { private boolean didDisplayConfigurationChange() {
String currentDisplayConfiguration = getMonitorSizes(); String currentDisplayConfiguration = getMonitorSizes();
String settingsDisplayConfiguration = settings.displayConfigurationProperty().get(); String settingsDisplayConfiguration = settings.displayConfiguration.get();
boolean configurationHasChanged = !settingsDisplayConfiguration.equals(currentDisplayConfiguration); boolean configurationHasChanged = !settingsDisplayConfiguration.equals(currentDisplayConfiguration);
if (configurationHasChanged) settings.displayConfigurationProperty().setValue(currentDisplayConfiguration); if (configurationHasChanged) settings.displayConfiguration.set(currentDisplayConfiguration);
return configurationHasChanged; return configurationHasChanged;
} }
@@ -170,10 +170,10 @@ public class ResizeController implements FxController {
@FXML @FXML
public void savePositionalSettings() { public void savePositionalSettings() {
settings.windowHeightProperty().setValue(window.getHeight()); settings.windowWidth.setValue(window.getWidth());
settings.windowWidthProperty().setValue(window.getWidth()); settings.windowHeight.setValue(window.getHeight());
settings.windowYPositionProperty().setValue(window.getY()); settings.windowXPosition.setValue(window.getX());
settings.windowXPositionProperty().setValue(window.getX()); settings.windowYPosition.setValue(window.getY());
} }
public BooleanBinding showResizingArrowsProperty() { public BooleanBinding showResizingArrowsProperty() {
@@ -50,7 +50,7 @@ public class VaultDetailMissingVaultController implements FxController {
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(resourceBundle.getString("addvaultwizard.existing.filePickerMimeDesc"), CRYPTOMATOR_FILENAME_GLOB)); fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(resourceBundle.getString("addvaultwizard.existing.filePickerMimeDesc"), CRYPTOMATOR_FILENAME_GLOB));
File masterkeyFile = fileChooser.showOpenDialog(window); File masterkeyFile = fileChooser.showOpenDialog(window);
if (masterkeyFile != null) { if (masterkeyFile != null) {
vault.get().getVaultSettings().path().setValue(masterkeyFile.toPath().toAbsolutePath().getParent()); vault.get().getVaultSettings().path.setValue(masterkeyFile.toPath().toAbsolutePath().getParent());
recheck(); recheck();
} }
} }
@@ -55,17 +55,17 @@ public class GeneralPreferencesController implements FxController {
@FXML @FXML
public void initialize() { public void initialize() {
startHiddenCheckbox.selectedProperty().bindBidirectional(settings.startHidden()); startHiddenCheckbox.selectedProperty().bindBidirectional(settings.startHidden);
autoCloseVaultsCheckbox.selectedProperty().bindBidirectional(settings.autoCloseVaults()); autoCloseVaultsCheckbox.selectedProperty().bindBidirectional(settings.autoCloseVaults);
debugModeCheckbox.selectedProperty().bindBidirectional(settings.debugMode()); debugModeCheckbox.selectedProperty().bindBidirectional(settings.debugMode);
autoStartProvider.ifPresent(autoStart -> autoStartCheckbox.setSelected(autoStart.isEnabled())); autoStartProvider.ifPresent(autoStart -> autoStartCheckbox.setSelected(autoStart.isEnabled()));
var keychainSettingsConverter = new KeychainProviderClassNameConverter(keychainAccessProviders); var keychainSettingsConverter = new KeychainProviderClassNameConverter(keychainAccessProviders);
keychainBackendChoiceBox.getItems().addAll(keychainAccessProviders); keychainBackendChoiceBox.getItems().addAll(keychainAccessProviders);
keychainBackendChoiceBox.setValue(keychainSettingsConverter.fromString(settings.keychainProvider().get())); keychainBackendChoiceBox.setValue(keychainSettingsConverter.fromString(settings.keychainProvider.get()));
keychainBackendChoiceBox.setConverter(new KeychainProviderDisplayNameConverter()); keychainBackendChoiceBox.setConverter(new KeychainProviderDisplayNameConverter());
Bindings.bindBidirectional(settings.keychainProvider(), keychainBackendChoiceBox.valueProperty(), keychainSettingsConverter); Bindings.bindBidirectional(settings.keychainProvider, keychainBackendChoiceBox.valueProperty(), keychainSettingsConverter);
useKeychainCheckbox.selectedProperty().bindBidirectional(settings.useKeychain()); useKeychainCheckbox.selectedProperty().bindBidirectional(settings.useKeychain);
keychainBackendChoiceBox.disableProperty().bind(useKeychainCheckbox.selectedProperty().not()); keychainBackendChoiceBox.disableProperty().bind(useKeychainCheckbox.selectedProperty().not());
} }
@@ -57,22 +57,22 @@ public class InterfacePreferencesController implements FxController {
@FXML @FXML
public void initialize() { public void initialize() {
themeChoiceBox.getItems().addAll(UiTheme.applicableValues()); themeChoiceBox.getItems().addAll(UiTheme.applicableValues());
if (!themeChoiceBox.getItems().contains(settings.theme().get())) { if (!themeChoiceBox.getItems().contains(settings.theme.get())) {
settings.theme().set(UiTheme.LIGHT); settings.theme.set(UiTheme.LIGHT);
} }
themeChoiceBox.valueProperty().bindBidirectional(settings.theme()); themeChoiceBox.valueProperty().bindBidirectional(settings.theme);
themeChoiceBox.setConverter(new UiThemeConverter(resourceBundle)); themeChoiceBox.setConverter(new UiThemeConverter(resourceBundle));
showMinimizeButtonCheckbox.selectedProperty().bindBidirectional(settings.showMinimizeButton()); showMinimizeButtonCheckbox.selectedProperty().bindBidirectional(settings.showMinimizeButton);
showTrayIconCheckbox.selectedProperty().bindBidirectional(settings.showTrayIcon()); showTrayIconCheckbox.selectedProperty().bindBidirectional(settings.showTrayIcon);
preferredLanguageChoiceBox.getItems().addAll(supportedLanguages.getLanguageTags()); preferredLanguageChoiceBox.getItems().addAll(supportedLanguages.getLanguageTags());
preferredLanguageChoiceBox.valueProperty().bindBidirectional(settings.languageProperty()); preferredLanguageChoiceBox.valueProperty().bindBidirectional(settings.language);
preferredLanguageChoiceBox.setConverter(new LanguageTagConverter(resourceBundle)); preferredLanguageChoiceBox.setConverter(new LanguageTagConverter(resourceBundle));
nodeOrientationLtr.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.LEFT_TO_RIGHT); nodeOrientationLtr.setSelected(settings.userInterfaceOrientation.get() == NodeOrientation.LEFT_TO_RIGHT);
nodeOrientationRtl.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.RIGHT_TO_LEFT); nodeOrientationRtl.setSelected(settings.userInterfaceOrientation.get() == NodeOrientation.RIGHT_TO_LEFT);
nodeOrientation.selectedToggleProperty().addListener(this::toggleNodeOrientation); nodeOrientation.selectedToggleProperty().addListener(this::toggleNodeOrientation);
} }
@@ -87,9 +87,9 @@ public class InterfacePreferencesController implements FxController {
private void toggleNodeOrientation(@SuppressWarnings("unused") ObservableValue<? extends Toggle> observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) { private void toggleNodeOrientation(@SuppressWarnings("unused") ObservableValue<? extends Toggle> observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) {
if (nodeOrientationLtr.equals(newValue)) { if (nodeOrientationLtr.equals(newValue)) {
settings.userInterfaceOrientation().set(NodeOrientation.LEFT_TO_RIGHT); settings.userInterfaceOrientation.set(NodeOrientation.LEFT_TO_RIGHT);
} else if (nodeOrientationRtl.equals(newValue)) { } else if (nodeOrientationRtl.equals(newValue)) {
settings.userInterfaceOrientation().set(NodeOrientation.RIGHT_TO_LEFT); settings.userInterfaceOrientation.set(NodeOrientation.RIGHT_TO_LEFT);
} else { } else {
LOG.warn("Unexpected toggle option {}", newValue); LOG.warn("Unexpected toggle option {}", newValue);
} }
@@ -48,7 +48,7 @@ public class SupporterCertificateController implements FxController {
private void registrationKeyChanged(@SuppressWarnings("unused") ObservableValue<? extends String> observable, @SuppressWarnings("unused") String oldValue, String newValue) { private void registrationKeyChanged(@SuppressWarnings("unused") ObservableValue<? extends String> observable, @SuppressWarnings("unused") String oldValue, String newValue) {
licenseHolder.validateAndStoreLicense(newValue); licenseHolder.validateAndStoreLicense(newValue);
if (!licenseHolder.isValidLicense()) { if (!licenseHolder.isValidLicense()) {
settings.theme().set(UiTheme.LIGHT); settings.theme.set(UiTheme.LIGHT);
} }
} }
@@ -42,7 +42,7 @@ public class UpdatesPreferencesController implements FxController {
} }
public void initialize() { public void initialize() {
checkForUpdatesCheckbox.selectedProperty().bindBidirectional(settings.checkForUpdates()); checkForUpdatesCheckbox.selectedProperty().bindBidirectional(settings.checkForUpdates);
} }
@FXML @FXML
@@ -27,6 +27,8 @@ import java.util.concurrent.atomic.AtomicReference;
public class VolumePreferencesController implements FxController { public class VolumePreferencesController implements FxController {
private static final String DOCS_MOUNTING_URL = "https://docs.cryptomator.org/en/1.7/desktop/volume-type/"; private static final String DOCS_MOUNTING_URL = "https://docs.cryptomator.org/en/1.7/desktop/volume-type/";
private static final int MIN_PORT = 1024;
private static final int MAX_PORT = 65535;
private final Settings settings; private final Settings settings;
private final ObservableValue<MountService> selectedMountService; private final ObservableValue<MountService> selectedMountService;
@@ -51,7 +53,7 @@ public class VolumePreferencesController implements FxController {
this.resourceBundle = resourceBundle; this.resourceBundle = resourceBundle;
var fallbackProvider = mountProviders.stream().findFirst().orElse(null); var fallbackProvider = mountProviders.stream().findFirst().orElse(null);
this.selectedMountService = ObservableUtil.mapWithDefault(settings.mountService(), serviceName -> mountProviders.stream().filter(s -> s.getClass().getName().equals(serviceName)).findFirst().orElse(fallbackProvider), fallbackProvider); this.selectedMountService = ObservableUtil.mapWithDefault(settings.mountService, serviceName -> mountProviders.stream().filter(s -> s.getClass().getName().equals(serviceName)).findFirst().orElse(fallbackProvider), fallbackProvider);
this.loopbackPortSupported = BooleanExpression.booleanExpression(selectedMountService.map(s -> s.hasCapability(MountCapability.LOOPBACK_PORT))); this.loopbackPortSupported = BooleanExpression.booleanExpression(selectedMountService.map(s -> s.hasCapability(MountCapability.LOOPBACK_PORT)));
this.mountToDirSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_WITHIN_EXISTING_PARENT) || s.hasCapability(MountCapability.MOUNT_TO_EXISTING_DIR)); this.mountToDirSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_WITHIN_EXISTING_PARENT) || s.hasCapability(MountCapability.MOUNT_TO_EXISTING_DIR));
this.mountToDriveLetterSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_AS_DRIVE_LETTER)); this.mountToDriveLetterSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_AS_DRIVE_LETTER));
@@ -69,15 +71,15 @@ public class VolumePreferencesController implements FxController {
volumeTypeChoiceBox.getItems().add(null); volumeTypeChoiceBox.getItems().add(null);
volumeTypeChoiceBox.getItems().addAll(mountProviders); volumeTypeChoiceBox.getItems().addAll(mountProviders);
volumeTypeChoiceBox.setConverter(new MountServiceConverter()); volumeTypeChoiceBox.setConverter(new MountServiceConverter());
boolean autoSelected = settings.mountService().get() == null; boolean autoSelected = settings.mountService.get() == null;
volumeTypeChoiceBox.getSelectionModel().select(autoSelected ? null : selectedMountService.getValue()); volumeTypeChoiceBox.getSelectionModel().select(autoSelected ? null : selectedMountService.getValue());
volumeTypeChoiceBox.valueProperty().addListener((observableValue, oldProvider, newProvider) -> { volumeTypeChoiceBox.valueProperty().addListener((observableValue, oldProvider, newProvider) -> {
var toSet = Optional.ofNullable(newProvider).map(nP -> nP.getClass().getName()).orElse(null); var toSet = Optional.ofNullable(newProvider).map(nP -> nP.getClass().getName()).orElse(null);
settings.mountService().set(toSet); settings.mountService.set(toSet);
}); });
loopbackPortField.setText(String.valueOf(settings.port().get())); loopbackPortField.setText(String.valueOf(settings.port.get()));
loopbackPortApplyButton.visibleProperty().bind(settings.port().asString().isNotEqualTo(loopbackPortField.textProperty())); loopbackPortApplyButton.visibleProperty().bind(settings.port.asString().isNotEqualTo(loopbackPortField.textProperty()));
loopbackPortApplyButton.disableProperty().bind(Bindings.createBooleanBinding(this::validateLoopbackPort, loopbackPortField.textProperty()).not()); loopbackPortApplyButton.disableProperty().bind(Bindings.createBooleanBinding(this::validateLoopbackPort, loopbackPortField.textProperty()).not());
} }
@@ -85,7 +87,7 @@ public class VolumePreferencesController implements FxController {
try { try {
int port = Integer.parseInt(loopbackPortField.getText()); int port = Integer.parseInt(loopbackPortField.getText());
return port == 0 // choose port automatically return port == 0 // choose port automatically
|| port >= Settings.MIN_PORT && port <= Settings.MAX_PORT; // port within range || port >= MIN_PORT && port <= MAX_PORT; // port within range
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
return false; return false;
} }
@@ -93,7 +95,7 @@ public class VolumePreferencesController implements FxController {
public void doChangeLoopbackPort() { public void doChangeLoopbackPort() {
if (validateLoopbackPort()) { if (validateLoopbackPort()) {
settings.port().set(Integer.parseInt(loopbackPortField.getText())); settings.port.set(Integer.parseInt(loopbackPortField.getText()));
} }
} }
@@ -1,5 +1,6 @@
package org.cryptomator.ui.unlock; package org.cryptomator.ui.unlock;
import org.cryptomator.common.mount.MountPointInUseException;
import org.cryptomator.common.mount.MountPointNotExistsException; import org.cryptomator.common.mount.MountPointNotExistsException;
import org.cryptomator.common.mount.MountPointNotSupportedException; import org.cryptomator.common.mount.MountPointNotSupportedException;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
@@ -7,6 +8,7 @@ import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.controls.FormattedLabel; import org.cryptomator.ui.controls.FormattedLabel;
import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.preferences.SelectedPreferencesTab; import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab;
import javax.inject.Inject; import javax.inject.Inject;
import javafx.fxml.FXML; import javafx.fxml.FXML;
@@ -20,9 +22,10 @@ public class UnlockInvalidMountPointController implements FxController {
private final Stage window; private final Stage window;
private final Vault vault; private final Vault vault;
private final AtomicReference<Throwable> unlockException;
private final FxApplicationWindows appWindows; private final FxApplicationWindows appWindows;
private final ResourceBundle resourceBundle; private final ResourceBundle resourceBundle;
private final ExceptionType exceptionType;
private final String exceptionMessage;
public FormattedLabel dialogDescription; public FormattedLabel dialogDescription;
@@ -30,21 +33,18 @@ public class UnlockInvalidMountPointController implements FxController {
UnlockInvalidMountPointController(@UnlockWindow Stage window, @UnlockWindow Vault vault, @UnlockWindow AtomicReference<Throwable> unlockException, FxApplicationWindows appWindows, ResourceBundle resourceBundle) { UnlockInvalidMountPointController(@UnlockWindow Stage window, @UnlockWindow Vault vault, @UnlockWindow AtomicReference<Throwable> unlockException, FxApplicationWindows appWindows, ResourceBundle resourceBundle) {
this.window = window; this.window = window;
this.vault = vault; this.vault = vault;
this.unlockException = unlockException;
this.appWindows = appWindows; this.appWindows = appWindows;
this.resourceBundle = resourceBundle; this.resourceBundle = resourceBundle;
var exc = unlockException.get();
this.exceptionType = getExceptionType(exc);
this.exceptionMessage = exc.getMessage();
} }
@FXML @FXML
public void initialize() { public void initialize() {
var e = unlockException.get(); dialogDescription.setFormat(resourceBundle.getString(exceptionType.translationKey));
var translationKey = switch (e) { dialogDescription.setArg1(exceptionMessage);
case MountPointNotSupportedException x -> "unlock.error.customPath.description.notSupported";
case MountPointNotExistsException x -> "unlock.error.customPath.description.notExists";
default -> "unlock.error.customPath.description.generic";
};
dialogDescription.setFormat(resourceBundle.getString(translationKey));
dialogDescription.setArg1(e.getMessage());
} }
@FXML @FXML
@@ -58,4 +58,51 @@ public class UnlockInvalidMountPointController implements FxController {
window.close(); window.close();
} }
@FXML
public void closeAndOpenVaultOptions() {
appWindows.showVaultOptionsWindow(vault, SelectedVaultOptionsTab.MOUNT);
window.close();
}
private ExceptionType getExceptionType(Throwable unlockException) {
return switch (unlockException) {
case MountPointNotSupportedException x -> ExceptionType.NOT_SUPPORTED;
case MountPointNotExistsException x -> ExceptionType.NOT_EXISTING;
case MountPointInUseException x -> ExceptionType.IN_USE;
default -> ExceptionType.GENERIC;
};
}
private enum ExceptionType {
NOT_SUPPORTED("unlock.error.customPath.description.notSupported", ButtonAction.SHOW_PREFERENCES),
NOT_EXISTING("unlock.error.customPath.description.notExists", ButtonAction.SHOW_VAULT_OPTIONS),
IN_USE("unlock.error.customPath.description.inUse", ButtonAction.SHOW_VAULT_OPTIONS),
GENERIC("unlock.error.customPath.description.generic", ButtonAction.SHOW_PREFERENCES);
private final String translationKey;
private final ButtonAction action;
ExceptionType(String translationKey, ButtonAction action) {
this.translationKey = translationKey;
this.action = action;
}
}
private enum ButtonAction {
SHOW_PREFERENCES,
SHOW_VAULT_OPTIONS;
}
/* Getter */
public boolean isShowPreferences() {
return exceptionType.action == ButtonAction.SHOW_PREFERENCES;
}
public boolean isShowVaultOptions() {
return exceptionType.action == ButtonAction.SHOW_VAULT_OPTIONS;
}
} }
@@ -50,7 +50,7 @@ public class UnlockSuccessController implements FxController {
LOG.trace("UnlockSuccessController.close()"); LOG.trace("UnlockSuccessController.close()");
window.close(); window.close();
if (rememberChoiceCheckbox.isSelected()) { if (rememberChoiceCheckbox.isSelected()) {
vault.getVaultSettings().actionAfterUnlock().setValue(WhenUnlocked.IGNORE); vault.getVaultSettings().actionAfterUnlock.setValue(WhenUnlocked.IGNORE);
} }
} }
@@ -73,7 +73,7 @@ public class UnlockSuccessController implements FxController {
}); });
executor.execute(revealTask); executor.execute(revealTask);
if (rememberChoiceCheckbox.isSelected()) { if (rememberChoiceCheckbox.isSelected()) {
vault.getVaultSettings().actionAfterUnlock().setValue(WhenUnlocked.REVEAL); vault.getVaultSettings().actionAfterUnlock.setValue(WhenUnlocked.REVEAL);
} }
} }
@@ -94,7 +94,7 @@ public class UnlockWorkflow extends Task<Boolean> {
protected void succeeded() { protected void succeeded() {
LOG.info("Unlock of '{}' succeeded.", vault.getDisplayName()); LOG.info("Unlock of '{}' succeeded.", vault.getDisplayName());
switch (vault.getVaultSettings().actionAfterUnlock().get()) { switch (vault.getVaultSettings().actionAfterUnlock.get()) {
case ASK -> Platform.runLater(() -> { case ASK -> Platform.runLater(() -> {
window.setScene(successScene.get()); window.setScene(successScene.get());
window.show(); window.show();
@@ -0,0 +1,38 @@
package org.cryptomator.ui.updatereminder;
import dagger.Lazy;
import dagger.Subcomponent;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.time.LocalDate;
@UpdateReminderScoped
@Subcomponent(modules = {UpdateReminderModule.class})
public interface UpdateReminderComponent {
@UpdateReminderWindow
Stage window();
@FxmlScene(FxmlFile.UPDATE_REMINDER)
Lazy<Scene> updateReminderScene();
Settings settings();
default void checkAndShowUpdateReminderWindow() {
if (LocalDate.parse(settings().lastUpdateCheck.get()).isBefore(LocalDate.now().minusDays(14)) && !settings().checkForUpdates.getValue()) {
Stage stage = window();
stage.setScene(updateReminderScene().get());
stage.sizeToScene();
stage.show();
}
}
@Subcomponent.Factory
interface Factory {
UpdateReminderComponent create();
}
}
@@ -0,0 +1,49 @@
package org.cryptomator.ui.updatereminder;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.UpdateChecker;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.stage.Stage;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
@UpdateReminderScoped
public class UpdateReminderController implements FxController {
private final Stage window;
private final Settings settings;
private final UpdateChecker updateChecker;
@Inject
UpdateReminderController(@UpdateReminderWindow Stage window, Settings settings, UpdateChecker updateChecker) {
this.window = window;
this.settings = settings;
this.updateChecker = updateChecker;
}
@FXML
public void cancel() {
settings.lastUpdateCheck.set(LocalDate.now().format(DateTimeFormatter.ISO_DATE));
window.close();
}
@FXML
public void once() {
settings.lastUpdateCheck.set(LocalDate.now().format(DateTimeFormatter.ISO_DATE));
updateChecker.checkForUpdatesNow();
window.close();
}
@FXML
public void automatically() {
settings.lastUpdateCheck.set(LocalDate.now().format(DateTimeFormatter.ISO_DATE));
updateChecker.checkForUpdatesNow();
settings.checkForUpdates.set(true);
window.close();
}
}
@@ -0,0 +1,59 @@
package org.cryptomator.ui.updatereminder;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
import org.cryptomator.ui.common.DefaultSceneFactory;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxControllerKey;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlLoaderFactory;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.StageFactory;
import javax.inject.Provider;
import javafx.scene.Scene;
import javafx.stage.Modality;
import javafx.stage.Stage;
import java.util.Map;
import java.util.ResourceBundle;
@Module
abstract class UpdateReminderModule {
@Provides
@UpdateReminderWindow
@UpdateReminderScoped
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
}
@Provides
@UpdateReminderWindow
@UpdateReminderScoped
static Stage provideStage(StageFactory factory, ResourceBundle resourceBundle) {
Stage stage = factory.create();
stage.setTitle(resourceBundle.getString("updateReminder.title"));
stage.setMinWidth(500);
stage.setMinHeight(100);
stage.initModality(Modality.APPLICATION_MODAL);
return stage;
}
@Provides
@FxmlScene(FxmlFile.UPDATE_REMINDER)
@UpdateReminderScoped
static Scene provideUpdateReminderScene(@UpdateReminderWindow FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.UPDATE_REMINDER);
}
// ------------------
@Binds
@IntoMap
@FxControllerKey(UpdateReminderController.class)
abstract FxController bindUpdateReminderController(UpdateReminderController controller);
}
@@ -0,0 +1,13 @@
package org.cryptomator.ui.updatereminder;
import javax.inject.Scope;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
@interface UpdateReminderScoped {
}
@@ -0,0 +1,14 @@
package org.cryptomator.ui.updatereminder;
import javax.inject.Qualifier;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Qualifier
@Documented
@Retention(RUNTIME)
@interface UpdateReminderWindow {
}
@@ -45,21 +45,21 @@ public class GeneralVaultOptionsController implements FxController {
@FXML @FXML
public void initialize() { public void initialize() {
vaultName.textProperty().set(vault.getVaultSettings().displayName().get()); vaultName.textProperty().set(vault.getVaultSettings().displayName.get());
vaultName.focusedProperty().addListener(this::trimVaultNameOnFocusLoss); vaultName.focusedProperty().addListener(this::trimVaultNameOnFocusLoss);
vaultName.setTextFormatter(new TextFormatter<>(this::checkVaultNameLength)); vaultName.setTextFormatter(new TextFormatter<>(this::checkVaultNameLength));
unlockOnStartupCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().unlockAfterStartup()); unlockOnStartupCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().unlockAfterStartup);
actionAfterUnlockChoiceBox.getItems().addAll(WhenUnlocked.values()); actionAfterUnlockChoiceBox.getItems().addAll(WhenUnlocked.values());
actionAfterUnlockChoiceBox.valueProperty().bindBidirectional(vault.getVaultSettings().actionAfterUnlock()); actionAfterUnlockChoiceBox.valueProperty().bindBidirectional(vault.getVaultSettings().actionAfterUnlock);
actionAfterUnlockChoiceBox.setConverter(new WhenUnlockedConverter(resourceBundle)); actionAfterUnlockChoiceBox.setConverter(new WhenUnlockedConverter(resourceBundle));
lockAfterTimeCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().autoLockWhenIdle()); lockAfterTimeCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().autoLockWhenIdle);
Bindings.bindBidirectional(lockTimeInMinutesTextField.textProperty(), vault.getVaultSettings().autoLockIdleSeconds(), new IdleTimeSecondsConverter()); Bindings.bindBidirectional(lockTimeInMinutesTextField.textProperty(), vault.getVaultSettings().autoLockIdleSeconds, new IdleTimeSecondsConverter());
} }
private void trimVaultNameOnFocusLoss(Observable observable, Boolean wasFocussed, Boolean isFocussed) { private void trimVaultNameOnFocusLoss(Observable observable, Boolean wasFocussed, Boolean isFocussed) {
if (!isFocussed) { if (!isFocussed) {
var trimmed = vaultName.getText().trim(); var trimmed = vaultName.getText().trim();
vault.getVaultSettings().displayName().set(trimmed); vault.getVaultSettings().displayName.set(trimmed);
} }
} }
@@ -74,18 +74,18 @@ public class MountOptionsController implements FxController {
this.mountpointDriveLetterSupported = mountService.map(as -> as.service().hasCapability(MountCapability.MOUNT_AS_DRIVE_LETTER)); this.mountpointDriveLetterSupported = mountService.map(as -> as.service().hasCapability(MountCapability.MOUNT_AS_DRIVE_LETTER));
this.mountFlagsSupported = mountService.map(as -> as.service().hasCapability(MountCapability.MOUNT_FLAGS)); this.mountFlagsSupported = mountService.map(as -> as.service().hasCapability(MountCapability.MOUNT_FLAGS));
this.readOnlySupported = mountService.map(as -> as.service().hasCapability(MountCapability.READ_ONLY)); this.readOnlySupported = mountService.map(as -> as.service().hasCapability(MountCapability.READ_ONLY));
this.directoryPath = vault.getVaultSettings().mountPoint().map(p -> isDriveLetter(p) ? null : p.toString()); this.directoryPath = vault.getVaultSettings().mountPoint.map(p -> isDriveLetter(p) ? null : p.toString());
this.applicationWindows = applicationWindows; this.applicationWindows = applicationWindows;
} }
@FXML @FXML
public void initialize() { public void initialize() {
// readonly: // readonly:
readOnlyCheckbox.selectedProperty().bindBidirectional(vaultSettings.usesReadOnlyMode()); readOnlyCheckbox.selectedProperty().bindBidirectional(vaultSettings.usesReadOnlyMode);
// custom mount flags: // custom mount flags:
mountFlagsField.disableProperty().bind(customMountFlagsCheckbox.selectedProperty().not()); mountFlagsField.disableProperty().bind(customMountFlagsCheckbox.selectedProperty().not());
customMountFlagsCheckbox.setSelected(!Strings.isNullOrEmpty(vaultSettings.mountFlags().getValue())); customMountFlagsCheckbox.setSelected(!Strings.isNullOrEmpty(vaultSettings.mountFlags.getValue()));
toggleUseCustomMountFlags(); toggleUseCustomMountFlags();
//driveLetter choice box //driveLetter choice box
@@ -93,14 +93,14 @@ public class MountOptionsController implements FxController {
driveLetterSelection.setConverter(new WinDriveLetterLabelConverter(windowsDriveLetters, resourceBundle)); driveLetterSelection.setConverter(new WinDriveLetterLabelConverter(windowsDriveLetters, resourceBundle));
//mountPoint toggle group //mountPoint toggle group
var mountPoint = vaultSettings.getMountPoint(); var mountPoint = vaultSettings.mountPoint.get();
if (mountPoint == null) { if (mountPoint == null) {
//prepare and select auto //prepare and select auto
mountPointToggleGroup.selectToggle(mountPointAutoBtn); mountPointToggleGroup.selectToggle(mountPointAutoBtn);
} else if (mountPoint.getParent() == null && isDriveLetter(mountPoint)) { } else if (mountPoint.getParent() == null && isDriveLetter(mountPoint)) {
//prepare and select drive letter //prepare and select drive letter
mountPointToggleGroup.selectToggle(mountPointDriveLetterBtn); mountPointToggleGroup.selectToggle(mountPointDriveLetterBtn);
driveLetterSelection.valueProperty().bindBidirectional(vaultSettings.mountPoint()); driveLetterSelection.valueProperty().bindBidirectional(vaultSettings.mountPoint);
} else { } else {
//prepare and select dir //prepare and select dir
mountPointToggleGroup.selectToggle(mountPointDirBtn); mountPointToggleGroup.selectToggle(mountPointDirBtn);
@@ -118,14 +118,14 @@ public class MountOptionsController implements FxController {
if (customMountFlagsCheckbox.isSelected()) { if (customMountFlagsCheckbox.isSelected()) {
readOnlyCheckbox.setSelected(false); // to prevent invalid states readOnlyCheckbox.setSelected(false); // to prevent invalid states
mountFlagsField.textProperty().unbind(); mountFlagsField.textProperty().unbind();
var mountFlags = vaultSettings.mountFlags().get(); var mountFlags = vaultSettings.mountFlags.get();
if (mountFlags == null || mountFlags.isBlank()) { if (mountFlags == null || mountFlags.isBlank()) {
vaultSettings.mountFlags().set(defaultMountFlags.getValue()); vaultSettings.mountFlags.set(defaultMountFlags.getValue());
} }
mountFlagsField.textProperty().bindBidirectional(vaultSettings.mountFlags()); mountFlagsField.textProperty().bindBidirectional(vaultSettings.mountFlags);
} else { } else {
mountFlagsField.textProperty().unbindBidirectional(vaultSettings.mountFlags()); mountFlagsField.textProperty().unbindBidirectional(vaultSettings.mountFlags);
vaultSettings.mountFlags().set(null); vaultSettings.mountFlags.set(null);
mountFlagsField.textProperty().bind(defaultMountFlags); mountFlagsField.textProperty().bind(defaultMountFlags);
} }
} }
@@ -134,7 +134,7 @@ public class MountOptionsController implements FxController {
public void chooseCustomMountPoint() { public void chooseCustomMountPoint() {
try { try {
Path chosenPath = chooseCustomMountPointInternal(); Path chosenPath = chooseCustomMountPointInternal();
vaultSettings.mountPoint().set(chosenPath); vaultSettings.mountPoint.set(chosenPath);
} catch (NoDirSelectedException e) { } catch (NoDirSelectedException e) {
//no-op //no-op
} }
@@ -151,7 +151,7 @@ public class MountOptionsController implements FxController {
DirectoryChooser directoryChooser = new DirectoryChooser(); DirectoryChooser directoryChooser = new DirectoryChooser();
directoryChooser.setTitle(resourceBundle.getString("vaultOptions.mount.mountPoint.directoryPickerTitle")); directoryChooser.setTitle(resourceBundle.getString("vaultOptions.mount.mountPoint.directoryPickerTitle"));
try { try {
var mp = vaultSettings.mountPoint().get(); var mp = vaultSettings.mountPoint.get();
var initialDir = mp != null && !isDriveLetter(mp) ? mp : Path.of(System.getProperty("user.home")); var initialDir = mp != null && !isDriveLetter(mp) ? mp : Path.of(System.getProperty("user.home"));
if (Files.isDirectory(initialDir)) { if (Files.isDirectory(initialDir)) {
@@ -170,13 +170,13 @@ public class MountOptionsController implements FxController {
private void selectedToggleChanged(ObservableValue<? extends Toggle> observable, Toggle oldToggle, Toggle newToggle) { private void selectedToggleChanged(ObservableValue<? extends Toggle> observable, Toggle oldToggle, Toggle newToggle) {
//Remark: the mountpoint corresponding to the newToggle must be null, otherwise it would not be new! //Remark: the mountpoint corresponding to the newToggle must be null, otherwise it would not be new!
driveLetterSelection.valueProperty().unbindBidirectional(vaultSettings.mountPoint()); driveLetterSelection.valueProperty().unbindBidirectional(vaultSettings.mountPoint);
if (mountPointDriveLetterBtn.equals(newToggle)) { if (mountPointDriveLetterBtn.equals(newToggle)) {
vaultSettings.mountPoint().set(windowsDriveLetters.getFirstDesiredAvailable().orElse(windowsDriveLetters.getAll().stream().findAny().get())); vaultSettings.mountPoint.set(windowsDriveLetters.getFirstDesiredAvailable().orElse(windowsDriveLetters.getAll().stream().findAny().get()));
driveLetterSelection.valueProperty().bindBidirectional(vaultSettings.mountPoint()); driveLetterSelection.valueProperty().bindBidirectional(vaultSettings.mountPoint);
} else if (mountPointDirBtn.equals(newToggle)) { } else if (mountPointDirBtn.equals(newToggle)) {
try { try {
vaultSettings.mountPoint().set(chooseCustomMountPointInternal()); vaultSettings.mountPoint.set(chooseCustomMountPointInternal());
} catch (NoDirSelectedException e) { } catch (NoDirSelectedException e) {
if (oldToggle != null && !mountPointDirBtn.equals(oldToggle)) { if (oldToggle != null && !mountPointDirBtn.equals(oldToggle)) {
mountPointToggleGroup.selectToggle(oldToggle); mountPointToggleGroup.selectToggle(oldToggle);
@@ -185,7 +185,7 @@ public class MountOptionsController implements FxController {
} }
} }
} else { } else {
vaultSettings.mountPoint().set(null); vaultSettings.mountPoint.set(null);
} }
} }
@@ -28,12 +28,13 @@ public interface VaultOptionsComponent {
ObjectProperty<SelectedVaultOptionsTab> selectedTabProperty(); ObjectProperty<SelectedVaultOptionsTab> selectedTabProperty();
default void showVaultOptionsWindow(SelectedVaultOptionsTab selectedTab) { default Stage showVaultOptionsWindow(SelectedVaultOptionsTab selectedTab) {
selectedTabProperty().set(selectedTab); selectedTabProperty().set(selectedTab);
Stage stage = window(); Stage stage = window();
stage.setScene(scene().get()); stage.setScene(scene().get());
stage.show(); stage.show();
stage.requestFocus(); stage.requestFocus();
return stage;
} }
@Subcomponent.Factory @Subcomponent.Factory
@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import org.cryptomator.ui.controls.NumericTextField?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Hyperlink?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.addvaultwizard.CreateNewVaultExpertSettingsController"
prefWidth="450"
prefHeight="450"
spacing="12"
alignment="CENTER_LEFT">
<padding>
<Insets topRightBottomLeft="24"/>
</padding>
<children>
<Region prefHeight="12" VBox.vgrow="NEVER"/>
<Label fx:id="vaultNameLabel" alignment="CENTER_RIGHT" graphicTextGap="6" wrapText="true">
<graphic>
<FontAwesome5IconView styleClass="glyph-icon-muted" wrappingWidth="12" glyph="PENCIL"/>
</graphic>
</Label>
<Label fx:id="vaultPathLabel" alignment="CENTER_RIGHT" graphicTextGap="6" wrapText="true">
<graphic>
<FontAwesome5IconView styleClass="glyph-icon-muted" wrappingWidth="12" glyph="HDD"/>
</graphic>
</Label>
<Region prefHeight="12" VBox.vgrow="NEVER"/>
<CheckBox fx:id="expertSettingsCheckBox" text="%addvaultwizard.new.expertSettings.enableExpertSettingsCheckbox" onAction="#toggleUseExpertSettings"/>
<VBox spacing="6" visible="${expertSettingsCheckBox.selected}">
<HBox spacing="2" HBox.hgrow="NEVER">
<Label text="%addvaultwizard.new.expertSettings.shorteningThreshold.title"/>
<Region prefWidth="2"/>
<Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#openDocs">
<graphic>
<FontAwesome5IconView glyph="QUESTION_CIRCLE" styleClass="glyph-icon-muted"/>
</graphic>
<tooltip>
<Tooltip text="%addvaultwizard.new.expertSettings.shorteningThreshold.tooltip" showDelay="10ms"/>
</tooltip>
</Hyperlink>
</HBox>
<NumericTextField fx:id="shorteningThresholdTextField"/>
<HBox alignment="TOP_RIGHT">
<Region minWidth="4" prefWidth="4" HBox.hgrow="NEVER"/>
<StackPane>
<Label styleClass="label-muted" text="%addvaultwizard.new.expertSettings.shorteningThreshold.invalid" textAlignment="RIGHT" alignment="CENTER_RIGHT" visible="${!controller.validShorteningThreshold}" managed="${!controller.validShorteningThreshold}" graphicTextGap="6">
<graphic>
<FontAwesome5IconView styleClass="glyph-icon-red" glyph="TIMES"/>
</graphic>
</Label>
<Label styleClass="label-muted" text="%addvaultwizard.new.expertSettings.shorteningThreshold.valid" textAlignment="RIGHT" alignment="CENTER_RIGHT" visible="${controller.validShorteningThreshold}" managed="${controller.validShorteningThreshold}" graphicTextGap="6">
<graphic>
<FontAwesome5IconView styleClass="glyph-icon-primary" glyph="CHECK"/>
</graphic>
</Label>
</StackPane>
</HBox>
</VBox>
<Region VBox.vgrow="ALWAYS"/>
<ButtonBar buttonMinWidth="120" buttonOrder="B+X">
<buttons>
<Button text="%generic.button.back" ButtonBar.buttonData="BACK_PREVIOUS" onAction="#back"/>
<Button text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" onAction="#next" defaultButton="true" disable="${!controller.validShorteningThreshold}"/>
</buttons>
</ButtonBar>
</children>
</VBox>
@@ -20,8 +20,8 @@
alignment="CENTER_LEFT"> alignment="CENTER_LEFT">
<fx:define> <fx:define>
<ToggleGroup fx:id="locationPresetsToggler"/> <ToggleGroup fx:id="locationPresetsToggler"/>
<FontAwesome5IconView fx:id="badLocation" styleClass="glyph-icon-red" glyph="TIMES" /> <FontAwesome5IconView fx:id="badLocation" styleClass="glyph-icon-red" glyph="TIMES"/>
<FontAwesome5IconView fx:id="goodLocation" styleClass="glyph-icon-primary" glyph="CHECK" /> <FontAwesome5IconView fx:id="goodLocation" styleClass="glyph-icon-primary" glyph="CHECK"/>
</fx:define> </fx:define>
<padding> <padding>
<Insets topRightBottomLeft="24"/> <Insets topRightBottomLeft="24"/>
@@ -47,7 +47,7 @@
<VBox spacing="6"> <VBox spacing="6">
<Label text="%addvaultwizard.new.locationLabel" labelFor="$locationTextField"/> <Label text="%addvaultwizard.new.locationLabel" labelFor="$locationTextField"/>
<TextField fx:id="locationTextField" promptText="%addvaultwizard.new.locationPrompt" text="${controller.vaultPath}" editable="false" disable="${!controller.anyRadioButtonSelected}" HBox.hgrow="ALWAYS"/> <TextField fx:id="locationTextField" promptText="%addvaultwizard.new.locationPrompt" text="${controller.vaultPath}" editable="false" disable="${!controller.anyRadioButtonSelected}" HBox.hgrow="ALWAYS"/>
<Label fx:id="locationStatusLabel" alignment="CENTER_RIGHT" wrapText="true" visible="${controller.anyRadioButtonSelected}" maxWidth="Infinity" graphicTextGap="6" /> <Label fx:id="locationStatusLabel" alignment="CENTER_RIGHT" wrapText="true" visible="${controller.anyRadioButtonSelected}" maxWidth="Infinity" graphicTextGap="6"/>
</VBox> </VBox>
<Region VBox.vgrow="ALWAYS"/> <Region VBox.vgrow="ALWAYS"/>
+21 -12
View File
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?> <?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import org.cryptomator.ui.controls.FontAwesome5Spinner?>
<?import org.cryptomator.ui.controls.FormattedLabel?> <?import org.cryptomator.ui.controls.FormattedLabel?>
<?import javafx.geometry.Insets?> <?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?> <?import javafx.scene.control.Button?>
@@ -31,20 +32,28 @@
</StackPane> </StackPane>
<VBox spacing="6" HBox.hgrow="ALWAYS"> <VBox spacing="6" HBox.hgrow="ALWAYS">
<FormattedLabel styleClass="label-extra-large" format="%error.message" arg1="${controller.errorCode}"/> <FormattedLabel styleClass="label-extra-large" format="%error.message" arg1="${controller.errorCode}"/>
<Label text="%error.description" wrapText="true"/> <FontAwesome5Spinner glyphSize="24" visible="${controller.isLoadingHttpResponse}" managed="${controller.isLoadingHttpResponse}"/>
<Hyperlink styleClass="hyperlink-underline" text="%error.hyperlink.lookup" onAction="#searchError" contentDisplay="LEFT"> <VBox visible="${!controller.isLoadingHttpResponse}" managed="${!controller.isLoadingHttpResponse}">
<graphic> <Label text="%error.existingSolutionDescription" wrapText="true" visible="${controller.errorSolutionFound}" managed="${controller.errorSolutionFound}"/>
<FontAwesome5IconView glyph="LINK" glyphSize="12"/> <Hyperlink styleClass="hyperlink-underline" text="%error.hyperlink.solution" onAction="#showSolution" contentDisplay="LEFT" visible="${controller.errorSolutionFound}" managed="${controller.errorSolutionFound}">
</graphic> <graphic>
</Hyperlink> <FontAwesome5IconView glyph="LINK" glyphSize="12"/>
<Hyperlink styleClass="hyperlink-underline" text="%error.hyperlink.report" onAction="#reportError" contentDisplay="LEFT"> </graphic>
<graphic> </Hyperlink>
<FontAwesome5IconView glyph="LINK" glyphSize="12"/> <Label text="%error.description" wrapText="true" visible="${!controller.errorSolutionFound}" managed="${!controller.errorSolutionFound}"/>
</graphic> <Hyperlink styleClass="hyperlink-underline" text="%error.hyperlink.lookup" onAction="#searchError" contentDisplay="LEFT" visible="${!controller.errorSolutionFound}" managed="${!controller.errorSolutionFound}">
</Hyperlink> <graphic>
<FontAwesome5IconView glyph="LINK" glyphSize="12"/>
</graphic>
</Hyperlink>
<Hyperlink styleClass="hyperlink-underline" text="%error.hyperlink.report" onAction="#reportError" contentDisplay="LEFT" visible="${!controller.errorSolutionFound}" managed="${!controller.errorSolutionFound}">
<graphic>
<FontAwesome5IconView glyph="LINK" glyphSize="12"/>
</graphic>
</Hyperlink>
</VBox>
</VBox> </VBox>
</HBox> </HBox>
<VBox spacing="6" VBox.vgrow="ALWAYS"> <VBox spacing="6" VBox.vgrow="ALWAYS">
<HBox> <HBox>
<Label text="%error.technicalDetails"/> <Label text="%error.technicalDetails"/>
@@ -46,7 +46,8 @@
<ButtonBar buttonMinWidth="120" buttonOrder="+CI"> <ButtonBar buttonMinWidth="120" buttonOrder="+CI">
<buttons> <buttons>
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/> <Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
<Button text="%hub.noKeychain.openBtn" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#closeAndOpenPreferences"/> <Button text="%hub.noKeychain.openBtn" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#closeAndOpenPreferences" visible="${controller.showPreferences}" managed="${controller.showPreferences}"/>
<Button text="%main.vaultDetail.optionsBtn" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#closeAndOpenVaultOptions" visible="${controller.showVaultOptions}" managed="${controller.showVaultOptions}"/>
</buttons> </buttons>
</ButtonBar> </ButtonBar>
</VBox> </VBox>
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.Group?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?>
<HBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.updatereminder.UpdateReminderController"
minWidth="500"
prefWidth="500"
minHeight="145"
spacing="12"
alignment="TOP_LEFT">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<Group>
<StackPane>
<padding>
<Insets topRightBottomLeft="6"/>
</padding>
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="QUESTION" glyphSize="24"/>
</StackPane>
</Group>
<VBox HBox.hgrow="ALWAYS">
<Label styleClass="label-large" text="%updateReminder.message" wrapText="true">
<padding>
<Insets bottom="6" top="6"/>
</padding>
</Label>
<Label text="%updateReminder.description" wrapText="true"/>
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
<ButtonBar buttonMinWidth="120" buttonOrder="+CY" >
<buttons>
<Button text="%updateReminder.notNow" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#cancel"/>
<Button text="%updateReminder.yesOnce" ButtonBar.buttonData="YES" onAction="#once"/>
<Button text="%updateReminder.yesAutomatically" ButtonBar.buttonData="YES" defaultButton="true" onAction="#automatically"/>
</buttons>
</ButtonBar>
</VBox>
</children>
</HBox>
+19 -1
View File
@@ -21,6 +21,9 @@ error.description=Cryptomator didn't expect this to happen. You can look up exis
error.hyperlink.lookup=Look up this error error.hyperlink.lookup=Look up this error
error.hyperlink.report=Report this error error.hyperlink.report=Report this error
error.technicalDetails=Details: error.technicalDetails=Details:
error.existingSolutionDescription=Cryptomator didn't expect this to happen. But we found an existing solution for this error. Please take a look at the following link.
error.hyperlink.solution=Look up the solution
# Defaults # Defaults
defaults.vault.vaultName=Vault defaults.vault.vaultName=Vault
@@ -60,6 +63,12 @@ addvaultwizard.new.validCharacters.message=The vault name may contain the follow
addvaultwizard.new.validCharacters.chars=Word characters (e.g. a, ж or 수) addvaultwizard.new.validCharacters.chars=Word characters (e.g. a, ж or 수)
addvaultwizard.new.validCharacters.numbers=Numbers addvaultwizard.new.validCharacters.numbers=Numbers
addvaultwizard.new.validCharacters.dashes=Hyphen (%s) or underscore (%s) addvaultwizard.new.validCharacters.dashes=Hyphen (%s) or underscore (%s)
### Expert Settings
addvaultwizard.new.expertSettings.enableExpertSettingsCheckbox=Enable expert settings
addvaultwizard.new.expertSettings.shorteningThreshold.invalid=Enter a value between 36 and 220 (default 220)
addvaultwizard.new.expertSettings.shorteningThreshold.tooltip=Open the documentation to learn more.
addvaultwizard.new.expertSettings.shorteningThreshold.title=Maximum length of encrypted file names
addvaultwizard.new.expertSettings.shorteningThreshold.valid=Valid
### Password ### Password
addvaultwizard.new.createVaultBtn=Create Vault addvaultwizard.new.createVaultBtn=Create Vault
addvaultwizard.new.generateRecoveryKeyChoice=You won't be able to access your data without your password. Do you want a recovery key for the case you lose your password? addvaultwizard.new.generateRecoveryKeyChoice=You won't be able to access your data without your password. Do you want a recovery key for the case you lose your password?
@@ -127,6 +136,7 @@ unlock.success.revealBtn=Reveal Drive
unlock.error.customPath.message=Unable to mount vault to custom path unlock.error.customPath.message=Unable to mount vault to custom path
unlock.error.customPath.description.notSupported=If you wish to keep using the custom path, please go to the preferences and select a volume type that supports it. Otherwise, go to the vault options and choose a supported mount point. unlock.error.customPath.description.notSupported=If you wish to keep using the custom path, please go to the preferences and select a volume type that supports it. Otherwise, go to the vault options and choose a supported mount point.
unlock.error.customPath.description.notExists=The custom mount path does not exist. Either create it in your local filesystem or change it in the vault options. unlock.error.customPath.description.notExists=The custom mount path does not exist. Either create it in your local filesystem or change it in the vault options.
unlock.error.customPath.description.inUse=Drive letter "%s" is already in use.
unlock.error.customPath.description.generic=You have selected a custom mount path for this vault, but using it failed with the message: %s unlock.error.customPath.description.generic=You have selected a custom mount path for this vault, but using it failed with the message: %s
## Hub ## Hub
hub.noKeychain.message=Unable to access device key hub.noKeychain.message=Unable to access device key
@@ -488,4 +498,12 @@ quit.lockAndQuitBtn=Lock and Quit
# Forced Quit # Forced Quit
quit.forced.message=Some vaults could not be locked quit.forced.message=Some vaults could not be locked
quit.forced.description=Locking vaults was blocked by pending operations or open files. You can force lock remaining vaults, however interrupting I/O may result in the loss of unsaved data. quit.forced.description=Locking vaults was blocked by pending operations or open files. You can force lock remaining vaults, however interrupting I/O may result in the loss of unsaved data.
quit.forced.forceAndQuitBtn=Force and Quit quit.forced.forceAndQuitBtn=Force and Quit
# Update Reminder
updateReminder.title=Update Check
updateReminder.message=Check for Updates?
updateReminder.description=Stay updated with new features, bug fixes, and security improvements. We recommend to automatically check for updates.
updateReminder.notNow=Not Now
updateReminder.yesOnce=Yes, Once
updateReminder.yesAutomatically=Yes, Automatically
+59 -8
View File
@@ -4,7 +4,7 @@
## Button ## Button
generic.button.apply=تطبيق generic.button.apply=تطبيق
generic.button.back=رجوع generic.button.back=رجوع
generic.button.cancel=الغاء generic.button.cancel=إلغاء
generic.button.change=تغيير generic.button.change=تغيير
generic.button.choose=اختر… generic.button.choose=اختر…
generic.button.close=إغلاق generic.button.close=إغلاق
@@ -16,13 +16,20 @@ generic.button.print=طباعة
# Error # Error
error.message=حدث خطأ ما error.message=حدث خطأ ما
error.description=لم يتوقع "كريبتوماتور" حدوث ذلك. يمكنك البحث عن الحلول الموجودة لهذا الخطأ. وإذا لم يتم الإبلاغ عنه بعد، لا تتردد في فعل ذلك.
error.hyperlink.lookup=ابحث عن هذا الخطأ
error.hyperlink.report=أبلغ عن هذا الخطأ
error.technicalDetails=التفاصيل:
error.existingSolutionDescription=لم يتوقع "كريبتوماتور" حدوث ذلك. لكننا وجدنا حلا لهذا الخطأ. يرجى إلقاء نظرة على الرابط التالي.
error.hyperlink.solution=ابحث عن الحل
# Defaults # Defaults
defaults.vault.vaultName=مخزن defaults.vault.vaultName=الخزينة
# Tray Menu # Tray Menu
traymenu.showMainWindow=اظهار traymenu.showMainWindow=عرض
traymenu.showPreferencesWindow=تفضيلات traymenu.showPreferencesWindow=التفضيلات
traymenu.lockAllVaults=تأمين الكل traymenu.lockAllVaults=تأمين الكل
traymenu.quitApplication=انهاء traymenu.quitApplication=انهاء
traymenu.vault.unlock=افتح traymenu.vault.unlock=افتح
@@ -37,7 +44,7 @@ addvaultwizard.welcome.existingButton=افتح مخزن موجود
## New ## New
### Name ### Name
addvaultwizard.new.nameInstruction=اختر اسم للمخزن addvaultwizard.new.nameInstruction=اختر اسم للمخزن
addvaultwizard.new.namePrompt=اسم المخزن addvaultwizard.new.namePrompt=اسم الخزينة
### Location ### Location
addvaultwizard.new.locationInstruction=أين يجب على Cryptomator تخزين الملفات المشفرة للمخزن الخاص بك؟ addvaultwizard.new.locationInstruction=أين يجب على Cryptomator تخزين الملفات المشفرة للمخزن الخاص بك؟
addvaultwizard.new.locationLabel=موقع التخزين addvaultwizard.new.locationLabel=موقع التخزين
@@ -49,6 +56,13 @@ addvaultwizard.new.fileAlreadyExists=ملف أو مجلد بنفس اسم الم
addvaultwizard.new.locationDoesNotExist=المجلد في المسار المحدد غير موجود أو لا يمكن الوصول إليه addvaultwizard.new.locationDoesNotExist=المجلد في المسار المحدد غير موجود أو لا يمكن الوصول إليه
addvaultwizard.new.locationIsNotWritable=لا يوجد صلاحيات للكتابة على المسار المحدد addvaultwizard.new.locationIsNotWritable=لا يوجد صلاحيات للكتابة على المسار المحدد
addvaultwizard.new.locationIsOk=الموقع المناسب للمخزن الخاص بك addvaultwizard.new.locationIsOk=الموقع المناسب للمخزن الخاص بك
addvaultwizard.new.invalidName=اسم المخزن غير صالح
addvaultwizard.new.validName=اسم المخزن صالح
addvaultwizard.new.validCharacters.message=قد يحتوي اسم المخزن على الأحرف التالية:
addvaultwizard.new.validCharacters.chars=أحرف الكلمات (أمثلة: a, ж, 수)
addvaultwizard.new.validCharacters.numbers=الأعداد
addvaultwizard.new.validCharacters.dashes=الشرطة (%s) أو الشرطة السفلية (%s)
### Expert Settings
### Password ### Password
addvaultwizard.new.createVaultBtn=إنشاء المخزن addvaultwizard.new.createVaultBtn=إنشاء المخزن
addvaultwizard.new.generateRecoveryKeyChoice=لن تتمكن من الوصول إلى بياناتك بدون كلمة المرور الخاصة بك. هل تريد مفتاح استرداد في حالة فقدان كلمة المرور الخاصة بك؟ addvaultwizard.new.generateRecoveryKeyChoice=لن تتمكن من الوصول إلى بياناتك بدون كلمة المرور الخاصة بك. هل تريد مفتاح استرداد في حالة فقدان كلمة المرور الخاصة بك؟
@@ -56,7 +70,7 @@ addvaultwizard.new.generateRecoveryKeyChoice.yes=نعم من فضلك، أن ت
addvaultwizard.new.generateRecoveryKeyChoice.no=لا شكراً، لن أفقد كلمة المرور الخاصة بي addvaultwizard.new.generateRecoveryKeyChoice.no=لا شكراً، لن أفقد كلمة المرور الخاصة بي
### Information ### Information
addvault.new.readme.storageLocation.fileName=هام.rtf addvault.new.readme.storageLocation.fileName=هام.rtf
addvault.new.readme.storageLocation.1=⚠️ ملفات الخزنة ⚠️ addvault.new.readme.storageLocation.1=⚠️ ملفات الخزينة ⚠️
addvault.new.readme.storageLocation.2=هذا هو موقع تخزين الخزنة الخاصة بك. addvault.new.readme.storageLocation.2=هذا هو موقع تخزين الخزنة الخاصة بك.
addvault.new.readme.storageLocation.3=لا تفعل addvault.new.readme.storageLocation.3=لا تفعل
addvault.new.readme.storageLocation.4=• تغيير أي ملفات داخل هذا الدليل أو addvault.new.readme.storageLocation.4=• تغيير أي ملفات داخل هذا الدليل أو
@@ -72,13 +86,17 @@ addvault.new.readme.accessLocation.2=هذا هو موقع الوصول للخر
addvault.new.readme.accessLocation.3=سيتم تشفير أي ملفات تضاف إلى هذا المجلد من قبل Cryptomator. يمكنك العمل عليه كأي مجلد آخر. هذه فقط طريقة عرض غير مشفرة لمحتوياتها، ملفاتك تبقى مشفرة على القرص الصلب الخاص بك طوال الوقت. addvault.new.readme.accessLocation.3=سيتم تشفير أي ملفات تضاف إلى هذا المجلد من قبل Cryptomator. يمكنك العمل عليه كأي مجلد آخر. هذه فقط طريقة عرض غير مشفرة لمحتوياتها، ملفاتك تبقى مشفرة على القرص الصلب الخاص بك طوال الوقت.
addvault.new.readme.accessLocation.4=لا تتردد في إزالة هذا الملف. addvault.new.readme.accessLocation.4=لا تتردد في إزالة هذا الملف.
## Existing ## Existing
addvaultwizard.existing.instruction=اختر ملف "vault.cryptomator" من خزينتك الحالية. إذا كان يوجد فقط ملف اسمه "masterkey.cryptomator"، اختره عوضاً عنه.
addvaultwizard.existing.chooseBtn=اختر… addvaultwizard.existing.chooseBtn=اختر…
addvaultwizard.existing.filePickerTitle=حدد مِلَفّ المخزن
addvaultwizard.existing.filePickerMimeDesc=مخزن Cryptomator
## Success ## Success
addvaultwizard.success.nextStepsInstructions=تم إضافة مخزن "%s".\nتحتاج إلى فتح هذا المخزن للوصول أو إضافة محتويات إليه. بدلاً من ذلك، يمكنك فتحه في أي وقت لاحق. addvaultwizard.success.nextStepsInstructions=تم إضافة مخزن "%s".\nتحتاج إلى فتح هذا المخزن للوصول أو إضافة محتويات إليه. بدلاً من ذلك، يمكنك فتحه في أي وقت لاحق.
addvaultwizard.success.unlockNow=افتح الان addvaultwizard.success.unlockNow=افتح الان
# Remove Vault # Remove Vault
removeVault.title=احذف الحافظة removeVault.title=احذف الحافظة
removeVault.message=حذف المخزن؟
removeVault.description=سيؤدي هذا إلى نسيان Cryptomator لهذا المخزن فقط. يمكنك إضافته مرة أخرى لاحقاً. لن يتم حذف أي من الملفات المشفرة من القرص الصلب الخاص بك. removeVault.description=سيؤدي هذا إلى نسيان Cryptomator لهذا المخزن فقط. يمكنك إضافته مرة أخرى لاحقاً. لن يتم حذف أي من الملفات المشفرة من القرص الصلب الخاص بك.
removeVault.confirmBtn=احذف الحافظة removeVault.confirmBtn=احذف الحافظة
@@ -93,30 +111,48 @@ forgetPassword.description=سيؤدي هذا إلى حذف كلمة المرور
forgetPassword.confirmBtn=نسيت كلمة المرور forgetPassword.confirmBtn=نسيت كلمة المرور
# Unlock # Unlock
unlock.title=فتح "%s"
unlock.passwordPrompt=‮أدخل كلمة السر لـ "%s": unlock.passwordPrompt=‮أدخل كلمة السر لـ "%s":
unlock.savePassword=تذكر كلمة المرور unlock.savePassword=تذكر كلمة المرور
unlock.unlockBtn=افتح unlock.unlockBtn=افتح
## Select ## Select
unlock.chooseMasterkey.message=لم يتم العثور على مفتاح التشفير الرئيسي
unlock.chooseMasterkey.description=لم يتمكن Cryptomator من العثور على مفتاح التشفير الرئيسي للمخزن "%s". الرجاء اختيار الملف يدوياً.
unlock.chooseMasterkey.filePickerTitle=اختر ملف الـ Masterkey unlock.chooseMasterkey.filePickerTitle=اختر ملف الـ Masterkey
unlock.chooseMasterkey.filePickerMimeDesc=مفتاح التشفير الرئيسي في Cryptomator
## Success ## Success
unlock.success.message=تم الفتح بنجاح
unlock.success.description=تم فتح "%s" بنجاح! يمكنك الآن الوصول لمخزنك عن طريق القرص الافتراضي الخاص به. unlock.success.description=تم فتح "%s" بنجاح! يمكنك الآن الوصول لمخزنك عن طريق القرص الافتراضي الخاص به.
unlock.success.rememberChoice=تذكر اختياري ولا تظهر هذا مرة أخرى unlock.success.rememberChoice=تذكر اختياري ولا تظهر هذا مرة أخرى
unlock.success.revealBtn=اظهار القرص unlock.success.revealBtn=اظهار القرص
## Failure ## Failure
unlock.error.customPath.description.inUse=حرف القرص "%s" قيد الاستخدام.
unlock.error.customPath.description.generic=لقد اخترت مسار تثبيت مخصص لهذه الخزينة، ولكن استخدامه فشل مع الرسالة: %s
## Hub ## Hub
hub.noKeychain.message=غير قادر على الوصول إلى مفتاح الجهاز
hub.noKeychain.openBtn=فتح التفضيلات
### Waiting ### Waiting
hub.auth.message=جاري الانتظار للمصادقة…
### Receive Key ### Receive Key
### Register Device ### Register Device
hub.register.message=اسم الجهاز متطلب
hub.register.nameLabel=اسم الجهاز
hub.register.occupiedMsg=الاسم مستخدم مسبقاً
hub.register.registerBtn=تأكيد hub.register.registerBtn=تأكيد
### Registration Success ### Registration Success
hub.registerSuccess.description=للدخول إلى الخزينة، يحتاج جهازك إلى إذن من مالك الخزينة.
### Registration Failed ### Registration Failed
hub.registerFailed.message=أخفق في تسمية الجهاز
### Unauthorized ### Unauthorized
hub.unauthorized.message=تم رفض الوصول
### License Exceeded ### License Exceeded
# Lock # Lock
## Force ## Force
lock.forced.message=فشلت عملية القفل
lock.forced.description=تم حظر قفل "%s" بواسطة العمليات المعلقة أو الملفات المفتوحة. يمكنك فرض قفل هذا المخزن، ولكن مقاطعة عمليات الادخال والاخراج I/O قد تؤدي لفقدان البيانات غير المحفوظة. lock.forced.description=تم حظر قفل "%s" بواسطة العمليات المعلقة أو الملفات المفتوحة. يمكنك فرض قفل هذا المخزن، ولكن مقاطعة عمليات الادخال والاخراج I/O قد تؤدي لفقدان البيانات غير المحفوظة.
lock.forced.retryBtn=اعد المحاولة lock.forced.retryBtn=اعد المحاولة
lock.forced.forceBtn=قفل قسري
## Failure ## Failure
lock.fail.message=فشلت عملية اقفال الخزنة. lock.fail.message=فشلت عملية اقفال الخزنة.
lock.fail.description=فشل عملية قفل %s". تأكد من حفظ العمل غير المحفوظ في مكان آخر وأن العمليات الهامة للقراءة/الكتابة قد انتهت. من أجل إغلاق المخزن، اقتل تطبيق Cryptomator. lock.fail.description=فشل عملية قفل %s". تأكد من حفظ العمل غير المحفوظ في مكان آخر وأن العمليات الهامة للقراءة/الكتابة قد انتهت. من أجل إغلاق المخزن، اقتل تطبيق Cryptomator.
@@ -152,10 +188,20 @@ migration.impossible.moreInfo=لا يزال ممكناً فتح المخزن ب
# Health Check # Health Check
## Start ## Start
health.intro.remarkFix=لا يمكن حل جميع المشاكل.
health.intro.affirmation=لقد قرأت وفهمت المعلومات الواردة أعلاه
## Start Failure ## Start Failure
health.fail.moreInfo=المزيد من المعلومات
## Check Selection ## Check Selection
health.checkList.selectAllButton=اختر جميع الفحوص
health.checkList.deselectAllButton=إلغاء اختيار جميع الفحوص
## Detail view ## Detail view
health.check.detail.fixAllSpecificBtn=إصلاح كل الأنواع
## Result view ## Result view
health.result.severityFilter.good=جيد
health.result.severityFilter.info=معلومات
health.result.severityFilter.warn=تحذير
health.result.severityFilter.crit=حرج
## Fix Application ## Fix Application
# Preferences # Preferences
@@ -196,6 +242,8 @@ stats.cacheHitRate=معدل استخدام الكاش
stats.read.throughput.idle=قراءة: خامل stats.read.throughput.idle=قراءة: خامل
stats.decr.total.data.none=تم فك تشفير البيانات:- stats.decr.total.data.none=تم فك تشفير البيانات:-
## Write ## Write
stats.write.total.data.mib=البيانات المكتوبة: %.1f م.ب
stats.write.total.data.gib=البيانات المكتوبة: %.1f ج.ب
stats.encr.total.data.none=البيانات المشفرة: - stats.encr.total.data.none=البيانات المشفرة: -
## Accesses ## Accesses
@@ -263,10 +311,11 @@ wrongFileAlert.link=لمزيد من المساعدة، قم بزيارة
## General ## General
vaultOptions.general=عام vaultOptions.general=عام
vaultOptions.general.vaultName=اسم المخزن vaultOptions.general.vaultName=اسم المخزن
vaultOptions.general.autoLock.lockAfterTimePart2=دقائق
vaultOptions.general.unlockAfterStartup=فتح قفل المخزن عند بدء تشغيل Cryptomator vaultOptions.general.unlockAfterStartup=فتح قفل المخزن عند بدء تشغيل Cryptomator
vaultOptions.general.actionAfterUnlock=بعد فتح القفل بنجاح vaultOptions.general.actionAfterUnlock=بعد فتح القفل بنجاح
vaultOptions.general.actionAfterUnlock.ignore=‮لا تفعل شيئاً vaultOptions.general.actionAfterUnlock.ignore=‮لا تفعل شيئاً
vaultOptions.general.actionAfterUnlock.reveal=اظهار القرص vaultOptions.general.actionAfterUnlock.reveal=إظهار القرص
vaultOptions.general.actionAfterUnlock.ask=اسأل vaultOptions.general.actionAfterUnlock.ask=اسأل
## Mount ## Mount
@@ -315,4 +364,6 @@ passwordStrength.messageLabel.4=قوية جداً
# Quit # Quit
quit.lockAndQuitBtn=قفل و خروج quit.lockAndQuitBtn=قفل و خروج
# Forced Quit # Forced Quit
# Update Reminder
+22 -1
View File
@@ -20,6 +20,9 @@ error.description=Cryptomator не чакаў такога павароту. Т
error.hyperlink.lookup=Шукаць гэтую памылку error.hyperlink.lookup=Шукаць гэтую памылку
error.hyperlink.report=Паведаміць пра гэтую памылку error.hyperlink.report=Паведаміць пра гэтую памылку
error.technicalDetails=Падрабязнасці: error.technicalDetails=Падрабязнасці:
error.existingSolutionDescription=Cryptomator не чакаў такога, але мы знайшлі, як можна выправіць гэтую хібу. Калі ласка, скарыстайся спасылкаю.
error.hyperlink.solution=Паглядзець на рашэнне
# Defaults # Defaults
defaults.vault.vaultName=Скарбніца defaults.vault.vaultName=Скарбніца
@@ -59,6 +62,7 @@ addvaultwizard.new.validCharacters.message=Нчзва скарбніцы мус
addvaultwizard.new.validCharacters.chars=Слоўныя знакі, накшталт a, ж або 수 addvaultwizard.new.validCharacters.chars=Слоўныя знакі, накшталт a, ж або 수
addvaultwizard.new.validCharacters.numbers=Лічбы addvaultwizard.new.validCharacters.numbers=Лічбы
addvaultwizard.new.validCharacters.dashes=Злучок (%s) або падкрэслінік (%s) addvaultwizard.new.validCharacters.dashes=Злучок (%s) або падкрэслінік (%s)
### Expert Settings
### Password ### Password
addvaultwizard.new.createVaultBtn=Стварыць скарбніцу addvaultwizard.new.createVaultBtn=Стварыць скарбніцу
addvaultwizard.new.generateRecoveryKeyChoice=Бяз гэтага пароля ты ня зможаш атрымаць доступ да сваіх даных. Ці хочаш ты мець ключ аднаўлення на выпадак, калі ты згубіш свой пароль? addvaultwizard.new.generateRecoveryKeyChoice=Бяз гэтага пароля ты ня зможаш атрымаць доступ да сваіх даных. Ці хочаш ты мець ключ аднаўлення на выпадак, калі ты згубіш свой пароль?
@@ -126,6 +130,7 @@ unlock.success.revealBtn=Паказаць дыск
unlock.error.customPath.message=Не магчыма змантажаваць скарбніцу да карыстальніцкай сцежкі unlock.error.customPath.message=Не магчыма змантажаваць скарбніцу да карыстальніцкай сцежкі
unlock.error.customPath.description.notSupported=Калі ты надалей жадаеш карыстацца адмысловаю сцежкаю, калі ласка, пайдзі ў налады ды абары там тып тому, які падтрымлівае яе. У іншым выпадку пайдзі ў опцыі скарбніцы ды абяры там пункт мантажавання, які падтрымліваецца. unlock.error.customPath.description.notSupported=Калі ты надалей жадаеш карыстацца адмысловаю сцежкаю, калі ласка, пайдзі ў налады ды абары там тып тому, які падтрымлівае яе. У іншым выпадку пайдзі ў опцыі скарбніцы ды абяры там пункт мантажавання, які падтрымліваецца.
unlock.error.customPath.description.notExists=Адмысловая сцежка мантажавання не існуе. Ствары яе ў сваёй файлавай сістэме, альбо змяні яе ў опцыях скарбніцы. unlock.error.customPath.description.notExists=Адмысловая сцежка мантажавання не існуе. Ствары яе ў сваёй файлавай сістэме, альбо змяні яе ў опцыях скарбніцы.
unlock.error.customPath.description.inUse=Дыскавая літара "%s" ужо выкарыстоўваецца.
unlock.error.customPath.description.generic=Ты абраў адмысловую сцежку мантажавання для гэтай скарбніцы, але скарыстацца ёю не ўдалося. Вось паведамленне пра памылку: %s unlock.error.customPath.description.generic=Ты абраў адмысловую сцежку мантажавання для гэтай скарбніцы, але скарыстацца ёю не ўдалося. Вось паведамленне пра памылку: %s
## Hub ## Hub
hub.noKeychain.message=Няма доступу да ключа прылады hub.noKeychain.message=Няма доступу да ключа прылады
@@ -193,11 +198,15 @@ migration.error.missingFileSystemCapabilities.reason.READ_ACCESS=Файлава
migration.error.missingFileSystemCapabilities.reason.WRITE_ACCESS=Файлавая сістэма не дазваляе пісаць у яе. migration.error.missingFileSystemCapabilities.reason.WRITE_ACCESS=Файлавая сістэма не дазваляе пісаць у яе.
## Impossible ## Impossible
migration.impossible.heading=Не мажліва перанесці скарбніцу migration.impossible.heading=Не мажліва перанесці скарбніцу
migration.impossible.reason=Скарбніца ня можа аўтаматычна зьміґравацца, бо ейнае месца сховішча альбо пункт доступу не падтрымліваюцца.
migration.impossible.moreInfo=Скарбніца ўсё яшчэ можа адчыняцца, нягледзячы на старую вэрсію. Інструкцыю ручнога міґравання скарбніцы ты знойдзеш тут:
# Health Check # Health Check
## Start ## Start
health.title=Тэст на цэласнасць для "%s" health.title=Тэст на цэласнасць для "%s"
health.intro.header=Тэст на цэласнасць health.intro.header=Тэст на цэласнасць
health.intro.text=Тэст на цэласнасьць – гэта калекцыя тэстаў для выяўлення ды выпраўленню хібаў унутранай структуры тваёй скарбніцы. Калі ласка, зьвярні ўвагу:
health.intro.remarkSync=Пераканайся, што ўсе прыстоі цалкам сынхранаваныя – гэта вырашае большасьць праблемаў.
health.intro.remarkFix=Ня ўсе хібы могуць быць выпраўленыя. health.intro.remarkFix=Ня ўсе хібы могуць быць выпраўленыя.
health.intro.remarkBackup=Калі даныя пашкоджаныя, дапаможа толькі рэзервовая копія. health.intro.remarkBackup=Калі даныя пашкоджаныя, дапаможа толькі рэзервовая копія.
health.intro.affirmation=Я прачытала і зразумела інфармацыю зверху health.intro.affirmation=Я прачытала і зразумела інфармацыю зверху
@@ -270,8 +279,10 @@ preferences.interface.showMinimizeButton=Паказаць кнопку згор
preferences.interface.showTrayIcon=Паказваць іконку на інфармацыйнай панэлі (спатрэбіцца перазапуск) preferences.interface.showTrayIcon=Паказваць іконку на інфармацыйнай панэлі (спатрэбіцца перазапуск)
## Volume ## Volume
preferences.volume=Віртуальны дыск preferences.volume=Віртуальны дыск
preferences.volume.type=Тып тому
preferences.volume.type.automatic=Аўтаматычна preferences.volume.type.automatic=Аўтаматычна
preferences.volume.docsTooltip=Адчыні дакумэнтацыю, каб даведацца больш пра розныя тыпы тому. preferences.volume.docsTooltip=Адчыні дакумэнтацыю, каб даведацца больш пра розныя тыпы тому.
preferences.volume.fuseRestartRequired=Каб дастасаваць зьмены, Cryptomator патрэбна перазапусьціць.
preferences.volume.tcp.port=Порт TCP preferences.volume.tcp.port=Порт TCP
preferences.volume.supportedFeatures=Абраны тып тому падтрымлівае наступныя функцыі: preferences.volume.supportedFeatures=Абраны тып тому падтрымлівае наступныя функцыі:
preferences.volume.feature.mountAuto=Аўтаматычны выбар пункту мантажавання preferences.volume.feature.mountAuto=Аўтаматычны выбар пункту мантажавання
@@ -348,6 +359,7 @@ main.vaultlist.contextMenu.reveal=Паказаць дыск
main.vaultlist.addVaultBtn=Дадаць скарбніцу main.vaultlist.addVaultBtn=Дадаць скарбніцу
## Vault Detail ## Vault Detail
### Welcome ### Welcome
main.vaultDetail.welcomeOnboarding=Дзякуй, што ты абраў Cryptomator для абароны тваіх файлаў. Калі табе патрэбна дапамога, калі ласка, паглядзі нашы інструкцыі:
### Locked ### Locked
main.vaultDetail.lockedStatus=ЗАМКНЁНА main.vaultDetail.lockedStatus=ЗАМКНЁНА
main.vaultDetail.unlockBtn=Адамкнуць… main.vaultDetail.unlockBtn=Адамкнуць…
@@ -386,6 +398,7 @@ main.vaultDetail.error.windowTitle=Памылка загрузкі скарбн
# Wrong File Alert # Wrong File Alert
wrongFileAlert.title=Як зашыфраваць файлы wrongFileAlert.title=Як зашыфраваць файлы
wrongFileAlert.message=Ці спрабаваў ты зашыфраваць гэтыя файлы? wrongFileAlert.message=Ці спрабаваў ты зашыфраваць гэтыя файлы?
wrongFileAlert.description=Для гэтай мэты Cryptomator стварае том у тваім мэнэджары файлавай сыстэмы.
wrongFileAlert.instruction.0=Каб зашыфраваць файлы, зрабі наступныя крокі: wrongFileAlert.instruction.0=Каб зашыфраваць файлы, зрабі наступныя крокі:
wrongFileAlert.instruction.1=1. Разамкні свая скарбніцу. wrongFileAlert.instruction.1=1. Разамкні свая скарбніцу.
wrongFileAlert.instruction.2=2. Пстрыкні па "Паказаць"; каб адчыніць том у тваім файлавым мэнэджары. wrongFileAlert.instruction.2=2. Пстрыкні па "Паказаць"; каб адчыніць том у тваім файлавым мэнэджары.
@@ -426,6 +439,7 @@ vaultOptions.masterkey.recoveryKeyExplanation=Ключ аднаўлення –
vaultOptions.masterkey.showRecoveryKeyBtn=Паказаць ключ аднаўлення vaultOptions.masterkey.showRecoveryKeyBtn=Паказаць ключ аднаўлення
vaultOptions.masterkey.recoverPasswordBtn=Скінуць пароль vaultOptions.masterkey.recoverPasswordBtn=Скінуць пароль
## Hub ## Hub
vaultOptions.hub=Аднаўленне
# Recovery Key # Recovery Key
## Display Recovery Key ## Display Recovery Key
@@ -437,6 +451,7 @@ recoveryKey.display.StorageHints=Захоўвай іх у бяспечным м
## Reset Password ## Reset Password
### Enter Recovery Key ### Enter Recovery Key
recoveryKey.recover.title=Скінуць пароль recoveryKey.recover.title=Скінуць пароль
recoveryKey.recover.prompt=Увядзі ключ аднаўлення для "%s":
recoveryKey.recover.correctKey=Гэта валідны ключ аднаўлення recoveryKey.recover.correctKey=Гэта валідны ключ аднаўлення
recoveryKey.recover.wrongKey=Гэты ключ аднаўлення належыць іншай скарбніцы recoveryKey.recover.wrongKey=Гэты ключ аднаўлення належыць іншай скарбніцы
recoveryKey.recover.invalidKey=Несапраўдны ключ аднаўлення recoveryKey.recover.invalidKey=Несапраўдны ключ аднаўлення
@@ -448,6 +463,10 @@ recoveryKey.recover.resetSuccess.message=Пароль паспяхова скі
recoveryKey.recover.resetSuccess.description=Ты можаш разамкнуць сваю скарбніцу з дапамогаю новага паролю. recoveryKey.recover.resetSuccess.description=Ты можаш разамкнуць сваю скарбніцу з дапамогаю новага паролю.
# Convert Vault # Convert Vault
convertVault.title=Перабудаваць скарбніцу
convertVault.convert.convertBtn.before=Перабудаваць
convertVault.convert.convertBtn.processing=Перабудаванне…
convertVault.success.message=Перабудаванне прайшло паспяхова
# New Password # New Password
newPassword.promptText=Увядзі новы пароль newPassword.promptText=Увядзі новы пароль
@@ -470,4 +489,6 @@ quit.lockAndQuitBtn=Замкнуць ды вайсці
# Forced Quit # Forced Quit
quit.forced.message=Некаторыя скарбніцы не магчыма замкнуць quit.forced.message=Некаторыя скарбніцы не магчыма замкнуць
quit.forced.description=Замыканне скарбніц было заблакавана праз дзеючыя аперацыі альбо праз адчыненыя файлы. Ты можаш прымусова замкнуць скарбніцы, але гэта можа прывесці да страты незахаваных даных. quit.forced.description=Замыканне скарбніц было заблакавана праз дзеючыя аперацыі альбо праз адчыненыя файлы. Ты можаш прымусова замкнуць скарбніцы, але гэта можа прывесці да страты незахаваных даных.
quit.forced.forceAndQuitBtn=Прымусіць і выйсці quit.forced.forceAndQuitBtn=Прымусіць і выйсці
# Update Reminder
+33 -1
View File
@@ -21,6 +21,7 @@ error.hyperlink.lookup=Търсене на грешката
error.hyperlink.report=Докладване на грешката error.hyperlink.report=Докладване на грешката
error.technicalDetails=Подробности: error.technicalDetails=Подробности:
# Defaults # Defaults
defaults.vault.vaultName=Хранилище defaults.vault.vaultName=Хранилище
@@ -59,6 +60,7 @@ addvaultwizard.new.validCharacters.message=Името на хранилищет
addvaultwizard.new.validCharacters.chars=Букви (напр. a, ж или 수) addvaultwizard.new.validCharacters.chars=Букви (напр. a, ж или 수)
addvaultwizard.new.validCharacters.numbers=Числа addvaultwizard.new.validCharacters.numbers=Числа
addvaultwizard.new.validCharacters.dashes=Тире (%s) или долна черта (%s) addvaultwizard.new.validCharacters.dashes=Тире (%s) или долна черта (%s)
### Expert Settings
### Password ### Password
addvaultwizard.new.createVaultBtn=Създаване addvaultwizard.new.createVaultBtn=Създаване
addvaultwizard.new.generateRecoveryKeyChoice=Без парола няма да имате достъп до данните си. Желаете ли да бъде създаден ключ за възстановяване, в случай че загубите паролата си? addvaultwizard.new.generateRecoveryKeyChoice=Без парола няма да имате достъп до данните си. Желаете ли да бъде създаден ключ за възстановяване, в случай че загубите паролата си?
@@ -118,17 +120,42 @@ unlock.chooseMasterkey.filePickerMimeDesc=Гкавен ключ на Крипт
## Success ## Success
unlock.success.message=Отключено е успешно unlock.success.message=Отключено е успешно
unlock.success.description=Съдържанието на хранилището „%s“ е достъпно в точката му на монтиране. unlock.success.description=Съдържанието на хранилището „%s“ е достъпно в точката му на монтиране.
unlock.success.rememberChoice=Запомняне на избора и да не бъде задаван отново този въпрос
unlock.success.revealBtn=Разкриване на диска unlock.success.revealBtn=Разкриване на диска
## Failure ## Failure
unlock.error.customPath.message=Хранилището не може да бъде монтирано в потребителския път
unlock.error.customPath.description.notSupported=Ако искате да продължите да използвате потребитрлския път, отидете в настройките и изберете вид на дял, който поддържа потребителски пътища. В противен случай отидете в настройките на хранилището и изберете поддържана точка за монтиране.
unlock.error.customPath.description.notExists=Потребителският път на монтиране не съществува. Създайте го в местната файлова система или го променете в настройките на хранилището.
unlock.error.customPath.description.inUse=Има друго устройство с буква „%s“.
unlock.error.customPath.description.generic=Избрали сте потребителски път за монтиране на това хранилище, но при използването на този път възникна следната грешка: %s
## Hub ## Hub
hub.noKeychain.message=Няма достъп до ключа на устройството
hub.noKeychain.description=За да отключите хранилищата в Hub е необходим ключ за устройството, който се защитава с помощта на ключодържател. За да продължите, разрешете „%s“ и изберете ключодържателя в настройките.
hub.noKeychain.openBtn=Към настройките hub.noKeychain.openBtn=Към настройките
### Waiting ### Waiting
hub.auth.message=Изчакване на удостоверяване…
hub.auth.description=Автоматично ще бъдете пренасочени към страницата за вход.
hub.auth.loginLink=Не сте пренасочени? Щракнете тук, за да посетите.
### Receive Key ### Receive Key
hub.receive.message=Обработване на отговора…
hub.receive.description=Криптоматор получава и обработва отговора от Hub. Изчакайте.
### Register Device ### Register Device
hub.register.message=Изисква се име на устройство
hub.register.description=Изглежда, че това е първи достъп до Hub от това устройство. За да го разпознаете при разрешаване на достъпа, трябва да му дадете име.
hub.register.nameLabel=Име на устройството
hub.register.occupiedMsg=Това име вече е заето
hub.register.registerBtn=Потвърждаване
### Registration Success ### Registration Success
hub.registerSuccess.message=Устройството е именувано
hub.registerSuccess.description=За да получи достъп до хранилището, устройството трябва да бъде упълномощено от собственика на хранилището.
### Registration Failed ### Registration Failed
hub.registerFailed.message=Грешка при именуване на устройството
hub.registerFailed.description=В процеса на именуване е допусната грешка. За повече подробности разгледайте дневника на приложението.
### Unauthorized ### Unauthorized
hub.unauthorized.message=Отказан достъп
hub.unauthorized.description=Устройството не е упълномощено за достъп до това хранилище. Поискайте достъп от собственика.
### License Exceeded ### License Exceeded
hub.invalidLicense.message=Лиценза за Hub е недействителен
# Lock # Lock
## Force ## Force
@@ -140,6 +167,7 @@ hub.noKeychain.openBtn=Към настройките
## Success ## Success
migration.success.unlockNow=Отключване сега migration.success.unlockNow=Отключване сега
## Missing file system capabilities ## Missing file system capabilities
migration.error.missingFileSystemCapabilities.title=Неподдържана файлова система
## Impossible ## Impossible
# Health Check # Health Check
@@ -166,7 +194,9 @@ preferences.title=Настройки
## General ## General
## Interface ## Interface
preferences.interface.theme=Оформление preferences.interface.theme=Оформление
preferences.interface.theme.automatic=Автоматично
## Volume ## Volume
preferences.volume.type.automatic=Автоматично
## Updates ## Updates
## Contribution ## Contribution
#<-- Add entries for donations and code/translation/documentation contribution --> #<-- Add entries for donations and code/translation/documentation contribution -->
@@ -230,4 +260,6 @@ vaultOptions.masterkey.changePasswordBtn=Промяна на парола
# Quit # Quit
# Forced Quit # Forced Quit
# Update Reminder

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