Merge branch cryptomator/develop

This commit is contained in:
Ralph Plawetzki
2023-07-01 18:45:45 +02:00
91 changed files with 1818 additions and 1215 deletions

136
.github/SECURITY.md vendored
View File

@@ -2,123 +2,25 @@
## 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>
<summary>PGP Key</summary>
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.
```
-----BEGIN PGP PUBLIC KEY BLOCK-----
Comment: GPGTools - https://gpgtools.org
If you prefer to report the vulnerability via email, please send an email to security@cryptomator.org.
mQINBFbgeicBEADM9AcU6DTgM5KZnBaJc6x9DBLr+TCMHntTt7YM9GLTlO2Z43Jt
oYoyqdRWAY28veqpLEFgRvvVD3fdBj/KUOxF1cr2JsErwXqbjwaLq0o/0KIXz7UK
a6pQSemZKfpOtJrfacofOTwvG6AuG9uakBYNMyxuojyOkoh3xsYS1KZ7TwPgCdET
t8/zva41Pa5kh5+GeSZJdCuygG6ynPBJEpmK5V7Qizvics5fziXecF+QaFZijafv
YahfxokvF9pXCQTmV4m57NQma9uK0w83U9nJCPjEd+x3wK0Hxrc1ojy8ZFTA1YND
AQg/MTABgHbQQkXDQhjS/TloOObqtbMBqNSbcSXpaR4teaCWKBl1MSq00nJLj8db
vPJGqfg7UbXhlALggp029/kskYlR5SmbxWquLbl0Xre3fDHuHEiWcJL6MS3454Wt
Mno13/4UhOlRFh5g0pLmPz7seOTJjDqc9abn/RXOLq0+3qX0gC0bDm5aCE5dQ2MV
FMbrrlw/dZESNLZvtB3gOsramSry1R3HVZ0QJ2vMaF2cxewebqcYbuecUNj6bxpv
5LEhEmqz6dG1meLLWDsvQLPEUWEIJnfpBiDSm342yxJq4pXnVF+aqAQsCL3FpmvZ
2j0FgFOs7iXOcFUJIiR0xUmWPk1NWYcUowqmRW8pMM9nFUzFF99iggPznwARAQAB
tC1DcnlwdG9tYXRvciBTdXBwb3J0IDxzdXBwb3J0QGNyeXB0b21hdG9yLm9yZz6J
AkAEEwEKACoCGwMFCQcrKAAFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AFAleu2cQC
GQEACgkQI7Xb75TU2B3+7A/7BKRWdo5/moCCEbBzYQ7vRMLFdwmjFFlSZ7aGC0fP
YHdeUwxPbO0cATwmNpGMma7rBn1FDg3Vto6/wottGxm+XIRwlyY84CD1VZAihZ/e
WvjOO28/7VgRy6PGKzlhpDSoT8GwFOgO69e7bEff1Zj562RZe7nXc4tDivILMB++
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>
PGP key fingerprint: `3647 9903 B23A E0A5 9359  9A3E 23B5 DBEF 94D4 D81D` ([public key](https://gist.github.com/cryptobot/864300b6b44ae2d2a15abedfe14bd040))
## Expectations
When reporting a vulnerability, please provide us with a detailed report that includes:
- A description of the vulnerability
- Steps to reproduce the vulnerability
- Possible impact of the vulnerability
- Any additional information that may be helpful
We ask that you do not publicly disclose the vulnerability until we have had a chance to address it.
## Thank You
We appreciate your help in keeping Cryptomator secure. Thank you for your contributions to the security of our project.

View File

@@ -86,12 +86,12 @@ jobs:
--java-options "-Xmx256m"
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
--java-options "-Dfile.encoding=\"utf-8\""
--java-options "-Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\""
--java-options "-Dcryptomator.pluginDir=\"~/.local/share/Cryptomator/plugins\""
--java-options "-Dcryptomator.settingsPath=\"~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json\""
--java-options "-Dcryptomator.p12Path=\"~/.config/Cryptomator/key.p12\""
--java-options "-Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\""
--java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\""
--java-options "-Dcryptomator.logDir=\"@{userhome}/.local/share/Cryptomator/logs\""
--java-options "-Dcryptomator.pluginDir=\"@{userhome}/.local/share/Cryptomator/plugins\""
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/.config/Cryptomator/settings.json:@{userhome}/.Cryptomator/settings.json\""
--java-options "-Dcryptomator.p12Path=\"@{userhome}/.config/Cryptomator/key.p12\""
--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.integrationsLinux.trayIconsDir=\"${TRAYICONSDIR}\"" \
--java-options "-Dcryptomator.buildNumber=\"appimage-${{ needs.get-version.outputs.revNum }}\""

View File

@@ -98,13 +98,13 @@ jobs:
--java-options "-Dapple.awt.enableTemplateImages=true"
--java-options "-Dsun.java2d.metal=true"
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
--java-options "-Dcryptomator.logDir=\"~/Library/Logs/Cryptomator\""
--java-options "-Dcryptomator.pluginDir=\"~/Library/Application Support/Cryptomator/Plugins\""
--java-options "-Dcryptomator.settingsPath=\"~/Library/Application Support/Cryptomator/settings.json\""
--java-options "-Dcryptomator.p12Path=\"~/Library/Application Support/Cryptomator/key.p12\""
--java-options "-Dcryptomator.ipcSocketPath=\"~/Library/Application Support/Cryptomator/ipc.socket\""
--java-options "-Dcryptomator.logDir=\"@{userhome}/Library/Logs/Cryptomator\""
--java-options "-Dcryptomator.pluginDir=\"@{userhome}/Library/Application Support/Cryptomator/Plugins\""
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/Library/Application Support/Cryptomator/settings.json\""
--java-options "-Dcryptomator.p12Path=\"@{userhome}/Library/Application Support/Cryptomator/key.p12\""
--java-options "-Dcryptomator.ipcSocketPath=\"@{userhome}/Library/Application Support/Cryptomator/ipc.socket\""
--java-options "-Dcryptomator.integrationsMac.keychainServiceName=\"Cryptomator\""
--java-options "-Dcryptomator.mountPointsDir=\"~/Cryptomator\""
--java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/Cryptomator\""
--java-options "-Dcryptomator.showTrayIcon=true"
--java-options "-Dcryptomator.buildNumber=\"dmg-${{ needs.get-version.outputs.revNum }}\""
--mac-package-identifier org.cryptomator

View File

@@ -112,17 +112,17 @@ jobs:
--java-options "-Xmx256m"
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
--java-options "-Dfile.encoding=\"utf-8\""
--java-options "-Dcryptomator.logDir=\"~/AppData/Roaming/Cryptomator\""
--java-options "-Dcryptomator.pluginDir=\"~/AppData/Roaming/Cryptomator/Plugins\""
--java-options "-Dcryptomator.settingsPath=\"~/AppData/Roaming/Cryptomator/settings.json\""
--java-options "-Dcryptomator.p12Path=\"~/AppData/Roaming/Cryptomator/key.p12\""
--java-options "-Dcryptomator.ipcSocketPath=\"~/AppData/Roaming/Cryptomator/ipc.socket\""
--java-options "-Dcryptomator.mountPointsDir=\"~/Cryptomator\""
--java-options "-Dcryptomator.logDir=\"@{localappdata}/Cryptomator\""
--java-options "-Dcryptomator.pluginDir=\"@{appdata}/Cryptomator/Plugins\""
--java-options "-Dcryptomator.settingsPath=\"@{appdata}/Cryptomator/settings.json:@{userhome}/AppData/Roaming/Cryptomator/settings.json\""
--java-options "-Dcryptomator.p12Path=\"@{appdata}/Cryptomator/key.p12:@{userhome}/AppData/Roaming/Cryptomator/key.p12\""
--java-options "-Dcryptomator.ipcSocketPath=\"@{localappdata}/Cryptomator/ipc.socket\""
--java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/Cryptomator\""
--java-options "-Dcryptomator.loopbackAlias=\"${{ env.LOOPBACK_ALIAS }}\""
--java-options "-Dcryptomator.showTrayIcon=true"
--java-options "-Dcryptomator.buildNumber=\"msi-${{ needs.get-version.outputs.revNum }}\""
--java-options "-Dcryptomator.integrationsWin.autoStartShellLinkName=\"Cryptomator\""
--java-options "-Dcryptomator.integrationsWin.keychainPaths=\"~/AppData/Roaming/Cryptomator/keychain.json\""
--java-options "-Dcryptomator.integrationsWin.keychainPaths=\"@{appdata}/Cryptomator/keychain.json:@{userhome}/AppData/Roaming/Cryptomator/keychain.json\""
--java-options "-Djavafx.verbose=${{ inputs.isDebug }}"
--resource-dir dist/win/resources
--icon dist/win/resources/Cryptomator.ico

View File

@@ -53,9 +53,10 @@
<option name="KEEP_LINE_BREAKS" value="false" />
<option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" />
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
<option name="KEEP_SIMPLE_BLOCKS_IN_ONE_LINE" value="true" />
<option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
<option name="KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE" value="true" />
<option name="KEEP_SIMPLE_CLASSES_IN_ONE_LINE" value="true" />
<option name="IF_BRACE_FORCE" value="3" />
<option name="ENUM_CONSTANTS_WRAP" value="2" />
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />

View File

@@ -2,7 +2,7 @@
<configuration default="false" name="Cryptomator Linux" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;~/.config/Cryptomator/settings.json&quot; -Dcryptomator.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,org.purejava.appindicator" />
<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,org.purejava.appindicator" />
<method v="2">
<option name="Make" enabled="true" />
</method>

View File

@@ -2,7 +2,7 @@
<configuration default="false" name="Cryptomator Linux Dev" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;~/.config/Cryptomator-Dev/settings.json&quot; -Dcryptomator.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,org.purejava.appindicator" />
<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,org.purejava.appindicator" />
<method v="2">
<option name="Make" enabled="true" />
</method>

View File

@@ -2,7 +2,7 @@
<configuration default="false" name="Cryptomator Windows" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;~/AppData/Roaming/Cryptomator/settings.json&quot; -Dcryptomator.ipcSocketPath=&quot;~/AppData/Roaming/Cryptomator/ipc.socket&quot; -Dcryptomator.logDir=&quot;~/AppData/Roaming/Cryptomator&quot; -Dcryptomator.pluginDir=&quot;~/AppData/Roaming/Cryptomator/Plugins&quot; -Dcryptomator.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">
<option name="Make" enabled="true" />
</method>

View File

@@ -2,7 +2,7 @@
<configuration default="false" name="Cryptomator Windows Dev" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;~/AppData/Roaming/Cryptomator-Dev/settings.json&quot; -Dcryptomator.ipcSocketPath=&quot;~/AppData/Roaming/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;~/AppData/Roaming/Cryptomator-Dev&quot; -Dcryptomator.pluginDir=&quot;~/AppData/Roaming/Cryptomator-Dev/Plugins&quot; -Dcryptomator.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">
<option name="Make" enabled="true" />
</method>

View File

@@ -5,7 +5,7 @@
</envs>
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dapple.awt.enableTemplateImages=true -Dcryptomator.settingsPath=&quot;~/Library/Application Support/Cryptomator/settings.json&quot; -Dcryptomator.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">
<option name="Make" enabled="true" />
</method>

View File

@@ -5,7 +5,7 @@
</envs>
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dapple.awt.enableTemplateImages=true -Dcryptomator.settingsPath=&quot;~/Library/Application Support/Cryptomator-Dev/settings.json&quot; -Dcryptomator.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">
<option name="Make" enabled="true" />
</method>

View File

@@ -50,12 +50,12 @@ ${JAVA_HOME}/bin/jpackage \
--java-options "-Xmx256m" \
--app-version "${VERSION}.${REVISION_NO}" \
--java-options "-Dfile.encoding=\"utf-8\"" \
--java-options "-Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\"" \
--java-options "-Dcryptomator.pluginDir=\"~/.local/share/Cryptomator/plugins\"" \
--java-options "-Dcryptomator.settingsPath=\"~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json\"" \
--java-options "-Dcryptomator.p12Path=\"~/.config/Cryptomator/key.p12\"" \
--java-options "-Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\"" \
--java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\"" \
--java-options "-Dcryptomator.logDir=\"@{userhome}/.local/share/Cryptomator/logs\"" \
--java-options "-Dcryptomator.pluginDir=\"@{userhome}/.local/share/Cryptomator/plugins\"" \
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/.config/Cryptomator/settings.json:@{userhome}/.Cryptomator/settings.json\"" \
--java-options "-Dcryptomator.p12Path=\"@{userhome}/.config/Cryptomator/key.p12\"" \
--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.integrationsLinux.trayIconsDir=\"${TRAYICONSDIR}\"" \
--java-options "-Dcryptomator.buildNumber=\"appimage-${REVISION_NO}\"" \

View File

@@ -66,6 +66,7 @@
</content_rating>
<releases>
<release date="2023-06-07" version="1.9.1"/>
<release date="2023-05-30" version="1.9.0"/>
<release date="2023-04-25" version="1.8.0"/>
<release date="2023-04-07" version="1.7.5"/>

View File

@@ -48,12 +48,12 @@ override_dh_auto_build:
--java-options "-Xss5m" \
--java-options "-Xmx256m" \
--java-options "-Dfile.encoding=\"utf-8\"" \
--java-options "-Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\"" \
--java-options "-Dcryptomator.pluginDir=\"~/.local/share/Cryptomator/plugins\"" \
--java-options "-Dcryptomator.settingsPath=\"~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json\"" \
--java-options "-Dcryptomator.p12Path=\"~/.config/Cryptomator/key.p12\"" \
--java-options "-Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\"" \
--java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\"" \
--java-options "-Dcryptomator.logDir=\"@{userhome}/.local/share/Cryptomator/logs\"" \
--java-options "-Dcryptomator.pluginDir=\"@{userhome}/.local/share/Cryptomator/plugins\"" \
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/.config/Cryptomator/settings.json:@{userhome}/.Cryptomator/settings.json\"" \
--java-options "-Dcryptomator.p12Path=\"@{userhome}/.config/Cryptomator/key.p12\"" \
--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.integrationsLinux.trayIconsDir=\"/usr/share/icons/hicolor/symbolic/apps\"" \
--java-options "-Dcryptomator.buildNumber=\"deb-${REVISION_NUM}\"" \

12
dist/mac/dmg/build.sh vendored
View File

@@ -74,13 +74,13 @@ ${JAVA_HOME}/bin/jpackage \
--java-options "-Dapple.awt.enableTemplateImages=true" \
--java-options "-Dsun.java2d.metal=true" \
--java-options "-Dcryptomator.appVersion=\"${VERSION_NO}\"" \
--java-options "-Dcryptomator.logDir=\"~/Library/Logs/${APP_NAME}\"" \
--java-options "-Dcryptomator.pluginDir=\"~/Library/Application Support/${APP_NAME}/Plugins\"" \
--java-options "-Dcryptomator.settingsPath=\"~/Library/Application Support/${APP_NAME}/settings.json\"" \
--java-options "-Dcryptomator.ipcSocketPath=\"~/Library/Application Support/${APP_NAME}/ipc.socket\"" \
--java-options "-Dcryptomator.p12Path=\"~/Library/Application Support/${APP_NAME}/key.p12\"" \
--java-options "-Dcryptomator.logDir=\"@{userhome}/Library/Logs/${APP_NAME}\"" \
--java-options "-Dcryptomator.pluginDir=\"@{userhome}/Library/Application Support/${APP_NAME}/Plugins\"" \
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/Library/Application Support/${APP_NAME}/settings.json\"" \
--java-options "-Dcryptomator.ipcSocketPath=\"@{userhome}/Library/Application Support/${APP_NAME}/ipc.socket\"" \
--java-options "-Dcryptomator.p12Path=\"@{userhome}/Library/Application Support/${APP_NAME}/key.p12\"" \
--java-options "-Dcryptomator.integrationsMac.keychainServiceName=\"${APP_NAME}\"" \
--java-options "-Dcryptomator.mountPointsDir=\"~/${APP_NAME}\"" \
--java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/${APP_NAME}\"" \
--java-options "-Dcryptomator.showTrayIcon=true" \
--java-options "-Dcryptomator.buildNumber=\"dmg-${REVISION_NO}\"" \
--mac-package-identifier ${PACKAGE_IDENTIFIER} \

14
dist/win/build.ps1 vendored
View File

@@ -82,15 +82,15 @@ if ($clean -and (Test-Path -Path $appPath)) {
--java-options "-Dcryptomator.appVersion=`"$semVerNo`"" `
--app-version "$semVerNo.$revisionNo" `
--java-options "-Dfile.encoding=`"utf-8`"" `
--java-options "-Dcryptomator.logDir=`"~/AppData/Roaming/$AppName`"" `
--java-options "-Dcryptomator.pluginDir=`"~/AppData/Roaming/$AppName/Plugins`"" `
--java-options "-Dcryptomator.settingsPath=`"~/AppData/Roaming/$AppName/settings.json`"" `
--java-options "-Dcryptomator.ipcSocketPath=`"~/AppData/Roaming/$AppName/ipc.socket`"" `
--java-options "-Dcryptomator.p12Path=`"~/AppData/Roaming/$AppName/key.p12`"" `
--java-options "-Dcryptomator.mountPointsDir=`"~/$AppName`"" `
--java-options "-Dcryptomator.logDir=`"@{localappdata}/$AppName`"" `
--java-options "-Dcryptomator.pluginDir=`"@{appdata}/$AppName/Plugins`"" `
--java-options "-Dcryptomator.settingsPath=`"@{appdata}/$AppName/settings.json:@{userhome}/AppData/Roaming/$AppName/settings.json`"" `
--java-options "-Dcryptomator.ipcSocketPath=`"@{localappdata}/$AppName/ipc.socket`"" `
--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.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.buildNumber=`"msi-$revisionNo`"" `
--resource-dir resources `

20
pom.xml
View File

@@ -33,8 +33,8 @@
<nonModularGroupIds>org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents</nonModularGroupIds>
<!-- cryptomator dependencies -->
<cryptomator.cryptofs.version>2.6.4</cryptomator.cryptofs.version>
<cryptomator.integrations.version>1.3.0-beta1</cryptomator.integrations.version>
<cryptomator.cryptofs.version>2.6.5</cryptomator.cryptofs.version>
<cryptomator.integrations.version>1.2.0</cryptomator.integrations.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.linux.version>1.3.0-beta4</cryptomator.integrations.linux.version>
@@ -46,8 +46,8 @@
<commons-lang3.version>3.12.0</commons-lang3.version>
<dagger.version>2.45</dagger.version>
<easybind.version>2.2</easybind.version>
<guava.version>32.0.0-jre</guava.version>
<gson.version>2.10.1</gson.version>
<guava.version>32.0.1-jre</guava.version>
<jackson.version>2.15.2</jackson.version>
<javafx.version>20.0.1</javafx.version>
<jwt.version>4.4.0</jwt.version>
<nimbus-jose.version>9.31</nimbus-jose.version>
@@ -63,7 +63,7 @@
<!-- build-time dependencies -->
<jetbrains.annotations.version>23.0.0</jetbrains.annotations.version>
<dependency-check.version>8.1.0</dependency-check.version>
<dependency-check.version>8.1.2</dependency-check.version>
<jacoco.version>0.8.9</jacoco.version>
</properties>
@@ -157,6 +157,11 @@
<artifactId>nimbus-jose-jwt</artifactId>
<version>${nimbus-jose.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- EasyBind -->
<dependency>
@@ -206,11 +211,6 @@
<artifactId>dagger</artifactId>
<version>${dagger.version}</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>${gson.version}</version>
</dependency>
<!-- JUnit / Mockito / Hamcrest -->
<dependency>

View File

@@ -5,6 +5,7 @@ import org.cryptomator.common.locationpresets.DropboxWindowsLocationPresetsProvi
import org.cryptomator.common.locationpresets.GoogleDriveLocationPresetsProvider;
import org.cryptomator.common.locationpresets.ICloudMacLocationPresetsProvider;
import org.cryptomator.common.locationpresets.ICloudWindowsLocationPresetsProvider;
import org.cryptomator.common.locationpresets.LeitzcloudLocationPresetsProvider;
import org.cryptomator.common.locationpresets.LocationPresetsProvider;
import org.cryptomator.common.locationpresets.MegaLocationPresetsProvider;
import org.cryptomator.common.locationpresets.OneDriveLinuxLocationPresetsProvider;
@@ -37,7 +38,7 @@ open module org.cryptomator.desktop {
requires ch.qos.logback.core;
requires com.auth0.jwt;
requires com.google.common;
requires com.google.gson;
requires com.fasterxml.jackson.databind;
requires com.nimbusds.jose.jwt;
requires com.nulabinc.zxcvbn;
requires com.tobiasdiez.easybind;
@@ -53,11 +54,12 @@ open module org.cryptomator.desktop {
provides TrayMenuController with AwtTrayMenuController;
provides Configurator with LogbackConfiguratorFactory;
provides LocationPresetsProvider with DropboxMacLocationPresetsProvider, //
DropboxWindowsLocationPresetsProvider, DropboxLinuxLocationPresetsProvider, //
ICloudMacLocationPresetsProvider, ICloudWindowsLocationPresetsProvider, //
provides LocationPresetsProvider with //
DropboxWindowsLocationPresetsProvider, DropboxMacLocationPresetsProvider, DropboxLinuxLocationPresetsProvider, //
GoogleDriveLocationPresetsProvider, //
PCloudLocationPresetsProvider, MegaLocationPresetsProvider, //
OneDriveLinuxLocationPresetsProvider, OneDriveWindowsLocationPresetsProvider, //
OneDriveMacLocationPresetsProvider;
ICloudWindowsLocationPresetsProvider, ICloudMacLocationPresetsProvider, //
LeitzcloudLocationPresetsProvider, //
MegaLocationPresetsProvider, //
OneDriveWindowsLocationPresetsProvider, OneDriveMacLocationPresetsProvider, OneDriveLinuxLocationPresetsProvider, //
PCloudLocationPresetsProvider;
}

View File

@@ -139,9 +139,9 @@ public abstract class CommonsModule {
@Provides
@Singleton
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";
return InetSocketAddress.createUnresolved(host, settings.port().intValue());
return InetSocketAddress.createUnresolved(host, settings.port.intValue());
});
}

View File

@@ -18,7 +18,6 @@ import java.util.stream.StreamSupport;
public class Environment {
private static final Logger LOG = LoggerFactory.getLogger(Environment.class);
private static final Path RELATIVE_HOME_DIR = Paths.get("~");
private static final char PATH_LIST_SEP = ':';
private static final int DEFAULT_MIN_PW_LENGTH = 8;
private static final String SETTINGS_PATH_PROP_NAME = "cryptomator.settingsPath";
@@ -80,7 +79,7 @@ public class Environment {
return getPaths(P12_PATH_PROP_NAME);
}
public Stream<Path> ipcSocketPath() {
public Stream<Path> getIpcSocketPath() {
return getPaths(IPC_SOCKET_PATH_PROP_NAME);
}
@@ -89,7 +88,7 @@ public class Environment {
}
public Optional<Path> getLogDir() {
return getPath(LOG_DIR_PROP_NAME).map(this::replaceHomeDir);
return getPath(LOG_DIR_PROP_NAME);
}
public Optional<String> getLoopbackAlias() {
@@ -97,11 +96,11 @@ public class Environment {
}
public Optional<Path> getPluginDir() {
return getPath(PLUGIN_DIR_PROP_NAME).map(this::replaceHomeDir);
return getPath(PLUGIN_DIR_PROP_NAME);
}
public Optional<Path> getMountPointsDir() {
return getPath(MOUNTPOINT_DIR_PROP_NAME).map(this::replaceHomeDir);
return getPath(MOUNTPOINT_DIR_PROP_NAME);
}
/**
@@ -131,22 +130,9 @@ public class Environment {
}
// visible for testing
public Path getHomeDir() {
return getPath("user.home").orElseThrow();
}
// visible for testing
public Stream<Path> getPaths(String propertyName) {
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;
}
return rawSettingsPaths.filter(Predicate.not(Strings::isNullOrEmpty)).map(Path::of);
}
private Stream<String> getRawList(String propertyName, char separator) {

View File

@@ -32,18 +32,15 @@ public class ErrorCode {
this.rootCauseSpecificFrames = rootCauseSpecificFrames;
}
// visible for testing
String methodCode() {
public String methodCode() {
return format(traceCode(rootCause, LATEST_FRAME));
}
// visible for testing
String rootCauseCode() {
public String rootCauseCode() {
return format(traceCode(rootCause, rootCauseSpecificFrames));
}
// visible for testing
String throwableCode() {
public String throwableCode() {
return format(traceCode(throwable, ALL_FRAMES));
}

View File

@@ -30,7 +30,7 @@ public class LicenseHolder {
this.licenseSubject = validJwtClaims.map(DecodedJWT::getSubject);
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));
}
@@ -38,7 +38,7 @@ public class LicenseHolder {
Optional<DecodedJWT> claims = licenseChecker.check(licenseKey);
validJwtClaims.set(claims.orElse(null));
if (claims.isPresent()) {
settings.licenseKey().set(licenseKey);
settings.licenseKey.set(licenseKey);
return true;
} else {
return false;

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -23,14 +23,14 @@ public class KeychainModule {
@Singleton
static ObjectExpression<KeychainAccessProvider> provideKeychainAccessProvider(Settings settings, List<KeychainAccessProvider> providers) {
return Bindings.createObjectBinding(() -> {
if (!settings.useKeychain().get()) {
if (!settings.useKeychain.get()) {
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 fallbackProvider = providers.stream().findFirst().orElse(null);
return selectedProvider.orElse(fallbackProvider);
}, settings.keychainProvider(), settings.useKeychain());
}, settings.keychainProvider, settings.useKeychain);
}
}

View File

@@ -5,6 +5,8 @@ import org.cryptomator.integrations.common.OperatingSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import static org.cryptomator.integrations.common.OperatingSystem.Value.MAC;
@@ -15,23 +17,25 @@ import static org.cryptomator.integrations.common.OperatingSystem.Value.WINDOWS;
@CheckAvailability
public final class GoogleDriveLocationPresetsProvider implements LocationPresetsProvider {
private static final Path LOCATION1 = LocationPresetsProvider.resolveLocation("~/GoogleDrive");
private static final Path LOCATION2 = LocationPresetsProvider.resolveLocation("~/GoogleDrive/My Drive");
private static final List<Path> LOCATIONS = Arrays.asList( //
LocationPresetsProvider.resolveLocation("~/GoogleDrive/My Drive"), //
LocationPresetsProvider.resolveLocation("~/Google Drive/My Drive"), //
LocationPresetsProvider.resolveLocation("~/GoogleDrive"), //
LocationPresetsProvider.resolveLocation("~/Google Drive") //
);
@CheckAvailability
public static boolean isPresent() {
return Files.isDirectory(LOCATION1) || Files.isDirectory(LOCATION2);
return LOCATIONS.stream().anyMatch(Files::isDirectory);
}
@Override
public Stream<LocationPreset> getLocations() {
if(Files.isDirectory(LOCATION1)) {
return Stream.of(new LocationPreset("Google Drive", LOCATION1));
} else if(Files.isDirectory(LOCATION2)) {
return Stream.of(new LocationPreset("Google Drive", LOCATION2));
} else {
return Stream.of();
}
return LOCATIONS.stream() //
.filter(Files::isDirectory) //
.map(location -> new LocationPreset("Google Drive", location)) //
.findFirst() //
.stream();
}
}

View File

@@ -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));
}
}

View File

@@ -5,6 +5,8 @@ import org.cryptomator.integrations.common.OperatingSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import static org.cryptomator.integrations.common.OperatingSystem.Value.MAC;
@@ -15,16 +17,23 @@ import static org.cryptomator.integrations.common.OperatingSystem.Value.WINDOWS;
@CheckAvailability
public final class PCloudLocationPresetsProvider implements LocationPresetsProvider {
private static final Path LOCATION = LocationPresetsProvider.resolveLocation("~/pCloudDrive");
private static final List<Path> LOCATIONS = Arrays.asList( //
LocationPresetsProvider.resolveLocation("~/pCloudDrive"), //
LocationPresetsProvider.resolveLocation("~/pCloud Drive") //
);
@CheckAvailability
public static boolean isPresent() {
return Files.isDirectory(LOCATION);
return LOCATIONS.stream().anyMatch(Files::isDirectory);
}
@Override
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();
}
}

View File

@@ -37,7 +37,7 @@ public class MountModule {
static ObservableValue<ActualMountService> provideMountService(Settings settings, List<MountService> serviceImpls, @Named("FUPFMS") AtomicReference<MountService> fupfms) {
var fallbackProvider = serviceImpls.stream().findFirst().orElse(null);
var observableMountService = ObservableUtil.mapWithDefault(settings.mountService(), //
var observableMountService = ObservableUtil.mapWithDefault(settings.mountService, //
desiredServiceImpl -> { //
var serviceFromSettings = serviceImpls.stream().filter(serviceImpl -> serviceImpl.getClass().getName().equals(desiredServiceImpl)).findAny(); //
var targetedService = serviceFromSettings.orElse(fallbackProvider);

View File

@@ -0,0 +1,8 @@
package org.cryptomator.common.mount;
public class MountPointInUseException extends IllegalMountPointException {
public MountPointInUseException(String msg) {
super(msg);
}
}

View File

@@ -54,19 +54,19 @@ public class Mounter {
switch (capability) {
case FILE_SYSTEM_NAME -> builder.setFileSystemName("cryptoFs");
case LOOPBACK_PORT ->
builder.setLoopbackPort(settings.port().get()); //TODO: move port from settings to vaultsettings (see https://github.com/cryptomator/cryptomator/tree/feature/mount-setting-per-vault)
builder.setLoopbackPort(settings.port.get()); //TODO: move port from settings to vaultsettings (see https://github.com/cryptomator/cryptomator/tree/feature/mount-setting-per-vault)
case LOOPBACK_HOST_NAME -> env.getLoopbackAlias().ifPresent(builder::setLoopbackHostName);
case READ_ONLY -> builder.setReadOnly(vaultSettings.usesReadOnlyMode().get());
case READ_ONLY -> builder.setReadOnly(vaultSettings.usesReadOnlyMode.get());
case MOUNT_FLAGS -> {
var mountFlags = vaultSettings.mountFlags().get();
var mountFlags = vaultSettings.mountFlags.get();
if (mountFlags == null || mountFlags.isBlank()) {
builder.setMountFlags(service.getDefaultMountFlags());
} else {
builder.setMountFlags(mountFlags);
}
}
case VOLUME_ID -> builder.setVolumeId(vaultSettings.getId());
case VOLUME_NAME -> builder.setVolumeName(vaultSettings.mountName().get());
case VOLUME_ID -> builder.setVolumeId(vaultSettings.id);
case VOLUME_NAME -> builder.setVolumeName(vaultSettings.mountName.get());
}
}
@@ -75,7 +75,7 @@ public class Mounter {
private Runnable prepareMountPoint() throws IOException {
Runnable cleanup = () -> {};
var userChosenMountPoint = vaultSettings.getMountPoint();
var userChosenMountPoint = vaultSettings.mountPoint.get();
var defaultMountPointBase = env.getMountPointsDir().orElseThrow();
var canMountToDriveLetter = service.hasCapability(MOUNT_AS_DRIVE_LETTER);
var canMountToParent = service.hasCapability(MOUNT_WITHIN_EXISTING_PARENT);
@@ -91,13 +91,17 @@ public class Mounter {
Files.createDirectories(defaultMountPointBase);
builder.setMountpoint(defaultMountPointBase);
} else if (canMountToDir) {
var mountPoint = defaultMountPointBase.resolve(vaultSettings.mountName().get());
var mountPoint = defaultMountPointBase.resolve(vaultSettings.mountName.get());
Files.createDirectories(mountPoint);
builder.setMountpoint(mountPoint);
}
} else {
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);
cleanup = () -> {
MountWithinParentUtil.cleanup(userChosenMountPoint);

View File

@@ -10,6 +10,8 @@ package org.cryptomator.common.settings;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Environment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
@@ -27,59 +29,85 @@ import java.util.function.Consumer;
public class Settings {
public static final int MIN_PORT = 1024;
public static final int MAX_PORT = 65535;
public static final boolean DEFAULT_ASKED_FOR_UPDATE_CHECK = false;
public static final boolean DEFAULT_CHECK_FOR_UPDATES = false;
public static final boolean DEFAULT_START_HIDDEN = false;
public static final boolean DEFAULT_AUTO_CLOSE_VAULTS = false;
public static final boolean DEFAULT_USE_KEYCHAIN = true;
public static final int DEFAULT_PORT = 42427;
public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
public static final boolean DEFAULT_DEBUG_MODE = false;
public static final UiTheme DEFAULT_THEME = UiTheme.LIGHT;
private static final Logger LOG = LoggerFactory.getLogger(Settings.class);
static final boolean DEFAULT_ASKED_FOR_UPDATE_CHECK = false;
static final boolean DEFAULT_CHECK_FOR_UPDATES = false;
static final boolean DEFAULT_START_HIDDEN = false;
static final boolean DEFAULT_AUTO_CLOSE_VAULTS = false;
static final boolean DEFAULT_USE_KEYCHAIN = true;
static final int DEFAULT_PORT = 42427;
static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
static final boolean DEFAULT_DEBUG_MODE = false;
static final UiTheme DEFAULT_THEME = UiTheme.LIGHT;
@Deprecated // to be changed to "whatever is available" eventually
public static final String DEFAULT_KEYCHAIN_PROVIDER = SystemUtils.IS_OS_WINDOWS ? "org.cryptomator.windows.keychain.WindowsProtectedKeychainAccess" : SystemUtils.IS_OS_MAC ? "org.cryptomator.macos.keychain.MacSystemKeychainAccess" : "org.cryptomator.linux.keychain.SecretServiceKeychainAccess";
public static final NodeOrientation DEFAULT_USER_INTERFACE_ORIENTATION = NodeOrientation.LEFT_TO_RIGHT;
public static final String DEFAULT_LICENSE_KEY = "";
public static final boolean DEFAULT_SHOW_MINIMIZE_BUTTON = false;
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();
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_USER_INTERFACE_ORIENTATION = NodeOrientation.LEFT_TO_RIGHT.name();
static final boolean DEFAULT_SHOW_MINIMIZE_BUTTON = false;
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;
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) {
this.showTrayIcon = new SimpleBooleanProperty(env.showTrayIcon());
Settings(SettingsJson json) {
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.directories.addAll(json.directories.stream().map(VaultSettings::new).toList());
migrateLegacySettings(json);
directories.addListener(this::somethingChanged);
askedForUpdateCheck.addListener(this::somethingChanged);
@@ -105,6 +133,72 @@ public class Settings {
mountService.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();
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) {
this.saveCmd = saveCmd;
}
@@ -119,90 +213,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;
}
}

View File

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

View File

@@ -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;
}
}

View File

@@ -8,12 +8,9 @@
*******************************************************************************/
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.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.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -22,12 +19,7 @@ import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
@@ -44,6 +36,7 @@ import java.util.stream.Stream;
@Singleton
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 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 Environment env;
private final ScheduledExecutorService scheduler;
private final Gson gson;
@Inject
public SettingsProvider(SettingsJsonAdapter settingsJsonAdapter, Environment env, ScheduledExecutorService scheduler) {
public SettingsProvider(Environment env, ScheduledExecutorService scheduler) {
this.env = env;
this.scheduler = scheduler;
this.gson = new GsonBuilder() //
.setPrettyPrinting().setLenient().disableHtmlEscaping() //
.registerTypeAdapter(Settings.class, settingsJsonAdapter) //
.create();
}
@Override
@@ -69,28 +57,25 @@ public class SettingsProvider implements Supplier<Settings> {
}
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);
return settings;
}
private Stream<Settings> tryLoad(Path path) {
LOG.debug("Attempting to load settings from {}", path);
try (InputStream in = Files.newInputStream(path, StandardOpenOption.READ); //
Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
JsonElement json = JsonParser.parseReader(reader);
if (json.isJsonObject()) {
Settings settings = gson.fromJson(json, Settings.class);
LOG.info("Settings loaded from {}", path);
return Stream.of(settings);
} else {
LOG.warn("Invalid json file {}", path);
return Stream.empty();
}
try (InputStream in = Files.newInputStream(path, StandardOpenOption.READ)) {
var json = JSON.reader().readValue(in, SettingsJson.class);
LOG.info("Settings loaded from {}", path);
var settings = new Settings(json);
return Stream.of(settings);
} catch (JacksonException e) {
LOG.warn("Failed to parse json file {}", path, e);
return Stream.empty();
} catch (NoSuchFileException e) {
return Stream.empty();
} catch (IOException | JsonParseException e) {
LOG.warn("Exception while loading settings from " + path, e);
} catch (IOException e) {
LOG.warn("Failed to load json file {}", path, e);
return Stream.empty();
}
}
@@ -116,13 +101,14 @@ public class SettingsProvider implements Supplier<Settings> {
try {
Files.createDirectories(settingsPath.getParent());
Path tmpPath = settingsPath.resolveSibling(settingsPath.getFileName().toString() + ".tmp");
try (OutputStream out = Files.newOutputStream(tmpPath, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE); //
Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)) {
gson.toJson(settings, writer);
try (OutputStream out = Files.newOutputStream(tmpPath, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) {
var jsonObj = settings.serialized();
jsonObj.writtenByVersion = env.getAppVersion() + env.getBuildNumber().map("-"::concat).orElse("");
JSON.writerWithDefaultPrettyPrinter().writeValue(out, jsonObj);
}
Files.move(tmpPath, settingsPath, StandardCopyOption.REPLACE_EXISTING);
LOG.info("Settings saved to {}", settingsPath);
} catch (IOException | JsonParseException e) {
} catch (IOException e) {
LOG.error("Failed to save settings.", e);
}
}

View File

@@ -1,9 +1,12 @@
package org.cryptomator.common.settings;
import com.fasterxml.jackson.annotation.JsonEnumDefaultValue;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.apache.commons.lang3.SystemUtils;
@JsonFormat(shape = JsonFormat.Shape.STRING)
public enum UiTheme {
LIGHT("preferences.interface.theme.light"), //
@JsonEnumDefaultValue LIGHT("preferences.interface.theme.light"), //
DARK("preferences.interface.theme.dark"), //
AUTOMATIC("preferences.interface.theme.automatic");

View File

@@ -6,7 +6,9 @@
package org.cryptomator.common.settings;
import com.google.common.base.CharMatcher;
import com.google.common.base.Strings;
import com.google.common.io.BaseEncoding;
import org.apache.commons.lang3.SystemUtils;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
@@ -20,6 +22,7 @@ import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Objects;
import java.util.Random;
@@ -28,33 +31,45 @@ import java.util.Random;
*/
public class VaultSettings {
public static final boolean DEFAULT_UNLOCK_AFTER_STARTUP = false;
public static final boolean DEFAULT_REVEAL_AFTER_MOUNT = true;
public static final boolean DEFAULT_USES_READONLY_MODE = false;
public static final String DEFAULT_MOUNT_FLAGS = "";
public static final int DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH = -1;
public static final WhenUnlocked DEFAULT_ACTION_AFTER_UNLOCK = WhenUnlocked.ASK;
public static final boolean DEFAULT_AUTOLOCK_WHEN_IDLE = false;
public static final int DEFAULT_AUTOLOCK_IDLE_SECONDS = 30 * 60;
static final boolean DEFAULT_UNLOCK_AFTER_STARTUP = false;
static final boolean DEFAULT_REVEAL_AFTER_MOUNT = true;
static final boolean DEFAULT_USES_READONLY_MODE = false;
static final String DEFAULT_MOUNT_FLAGS = ""; // TODO: remove empty default mount flags and let this property be null if not used
static final int DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH = -1;
static final WhenUnlocked DEFAULT_ACTION_AFTER_UNLOCK = WhenUnlocked.ASK;
static final boolean DEFAULT_AUTOLOCK_WHEN_IDLE = false;
static final int DEFAULT_AUTOLOCK_IDLE_SECONDS = 30 * 60;
private static final Random RNG = new Random();
private final String id;
private final ObjectProperty<Path> path = new SimpleObjectProperty<>();
private final StringProperty displayName = new SimpleStringProperty();
private final BooleanProperty unlockAfterStartup = new SimpleBooleanProperty(DEFAULT_UNLOCK_AFTER_STARTUP);
private final BooleanProperty revealAfterMount = new SimpleBooleanProperty(DEFAULT_REVEAL_AFTER_MOUNT);
private final BooleanProperty usesReadOnlyMode = new SimpleBooleanProperty(DEFAULT_USES_READONLY_MODE);
private final StringProperty mountFlags = new SimpleStringProperty(DEFAULT_MOUNT_FLAGS); //TODO: remove empty default mount flags and let this property be null if not used
private final IntegerProperty maxCleartextFilenameLength = new SimpleIntegerProperty(DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH);
private final ObjectProperty<WhenUnlocked> actionAfterUnlock = new SimpleObjectProperty<>(DEFAULT_ACTION_AFTER_UNLOCK);
private final BooleanProperty autoLockWhenIdle = new SimpleBooleanProperty(DEFAULT_AUTOLOCK_WHEN_IDLE);
private final IntegerProperty autoLockIdleSeconds = new SimpleIntegerProperty(DEFAULT_AUTOLOCK_IDLE_SECONDS);
private final StringExpression mountName;
private final ObjectProperty<Path> mountPoint = new SimpleObjectProperty<>();
public final String id;
public final ObjectProperty<Path> path;
public final StringProperty displayName;
public final BooleanProperty unlockAfterStartup;
public final BooleanProperty revealAfterMount;
public final BooleanProperty usesReadOnlyMode;
public final StringProperty mountFlags;
public final IntegerProperty maxCleartextFilenameLength;
public final ObjectProperty<WhenUnlocked> actionAfterUnlock;
public final BooleanProperty autoLockWhenIdle;
public final IntegerProperty autoLockIdleSeconds;
public final ObjectProperty<Path> mountPoint;
public final StringExpression mountName;
public VaultSettings(String id) {
this.id = Objects.requireNonNull(id);
VaultSettings(VaultSettingsJson json) {
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(() -> {
final String name;
if (displayName.isEmpty().get()) {
@@ -64,6 +79,18 @@ public class VaultSettings {
}
return normalizeDisplayName(name);
}, 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() {
@@ -71,7 +98,9 @@ public class VaultSettings {
}
public static VaultSettings withRandomId() {
return new VaultSettings(generateId());
var defaults = new VaultSettingsJson();
defaults.id = generateId();
return new VaultSettings(defaults);
}
private static String generateId() {
@@ -80,6 +109,23 @@ public class VaultSettings {
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
static String normalizeDisplayName(String original) {
if (original.isBlank() || ".".equals(original) || "..".equals(original)) {
@@ -93,68 +139,6 @@ public class VaultSettings {
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 */
@Override

View File

@@ -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;
}

View File

@@ -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;
}
}
}

View File

@@ -1,9 +1,13 @@
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 {
IGNORE("vaultOptions.general.actionAfterUnlock.ignore"),
REVEAL("vaultOptions.general.actionAfterUnlock.reveal"),
ASK("vaultOptions.general.actionAfterUnlock.ask");
@JsonEnumDefaultValue ASK("vaultOptions.general.actionAfterUnlock.ask");
private String displayName;

View File

@@ -50,8 +50,8 @@ public class AutoLocker {
private boolean exceedsIdleTime(Vault vault) {
assert vault.isUnlocked();
if (vault.getVaultSettings().autoLockWhenIdle().get()) {
int maxIdleSeconds = vault.getVaultSettings().autoLockIdleSeconds().get();
if (vault.getVaultSettings().autoLockWhenIdle.get()) {
int maxIdleSeconds = vault.getVaultSettings().autoLockIdleSeconds.get();
var deadline = vault.getStats().getLastActivity().plusSeconds(maxIdleSeconds);
return deadline.isBefore(Instant.now());
} else {

View File

@@ -80,7 +80,7 @@ public class Vault {
this.state = state;
this.lastKnownException = lastKnownException;
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.processing = Bindings.createBooleanBinding(this::isProcessing, state);
this.unlocked = Bindings.createBooleanBinding(this::isUnlocked, state);
@@ -98,29 +98,29 @@ public class Vault {
private CryptoFileSystem createCryptoFileSystem(MasterkeyLoader keyLoader) throws IOException, MasterkeyLoadingFailedException {
Set<FileSystemFlags> flags = EnumSet.noneOf(FileSystemFlags.class);
if (vaultSettings.usesReadOnlyMode().get()) {
if (vaultSettings.usesReadOnlyMode.get()) {
flags.add(FileSystemFlags.READONLY);
} else if (vaultSettings.maxCleartextFilenameLength().get() == -1) {
} else if (vaultSettings.maxCleartextFilenameLength.get() == -1) {
LOG.debug("Determining cleartext filename length limitations...");
var checker = new FileSystemCapabilityChecker();
int shorteningThreshold = configCache.get().allegedShorteningThreshold();
int ciphertextLimit = checker.determineSupportedCiphertextFileNameLength(getPath());
if (ciphertextLimit < shorteningThreshold) {
int cleartextLimit = checker.determineSupportedCleartextFileNameLength(getPath());
vaultSettings.maxCleartextFilenameLength().set(cleartextLimit);
vaultSettings.maxCleartextFilenameLength.set(cleartextLimit);
} else {
vaultSettings.maxCleartextFilenameLength().setValue(UNLIMITED_FILENAME_LENGTH);
vaultSettings.maxCleartextFilenameLength.setValue(UNLIMITED_FILENAME_LENGTH);
}
}
if (vaultSettings.maxCleartextFilenameLength().get() < UNLIMITED_FILENAME_LENGTH) {
LOG.warn("Limiting cleartext filename length on this device to {}.", vaultSettings.maxCleartextFilenameLength().get());
if (vaultSettings.maxCleartextFilenameLength.get() < UNLIMITED_FILENAME_LENGTH) {
LOG.warn("Limiting cleartext filename length on this device to {}.", vaultSettings.maxCleartextFilenameLength.get());
}
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties() //
.withKeyLoader(keyLoader) //
.withFlags(flags) //
.withMaxCleartextNameLength(vaultSettings.maxCleartextFilenameLength().get()) //
.withMaxCleartextNameLength(vaultSettings.maxCleartextFilenameLength.get()) //
.withVaultConfigFilename(Constants.VAULTCONFIG_FILENAME) //
.build();
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
@@ -253,11 +253,11 @@ public class Vault {
}
public ReadOnlyStringProperty displayNameProperty() {
return vaultSettings.displayName();
return vaultSettings.displayName;
}
public String getDisplayName() {
return vaultSettings.displayName().get();
return vaultSettings.displayName.get();
}
public ObjectBinding<Mountpoint> mountPointProperty() {
@@ -274,7 +274,7 @@ public class Vault {
}
public String getDisplayablePath() {
Path p = vaultSettings.path().get();
Path p = vaultSettings.path.get();
if (p.startsWith(HOME_DIR)) {
Path relativePath = HOME_DIR.relativize(p);
String homePrefix = SystemUtils.IS_OS_WINDOWS ? "~\\" : "~/";
@@ -311,7 +311,7 @@ public class Vault {
}
public Path getPath() {
return vaultSettings.path().getValue();
return vaultSettings.path.get();
}
/**
@@ -346,7 +346,7 @@ public class Vault {
}
public String getId() {
return vaultSettings.getId();
return vaultSettings.id;
}
// ******************************************************************************

View File

@@ -27,7 +27,7 @@ public class VaultConfigCache {
void reloadConfig() throws IOException {
try {
config.set(readConfigFromStorage(this.settings.path().get()));
config.set(readConfigFromStorage(this.settings.path.get()));
} catch (IOException e) {
config.set(null);
throw e;

View File

@@ -49,8 +49,8 @@ public class VaultListManager {
this.vaultComponentFactory = vaultComponentFactory;
this.defaultVaultName = resourceBundle.getString("defaults.vault.vaultName");
addAll(settings.getDirectories());
vaultList.addListener(new VaultListChangeListener(settings.getDirectories()));
addAll(settings.directories);
vaultList.addListener(new VaultListChangeListener(settings.directories));
autoLocker.init();
}
@@ -70,11 +70,11 @@ public class VaultListManager {
private VaultSettings newVaultSettings(Path path) {
VaultSettings vaultSettings = VaultSettings.withRandomId();
vaultSettings.path().set(path);
vaultSettings.path.set(path);
if (path.getFileName() != null) {
vaultSettings.displayName().set(path.getFileName().toString());
vaultSettings.displayName.set(path.getFileName().toString());
} else {
vaultSettings.displayName().set(defaultVaultName);
vaultSettings.displayName.set(defaultVaultName);
}
return vaultSettings;
}
@@ -95,13 +95,13 @@ public class VaultListManager {
private Vault create(VaultSettings vaultSettings) {
var wrapper = new VaultConfigCache(vaultSettings);
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
wrapper.reloadConfig();
}
return vaultComponentFactory.create(vaultSettings, wrapper, vaultState, null).vault();
} 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();
}
}

View File

@@ -9,6 +9,7 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder;
import dagger.Lazy;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Environment;
import org.cryptomator.common.SubstitutingProperties;
import org.cryptomator.common.ShutdownHook;
import org.cryptomator.ipc.IpcCommunicator;
import org.cryptomator.logging.DebugMode;
@@ -29,10 +30,18 @@ import java.util.concurrent.Executors;
public class Cryptomator {
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.
// 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 Logger LOG = LoggerFactory.getLogger(Cryptomator.class);
private static final CryptomatorComponent CRYPTOMATOR_COMPONENT;
private static final Logger LOG;
private final DebugMode debugMode;
private final SupportedLanguages supportedLanguages;
@@ -63,7 +72,6 @@ public class Cryptomator {
System.out.printf("Cryptomator version %s (build %s)%n", appVer, buildNumber);
return;
}
int exitCode = CRYPTOMATOR_COMPONENT.application().run(args);
LOG.info("Exit {}", exitCode);
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.
* 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()) {
communicator.sendHandleLaunchargs(List.of(args));
communicator.sendRevealRunningApp();

View File

@@ -29,12 +29,12 @@ public class SupportedLanguages {
@Inject
public SupportedLanguages(Settings settings) {
var preferredLanguage = settings.languageProperty().get();
var preferredLanguage = settings.language.get();
preferredLocale = preferredLanguage == null ? Locale.getDefault() : Locale.forLanguageTag(preferredLanguage);
var collator = Collator.getInstance(preferredLocale);
collator.setStrength(Collator.PRIMARY);
var sorted = new ArrayList<String>();
sorted.add(0, Settings.DEFAULT_LANGUAGE);
sorted.add(0, null);
sorted.add(1, ENGLISH);
LANGUAGE_TAGS.stream() //
.sorted((a, b) -> collator.compare(Locale.forLanguageTag(a).getDisplayName(), Locale.forLanguageTag(b).getDisplayName())) //

View File

@@ -26,8 +26,8 @@ public class DebugMode {
}
public void initialize() {
setLogLevels(settings.debugMode().get());
settings.debugMode().addListener(this::logLevelChanged);
setLogLevels(settings.debugMode.get());
settings.debugMode.addListener(this::logLevelChanged);
}
private void logLevelChanged(@SuppressWarnings("unused") ObservableValue<? extends Boolean> observable, @SuppressWarnings("unused") Boolean oldValue, Boolean newValue) {

View File

@@ -36,7 +36,7 @@ public class DefaultSceneFactory implements Function<Parent, Scene> {
}
protected void configureRoot(Parent root) {
root.nodeOrientationProperty().bind(settings.userInterfaceOrientation());
root.nodeOrientationProperty().bind(settings.userInterfaceOrientation);
}
protected void configureScene(Scene scene) {

View File

@@ -1,28 +1,48 @@
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.ErrorCode;
import org.cryptomator.common.Nullable;
import org.cryptomator.ui.common.FxController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.BooleanExpression;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.stage.Stage;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
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.util.Comparator;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
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 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 ";
@@ -46,16 +66,28 @@ public class ErrorController implements FxController {
private final Stage window;
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
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.stackTrace = stackTrace;
this.errorCode = errorCode;
this.previousScene = previousScene;
this.window = window;
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
@@ -70,6 +102,16 @@ public class ErrorController implements FxController {
window.close();
}
@FXML
public void showSolution() {
if (matchingErrorDiscussion.isNotNull().get()) {
var discussion = matchingErrorDiscussion.get();
if (discussion != null) {
application.getHostServices().showDocument(discussion.url);
}
}
}
@FXML
public void searchError() {
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);
copiedDetails.set(true);
CompletableFuture.delayedExecutor(2, TimeUnit.SECONDS, Platform::runLater).execute(() -> {
copiedDetails.set(false);
});
CompletableFuture.delayedExecutor(2, TimeUnit.SECONDS, Platform::runLater).execute(() -> 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 */
public boolean isPreviousScenePresent() {
return previousScene != null;
}
@@ -125,4 +276,21 @@ public class ErrorController implements FxController {
public boolean getCopiedDetails() {
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();
}
}

View File

@@ -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 {
}
}

View File

@@ -36,7 +36,7 @@ public class AutoUnlocker {
public void tryUnlockForTimespan(int timespan, TimeUnit timeUnit) {
// 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));
}
@@ -80,6 +80,6 @@ public class AutoUnlocker {
private Stream<Vault> getMissingAutoUnlockVaults() {
return vaults.stream()
.filter(Vault::isMissing)
.filter(v -> v.getVaultSettings().unlockAfterStartup().get());
.filter(v -> v.getVaultSettings().unlockAfterStartup.get());
}
}

View File

@@ -45,7 +45,7 @@ public class FxApplication {
// init system tray
final boolean hasTrayIcon;
if (settings.showTrayIcon().get() && trayMenu.get().isSupported()) {
if (settings.showTrayIcon.get() && trayMenu.get().isSupported()) {
trayMenu.get().initializeTrayIcon();
Platform.setImplicitExit(false); // don't quit when closing all windows
hasTrayIcon = true;
@@ -55,7 +55,7 @@ public class FxApplication {
// show main window
appWindows.showMainWindow().thenAccept(stage -> {
if (settings.startHidden().get()) {
if (settings.startHidden.get()) {
if (hasTrayIcon) {
stage.hide();
} else {

View File

@@ -36,8 +36,8 @@ public class FxApplicationStyle {
}
public void initialize() {
settings.theme().addListener(this::appThemeChanged);
loadSelectedStyleSheet(settings.theme().get());
settings.theme.addListener(this::appThemeChanged);
loadSelectedStyleSheet(settings.theme.get());
}
private void appThemeChanged(@SuppressWarnings("unused") ObservableValue<? extends UiTheme> observable, @SuppressWarnings("unused") UiTheme oldValue, UiTheme newValue) {

View File

@@ -107,7 +107,7 @@ public class FxApplicationTerminator {
if (allowQuitWithoutPrompt.get()) {
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);
lockAllTask.setOnSucceeded(event -> {
LOG.info("Locked remaining vaults was succesful.");

View File

@@ -38,7 +38,7 @@ public class UpdateChecker {
}
public void automaticallyCheckForUpdatesIfEnabled() {
if (settings.checkForUpdates().get()) {
if (settings.checkForUpdates.get()) {
startCheckingForUpdates(AUTOCHECK_DELAY);
}
}

View File

@@ -70,7 +70,7 @@ public abstract class UpdateCheckerModule {
@Named("checkForUpdatesInterval")
@FxApplicationScoped
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

View File

@@ -1,9 +1,7 @@
package org.cryptomator.ui.fxapp;
import com.fasterxml.jackson.databind.ObjectMapper;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -11,20 +9,16 @@ import org.slf4j.LoggerFactory;
import javafx.concurrent.Task;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Map;
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 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 HttpRequest checkForUpdatesRequest;
@@ -48,16 +42,14 @@ public class UpdateCheckerTask extends Task<String> {
private String processBody(HttpResponse<InputStream> response) throws IOException {
try (InputStream in = response.body(); //
InputStream limitedIn = ByteStreams.limit(in, MAX_RESPONSE_SIZE); //
Reader reader = new InputStreamReader(limitedIn, StandardCharsets.UTF_8)) {
Map<String, String> map = GSON.fromJson(reader, new TypeToken<Map<String, String>>() {
}.getType());
InputStream limitedIn = ByteStreams.limit(in, MAX_RESPONSE_SIZE)) {
var json = JSON.reader().readTree(limitedIn);
if (SystemUtils.IS_OS_MAC_OSX) {
return map.get("mac");
return json.get("mac").asText();
} else if (SystemUtils.IS_OS_WINDOWS) {
return map.get("win");
return json.get("win").asText();
} else if (SystemUtils.IS_OS_LINUX) {
return map.get("linux");
return json.get("linux").asText();
} else {
throw new IllegalStateException("Unsupported operating system");
}

View File

@@ -1,6 +1,6 @@
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.TinyOAuth2;
import io.github.coffeelibs.tinyoauth2client.http.response.Response;
@@ -12,6 +12,8 @@ import java.util.function.Consumer;
class AuthFlowTask extends Task<String> {
private static final ObjectMapper JSON = new ObjectMapper();
private final HubConfig hubConfig;
private final AuthFlowContext authFlowContext;
private final Consumer<URI> redirectUriConsumer;
@@ -39,8 +41,7 @@ class AuthFlowTask extends Task<String> {
if (response.statusCode() != 200) {
throw new NotOkResponseException("Authorization returned status code " + response.statusCode());
}
var json = JsonParser.parseString(response.body());
return json.getAsJsonObject().get("access_token").getAsString();
return JSON.reader().readTree(response.body()).get("access_token").asText();
}
public static class NotOkResponseException extends RuntimeException {

View File

@@ -1,9 +1,5 @@
package org.cryptomator.ui.keyloading.hub;
class CreateDeviceDto {
public String id;
public String name;
public String publicKey;
record CreateDeviceDto(String id, String name, String publicKey) {
}

View File

@@ -1,14 +1,10 @@
package org.cryptomator.ui.keyloading.hub;
import com.google.common.io.CharStreams;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;

View File

@@ -1,6 +1,9 @@
package org.cryptomator.ui.keyloading.hub;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
// needs to be accessible by JSON decoder
@JsonIgnoreProperties(ignoreUnknown = true)
public class HubConfig {
public String clientId;

View File

@@ -2,9 +2,9 @@ package org.cryptomator.ui.keyloading.hub;
import com.auth0.jwt.JWT;
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.gson.Gson;
import com.google.gson.GsonBuilder;
import com.nimbusds.jose.JWEObject;
import dagger.Lazy;
import org.cryptomator.common.settings.DeviceKey;
@@ -46,7 +46,7 @@ import java.util.concurrent.atomic.AtomicReference;
public class RegisterDeviceController implements FxController {
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 final Stage window;
@@ -101,11 +101,8 @@ public class RegisterDeviceController implements FxController {
var keyUri = URI.create(hubConfig.devicesResourceUrl + deviceId);
var deviceKey = keyPair.getPublic().getEncoded();
var dto = new CreateDeviceDto();
dto.id = deviceId;
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 dto = new CreateDeviceDto(deviceId, deviceNameField.getText(), BaseEncoding.base64Url().omitPadding().encode(deviceKey));
var json = toJson(dto);
var request = HttpRequest.newBuilder(keyUri) //
.header("Authorization", "Bearer " + bearerToken) //
.header("Content-Type", "application/json").PUT(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8)) //
@@ -127,6 +124,14 @@ public class RegisterDeviceController implements FxController {
}, 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) {
assert EXPECTED_RESPONSE_CODES.contains(voidHttpResponse.statusCode());

View File

@@ -49,7 +49,7 @@ public class MainWindowTitleController implements FxController {
this.updateAvailable = updateChecker.latestVersionProperty().isNotNull();
this.licenseHolder = licenseHolder;
this.settings = settings;
this.showMinimizeButton = Bindings.createBooleanBinding(this::isShowMinimizeButton, settings.showMinimizeButton(), settings.showTrayIcon());
this.showMinimizeButton = Bindings.createBooleanBinding(this::isShowMinimizeButton, settings.showMinimizeButton, settings.showTrayIcon);
}
@FXML
@@ -85,10 +85,10 @@ public class MainWindowTitleController implements FxController {
}
private void saveWindowSettings() {
settings.windowYPositionProperty().setValue(window.getY());
settings.windowXPositionProperty().setValue(window.getX());
settings.windowWidthProperty().setValue(window.getWidth());
settings.windowHeightProperty().setValue(window.getHeight());
settings.windowXPosition.setValue(window.getX());
settings.windowYPosition.setValue(window.getY());
settings.windowWidth.setValue(window.getWidth());
settings.windowHeight.setValue(window.getHeight());
}
@FXML
@@ -139,7 +139,7 @@ public class MainWindowTitleController implements FxController {
}
public ReadOnlyBooleanProperty debugModeEnabledProperty() {
return settings.debugMode();
return settings.debugMode;
}
public boolean isDebugModeEnabled() {
@@ -152,6 +152,6 @@ public class MainWindowTitleController implements FxController {
public boolean isShowMinimizeButton() {
// 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();
}
}

View File

@@ -54,7 +54,7 @@ public class ResizeController implements FxController {
LOG.trace("init ResizeController");
if (neverTouched()) {
settings.displayConfigurationProperty().setValue(getMonitorSizes());
settings.displayConfiguration.set(getMonitorSizes());
return;
} else {
if (didDisplayConfigurationChange()) {
@@ -65,24 +65,24 @@ public class ResizeController implements FxController {
window.setWidth(window.getMinWidth());
window.setHeight(window.getMinHeight());
} else {
window.setHeight(settings.windowHeightProperty().get() > window.getMinHeight() ? settings.windowHeightProperty().get() : window.getMinHeight());
window.setWidth(settings.windowWidthProperty().get() > window.getMinWidth() ? settings.windowWidthProperty().get() : window.getMinWidth());
window.setX(settings.windowXPositionProperty().get());
window.setY(settings.windowYPositionProperty().get());
window.setHeight(settings.windowHeight.get() > window.getMinHeight() ? settings.windowHeight.get() : window.getMinHeight());
window.setWidth(settings.windowWidth.get() > window.getMinWidth() ? settings.windowWidth.get() : window.getMinWidth());
window.setX(settings.windowXPosition.get());
window.setY(settings.windowYPosition.get());
}
}
savePositionalSettings();
}
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() {
String currentDisplayConfiguration = getMonitorSizes();
String settingsDisplayConfiguration = settings.displayConfigurationProperty().get();
String settingsDisplayConfiguration = settings.displayConfiguration.get();
boolean configurationHasChanged = !settingsDisplayConfiguration.equals(currentDisplayConfiguration);
if (configurationHasChanged) settings.displayConfigurationProperty().setValue(currentDisplayConfiguration);
if (configurationHasChanged) settings.displayConfiguration.set(currentDisplayConfiguration);
return configurationHasChanged;
}
@@ -170,10 +170,10 @@ public class ResizeController implements FxController {
@FXML
public void savePositionalSettings() {
settings.windowHeightProperty().setValue(window.getHeight());
settings.windowWidthProperty().setValue(window.getWidth());
settings.windowYPositionProperty().setValue(window.getY());
settings.windowXPositionProperty().setValue(window.getX());
settings.windowWidth.setValue(window.getWidth());
settings.windowHeight.setValue(window.getHeight());
settings.windowXPosition.setValue(window.getX());
settings.windowYPosition.setValue(window.getY());
}
public BooleanBinding showResizingArrowsProperty() {

View File

@@ -50,7 +50,7 @@ public class VaultDetailMissingVaultController implements FxController {
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(resourceBundle.getString("addvaultwizard.existing.filePickerMimeDesc"), CRYPTOMATOR_FILENAME_GLOB));
File masterkeyFile = fileChooser.showOpenDialog(window);
if (masterkeyFile != null) {
vault.get().getVaultSettings().path().setValue(masterkeyFile.toPath().toAbsolutePath().getParent());
vault.get().getVaultSettings().path.setValue(masterkeyFile.toPath().toAbsolutePath().getParent());
recheck();
}
}

View File

@@ -55,17 +55,17 @@ public class GeneralPreferencesController implements FxController {
@FXML
public void initialize() {
startHiddenCheckbox.selectedProperty().bindBidirectional(settings.startHidden());
autoCloseVaultsCheckbox.selectedProperty().bindBidirectional(settings.autoCloseVaults());
debugModeCheckbox.selectedProperty().bindBidirectional(settings.debugMode());
startHiddenCheckbox.selectedProperty().bindBidirectional(settings.startHidden);
autoCloseVaultsCheckbox.selectedProperty().bindBidirectional(settings.autoCloseVaults);
debugModeCheckbox.selectedProperty().bindBidirectional(settings.debugMode);
autoStartProvider.ifPresent(autoStart -> autoStartCheckbox.setSelected(autoStart.isEnabled()));
var keychainSettingsConverter = new KeychainProviderClassNameConverter(keychainAccessProviders);
keychainBackendChoiceBox.getItems().addAll(keychainAccessProviders);
keychainBackendChoiceBox.setValue(keychainSettingsConverter.fromString(settings.keychainProvider().get()));
keychainBackendChoiceBox.setValue(keychainSettingsConverter.fromString(settings.keychainProvider.get()));
keychainBackendChoiceBox.setConverter(new KeychainProviderDisplayNameConverter());
Bindings.bindBidirectional(settings.keychainProvider(), keychainBackendChoiceBox.valueProperty(), keychainSettingsConverter);
useKeychainCheckbox.selectedProperty().bindBidirectional(settings.useKeychain());
Bindings.bindBidirectional(settings.keychainProvider, keychainBackendChoiceBox.valueProperty(), keychainSettingsConverter);
useKeychainCheckbox.selectedProperty().bindBidirectional(settings.useKeychain);
keychainBackendChoiceBox.disableProperty().bind(useKeychainCheckbox.selectedProperty().not());
}

View File

@@ -57,22 +57,22 @@ public class InterfacePreferencesController implements FxController {
@FXML
public void initialize() {
themeChoiceBox.getItems().addAll(UiTheme.applicableValues());
if (!themeChoiceBox.getItems().contains(settings.theme().get())) {
settings.theme().set(UiTheme.LIGHT);
if (!themeChoiceBox.getItems().contains(settings.theme.get())) {
settings.theme.set(UiTheme.LIGHT);
}
themeChoiceBox.valueProperty().bindBidirectional(settings.theme());
themeChoiceBox.valueProperty().bindBidirectional(settings.theme);
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.valueProperty().bindBidirectional(settings.languageProperty());
preferredLanguageChoiceBox.valueProperty().bindBidirectional(settings.language);
preferredLanguageChoiceBox.setConverter(new LanguageTagConverter(resourceBundle));
nodeOrientationLtr.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.LEFT_TO_RIGHT);
nodeOrientationRtl.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.RIGHT_TO_LEFT);
nodeOrientationLtr.setSelected(settings.userInterfaceOrientation.get() == NodeOrientation.LEFT_TO_RIGHT);
nodeOrientationRtl.setSelected(settings.userInterfaceOrientation.get() == NodeOrientation.RIGHT_TO_LEFT);
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) {
if (nodeOrientationLtr.equals(newValue)) {
settings.userInterfaceOrientation().set(NodeOrientation.LEFT_TO_RIGHT);
settings.userInterfaceOrientation.set(NodeOrientation.LEFT_TO_RIGHT);
} else if (nodeOrientationRtl.equals(newValue)) {
settings.userInterfaceOrientation().set(NodeOrientation.RIGHT_TO_LEFT);
settings.userInterfaceOrientation.set(NodeOrientation.RIGHT_TO_LEFT);
} else {
LOG.warn("Unexpected toggle option {}", newValue);
}

View File

@@ -48,7 +48,7 @@ public class SupporterCertificateController implements FxController {
private void registrationKeyChanged(@SuppressWarnings("unused") ObservableValue<? extends String> observable, @SuppressWarnings("unused") String oldValue, String newValue) {
licenseHolder.validateAndStoreLicense(newValue);
if (!licenseHolder.isValidLicense()) {
settings.theme().set(UiTheme.LIGHT);
settings.theme.set(UiTheme.LIGHT);
}
}

View File

@@ -42,7 +42,7 @@ public class UpdatesPreferencesController implements FxController {
}
public void initialize() {
checkForUpdatesCheckbox.selectedProperty().bindBidirectional(settings.checkForUpdates());
checkForUpdatesCheckbox.selectedProperty().bindBidirectional(settings.checkForUpdates);
}
@FXML

View File

@@ -27,6 +27,8 @@ import java.util.concurrent.atomic.AtomicReference;
public class VolumePreferencesController implements FxController {
private static final String DOCS_MOUNTING_URL = "https://docs.cryptomator.org/en/1.7/desktop/volume-type/";
private static final int MIN_PORT = 1024;
private static final int MAX_PORT = 65535;
private final Settings settings;
private final ObservableValue<MountService> selectedMountService;
@@ -51,7 +53,7 @@ public class VolumePreferencesController implements FxController {
this.resourceBundle = resourceBundle;
var fallbackProvider = mountProviders.stream().findFirst().orElse(null);
this.selectedMountService = ObservableUtil.mapWithDefault(settings.mountService(), serviceName -> mountProviders.stream().filter(s -> s.getClass().getName().equals(serviceName)).findFirst().orElse(fallbackProvider), fallbackProvider);
this.selectedMountService = ObservableUtil.mapWithDefault(settings.mountService, serviceName -> mountProviders.stream().filter(s -> s.getClass().getName().equals(serviceName)).findFirst().orElse(fallbackProvider), fallbackProvider);
this.loopbackPortSupported = BooleanExpression.booleanExpression(selectedMountService.map(s -> s.hasCapability(MountCapability.LOOPBACK_PORT)));
this.mountToDirSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_WITHIN_EXISTING_PARENT) || s.hasCapability(MountCapability.MOUNT_TO_EXISTING_DIR));
this.mountToDriveLetterSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_AS_DRIVE_LETTER));
@@ -69,15 +71,15 @@ public class VolumePreferencesController implements FxController {
volumeTypeChoiceBox.getItems().add(null);
volumeTypeChoiceBox.getItems().addAll(mountProviders);
volumeTypeChoiceBox.setConverter(new MountServiceConverter());
boolean autoSelected = settings.mountService().get() == null;
boolean autoSelected = settings.mountService.get() == null;
volumeTypeChoiceBox.getSelectionModel().select(autoSelected ? null : selectedMountService.getValue());
volumeTypeChoiceBox.valueProperty().addListener((observableValue, oldProvider, newProvider) -> {
var toSet = Optional.ofNullable(newProvider).map(nP -> nP.getClass().getName()).orElse(null);
settings.mountService().set(toSet);
settings.mountService.set(toSet);
});
loopbackPortField.setText(String.valueOf(settings.port().get()));
loopbackPortApplyButton.visibleProperty().bind(settings.port().asString().isNotEqualTo(loopbackPortField.textProperty()));
loopbackPortField.setText(String.valueOf(settings.port.get()));
loopbackPortApplyButton.visibleProperty().bind(settings.port.asString().isNotEqualTo(loopbackPortField.textProperty()));
loopbackPortApplyButton.disableProperty().bind(Bindings.createBooleanBinding(this::validateLoopbackPort, loopbackPortField.textProperty()).not());
}
@@ -85,7 +87,7 @@ public class VolumePreferencesController implements FxController {
try {
int port = Integer.parseInt(loopbackPortField.getText());
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) {
return false;
}
@@ -93,7 +95,7 @@ public class VolumePreferencesController implements FxController {
public void doChangeLoopbackPort() {
if (validateLoopbackPort()) {
settings.port().set(Integer.parseInt(loopbackPortField.getText()));
settings.port.set(Integer.parseInt(loopbackPortField.getText()));
}
}

View File

@@ -1,5 +1,6 @@
package org.cryptomator.ui.unlock;
import org.cryptomator.common.mount.MountPointInUseException;
import org.cryptomator.common.mount.MountPointNotExistsException;
import org.cryptomator.common.mount.MountPointNotSupportedException;
import org.cryptomator.common.vaults.Vault;
@@ -41,6 +42,7 @@ public class UnlockInvalidMountPointController implements FxController {
var translationKey = switch (e) {
case MountPointNotSupportedException x -> "unlock.error.customPath.description.notSupported";
case MountPointNotExistsException x -> "unlock.error.customPath.description.notExists";
case MountPointInUseException x -> "unlock.error.customPath.description.inUse";
default -> "unlock.error.customPath.description.generic";
};
dialogDescription.setFormat(resourceBundle.getString(translationKey));

View File

@@ -50,7 +50,7 @@ public class UnlockSuccessController implements FxController {
LOG.trace("UnlockSuccessController.close()");
window.close();
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);
if (rememberChoiceCheckbox.isSelected()) {
vault.getVaultSettings().actionAfterUnlock().setValue(WhenUnlocked.REVEAL);
vault.getVaultSettings().actionAfterUnlock.setValue(WhenUnlocked.REVEAL);
}
}

View File

@@ -94,7 +94,7 @@ public class UnlockWorkflow extends Task<Boolean> {
protected void succeeded() {
LOG.info("Unlock of '{}' succeeded.", vault.getDisplayName());
switch (vault.getVaultSettings().actionAfterUnlock().get()) {
switch (vault.getVaultSettings().actionAfterUnlock.get()) {
case ASK -> Platform.runLater(() -> {
window.setScene(successScene.get());
window.show();

View File

@@ -45,21 +45,21 @@ public class GeneralVaultOptionsController implements FxController {
@FXML
public void initialize() {
vaultName.textProperty().set(vault.getVaultSettings().displayName().get());
vaultName.textProperty().set(vault.getVaultSettings().displayName.get());
vaultName.focusedProperty().addListener(this::trimVaultNameOnFocusLoss);
vaultName.setTextFormatter(new TextFormatter<>(this::checkVaultNameLength));
unlockOnStartupCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().unlockAfterStartup());
unlockOnStartupCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().unlockAfterStartup);
actionAfterUnlockChoiceBox.getItems().addAll(WhenUnlocked.values());
actionAfterUnlockChoiceBox.valueProperty().bindBidirectional(vault.getVaultSettings().actionAfterUnlock());
actionAfterUnlockChoiceBox.valueProperty().bindBidirectional(vault.getVaultSettings().actionAfterUnlock);
actionAfterUnlockChoiceBox.setConverter(new WhenUnlockedConverter(resourceBundle));
lockAfterTimeCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().autoLockWhenIdle());
Bindings.bindBidirectional(lockTimeInMinutesTextField.textProperty(), vault.getVaultSettings().autoLockIdleSeconds(), new IdleTimeSecondsConverter());
lockAfterTimeCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().autoLockWhenIdle);
Bindings.bindBidirectional(lockTimeInMinutesTextField.textProperty(), vault.getVaultSettings().autoLockIdleSeconds, new IdleTimeSecondsConverter());
}
private void trimVaultNameOnFocusLoss(Observable observable, Boolean wasFocussed, Boolean isFocussed) {
if (!isFocussed) {
var trimmed = vaultName.getText().trim();
vault.getVaultSettings().displayName().set(trimmed);
vault.getVaultSettings().displayName.set(trimmed);
}
}

View File

@@ -74,18 +74,18 @@ public class MountOptionsController implements FxController {
this.mountpointDriveLetterSupported = mountService.map(as -> as.service().hasCapability(MountCapability.MOUNT_AS_DRIVE_LETTER));
this.mountFlagsSupported = mountService.map(as -> as.service().hasCapability(MountCapability.MOUNT_FLAGS));
this.readOnlySupported = mountService.map(as -> as.service().hasCapability(MountCapability.READ_ONLY));
this.directoryPath = vault.getVaultSettings().mountPoint().map(p -> isDriveLetter(p) ? null : p.toString());
this.directoryPath = vault.getVaultSettings().mountPoint.map(p -> isDriveLetter(p) ? null : p.toString());
this.applicationWindows = applicationWindows;
}
@FXML
public void initialize() {
// readonly:
readOnlyCheckbox.selectedProperty().bindBidirectional(vaultSettings.usesReadOnlyMode());
readOnlyCheckbox.selectedProperty().bindBidirectional(vaultSettings.usesReadOnlyMode);
// custom mount flags:
mountFlagsField.disableProperty().bind(customMountFlagsCheckbox.selectedProperty().not());
customMountFlagsCheckbox.setSelected(!Strings.isNullOrEmpty(vaultSettings.mountFlags().getValue()));
customMountFlagsCheckbox.setSelected(!Strings.isNullOrEmpty(vaultSettings.mountFlags.getValue()));
toggleUseCustomMountFlags();
//driveLetter choice box
@@ -93,14 +93,14 @@ public class MountOptionsController implements FxController {
driveLetterSelection.setConverter(new WinDriveLetterLabelConverter(windowsDriveLetters, resourceBundle));
//mountPoint toggle group
var mountPoint = vaultSettings.getMountPoint();
var mountPoint = vaultSettings.mountPoint.get();
if (mountPoint == null) {
//prepare and select auto
mountPointToggleGroup.selectToggle(mountPointAutoBtn);
} else if (mountPoint.getParent() == null && isDriveLetter(mountPoint)) {
//prepare and select drive letter
mountPointToggleGroup.selectToggle(mountPointDriveLetterBtn);
driveLetterSelection.valueProperty().bindBidirectional(vaultSettings.mountPoint());
driveLetterSelection.valueProperty().bindBidirectional(vaultSettings.mountPoint);
} else {
//prepare and select dir
mountPointToggleGroup.selectToggle(mountPointDirBtn);
@@ -118,14 +118,14 @@ public class MountOptionsController implements FxController {
if (customMountFlagsCheckbox.isSelected()) {
readOnlyCheckbox.setSelected(false); // to prevent invalid states
mountFlagsField.textProperty().unbind();
var mountFlags = vaultSettings.mountFlags().get();
var mountFlags = vaultSettings.mountFlags.get();
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 {
mountFlagsField.textProperty().unbindBidirectional(vaultSettings.mountFlags());
vaultSettings.mountFlags().set(null);
mountFlagsField.textProperty().unbindBidirectional(vaultSettings.mountFlags);
vaultSettings.mountFlags.set(null);
mountFlagsField.textProperty().bind(defaultMountFlags);
}
}
@@ -134,7 +134,7 @@ public class MountOptionsController implements FxController {
public void chooseCustomMountPoint() {
try {
Path chosenPath = chooseCustomMountPointInternal();
vaultSettings.mountPoint().set(chosenPath);
vaultSettings.mountPoint.set(chosenPath);
} catch (NoDirSelectedException e) {
//no-op
}
@@ -151,7 +151,7 @@ public class MountOptionsController implements FxController {
DirectoryChooser directoryChooser = new DirectoryChooser();
directoryChooser.setTitle(resourceBundle.getString("vaultOptions.mount.mountPoint.directoryPickerTitle"));
try {
var mp = vaultSettings.mountPoint().get();
var mp = vaultSettings.mountPoint.get();
var initialDir = mp != null && !isDriveLetter(mp) ? mp : Path.of(System.getProperty("user.home"));
if (Files.isDirectory(initialDir)) {
@@ -170,13 +170,13 @@ public class MountOptionsController implements FxController {
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!
driveLetterSelection.valueProperty().unbindBidirectional(vaultSettings.mountPoint());
driveLetterSelection.valueProperty().unbindBidirectional(vaultSettings.mountPoint);
if (mountPointDriveLetterBtn.equals(newToggle)) {
vaultSettings.mountPoint().set(windowsDriveLetters.getFirstDesiredAvailable().orElse(windowsDriveLetters.getAll().stream().findAny().get()));
driveLetterSelection.valueProperty().bindBidirectional(vaultSettings.mountPoint());
vaultSettings.mountPoint.set(windowsDriveLetters.getFirstDesiredAvailable().orElse(windowsDriveLetters.getAll().stream().findAny().get()));
driveLetterSelection.valueProperty().bindBidirectional(vaultSettings.mountPoint);
} else if (mountPointDirBtn.equals(newToggle)) {
try {
vaultSettings.mountPoint().set(chooseCustomMountPointInternal());
vaultSettings.mountPoint.set(chooseCustomMountPointInternal());
} catch (NoDirSelectedException e) {
if (oldToggle != null && !mountPointDirBtn.equals(oldToggle)) {
mountPointToggleGroup.selectToggle(oldToggle);
@@ -185,7 +185,7 @@ public class MountOptionsController implements FxController {
}
}
} else {
vaultSettings.mountPoint().set(null);
vaultSettings.mountPoint.set(null);
}
}

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import org.cryptomator.ui.controls.FontAwesome5Spinner?>
<?import org.cryptomator.ui.controls.FormattedLabel?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
@@ -31,20 +32,28 @@
</StackPane>
<VBox spacing="6" HBox.hgrow="ALWAYS">
<FormattedLabel styleClass="label-extra-large" format="%error.message" arg1="${controller.errorCode}"/>
<Label text="%error.description" wrapText="true"/>
<Hyperlink styleClass="hyperlink-underline" text="%error.hyperlink.lookup" onAction="#searchError" contentDisplay="LEFT">
<graphic>
<FontAwesome5IconView glyph="LINK" glyphSize="12"/>
</graphic>
</Hyperlink>
<Hyperlink styleClass="hyperlink-underline" text="%error.hyperlink.report" onAction="#reportError" contentDisplay="LEFT">
<graphic>
<FontAwesome5IconView glyph="LINK" glyphSize="12"/>
</graphic>
</Hyperlink>
<FontAwesome5Spinner glyphSize="24" visible="${controller.isLoadingHttpResponse}" managed="${controller.isLoadingHttpResponse}"/>
<VBox visible="${!controller.isLoadingHttpResponse}" managed="${!controller.isLoadingHttpResponse}">
<Label text="%error.existingSolutionDescription" wrapText="true" visible="${controller.errorSolutionFound}" managed="${controller.errorSolutionFound}"/>
<Hyperlink styleClass="hyperlink-underline" text="%error.hyperlink.solution" onAction="#showSolution" contentDisplay="LEFT" visible="${controller.errorSolutionFound}" managed="${controller.errorSolutionFound}">
<graphic>
<FontAwesome5IconView glyph="LINK" glyphSize="12"/>
</graphic>
</Hyperlink>
<Label text="%error.description" wrapText="true" visible="${!controller.errorSolutionFound}" managed="${!controller.errorSolutionFound}"/>
<Hyperlink styleClass="hyperlink-underline" text="%error.hyperlink.lookup" onAction="#searchError" contentDisplay="LEFT" visible="${!controller.errorSolutionFound}" managed="${!controller.errorSolutionFound}">
<graphic>
<FontAwesome5IconView glyph="LINK" glyphSize="12"/>
</graphic>
</Hyperlink>
<Hyperlink styleClass="hyperlink-underline" text="%error.hyperlink.report" onAction="#reportError" contentDisplay="LEFT" visible="${!controller.errorSolutionFound}" managed="${!controller.errorSolutionFound}">
<graphic>
<FontAwesome5IconView glyph="LINK" glyphSize="12"/>
</graphic>
</Hyperlink>
</VBox>
</VBox>
</HBox>
<VBox spacing="6" VBox.vgrow="ALWAYS">
<HBox>
<Label text="%error.technicalDetails"/>

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.report=Report this error
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.vault.vaultName=Vault
@@ -127,6 +130,7 @@ unlock.success.revealBtn=Reveal Drive
unlock.error.customPath.message=Unable to mount vault to custom path
unlock.error.customPath.description.notSupported=If you wish to keep using the custom path, please go to the preferences and select a volume type that supports it. Otherwise, go to the vault options and choose a supported mount point.
unlock.error.customPath.description.notExists=The custom mount path does not exist. Either create it in your local filesystem or change it in the vault options.
unlock.error.customPath.description.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
## Hub
hub.noKeychain.message=Unable to access device key

View File

@@ -74,40 +74,112 @@ addvault.new.readme.storageLocation.5=• liitä kaikki tiedostot salaukseen tä
addvault.new.readme.storageLocation.6=Jos haluat salata tiedostoja ja tarkastella Vaultin sisältöä, tee seuraavat toimet:
addvault.new.readme.storageLocation.7=1. Lisää tämä Vault Cryptomatoriin.
addvault.new.readme.storageLocation.8=2. Avaa Vault Cryptomatorissa.
addvault.new.readme.storageLocation.9=3. Avaa tallennuskansiosi klikkaamalla "Näytä" -painiketta.
addvault.new.readme.storageLocation.10=Mikäli tarvitset apua, konsultoi Cryptomatorin dokumentaatiota: %s
addvault.new.readme.accessLocation.fileName=TERVETULOA.rtf
addvault.new.readme.accessLocation.1=🔐️ SALATTU VOLYYMI 🔐️
addvault.new.readme.accessLocation.2=Tämä on salatun holvisi tallennuskansio.
addvault.new.readme.accessLocation.3=Cryptomator salaa kaikki tänne siirtämäsi tiedostot. Voit työskennellä tässä kansiossa aivan samalla tavalla kuin muutenkin. Tämä on ainoastaan näkymä jossa salaus on purettu. Todellisuudessa kaikki tiedostot pysyvät salattuina aina.
addvault.new.readme.accessLocation.4=Voit halutessasi poistaa tämän tiedoston. Poistaminen ei vaikuta Cryptomatorin toimintaan.
## Existing
addvaultwizard.existing.instruction=Valitse olemassa olevan Cryptomator holvin "vault.cryptomator" -tiedosto. Mikäli "vault.cryptomator" -tiedostoa ei ole, valitse "masterkey.cryptomator" -tiedosto.
addvaultwizard.existing.chooseBtn=Valitse…
addvaultwizard.existing.filePickerTitle=Valitse Holvi -tiedosto
addvaultwizard.existing.filePickerMimeDesc=Cryptomator Holvi
## Success
addvaultwizard.success.nextStepsInstructions=Holvi "%s" on lisätty.\nSinun täytyy avata kyseinen holvi tarkastellaksesi sen sisältöä. Vaihtoehtoisesti voit avata sen myöhemmin koska tahansa.
addvaultwizard.success.unlockNow=Avaa Nyt
# Remove Vault
removeVault.title=Poista "%s"
removeVault.message=Poistetaanko holvi?
removeVault.description=Tämä toiminto ainoastaan poistaa valitun holvin Cryptomatorin näkymästä. Salattuja tiedostoja tai holvia itsessään ei poisteta.
removeVault.confirmBtn=Poista Holvi
# Change Password
changepassword.title=Vaihda salasana
changepassword.enterOldPassword=Syötä "%s": n nykyinen salasana
changepassword.finalConfirmation=Ymmärrän, että tiedostojani ei voi palauttaa, mikäli unohdan salasanani.
# Forget Password
forgetPassword.title=Unohda Salasana
forgetPassword.message=Unohda tallennetut salasanat?
forgetPassword.description=Tämä poistaa valitun holvin tallennetun salasanan järjestelmäsi avainrenkaasta.
forgetPassword.confirmBtn=Unohda Salasana
# Unlock
unlock.title=Avaa "%s"
unlock.passwordPrompt=Syötä salasana "%s":lle
unlock.savePassword=Muista salasana
unlock.unlockBtn=Avaa
## Select
unlock.chooseMasterkey.message=Masterkey -tiedostoa ei löydy
unlock.chooseMasterkey.description=Cryptomator ei paikantanut masterkey -tiedostoa holville "%s". Ole hyvä ja valitse tiedosto manuaalisesti.
unlock.chooseMasterkey.filePickerTitle=Valitse Masterkey -tiedosto
unlock.chooseMasterkey.filePickerMimeDesc=Cryptomator Masterkey
## Success
unlock.success.message=Lukituksen purku onnistui
unlock.success.description="%s" holvin sisältö on nyt saatavilla sen hallinnointikohteessa.
unlock.success.rememberChoice=Muista valintani. Älä kysy uudelleen.
unlock.success.revealBtn=Paljasta Asema
## Failure
unlock.error.customPath.message=Holvia ei pystytty yhdistämään valittuun polkuun
unlock.error.customPath.description.notSupported=Mikäli haluat jatkaa mukautetun polun käyttöä, ole hyvä ja mene Asetuksiin ja valitse volyymityyppi mikä tukee sitä. Muussa tapauksessa avaa holvin asetukset ja valitse tuettu mukautettu polku.
unlock.error.customPath.description.notExists=Valitsemaasi mukautettua polkua ei ole olemassa. Voit joko luoda uuden polun tai muuttaa polkua holvisi asetuksissa.
unlock.error.customPath.description.generic=Olet valinnut mukautetun polun holvillesi, mutta sen kanssa ilmeni ongelma: %s
## Hub
hub.noKeychain.message=Laitteen avainta ei löytynyt
hub.noKeychain.description=Hub-holvien purkamiseksi tarvitaan laiteavain joka on suojattu avainrenkaalla. Jatkaaksesi, kytke “%s” päälle ja valitse avainrengas asetuksista.
hub.noKeychain.openBtn=Avaa asetukset
### Waiting
hub.auth.message=Odotetaan todennusta…
hub.auth.description=Pitäisi ohjata sinut automaattisesti uudelleen kirjautumissivulle.
hub.auth.loginLink=Uudelleenohjaus epäonnistui? Avaa tästä manuaalisesti.
### Receive Key
hub.receive.message=Odotetaan vastausta…
hub.receive.description=Cryptomator yhdistää Hub:iin. Ole hyvä ja odota.
### Register Device
hub.register.message=Laitteen nimi vaaditaan
hub.register.description=Et ole ilmeisesti ennen yhdistänyt Hub:iin tältä laitteelta. Jotta pääsyoikeus voidaan todentaa, sinun täytyy nimetä tämä laite.
hub.register.nameLabel=Laitteen Nimi
hub.register.occupiedMsg=Tämä nimi on jo käytössä
hub.register.registerBtn=Vahvista
### Registration Success
hub.registerSuccess.message=Laite nimetty onnistuneesti
hub.registerSuccess.description=Käyttääksesi holvia, holvin omistajan on valtuutettava laitteesi.
### Registration Failed
hub.registerFailed.message=Laitteen nimeäminen epäonnistui
hub.registerFailed.description=Nimeämisprosessissa tapahtui virhe. Löydät lisää tietoja lokitiedostoista.
### Unauthorized
hub.unauthorized.message=Pääsy estetty
hub.unauthorized.description=Laitteellasi ei ole pääsyvaltuutusta tähän holviin. Pyydä holvin omistajaa lisäämän valtuutus laitteellesi.
### License Exceeded
hub.invalidLicense.message=Hub-lisenssi ei ole voimassa
hub.invalidLicense.description=Cryptomator Hub:illasi ei ole voimassa olevaa lisenssiä. Ole hyvä ja ilmoita Hubin järjestelmänvalvojalle lisenssin päivittämiseksi tai sen uusimiseksi.
# Lock
## Force
lock.forced.message=Lukitus epäonnistui
lock.forced.description="%s":n lukitus epäonnistui, koska sinulla on avoimia tiedostoja tai niihin liittyviä päivityksiä vireillä. Voit halutessasi pakottaa lukituksen, mutta tämä saattaa johtaa tallentamattomien tiedostojen katoamiseen.
lock.forced.retryBtn=Yritä uudestaan
lock.forced.forceBtn=Pakota Lukitus
## Failure
lock.fail.message=Holvin lukitseminen epäonnistui
lock.fail.description="%s" holvia ei voitu lukita. Varmista, että avoinna olevat tiedostot tallennetaan turvalliseen paikkaan ja että tiedostot eivät ole avoinna tai käytössä. Lukitaksesi holvin, pakota Cryptomator -prosessin lopetus manuaalisesti.
# Migration
migration.title=Päivitä Holvi
## Start
migration.start.header=Päivitä Holvi
migration.start.text="%s" holvi tulee päivittää uuteen versioon, jotta voit avata sen Cryptomatorissa. Ennen kuin jatkat, varmista että ymmärrät seuraavan:
migration.start.remarkUndone=Päivitystä ei voi peruuttaa.
migration.start.remarkVersions=Cryptomatorin vanhemmat versiot eivät voi avata päivitettyä holvia.
migration.start.remarkCanRun=Sinun tulee varmistaa, että kaikki laitteet joilla haluat käyttää tätä holvia on päivitetty Cryptomatorin uusimpaan versioon.
migration.start.remarkSynced=Sinun tulee varmistaa, että holvisi on täysin ajantasalla tällä laitteella ja muilla laitteillasi, ennen kuin aloitat päivitysprosessin.
migration.start.confirm=Olen lukenut ja ymmärtänyt yllä ilmoitetut ohjeet
## Run
## Success
migration.success.unlockNow=Avaa Nyt
## Missing file system capabilities
## Impossible
@@ -142,14 +214,19 @@ main.closeBtn.tooltip=Sulje
main.preferencesBtn.tooltip=Asetukset
## Vault List
main.vaultlist.contextMenu.lock=Lukitse
main.vaultlist.contextMenu.unlockNow=Avaa Nyt
main.vaultlist.contextMenu.reveal=Paljasta Asema
main.vaultlist.addVaultBtn=Lisää Vault
## Vault Detail
### Welcome
### Locked
main.vaultDetail.unlockNowBtn=Avaa Nyt
### Unlocked
main.vaultDetail.revealBtn=Paljasta Asema
main.vaultDetail.lockBtn=Lukitse
### Missing
### Needs Migration
main.vaultDetail.migrateButton=Päivitä Holvi
### Error
# Wrong File Alert
@@ -157,10 +234,12 @@ main.vaultDetail.lockBtn=Lukitse
# Vault Options
## General
vaultOptions.general.vaultName=Vault Nimi
vaultOptions.general.actionAfterUnlock.reveal=Paljasta Asema
## Mount
vaultOptions.mount.mountPoint.directoryPickerButton=Valitse…
## Master Key
vaultOptions.masterkey.changePasswordBtn=Vaihda salasana
## Hub
# Recovery Key

View File

@@ -50,26 +50,41 @@ addvaultwizard.new.directoryPickerLabel=Custom Location
addvaultwizard.new.directoryPickerButton=Mamili…
addvaultwizard.new.directoryPickerTitle=Pumili ng Direktoryo
### Password
addvaultwizard.new.createVaultBtn=Gumawa ng bagong Vault
### Information
addvault.new.readme.storageLocation.2=Ito ang lokasyon ng iyong vault na imbakan.
addvault.new.readme.storageLocation.3=HUWAG
addvault.new.readme.storageLocation.6=Kung gusto mo i-encrypt ang mga files at makita nang nilalaman ng vault, gawin ang nakasabi:
addvault.new.readme.storageLocation.7=1. Idagdag ang vault na ito sa Cryptomator.
addvault.new.readme.accessLocation.2=Ito ang lokasyon ng iyong vault.
## Existing
addvaultwizard.existing.chooseBtn=Mamili…
## Success
# Remove Vault
removeVault.title=Tanggalin %s
removeVault.message=Itangal ang vault?
removeVault.confirmBtn=Itangal ang vault
# Change Password
# Forget Password
# Unlock
unlock.savePassword=Maalala ang password
unlock.unlockBtn=I-unlock
## Select
unlock.chooseMasterkey.filePickerTitle=Piliin ang masterkey file
unlock.chooseMasterkey.filePickerMimeDesc=Cryptomator Masterkey
## Success
unlock.success.rememberChoice=Paalala ang pinili ko, huwag mag tanong ulit
## Failure
## Hub
### Waiting
### Receive Key
### Register Device
hub.register.occupiedMsg=Ang pangalan ay nagamit na
hub.register.registerBtn=Kumpirmahin
### Registration Success
### Registration Failed
### Unauthorized

View File

@@ -56,6 +56,9 @@ addvaultwizard.new.locationIsOk=Megfelelő hely a trezornak
addvaultwizard.new.invalidName=Érvénytelen vault név
addvaultwizard.new.validName=Érvényes vault név
addvaultwizard.new.validCharacters.message=A vault neve a következő karaktereket tartalmazhatja:
addvaultwizard.new.validCharacters.chars=Szókarakterek (pl. a, ж vagy 수)
addvaultwizard.new.validCharacters.numbers=Számok
addvaultwizard.new.validCharacters.dashes=Kötőjel (%s) vagy alulvonás (%s)
### Password
addvaultwizard.new.createVaultBtn=Új széf létrehozása
addvaultwizard.new.generateRecoveryKeyChoice=Nem fog tudni hozzáférni az adataihoz a jelszó nélkül. Akar egy visszaállítási kulcsot arra az esetre, ha elveszíti a jelszavát?
@@ -110,28 +113,49 @@ unlock.passwordPrompt=Írja be a jelszavát a következő széfhez "%s":
unlock.savePassword=Jelszó megjegyzése
unlock.unlockBtn=Feloldás
## Select
unlock.chooseMasterkey.message=Mesterkulcs fájl nem található
unlock.chooseMasterkey.description=Nem található a tároló kulcsfájlja a várt helyen. Kérjük válassza ki a kulcsfájlt manuálisan.
unlock.chooseMasterkey.filePickerTitle=Mesterkulcs fájl kiválasztása
unlock.chooseMasterkey.filePickerMimeDesc=Cryptomator Mesterkulcs
## Success
unlock.success.message=Sikeres feloldás
unlock.success.description="%s" sikreresen feloldásra került! Mostmár hozzáférhet a virtuális trezorhoz.
unlock.success.rememberChoice=Jegyezze meg a választást és ne mutassa többet
unlock.success.revealBtn=Széf megjelenítése
## Failure
unlock.error.customPath.message=Nem lehet csatolni a széfet az egyéni útvonalhoz
unlock.error.customPath.description.notSupported=Ha szeretné továbbra is az egyéni útvonalat használni, kérem, menjen a beállításokba és válasszon egy kötet típust, amely támogatja azt. Máskülönben, menjen a széf opciókhoz és válasszon egy támogatott csatoláspontot.
unlock.error.customPath.description.notExists=Az egyéni csatolás útvonal nem létezik. Hozza létre a helyi fájlrendszerében vagy változtassa meg a széf opciókban.
unlock.error.customPath.description.generic=Egyéni csatolási útvonalat választott ehhez a széfhez, de használatakor ez a hibaüzenet érkezett: %s
## Hub
hub.noKeychain.message=Nem lehet az eszközkulcshoz hozzáférni
hub.noKeychain.description=Hogy feloldja a Hub széfeket, egy eszközkulcs szükséges, mely egy kulcslánccal van biztosítva. A folytatáshoz engedélyezze a következőt: “%s” és válasszon egy kulcsláncot a beállításokban.
hub.noKeychain.openBtn=Beállítások megnyitása
### Waiting
hub.auth.message=Várakozás a hitelesítésre…
hub.auth.description=Automatikusan átirányítjuk a bejelentkezési oldalra.
hub.auth.loginLink=Nem sikerült az átirányítás? Kattintson ide a megnyitáshoz.
### Receive Key
hub.receive.message=Válasz feldolgozása…
hub.receive.description=Cryptomator fogadja és feldolgozza a Hub válaszát. Kérem, várjon.
### Register Device
hub.register.message=Eszköznév szükséges
hub.register.description=Úgy tűnik, ez az első Hub-hozzáférés erről az eszközről. A hozzáférési jogosultság azonosításához el kell neveznie ezt az eszközt.
hub.register.nameLabel=Készülék neve
hub.register.occupiedMsg=Ez a név már használatban van
hub.register.registerBtn=Megerősítés
### Registration Success
hub.registerSuccess.message=Eszköz elnevezve
hub.registerSuccess.description=A széf hozzáféréséhez a széf tulajdonosának hitelesítenie kell az eszközét.
### Registration Failed
hub.registerFailed.message=Az eszköz elnevezése sikertelen volt
hub.registerFailed.description=Hiba állt fel az elnevezési folyamatban. További részletekért tekintse meg az alkalmazásnaplót.
### Unauthorized
hub.unauthorized.message=Hozzáférés megtagadva
hub.unauthorized.description=Eszköze még nem kapott engedélyt ehhez a széfhez. Kérje a széf tulajdonosát, hogy engedélyezze a hozzáférést.
### License Exceeded
hub.invalidLicense.message=Érvénytelen Hub licenc
hub.invalidLicense.description=Az Ön Cryptomator Hub példánya érvénytelen licenccel rendelkezik. Kérem, értesítsen egy Hub rendszergazdát hogy frissítse vagy újítsa meg a licencet.
# Lock
## Force
@@ -147,7 +171,11 @@ lock.fail.description=A "%s" tárolót nem lehetett zárolni. Győződjön meg a
migration.title=Széf frissítése
## Start
migration.start.header=Széf frissítése
migration.start.text=Hogy megnyissa a "%s" nevű széfét a Cryptomator ezen új verziójában, a széfet frissíteni kell egy újabb formátumra. Mielőtt ezt megteszi, az alábbiakat érdemes figyelembe vennie:
migration.start.remarkUndone=Ezt a módosítást nem lehet visszafordítani.
migration.start.remarkVersions=A Cryptomator régebbi verziói nem fogják tudni megnyitni a frissített széfet.
migration.start.remarkCanRun=Meg kell győződnie arról, hogy minden eszköz, amelyről eléri ezt a széfet képes futtatni a Cryptomator ezen verzióját.
migration.start.remarkSynced=Meg kell győződnie arról, hogy a széfe teljesen szinkronizálva van ezen és az összes többi eszközön a frissítés előtt.
migration.start.confirm=Elolvastam és megértettem a fenti információkat
## Run
migration.run.enterPassword=Írja be a jelszót a következőhöz Enter the password for "%s"
@@ -205,10 +233,16 @@ health.result.severityFilter.good=Rendben
health.result.severityFilter.info=Infó
health.result.severityFilter.warn=Figyelmeztetés
health.result.severityFilter.crit=Kritikus
health.result.severityTip.good=Súlyosság: Jó\nNormális séf struktúra.
health.result.severityTip.info=Súlyosság: Info\nA széf struktúrája ép, javítás ajánlott.
health.result.severityTip.warn=Súlyosság: Figyelmeztetés\nA széf struktúrája sérült, javítás erősen ajánlott.
health.result.severityTip.crit=Súlyosság: Kritikus\nA széf struktúrája sérült, adatvesztés áll fönt.
health.result.fixStateFilter.all=Javítás státusz - Mind
health.result.fixStateFilter.fixable=Javítható
health.result.fixStateFilter.notFixable=Nem javítható
health.result.fixStateFilter.fixing=Javítás alatt…
health.result.fixStateFilter.fixed=Kijavított
health.result.fixStateFilter.fixFailed=Sikertelen javítás
## Fix Application
health.fix.fixBtn=Javítás
health.fix.successTip=Javítás sikeres
@@ -219,6 +253,7 @@ preferences.title=Beállítások
## General
preferences.general=Általános
preferences.general.startHidden=Az ablak elrejtése a Cryptomator indítása után
preferences.general.autoCloseVaults=A széfek automatikus lezárása az alkalmazás bezárásakor
preferences.general.debugLogging=Hibakeresési naplózás engedélyezése
preferences.general.debugDirectory=Naplófájlok megjelenítése
preferences.general.autoStart=Cryptomator indítása a rendszerrel együtt
@@ -239,7 +274,17 @@ preferences.interface.showMinimizeButton=Kicsinyítés ikon megjelenítése
preferences.interface.showTrayIcon=Tálca ikon megjelenítése (újraindítás szükséges)
## Volume
preferences.volume=Virtuális meghajtó
preferences.volume.type=Kötet Típusa
preferences.volume.type.automatic=Automatikus
preferences.volume.docsTooltip=További információért a kötet típusokról kattintson ide, hogy megnyissa a dokumentációt.
preferences.volume.fuseRestartRequired=A változtatások alkalmazásához újra kell indítania a Cryptomatort.
preferences.volume.tcp.port=TCP Port
preferences.volume.supportedFeatures=A kiválaszott kötet típus az alábbi funkciókat támogatja:
preferences.volume.feature.mountAuto=Automatikus csatlakozási pont választás
preferences.volume.feature.mountToDir=Egyéni mappa csatlakozási pontként
preferences.volume.feature.mountToDriveLetter=Meghajtó betűjel csatolási pontként
preferences.volume.feature.mountFlags=Egyéni csatolási opciók
preferences.volume.feature.readOnly=Csak olvasható csatolás
## Updates
preferences.updates=Frissítések
preferences.updates.currentVersion=Jelenlegi verzió: %s
@@ -262,26 +307,34 @@ stats.title=Statisztika ehhez %s
stats.cacheHitRate=Gyorsítótár találati arány
## Read
stats.read.throughput.idle=Olvasás: tétlen
stats.read.throughput.kibs=Olvasás: %.2f KiB/s
stats.read.throughput.mibs=Olvasás: %.2f MiB/s
stats.read.total.data.none=Olvasott adat: -
stats.read.total.data.kib=Adat beolvasva: %.1f KiB
stats.read.total.data.mib=Olvasott adat: %.1f MiB
stats.read.total.data.gib=Olvasott adat: %.1f GiB
stats.decr.total.data.none=Dekódolt adat: -
stats.decr.total.data.kib=Adat visszafejtve: %.1f KiB
stats.decr.total.data.mib=Dekódolt adat: %.1f MiB
stats.decr.total.data.gib=Dekódolt adat: %.1f GiB
stats.read.accessCount=Összes olvasás: %d
## Write
stats.write.throughput.idle=Írás: tétlen
stats.write.throughput.kibs=Írás: %.2f KiB/s
stats.write.throughput.mibs=Írás: %.2f MiB/s
stats.write.total.data.none=Írva: -
stats.write.total.data.kib=Írt adatok: %.1f KiB
stats.write.total.data.mib=Írott adat: %.1f MiB
stats.write.total.data.gib=Írott adat: %.1f GiB
stats.encr.total.data.none=Titkosított adat: -
stats.encr.total.data.kib=Titkosított adatok: %.1f KiB
stats.encr.total.data.mib=Titkosított adat: %.1f MiB
stats.encr.total.data.gib=Titkosított adat: %.1f GiB
stats.write.accessCount=Összes írás: %d
## Accesses
stats.access.current=Hozzáférések: %d
stats.access.total=Összes hozzáférés: %d
# Main Window
@@ -312,12 +365,18 @@ main.vaultDetail.passwordSavedInKeychain=Jelszó mentve
main.vaultDetail.unlockedStatus=FELOLDVA
main.vaultDetail.accessLocation=A széf tartalma itt érhető el:
main.vaultDetail.revealBtn=Széf megjelenítése
main.vaultDetail.copyUri=URI másolása
main.vaultDetail.lockBtn=Zárolás
main.vaultDetail.bytesPerSecondRead=Olvasás:
main.vaultDetail.bytesPerSecondWritten=Írás:
main.vaultDetail.throughput.idle=tétlen
main.vaultDetail.throughput.kbps=%.1f KiB/s
main.vaultDetail.throughput.mbps=%.1f MiB/s
main.vaultDetail.stats=Széf statisztika
main.vaultDetail.locateEncryptedFileBtn=Titkosított fájl megkeresése
main.vaultDetail.locateEncryptedFileBtn.tooltip=Válasszon a széfből egy fájlt a titkosított megfelelőjének megkereséséhez
main.vaultDetail.encryptedPathsCopied=Az útvonal a vágólapra került!
main.vaultDetail.filePickerTitle=Fájl választása a széfben
### Missing
main.vaultDetail.missing.info=A Cryptomator nem talált széfet ezen az útvonalon.
main.vaultDetail.missing.recheck=Ellenőrizze újra
@@ -356,36 +415,56 @@ vaultOptions.general.startHealthCheckBtn=Épség-ellenőrzés indítása
## Mount
vaultOptions.mount=Felcsatolás
vaultOptions.mount.info=Az opciók a kiválasztott kötet típustól függőek.
vaultOptions.mount.linkToPreferences=Virtuális meghajtó opciók megnyitása
vaultOptions.mount.readonly=Csak-olvasható
vaultOptions.mount.customMountFlags=Egyedi csatolási paraméterek
vaultOptions.mount.winDriveLetterOccupied=foglalt
vaultOptions.mount.mountPoint=Csatolási pont
vaultOptions.mount.mountPoint.auto=Válasszon egy megfelelő helyet automatikusan
vaultOptions.mount.mountPoint.driveLetter=Használja a kiválasztott meghajtó betűjelét
vaultOptions.mount.mountPoint.custom=Egyéni könyvtár használata
vaultOptions.mount.mountPoint.directoryPickerButton=Kiválaszt…
vaultOptions.mount.mountPoint.directoryPickerTitle=Válasszon könyvtárat
## Master Key
vaultOptions.masterkey=Jelszó
vaultOptions.masterkey.changePasswordBtn=Jelszó megváltoztatása
vaultOptions.masterkey.forgetSavedPasswordBtn=Mentett jelszó törlése
vaultOptions.masterkey.recoveryKeyExplanation=A helyreállítási kulcs az egyetlen módja annak, hogy visszaállítsa a széfhez való hozzáférést, ha elveíti a jelszavát.
vaultOptions.masterkey.showRecoveryKeyBtn=Visszaállítási kulcs megjelenítése
vaultOptions.masterkey.recoverPasswordBtn=Jelszó visszaállítása
## Hub
vaultOptions.hub=Helyreállítás
vaultOptions.hub.convertInfo=Ezen visszaállítási kulcs segítségével átváltoztathatja ezt a Hub széfet egy jelszó-alapú széffé vészhelyzet esetén.
vaultOptions.hub.convertBtn=Jelszó-alapú széffé alakítás
# Recovery Key
## Display Recovery Key
recoveryKey.display.title=Visszaállítási kulcs mutatása
recoveryKey.create.message=Jelszó szükséges
recoveryKey.create.description=Írja be a jelszavát a "%s" visszaállítási kulcsának megjelenítéséhez:
recoveryKey.display.description=A következő helyreállítási kulcs használható a "%s" hozzáférésének visszaállítására:
recoveryKey.display.StorageHints=Tartsa nagyon biztonságos helyen. pl.:\n •Tárolja egy jelszókezelővel\n •Mentse el egy USB meghajtóra\n •Nyomtassa egy papírra
## Reset Password
### Enter Recovery Key
recoveryKey.recover.title=Jelszó visszaállítása
recoveryKey.recover.prompt=Írja be a visszaállítási kulcsot a következőhöz "%s":
recoveryKey.recover.correctKey=Ez a visszaállítási kulcs érvényes
recoveryKey.recover.wrongKey=Ez a visszaállítási kulcs egy másik széfhez tartozik
recoveryKey.recover.invalidKey=Ez a visszaállítási kulcs nem érvényes
recoveryKey.printout.heading=Cryptomator visszaállítási kulcs\n"%s"\n
### Reset Password
recoveryKey.recover.resetBtn=Visszaállítás
### Recovery Key Password Reset Success
recoveryKey.recover.resetSuccess.message=A jelszó alaphelyzetbe állítása sikeresen megtörtént
recoveryKey.recover.resetSuccess.description=Feloldhatja a széfet az új jelszóval.
# Convert Vault
convertVault.title=Széf átalakítása
convertVault.convert.convertBtn.before=Átalakítás
convertVault.convert.convertBtn.processing=Átalakítás…
convertVault.success.message=Sikeres átalakítás
convertVault.hubToPassword.success.description=Feloldhatja a széfet a kiválasztott jelszóval Hub hozzáférés nélkül.
# New Password
newPassword.promptText=Adjon meg egy új jelszót
@@ -400,6 +479,12 @@ passwordStrength.messageLabel.3=Erős
passwordStrength.messageLabel.4=Nagyon erős
# Quit
quit.title=Alkalmazás bezárása
quit.message=Vannak feloldott széfek
quit.description=Kérem, erősítse meg a kilépési szándékát. A Cryptomator lezárja az összes feloldott széfet az adatvesztés elkerülése érdekében.
quit.lockAndQuitBtn=Zárolás és kilépés
# Forced Quit
# Forced Quit
quit.forced.message=Egyes széfeket nem lehetett lezárni
quit.forced.description=A széfek zárolását függőbben lévő műveletek vagy megnyitott fájlok blokkolták. Kényszerítheti a maradék széfek zárolását, de az I/O megszakítása nem mentett adatok elvesztéséhez vezethet.
quit.forced.forceAndQuitBtn=Kényszerítés és Kilépés

View File

@@ -432,6 +432,7 @@ vaultOptions.masterkey.recoveryKeyExplanation=Kľúč pre obnovu je Vašou jedin
vaultOptions.masterkey.showRecoveryKeyBtn=Ukázať klúč obnovy
vaultOptions.masterkey.recoverPasswordBtn=Resetovanie hesla
## Hub
vaultOptions.hub=Obnova
# Recovery Key
## Display Recovery Key
@@ -454,6 +455,10 @@ recoveryKey.recover.resetSuccess.message=Heslo úspešne zresetované
recoveryKey.recover.resetSuccess.description=Môžte odomknúť trezor s novým heslom.
# Convert Vault
convertVault.title=Konvertovať trezor
convertVault.convert.convertBtn.before=Konvertovať
convertVault.convert.convertBtn.processing=Konvertujem…
convertVault.success.message=Konverzia úspešná
# New Password
newPassword.promptText=Zadajte nové heslo

View File

@@ -13,6 +13,7 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
@DisplayName("Environment Variables Test")
public class EnvironmentTest {
@@ -22,41 +23,7 @@ public class EnvironmentTest {
@BeforeEach
public void init() {
env = Mockito.spy(Environment.getInstance());
Mockito.when(env.getHomeDir()).thenReturn(Path.of("/home/testuser"));
}
@Test
@DisplayName("cryptomator.settingsPath=~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json")
public void testSettingsPath() {
System.setProperty("cryptomator.settingsPath", "~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json");
List<Path> result = env.getSettingsPath().toList();
MatcherAssert.assertThat(result, Matchers.hasSize(2));
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/settings.json"), //
Paths.get("/home/testuser/.Cryptomator/settings.json")));
}
@Test
@DisplayName("cryptomator.ipcSocketPath=~/.config/Cryptomator/ipc.socket:~/.Cryptomator/ipc.socket")
public void testIpcSocketPath() {
System.setProperty("cryptomator.ipcSocketPath", "~/.config/Cryptomator/ipc.socket:~/.Cryptomator/ipc.socket");
List<Path> result = env.ipcSocketPath().toList();
MatcherAssert.assertThat(result, Matchers.hasSize(2));
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/ipc.socket"), //
Paths.get("/home/testuser/.Cryptomator/ipc.socket")));
}
@Test
@DisplayName("cryptomator.integrationsWin.keychainPaths=~/AppData/Roaming/Cryptomator/keychain.json")
public void testKeychainPath() {
System.setProperty("cryptomator.integrationsWin.keychainPaths", "~/AppData/Roaming/Cryptomator/keychain.json");
List<Path> result = env.getKeychainPath().toList();
MatcherAssert.assertThat(result, Matchers.hasSize(1));
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/AppData/Roaming/Cryptomator/keychain.json")));
}
@Test
@DisplayName("cryptomator.logDir=/foo/bar")
public void testAbsoluteLogDir() {
@@ -67,20 +34,9 @@ public class EnvironmentTest {
Assertions.assertTrue(logDir.isPresent());
}
@Test
@DisplayName("cryptomator.logDir=~/foo/bar")
public void testRelativeLogDir() {
System.setProperty("cryptomator.logDir", "~/foo/bar");
Optional<Path> logDir = env.getLogDir();
Assertions.assertTrue(logDir.isPresent());
Assertions.assertEquals(Paths.get("/home/testuser/foo/bar"), logDir.get());
}
@Nested
@DisplayName("Path Lists")
public class SettingsPath {
@DisplayName("Testing parsing path lists")
public class PathLists {
@Test
@DisplayName("test.path.property=")
@@ -93,7 +49,7 @@ public class EnvironmentTest {
@Test
@DisplayName("test.path.property=/foo/bar/test")
public void testSingleAbsolutePath() {
public void testSinglePath() {
System.setProperty("test.path.property", "/foo/bar/test");
List<Path> result = env.getPaths("test.path.property").toList();
@@ -102,27 +58,44 @@ public class EnvironmentTest {
}
@Test
@DisplayName("test.path.property=~/test")
public void testSingleHomeRelativePath() {
System.setProperty("test.path.property", "~/test");
@DisplayName("test.path.property=/foo/bar/test:/bar/nez/tost")
public void testTwoPaths() {
System.setProperty("test.path.property", "/foo/bar/test:bar/nez/tost");
List<Path> result = env.getPaths("test.path.property").toList();
MatcherAssert.assertThat(result, Matchers.hasSize(1));
MatcherAssert.assertThat(result, Matchers.hasItem(Paths.get("/home/testuser/test")));
}
@Test
@DisplayName("test.path.property=~/test:~/test2:/foo/bar/test")
public void testMultiplePaths() {
System.setProperty("test.path.property", "~/test:~/test2:/foo/bar/test");
List<Path> result = env.getPaths("test.path.property").toList();
MatcherAssert.assertThat(result, Matchers.hasSize(3));
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/test"), //
Paths.get("/home/testuser/test2"), //
Paths.get("/foo/bar/test")));
MatcherAssert.assertThat(result, Matchers.hasSize(2));
MatcherAssert.assertThat(result, Matchers.hasItems(Path.of("/foo/bar/test"), Path.of("bar/nez/tost")));
}
}
@Nested
public class VariablesContainingPathLists {
@Test
public void testSettingsPath() {
Mockito.doReturn(Stream.of()).when(env).getPaths(Mockito.anyString());
env.getSettingsPath();
Mockito.verify(env).getPaths("cryptomator.settingsPath");
}
@Test
public void testP12Path() {
Mockito.doReturn(Stream.of()).when(env).getPaths(Mockito.anyString());
env.getP12Path();
Mockito.verify(env).getPaths("cryptomator.p12Path");
}
@Test
public void testIpcSocketPath() {
Mockito.doReturn(Stream.of()).when(env).getPaths(Mockito.anyString());
env.getIpcSocketPath();
Mockito.verify(env).getPaths("cryptomator.ipcSocketPath");
}
@Test
public void testKeychainPath() {
Mockito.doReturn(Stream.of()).when(env).getPaths(Mockito.anyString());
env.getKeychainPath();
Mockito.verify(env).getPaths("cryptomator.integrationsWin.keychainPaths");
}
}
}

View File

@@ -0,0 +1,151 @@
package org.cryptomator.common;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.Mockito;
import java.util.Map;
import java.util.Properties;
public class SubstitutingPropertiesTest {
SubstitutingProperties inTest;
@Nested
public class Processing {
@ParameterizedTest
@DisplayName("Test template replacement")
@CsvSource(textBlock = """
unknown.@{testToken}.test, unknown.@{testToken}.test
@{only*words*digits*under_score},@{only*words*digits*under_score}
C:\\Users\\@{appdir}\\dir, C:\\Users\\foobar\\dir
@{@{appdir}},@{foobar}
Replacing several @{appdir} with @{appdir}., Replacing several foobar with foobar.""")
public void test(String propertyValue, String expected) {
SubstitutingProperties inTest = new SubstitutingProperties(Mockito.mock(Properties.class), Map.of("APPDIR", "foobar"));
var result = inTest.process(propertyValue);
Assertions.assertEquals(expected, result);
}
@Test
@DisplayName("@{userhome} is replaced with the user home directory")
public void testPropSubstitutions() {
var props = new Properties();
props.setProperty("user.home", "OneUponABit");
inTest = new SubstitutingProperties(props, Map.of());
var result = inTest.process("@{userhome}");
Assertions.assertEquals("OneUponABit", result);
}
@DisplayName("Other keywords are replaced accordingly")
@ParameterizedTest(name = "Token \"{0}\" replaced with content of {1}")
@CsvSource(value = {"appdir, APPDIR, foobar", "appdata, APPDATA, bazbaz", "localappdata, LOCALAPPDATA, boboAlice"})
public void testEnvSubstitutions(String token, String envName, String expected) {
inTest = new SubstitutingProperties(new Properties(), Map.of(envName, expected));
var result = inTest.process("@{" + token + "}");
Assertions.assertEquals(expected, result);
}
}
@Nested
public class GetProperty {
@Test
@DisplayName("Undefined properties are not processed")
public void testNoProcessingOnNull() {
inTest = Mockito.spy(new SubstitutingProperties(new Properties(), Map.of()));
var result = inTest.getProperty("some.prop");
Assertions.assertNull(result);
Mockito.verify(inTest, Mockito.never()).process(Mockito.anyString());
}
@ParameterizedTest
@DisplayName("Properties not starting with \"cryptomator.\" are not processed")
@ValueSource(strings = {"example.foo", "cryptomatorSomething.foo", "org.cryptomator.foo", "cryPtoMAtor.foo"})
public void testNoProcessingOnNotCryptomator(String propKey) {
var props = new Properties();
props.setProperty(propKey, "someValue");
inTest = Mockito.spy(new SubstitutingProperties(props, Map.of()));
var result = inTest.getProperty("some.prop");
Assertions.assertNull(result);
Mockito.verify(inTest, Mockito.never()).process(Mockito.anyString());
}
@Test
@DisplayName("Non-null property starting with \"cryptomator.\" is processed")
public void testProcessing() {
var props = new Properties();
props.setProperty("cryptomator.prop", "someValue");
inTest = Mockito.spy(new SubstitutingProperties(props, Map.of()));
Mockito.doReturn("someValue").when(inTest).process(Mockito.anyString());
inTest.getProperty("cryptomator.prop");
Mockito.verify(inTest).process("someValue");
}
@Test
@DisplayName("Default value is not processed")
public void testNoProcessingDefault() {
var props = Mockito.mock(Properties.class);
Mockito.when(props.getProperty("cryptomator.prop")).thenReturn(null);
inTest = Mockito.spy(new SubstitutingProperties(props, Map.of()));
Mockito.doReturn("someValue").when(inTest).process(Mockito.anyString());
var result = inTest.getProperty("cryptomator.prop", "a default");
Assertions.assertEquals("a default", result);
Mockito.verify(inTest, Mockito.never()).process(Mockito.any());
}
}
@ParameterizedTest(name = "{0}={1} -> {0}={2}")
@DisplayName("Replace @{userhome} during getProperty()")
@CsvSource(quoteCharacter = '"', textBlock = """
cryptomator.settingsPath, "@{userhome}/.config/Cryptomator/settings.json:@{userhome}/.Cryptomator/settings.json", "/home/.config/Cryptomator/settings.json:/home/.Cryptomator/settings.json"
cryptomator.ipcSocketPath, "@{userhome}/.config/Cryptomator/ipc.socket:@{userhome}/.Cryptomator/ipc.socket", "/home/.config/Cryptomator/ipc.socket:/home/.Cryptomator/ipc.socket"
not.cryptomator.not.substituted, "@{userhome}/foo", "@{userhome}/foo"
cryptomator.no.placeholder.found, "foo/bar", "foo/bar"
""")
public void testEndToEndPropsSource(String key, String raw, String substituted) {
var delegate = Mockito.mock(Properties.class);
Mockito.doReturn("/home").when(delegate).getProperty("user.home");
Mockito.doReturn(raw).when(delegate).getProperty(key);
var inTest = new SubstitutingProperties(delegate, Map.of());
var result = inTest.getProperty(key);
Assertions.assertEquals(substituted, result);
}
@ParameterizedTest(name = "{0}={1} -> {0}={2}")
@DisplayName("Replace appdata,localappdata or appdir during getProperty()")
@CsvSource(quoteCharacter = '"', textBlock = """
cryptomator.settingsPath, "@{appdata}/Cryptomator/settings.json", "C:\\Users\\JimFang\\AppData\\Roaming/Cryptomator/settings.json"
cryptomator.ipcSocketPath, "@{localappdata}/Cryptomator/ipc.socket", "C:\\Users\\JimFang\\AppData\\Local/Cryptomator/ipc.socket"
cryptomator.integrationsLinux.trayIconsDir, "@{appdir}/hicolor", "/squashfs1337/usr/hicolor"
not.cryptomator.not.substituted, "@{appdir}/foo", "@{appdir}/foo"
cryptomator.no.placeholder.found, "foo/bar", "foo/bar"
""")
public void testEndToEndEnvSource(String key, String raw, String substituted) {
var delegate = Mockito.mock(Properties.class);
Mockito.doReturn(raw).when(delegate).getProperty(key);
var env = Map.of("APPDATA", "C:\\Users\\JimFang\\AppData\\Roaming", //
"LOCALAPPDATA", "C:\\Users\\JimFang\\AppData\\Local", //
"APPDIR", "/squashfs1337/usr");
var inTest = new SubstitutingProperties(delegate, env);
var result = inTest.getProperty(key);
Assertions.assertEquals(substituted, result);
}
}

View File

@@ -1,61 +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 org.cryptomator.common.Environment;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.Mockito;
import java.io.IOException;
public class SettingsJsonAdapterTest {
private final Environment env = Mockito.mock(Environment.class);
private final SettingsJsonAdapter adapter = new SettingsJsonAdapter(env);
@Test
public void testDeserialize() throws IOException {
String json = """
{
"directories": [
{"id": "1", "path": "/vault1", "mountName": "vault1", "winDriveLetter": "X"},
{"id": "2", "path": "/vault2", "mountName": "vault2", "winDriveLetter": "Y"}
],
"autoCloseVaults" : true,
"checkForUpdatesEnabled": true,
"port": 8080,
"language": "de-DE",
"numTrayNotifications": 42
}
""";
Settings settings = adapter.fromJson(json);
Assertions.assertTrue(settings.checkForUpdates().get());
Assertions.assertEquals(2, settings.getDirectories().size());
Assertions.assertEquals(8080, settings.port().get());
Assertions.assertEquals(true, settings.autoCloseVaults().get());
Assertions.assertEquals("de-DE", settings.languageProperty().get());
Assertions.assertEquals(42, settings.numTrayNotifications().get());
}
@SuppressWarnings("SpellCheckingInspection")
@ParameterizedTest(name = "fromJson() should throw IOException for input: {0}")
@ValueSource(strings = { //
"", //
"<html>", //
"{invalidjson}" //
})
public void testDeserializeMalformed(String input) {
Assertions.assertThrows(IOException.class, () -> {
adapter.fromJson(input);
});
}
}

View File

@@ -0,0 +1,79 @@
package org.cryptomator.common.settings;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.hamcrest.MatcherAssert;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import java.io.IOException;
import java.util.List;
import static org.hamcrest.CoreMatchers.containsString;
public class SettingsJsonTest {
@Test
public void testDeserialize() throws IOException {
String jsonStr = """
{
"directories": [
{"id": "1", "path": "/vault1", "mountName": "vault1", "winDriveLetter": "X", "shouldBeIgnored": true},
{"id": "2", "path": "/vault2", "mountName": "vault2", "winDriveLetter": "Y", "mountFlags":"--foo --bar"}
],
"autoCloseVaults" : true,
"checkForUpdatesEnabled": true,
"port": 8080,
"language": "de-DE",
"numTrayNotifications": 42
}
""";
var jsonObj = new ObjectMapper().reader().readValue(jsonStr, SettingsJson.class);
Assertions.assertTrue(jsonObj.checkForUpdatesEnabled);
Assertions.assertEquals(2, jsonObj.directories.size());
Assertions.assertEquals("/vault1", jsonObj.directories.get(0).path);
Assertions.assertEquals("/vault2", jsonObj.directories.get(1).path);
Assertions.assertEquals("--foo --bar", jsonObj.directories.get(1).mountFlags);
Assertions.assertEquals(8080, jsonObj.port);
Assertions.assertTrue(jsonObj.autoCloseVaults);
Assertions.assertEquals("de-DE", jsonObj.language);
Assertions.assertEquals(42, jsonObj.numTrayNotifications);
}
@SuppressWarnings("SpellCheckingInspection")
@ParameterizedTest(name = "throw JacksonException for input: {0}")
@ValueSource(strings = { //
"", //
"<html>", //
"{invalidjson}" //
})
public void testDeserializeMalformed(String input) {
var objectMapper = new ObjectMapper().reader();
Assertions.assertThrows(JacksonException.class, () -> {
objectMapper.readValue(input, SettingsJson.class);
});
}
@Test
public void testSerialize() throws JsonProcessingException {
var jsonObj = new SettingsJson();
jsonObj.directories = List.of(new VaultSettingsJson(), new VaultSettingsJson());
jsonObj.directories.get(0).id = "test";
jsonObj.theme = UiTheme.DARK;
jsonObj.showTrayIcon = false;
var jsonStr = new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(jsonObj);
MatcherAssert.assertThat(jsonStr, containsString("\"theme\" : \"DARK\""));
MatcherAssert.assertThat(jsonStr, containsString("\"showTrayIcon\" : false"));
MatcherAssert.assertThat(jsonStr, containsString("\"useKeychain\" : true"));
MatcherAssert.assertThat(jsonStr, containsString("\"actionAfterUnlock\" : \"ASK\""));
}
}

View File

@@ -18,21 +18,21 @@ public class SettingsTest {
Environment env = Mockito.mock(Environment.class);
@SuppressWarnings("unchecked") Consumer<Settings> changeListener = Mockito.mock(Consumer.class);
Settings settings = new Settings(env);
Settings settings = Settings.create(env);
settings.setSaveCmd(changeListener);
VaultSettings vaultSettings = VaultSettings.withRandomId();
Mockito.verify(changeListener, Mockito.times(0)).accept(settings);
// first change (to property):
settings.port().set(42428);
settings.port.set(42428);
Mockito.verify(changeListener, Mockito.times(1)).accept(settings);
// second change (to list):
settings.getDirectories().add(vaultSettings);
settings.directories.add(vaultSettings);
Mockito.verify(changeListener, Mockito.times(2)).accept(settings);
// third change (to property of list item):
vaultSettings.displayName().set("asd");
vaultSettings.displayName.set("asd");
Mockito.verify(changeListener, Mockito.times(3)).accept(settings);
}

View File

@@ -1,67 +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.JsonWriter;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.file.Paths;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class VaultSettingsJsonAdapterTest {
private final VaultSettingsJsonAdapter adapter = new VaultSettingsJsonAdapter();
@Test
public void testDeserialize() throws IOException {
String json = "{\"id\": \"foo\", \"path\": \"/foo/bar\", \"displayName\": \"test\", \"winDriveLetter\": \"X\", \"shouldBeIgnored\": true, \"individualMountPath\": \"/home/test/crypto\", \"mountFlags\":\"--foo --bar\"}";
JsonReader jsonReader = new JsonReader(new StringReader(json));
VaultSettings vaultSettings = adapter.read(jsonReader);
assertAll(
() -> assertEquals("foo", vaultSettings.getId()),
() -> assertEquals(Paths.get("/foo/bar"), vaultSettings.path().get()),
() -> assertEquals("test", vaultSettings.displayName().get()),
() -> assertEquals("--foo --bar", vaultSettings.mountFlags().get())
);
}
@SuppressWarnings("SpellCheckingInspection")
@Test
public void testSerialize() throws IOException {
VaultSettings vaultSettings = new VaultSettings("test");
vaultSettings.path().set(Paths.get("/foo/bar"));
vaultSettings.displayName().set("mountyMcMountFace");
vaultSettings.mountFlags().set("--foo --bar");
StringWriter buf = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(buf);
adapter.write(jsonWriter, vaultSettings);
String result = buf.toString();
assertAll(
() -> assertThat(result, containsString("\"id\":\"test\"")),
() -> {
if (System.getProperty("os.name").contains("Windows")) {
assertThat(result, containsString("\"path\":\"\\\\foo\\\\bar\""));
} else {
assertThat(result, containsString("\"path\":\"/foo/bar\""));
}
},
() -> assertThat(result, containsString("\"displayName\":\"mountyMcMountFace\"")),
() -> assertThat(result, containsString("\"mountFlags\":\"--foo --bar\""))
);
}
}

View File

@@ -1,29 +0,0 @@
package org.cryptomator.common.vaults;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.Mockito;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import java.nio.file.Path;
public class VaultModuleTest {
private final Settings settings = Mockito.mock(Settings.class);
private final VaultSettings vaultSettings = Mockito.mock(VaultSettings.class);
private final VaultModule module = new VaultModule();
@BeforeEach
public void setup(@TempDir Path tmpDir) {
Mockito.when(vaultSettings.mountName()).thenReturn(Bindings.createStringBinding(() -> "TEST"));
Mockito.when(vaultSettings.usesReadOnlyMode()).thenReturn(new SimpleBooleanProperty(true));
Mockito.when(vaultSettings.displayName()).thenReturn(new SimpleStringProperty("Vault"));
System.setProperty("user.home", tmpDir.toString());
}
}

View File

@@ -0,0 +1,145 @@
package org.cryptomator.ui.error;
import org.cryptomator.common.Environment;
import org.cryptomator.common.ErrorCode;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.mockito.Mockito;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.util.concurrent.ExecutorService;
class ErrorControllerTest {
Application application;
String stackTrace;
ErrorCode errorCode;
Scene previousScene;
Stage window;
Environment environment;
ExecutorService executorService;
ErrorController errorController;
@BeforeEach
public void beforeEach() {
application = Mockito.mock(Application.class);
stackTrace = "This is a stackTrace mock";
errorCode = Mockito.mock(ErrorCode.class);
previousScene = Mockito.mock(Scene.class);
window = Mockito.mock(Stage.class);
environment = Mockito.mock(Environment.class);
executorService = Mockito.mock(ExecutorService.class);
errorController = new ErrorController(application, stackTrace, errorCode, previousScene, window, environment, executorService);
}
private ErrorDiscussion createErrorDiscussion(String title, int upvoteCount, ErrorDiscussion.Answer answer) {
ErrorDiscussion ed = new ErrorDiscussion();
ed.title = title;
ed.upvoteCount = upvoteCount;
ed.answer = answer;
return ed;
}
@DisplayName("compare error discussions by upvote count")
@ParameterizedTest
@CsvSource(textBlock = """
10, <, 5
8, >, 15
10, =, 10
""")
public void testCompareUpvoteCount(int leftUpvoteCount, char expected, int rightUpvoteCount) {
int expectedResult = switch (expected) {
case '<' -> -1;
case '>' -> +1;
default -> 0;
};
var left = createErrorDiscussion("", leftUpvoteCount, null);
var right = createErrorDiscussion("", rightUpvoteCount, null);
int result = errorController.compareUpvoteCount(left, right);
Assertions.assertEquals(expectedResult, Integer.signum(result));
}
@DisplayName("compare error discussions by existence of an answer")
@ParameterizedTest
@CsvSource(textBlock = """
false, =, false
true, =, true
true, <, false
false, >, true
""")
public void testCompareIsAnswered(boolean leftIsAnswered, char expected, boolean rightIsAnswered) {
var answer = new ErrorDiscussion.Answer();
int expectedResult = switch (expected) {
case '<' -> -1;
case '>' -> +1;
default -> 0;
};
var left = createErrorDiscussion("", 0, leftIsAnswered ? answer : null);
var right = createErrorDiscussion("", 0, rightIsAnswered ? answer : null);
int result = errorController.compareIsAnswered(left, right);
Assertions.assertEquals(expectedResult, result);
}
@DisplayName("compare error codes by full error code")
@ParameterizedTest
@CsvSource(textBlock = """
Error 0000:0000:0000, =, Error 0000:0000:0000
Error 6HU1:12H1:HU7J, <, Error 0000:0000:0000
Error 0000:0000:0000, >, Error 6HU1:12H1:HU7J
""")
public void testCompareByFullErrorCode(String leftTitle, char expected, String rightTitle) {
Mockito.when(errorCode.toString()).thenReturn("6HU1:12H1:HU7J");
int expectedResult = switch (expected) {
case '<' -> -1;
case '>' -> +1;
default -> 0;
};
var left = createErrorDiscussion(leftTitle, 0, null);
var right = createErrorDiscussion(rightTitle, 0, null);
int result = errorController.compareByFullErrorCode(left, right);
Assertions.assertEquals(expectedResult, result);
}
@DisplayName("compare error codes by root cause")
@ParameterizedTest
@CsvSource(textBlock = """
Error 6HU1:12H1:0000, =, Error 6HU1:12H1:0000
Error 6HU1:12H1:0007, =, Error 6HU1:12H1:0042
Error 0000:0000:0000, =, Error 0000:0000:0000
Error 6HU1:12H1:0000, <, Error 0000:0000:0000
Error 6HU1:12H1:0000, <, Error 6HU1:0000:0000
Error 0000:0000:0000, >, Error 6HU1:12H1:0000
Error 6HU1:0000:0000, >, Error 6HU1:12H1:0000
""")
public void testCompareByRootCauseCode(String leftTitle, char expected, String rightTitle) {
Mockito.when(errorCode.methodCode()).thenReturn("6HU1");
Mockito.when(errorCode.rootCauseCode()).thenReturn("12H1");
int expectedResult = switch (expected) {
case '<' -> -1;
case '>' -> +1;
default -> 0;
};
var left = createErrorDiscussion(leftTitle, 0, null);
var right = createErrorDiscussion(rightTitle, 0, null);
int result = errorController.compareByRootCauseCode(left, right);
Assertions.assertEquals(expectedResult, result);
}
@DisplayName("check if the error code contains the method code")
@ParameterizedTest
@CsvSource(textBlock = """
Error 6HU1:0000:0000, true
Error 0000:0000:0000, false
""")
public void testContainsMethodCode(String title, boolean expectedResult) {
Mockito.when(errorCode.methodCode()).thenReturn("6HU1");
var ed = createErrorDiscussion(title, 0, null);
boolean result = errorController.containsMethodCode(ed);
Assertions.assertEquals(expectedResult, result);
}
}

View File

@@ -0,0 +1,19 @@
package org.cryptomator.ui.keyloading.hub;
import com.auth0.jwt.JWT;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class HubConfigTest {
@Test
@DisplayName("can parse JWT with unknown fields in header claim \"hub\"")
public void testParseJWTWithUnknownFields() {
var jwt = JWT.decode("eyJraWQiOiIxMjMiLCJ0eXAiOiJqd3QiLCJhbGciOiJIUzI1NiIsImh1YiI6eyJ1bmtub3duRmllbGQiOjQyLCJjbGllbnRJZCI6ImNyeXB0b21hdG9yIn19.eyJqdGkiOiI0NTYifQ.e1CStFf5fdh9ofX_6O8_LfbHfHEJZqUpuYNWz9xZp0I");
var claim = jwt.getHeaderClaim("hub");
var hubConfig = Assertions.assertDoesNotThrow(() -> claim.as(HubConfig.class));
Assertions.assertEquals("cryptomator", hubConfig.clientId);
}
}

View File

@@ -55,4 +55,12 @@
<cve>CVE-2022-45688</cve>
</suppress>
</suppressions>
<suppress>
<notes><![CDATA[
False positive for jackson-databind-2.14.2.jar, see https://github.com/FasterXML/jackson-databind/issues/3972
]]></notes>
<packageUrl regex="true">^pkg:maven/com\.fasterxml\.jackson\.core/jackson\-databind@.*$</packageUrl>
<cve>CVE-2023-35116</cve>
</suppress>
</suppressions>