mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-17 10:11:27 +00:00
Merge branch 'develop' into feature/custom-shortening-threshold
This commit is contained in:
12
.github/workflows/appimage.yml
vendored
12
.github/workflows/appimage.yml
vendored
@@ -85,12 +85,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.buildNumber=\"appimage-${{ needs.get-version.outputs.revNum }}\""
|
||||
--add-launcher Cryptomator-gtk2=launcher-gtk2.properties
|
||||
|
||||
15
.github/workflows/debian.yml
vendored
15
.github/workflows/debian.yml
vendored
@@ -3,9 +3,6 @@ name: Build Debian Package
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref:
|
||||
description: 'GitHub Ref (e.g. refs/tags/1.6.16)'
|
||||
required: true
|
||||
semver:
|
||||
description: 'SemVer String (e.g. 1.7.0-beta1)'
|
||||
required: true
|
||||
@@ -29,9 +26,6 @@ jobs:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ inputs.ref }}
|
||||
fetch-depth: 0
|
||||
- id: versions
|
||||
name: Get version information
|
||||
run: |
|
||||
@@ -141,15 +135,10 @@ jobs:
|
||||
run: dput ppa:sebastian-stenzel/cryptomator-beta cryptomator_*_source.changes
|
||||
|
||||
# If ref is a tag, also upload to GitHub Releases:
|
||||
- name: Determine tag name
|
||||
if: startsWith(inputs.ref, 'refs/tags/')
|
||||
run: |
|
||||
REF=${{ inputs.ref }}
|
||||
echo "TAG_NAME=${REF##*/}" >> $GITHUB_ENV
|
||||
- name: Publish Debian package on GitHub Releases
|
||||
if: startsWith(inputs.ref, 'refs/tags/')
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
||||
run: |
|
||||
artifacts=$(ls | grep cryptomator*.deb)
|
||||
gh release upload ${{ env.TAG_NAME }} $artifacts
|
||||
gh release upload ${{ github.ref_name }} $artifacts
|
||||
12
.github/workflows/mac-dmg.yml
vendored
12
.github/workflows/mac-dmg.yml
vendored
@@ -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}/Library/Application Support/Cryptomator/mnt\""
|
||||
--java-options "-Dcryptomator.showTrayIcon=true"
|
||||
--java-options "-Dcryptomator.buildNumber=\"dmg-${{ needs.get-version.outputs.revNum }}\""
|
||||
--mac-package-identifier org.cryptomator
|
||||
|
||||
14
.github/workflows/win-exe.yml
vendored
14
.github/workflows/win-exe.yml
vendored
@@ -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
|
||||
|
||||
3
.idea/codeStyles/Project.xml
generated
3
.idea/codeStyles/Project.xml
generated
@@ -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" />
|
||||
|
||||
2
.idea/runConfigurations/Cryptomator_Linux.xml
generated
2
.idea/runConfigurations/Cryptomator_Linux.xml
generated
@@ -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="~/.config/Cryptomator/settings.json" -Dcryptomator.p12Path="~/.config/Cryptomator/key.p12" -Dcryptomator.ipcSocketPath="~/.config/Cryptomator/ipc.socket" -Dcryptomator.logDir="~/.local/share/Cryptomator/logs" -Dcryptomator.pluginDir="~/.local/share/Cryptomator/plugins" -Dcryptomator.mountPointsDir="~/.local/share/Cryptomator/mnt" -Dcryptomator.showTrayIcon=true -Xss20m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64" />
|
||||
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath="@{userhome}/.config/Cryptomator/settings.json" -Dcryptomator.p12Path="@{userhome}/.config/Cryptomator/key.p12" -Dcryptomator.ipcSocketPath="@{userhome}/.config/Cryptomator/ipc.socket" -Dcryptomator.logDir="@{userhome}/.local/share/Cryptomator/logs" -Dcryptomator.pluginDir="@{userhome}/.local/share/Cryptomator/plugins" -Dcryptomator.mountPointsDir="@{userhome}/.local/share/Cryptomator/mnt" -Dcryptomator.showTrayIcon=true -Xss20m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
|
||||
@@ -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="~/.config/Cryptomator-Dev/settings.json" -Dcryptomator.p12Path="~/.config/Cryptomator-Dev/key.p12" -Dcryptomator.ipcSocketPath="~/.config/Cryptomator-Dev/ipc.socket" -Dcryptomator.logDir="~/.local/share/Cryptomator-Dev/logs" -Dcryptomator.pluginDir="~/.local/share/Cryptomator-Dev/plugins" -Dcryptomator.mountPointsDir="~/.local/share/Cryptomator-Dev/mnt" -Dcryptomator.showTrayIcon=true -Dfuse.experimental="true" -Xss20m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64" />
|
||||
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath="@{userhome}/.config/Cryptomator-Dev/settings.json" -Dcryptomator.p12Path="@{userhome}/.config/Cryptomator-Dev/key.p12" -Dcryptomator.ipcSocketPath="@{userhome}/.config/Cryptomator-Dev/ipc.socket" -Dcryptomator.logDir="@{userhome}/.local/share/Cryptomator-Dev/logs" -Dcryptomator.pluginDir="@{userhome}/.local/share/Cryptomator-Dev/plugins" -Dcryptomator.mountPointsDir="@{userhome}/.local/share/Cryptomator-Dev/mnt" -Dcryptomator.showTrayIcon=true -Dfuse.experimental="true" -Xss20m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
|
||||
2
.idea/runConfigurations/Cryptomator_Windows.xml
generated
2
.idea/runConfigurations/Cryptomator_Windows.xml
generated
@@ -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="~/AppData/Roaming/Cryptomator/settings.json" -Dcryptomator.ipcSocketPath="~/AppData/Roaming/Cryptomator/ipc.socket" -Dcryptomator.logDir="~/AppData/Roaming/Cryptomator" -Dcryptomator.pluginDir="~/AppData/Roaming/Cryptomator/Plugins" -Dcryptomator.integrationsWin.keychainPaths="~/AppData/Roaming/Cryptomator/keychain.json" -Dcryptomator.p12Path="~/AppData/Roaming/Cryptomator/key.p12" -Dcryptomator.mountPointsDir="~/Cryptomator" -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.win" />
|
||||
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath="@{appdata}/Cryptomator/settings.json:@{userhome}/AppData/Roaming/Cryptomator/settings.json" -Dcryptomator.ipcSocketPath="@{localappdata}/Cryptomator/ipc.socket" -Dcryptomator.logDir="@{localappdata}/Cryptomator" -Dcryptomator.pluginDir="@{appdata}/Cryptomator/Plugins" -Dcryptomator.integrationsWin.keychainPaths="@{appdata}/Cryptomator/keychain.json:@{userhome}/AppData/Roaming/Cryptomator/keychain.json" -Dcryptomator.p12Path="@{appdata}/Cryptomator/key.p12:@{userhome}/AppData/Roaming/Cryptomator/key.p12" -Dcryptomator.mountPointsDir="@{userhome}/Cryptomator" -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.win" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
|
||||
@@ -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="~/AppData/Roaming/Cryptomator-Dev/settings.json" -Dcryptomator.ipcSocketPath="~/AppData/Roaming/Cryptomator-Dev/ipc.socket" -Dcryptomator.logDir="~/AppData/Roaming/Cryptomator-Dev" -Dcryptomator.pluginDir="~/AppData/Roaming/Cryptomator-Dev/Plugins" -Dcryptomator.integrationsWin.keychainPaths="~/AppData/Roaming/Cryptomator-Dev/keychain.json" -Dcryptomator.p12Path="~/AppData/Roaming/Cryptomator-Dev/key.p12" -Dcryptomator.mountPointsDir="~/Cryptomator-Dev" -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.win" />
|
||||
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath="@{appdata}/Cryptomator-Dev/settings.json:@{userhome}/AppData/Roaming/Cryptomator-Dev/settings.json" -Dcryptomator.ipcSocketPath="@{localappdata}/Cryptomator-Dev/ipc.socket" -Dcryptomator.logDir="@{localappdata}/Cryptomator-Dev" -Dcryptomator.pluginDir="@{appdata}/Cryptomator-Dev/Plugins" -Dcryptomator.integrationsWin.keychainPaths="@{appdata}/Cryptomator-Dev/keychain.json:@{userhome}/AppData/Roaming/Cryptomator-Dev/keychain.json" -Dcryptomator.p12Path="@{appdata}/Cryptomator-Dev/key.p12:@{userhome}/AppData/Roaming/Cryptomator-Dev/key.p12" -Dcryptomator.mountPointsDir="@{userhome}/Cryptomator-Dev" -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.win" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
|
||||
2
.idea/runConfigurations/Cryptomator_macOS.xml
generated
2
.idea/runConfigurations/Cryptomator_macOS.xml
generated
@@ -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="~/Library/Application Support/Cryptomator/settings.json" -Dcryptomator.p12Path="~/Library/Application Support/Cryptomator/key.p12" -Dcryptomator.ipcSocketPath="~/Library/Application Support/Cryptomator/ipc.socket" -Dcryptomator.logDir="~/Library/Logs/Cryptomator" -Dcryptomator.pluginDir="~/Library/Application Support/Cryptomator/Plugins" -Dcryptomator.mountPointsDir="~/Cryptomator" -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="@{userhome}/Library/Application Support/Cryptomator/settings.json" -Dcryptomator.p12Path="@{userhome}/Library/Application Support/Cryptomator/key.p12" -Dcryptomator.ipcSocketPath="@{userhome}/Library/Application Support/Cryptomator/ipc.socket" -Dcryptomator.logDir="@{userhome}/Library/Logs/Cryptomator" -Dcryptomator.pluginDir="@{userhome}/Library/Application Support/Cryptomator/Plugins" -Dcryptomator.mountPointsDir="@{userhome}/Cryptomator" -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>
|
||||
|
||||
@@ -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="~/Library/Application Support/Cryptomator-Dev/settings.json" -Dcryptomator.p12Path="~/Library/Application Support/Cryptomator-Dev/key.p12" -Dcryptomator.ipcSocketPath="~/Library/Application Support/Cryptomator-Dev/ipc.socket" -Dcryptomator.logDir="~/Library/Logs/Cryptomator-Dev" -Dcryptomator.pluginDir="~/Library/Application Support/Cryptomator-Dev/Plugins" -Dcryptomator.mountPointsDir="~/Cryptomator" -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="@{userhome}/Library/Application Support/Cryptomator-Dev/settings.json" -Dcryptomator.p12Path="@{userhome}/Library/Application Support/Cryptomator-Dev/key.p12" -Dcryptomator.ipcSocketPath="@{userhome}/Library/Application Support/Cryptomator-Dev/ipc.socket" -Dcryptomator.logDir="@{userhome}/Library/Logs/Cryptomator-Dev" -Dcryptomator.pluginDir="@{userhome}/Library/Application Support/Cryptomator-Dev/Plugins" -Dcryptomator.mountPointsDir="@{userhome}/Cryptomator" -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>
|
||||
|
||||
12
dist/linux/appimage/build.sh
vendored
12
dist/linux/appimage/build.sh
vendored
@@ -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.buildNumber=\"appimage-${REVISION_NO}\"" \
|
||||
--add-launcher cryptomator-gtk2=launcher-gtk2.properties \
|
||||
|
||||
12
dist/linux/debian/rules
vendored
12
dist/linux/debian/rules
vendored
@@ -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.buildNumber=\"deb-${REVISION_NUM}\"" \
|
||||
--java-options "-Dcryptomator.appVersion=\"${SEMVER_STR}\"" \
|
||||
|
||||
12
dist/mac/dmg/build.sh
vendored
12
dist/mac/dmg/build.sh
vendored
@@ -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}/Library/Application Support${APP_NAME}/mnt\"" \
|
||||
--java-options "-Dcryptomator.showTrayIcon=true" \
|
||||
--java-options "-Dcryptomator.buildNumber=\"dmg-${REVISION_NO}\"" \
|
||||
--mac-package-identifier ${PACKAGE_IDENTIFIER} \
|
||||
|
||||
14
dist/win/build.ps1
vendored
14
dist/win/build.ps1
vendored
@@ -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 `
|
||||
|
||||
14
pom.xml
14
pom.xml
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
171
src/main/java/org/cryptomator/common/PropertiesDecorator.java
Normal file
171
src/main/java/org/cryptomator/common/PropertiesDecorator.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.cryptomator.common;
|
||||
|
||||
import org.jetbrains.annotations.VisibleForTesting;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class SubstitutingProperties extends PropertiesDecorator {
|
||||
|
||||
private static final Pattern TEMPLATE = Pattern.compile("@\\{(\\w+)}");
|
||||
|
||||
private final Map<String, String> env;
|
||||
|
||||
public SubstitutingProperties(Properties props, Map<String, String> systemEnvironment) {
|
||||
super(props);
|
||||
this.env = systemEnvironment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProperty(String key) {
|
||||
var value = delegate.getProperty(key);
|
||||
if (key.startsWith("cryptomator.") && value != null) {
|
||||
return process(value);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProperty(String key, String defaultValue) {
|
||||
var result = getProperty(key);
|
||||
return result != null ? result : defaultValue;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
String process(String value) {
|
||||
return TEMPLATE.matcher(value).replaceAll(match -> //
|
||||
switch (match.group(1)) {
|
||||
case "appdir" -> resolveFrom("APPDIR", Source.ENV);
|
||||
case "appdata" -> resolveFrom("APPDATA", Source.ENV);
|
||||
case "localappdata" -> resolveFrom("LOCALAPPDATA", Source.ENV);
|
||||
case "userhome" -> resolveFrom("user.home", Source.PROPS);
|
||||
default -> {
|
||||
LoggerFactory.getLogger(SubstitutingProperties.class).warn("Unknown variable {} in property value {}.", match.group(), value);
|
||||
yield match.group();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String resolveFrom(String key, Source src) {
|
||||
var val = switch (src) {
|
||||
case ENV -> env.get(key);
|
||||
case PROPS -> delegate.getProperty(key);
|
||||
};
|
||||
if (val == null) {
|
||||
LoggerFactory.getLogger(SubstitutingProperties.class).warn("Variable {} used for substitution not found in {}. Replaced with empty string.", key, src);
|
||||
return "";
|
||||
} else {
|
||||
return val.replace("\\", "\\\\");
|
||||
}
|
||||
}
|
||||
|
||||
private enum Source {
|
||||
ENV,
|
||||
PROPS;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,14 +23,14 @@ public class KeychainModule {
|
||||
@Singleton
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.cryptomator.common.locationpresets;
|
||||
|
||||
import org.cryptomator.integrations.common.CheckAvailability;
|
||||
import org.cryptomator.integrations.common.OperatingSystem;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.cryptomator.integrations.common.OperatingSystem.Value.MAC;
|
||||
import static org.cryptomator.integrations.common.OperatingSystem.Value.WINDOWS;
|
||||
|
||||
@OperatingSystem(WINDOWS)
|
||||
@OperatingSystem(MAC)
|
||||
@CheckAvailability
|
||||
public final class LeitzcloudLocationPresetsProvider implements LocationPresetsProvider {
|
||||
|
||||
private static final Path LOCATION = LocationPresetsProvider.resolveLocation("~/leitzcloud");
|
||||
|
||||
@CheckAvailability
|
||||
public static boolean isPresent() {
|
||||
return Files.isDirectory(LOCATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<LocationPreset> getLocations() {
|
||||
return Stream.of(new LocationPreset("leitzcloud", LOCATION));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,6 +5,8 @@ import org.cryptomator.integrations.common.OperatingSystem;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.cryptomator.common.mount;
|
||||
|
||||
public class MountPointInUseException extends IllegalMountPointException {
|
||||
|
||||
public MountPointInUseException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
@@ -54,19 +54,19 @@ public class Mounter {
|
||||
switch (capability) {
|
||||
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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import javafx.geometry.NodeOrientation;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Singleton
|
||||
public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SettingsJsonAdapter.class);
|
||||
|
||||
private final VaultSettingsJsonAdapter vaultSettingsJsonAdapter = new VaultSettingsJsonAdapter();
|
||||
private final Environment env;
|
||||
|
||||
@Inject
|
||||
public SettingsJsonAdapter(Environment env) {
|
||||
this.env = env;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter out, Settings value) throws IOException {
|
||||
out.beginObject();
|
||||
out.name("writtenByVersion").value(env.getAppVersion() + env.getBuildNumber().map("-"::concat).orElse(""));
|
||||
out.name("directories");
|
||||
writeVaultSettingsArray(out, value.getDirectories());
|
||||
out.name("askedForUpdateCheck").value(value.askedForUpdateCheck().get());
|
||||
out.name("autoCloseVaults").value(value.autoCloseVaults().get());
|
||||
out.name("checkForUpdatesEnabled").value(value.checkForUpdates().get());
|
||||
out.name("debugMode").value(value.debugMode().get());
|
||||
out.name("displayConfiguration").value((value.displayConfigurationProperty().get()));
|
||||
out.name("keychainProvider").value(value.keychainProvider().get());
|
||||
out.name("language").value((value.languageProperty().get()));
|
||||
out.name("licenseKey").value(value.licenseKey().get());
|
||||
out.name("mountService").value(value.mountService().get());
|
||||
out.name("numTrayNotifications").value(value.numTrayNotifications().get());
|
||||
out.name("port").value(value.port().get());
|
||||
out.name("showMinimizeButton").value(value.showMinimizeButton().get());
|
||||
out.name("showTrayIcon").value(value.showTrayIcon().get());
|
||||
out.name("startHidden").value(value.startHidden().get());
|
||||
out.name("theme").value(value.theme().get().name());
|
||||
out.name("uiOrientation").value(value.userInterfaceOrientation().get().name());
|
||||
out.name("useKeychain").value(value.useKeychain().get());
|
||||
out.name("windowHeight").value((value.windowHeightProperty().get()));
|
||||
out.name("windowWidth").value((value.windowWidthProperty().get()));
|
||||
out.name("windowXPosition").value((value.windowXPositionProperty().get()));
|
||||
out.name("windowYPosition").value((value.windowYPositionProperty().get()));
|
||||
out.endObject();
|
||||
}
|
||||
|
||||
private void writeVaultSettingsArray(JsonWriter out, Iterable<VaultSettings> vaultSettings) throws IOException {
|
||||
out.beginArray();
|
||||
for (VaultSettings value : vaultSettings) {
|
||||
vaultSettingsJsonAdapter.write(out, value);
|
||||
}
|
||||
out.endArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Settings read(JsonReader in) throws IOException {
|
||||
Settings settings = new Settings(env);
|
||||
//1.6.x legacy
|
||||
String volumeImpl = null;
|
||||
//legacy end
|
||||
in.beginObject();
|
||||
while (in.hasNext()) {
|
||||
String name = in.nextName();
|
||||
switch (name) {
|
||||
case "writtenByVersion" -> in.skipValue(); //noop
|
||||
case "directories" -> settings.getDirectories().addAll(readVaultSettingsArray(in));
|
||||
case "askedForUpdateCheck" -> settings.askedForUpdateCheck().set(in.nextBoolean());
|
||||
case "autoCloseVaults" -> settings.autoCloseVaults().set(in.nextBoolean());
|
||||
case "checkForUpdatesEnabled" -> settings.checkForUpdates().set(in.nextBoolean());
|
||||
case "debugMode" -> settings.debugMode().set(in.nextBoolean());
|
||||
case "displayConfiguration" -> settings.displayConfigurationProperty().set(in.nextString());
|
||||
case "keychainProvider" -> settings.keychainProvider().set(in.nextString());
|
||||
case "language" -> settings.languageProperty().set(in.nextString());
|
||||
case "licenseKey" -> settings.licenseKey().set(in.nextString());
|
||||
case "mountService" -> {
|
||||
var token = in.peek();
|
||||
if (JsonToken.STRING == token) {
|
||||
settings.mountService().set(in.nextString());
|
||||
}
|
||||
}
|
||||
case "numTrayNotifications" -> settings.numTrayNotifications().set(in.nextInt());
|
||||
case "port" -> settings.port().set(in.nextInt());
|
||||
case "showMinimizeButton" -> settings.showMinimizeButton().set(in.nextBoolean());
|
||||
case "showTrayIcon" -> settings.showTrayIcon().set(in.nextBoolean());
|
||||
case "startHidden" -> settings.startHidden().set(in.nextBoolean());
|
||||
case "theme" -> settings.theme().set(parseUiTheme(in.nextString()));
|
||||
case "uiOrientation" -> settings.userInterfaceOrientation().set(parseUiOrientation(in.nextString()));
|
||||
case "useKeychain" -> settings.useKeychain().set(in.nextBoolean());
|
||||
case "windowHeight" -> settings.windowHeightProperty().set(in.nextInt());
|
||||
case "windowWidth" -> settings.windowWidthProperty().set(in.nextInt());
|
||||
case "windowXPosition" -> settings.windowXPositionProperty().set(in.nextInt());
|
||||
case "windowYPosition" -> settings.windowYPositionProperty().set(in.nextInt());
|
||||
//1.6.x legacy
|
||||
case "preferredVolumeImpl" -> volumeImpl = in.nextString();
|
||||
//legacy end
|
||||
default -> {
|
||||
LOG.warn("Unsupported vault setting found in JSON: {}", name);
|
||||
in.skipValue();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
in.endObject();
|
||||
|
||||
//1.6.x legacy
|
||||
if (volumeImpl != null) {
|
||||
settings.mountService().set(convertLegacyVolumeImplToMountService(volumeImpl));
|
||||
}
|
||||
//legacy end
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
private String convertLegacyVolumeImplToMountService(String volumeImpl) {
|
||||
if (volumeImpl.equals("Dokany")) {
|
||||
return "org.cryptomator.frontend.dokany.mount.DokanyMountProvider";
|
||||
} else if (volumeImpl.equals("FUSE")) {
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
return "org.cryptomator.frontend.fuse.mount.WinFspNetworkMountProvider";
|
||||
} else if (SystemUtils.IS_OS_MAC) {
|
||||
return "org.cryptomator.frontend.fuse.mount.MacFuseMountProvider";
|
||||
} else {
|
||||
return "org.cryptomator.frontend.fuse.mount.LinuxFuseMountProvider";
|
||||
}
|
||||
} else {
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
return "org.cryptomator.frontend.webdav.mount.WindowsMounter";
|
||||
} else if (SystemUtils.IS_OS_MAC) {
|
||||
return "org.cryptomator.frontend.webdav.mount.MacAppleScriptMounter";
|
||||
} else {
|
||||
return "org.cryptomator.frontend.webdav.mount.LinuxGioMounter";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private UiTheme parseUiTheme(String uiThemeName) {
|
||||
try {
|
||||
return UiTheme.valueOf(uiThemeName.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.warn("Invalid ui theme {}. Defaulting to {}.", uiThemeName, Settings.DEFAULT_THEME);
|
||||
return Settings.DEFAULT_THEME;
|
||||
}
|
||||
}
|
||||
|
||||
private NodeOrientation parseUiOrientation(String uiOrientationName) {
|
||||
try {
|
||||
return NodeOrientation.valueOf(uiOrientationName.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.warn("Invalid ui orientation {}. Defaulting to {}.", uiOrientationName, Settings.DEFAULT_USER_INTERFACE_ORIENTATION);
|
||||
return Settings.DEFAULT_USER_INTERFACE_ORIENTATION;
|
||||
}
|
||||
}
|
||||
|
||||
private List<VaultSettings> readVaultSettingsArray(JsonReader in) throws IOException {
|
||||
List<VaultSettings> result = new ArrayList<>();
|
||||
in.beginArray();
|
||||
while (!JsonToken.END_ARRAY.equals(in.peek())) {
|
||||
result.add(vaultSettingsJsonAdapter.read(in));
|
||||
}
|
||||
in.endArray();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,9 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAlias;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
class VaultSettingsJson {
|
||||
|
||||
@JsonProperty(value = "id", required = true)
|
||||
String id;
|
||||
|
||||
@JsonProperty(value = "path")
|
||||
String path;
|
||||
|
||||
@JsonProperty("displayName")
|
||||
String displayName;
|
||||
|
||||
@JsonProperty("unlockAfterStartup")
|
||||
boolean unlockAfterStartup = VaultSettings.DEFAULT_UNLOCK_AFTER_STARTUP;
|
||||
|
||||
@JsonProperty("revealAfterMount")
|
||||
boolean revealAfterMount = VaultSettings.DEFAULT_REVEAL_AFTER_MOUNT;
|
||||
|
||||
@JsonProperty("mountPoint")
|
||||
String mountPoint;
|
||||
|
||||
@JsonProperty("usesReadOnlyMode")
|
||||
boolean usesReadOnlyMode = VaultSettings.DEFAULT_USES_READONLY_MODE;
|
||||
|
||||
@JsonProperty("mountFlags")
|
||||
String mountFlags = VaultSettings.DEFAULT_MOUNT_FLAGS;
|
||||
|
||||
@JsonProperty("maxCleartextFilenameLength")
|
||||
int maxCleartextFilenameLength = VaultSettings.DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH;
|
||||
|
||||
@JsonProperty("actionAfterUnlock")
|
||||
WhenUnlocked actionAfterUnlock = VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK;
|
||||
|
||||
@JsonProperty("autoLockWhenIdle")
|
||||
boolean autoLockWhenIdle = VaultSettings.DEFAULT_AUTOLOCK_WHEN_IDLE;
|
||||
|
||||
@JsonProperty("autoLockIdleSeconds")
|
||||
int autoLockIdleSeconds = VaultSettings.DEFAULT_AUTOLOCK_IDLE_SECONDS;
|
||||
|
||||
@Deprecated(since = "1.7.0")
|
||||
@JsonProperty(value = "winDriveLetter", access = JsonProperty.Access.WRITE_ONLY) // WRITE_ONLY means value is "written" into the java object during deserialization. Upvote this: https://github.com/FasterXML/jackson-annotations/issues/233
|
||||
String winDriveLetter;
|
||||
|
||||
@Deprecated(since = "1.7.0")
|
||||
@JsonProperty(value = "useCustomMountPath", access = JsonProperty.Access.WRITE_ONLY) // WRITE_ONLY means value is "written" into the java object during deserialization. Upvote this: https://github.com/FasterXML/jackson-annotations/issues/233
|
||||
@JsonAlias("usesIndividualMountPath")
|
||||
boolean useCustomMountPath;
|
||||
|
||||
@Deprecated(since = "1.7.0")
|
||||
@JsonProperty(value = "customMountPath", access = JsonProperty.Access.WRITE_ONLY) // WRITE_ONLY means value is "written" into the java object during deserialization. Upvote this: https://github.com/FasterXML/jackson-annotations/issues/233
|
||||
@JsonAlias("individualMountPath")
|
||||
String customMountPath;
|
||||
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
class VaultSettingsJsonAdapter {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(VaultSettingsJsonAdapter.class);
|
||||
|
||||
public void write(JsonWriter out, VaultSettings value) throws IOException {
|
||||
out.beginObject();
|
||||
out.name("id").value(value.getId());
|
||||
out.name("path").value(value.path().get().toString());
|
||||
out.name("displayName").value(value.displayName().get());
|
||||
out.name("unlockAfterStartup").value(value.unlockAfterStartup().get());
|
||||
out.name("revealAfterMount").value(value.revealAfterMount().get());
|
||||
var mountPoint = value.mountPoint().get();
|
||||
out.name("mountPoint").value(mountPoint != null ? mountPoint.toAbsolutePath().toString() : null);
|
||||
out.name("usesReadOnlyMode").value(value.usesReadOnlyMode().get());
|
||||
out.name("mountFlags").value(value.mountFlags().get());
|
||||
out.name("maxCleartextFilenameLength").value(value.maxCleartextFilenameLength().get());
|
||||
out.name("actionAfterUnlock").value(value.actionAfterUnlock().get().name());
|
||||
out.name("autoLockWhenIdle").value(value.autoLockWhenIdle().get());
|
||||
out.name("autoLockIdleSeconds").value(value.autoLockIdleSeconds().get());
|
||||
out.endObject();
|
||||
}
|
||||
|
||||
public VaultSettings read(JsonReader in) throws IOException {
|
||||
String id = null;
|
||||
String path = null;
|
||||
String mountName = null; //see https://github.com/cryptomator/cryptomator/pull/1318
|
||||
String displayName = null;
|
||||
boolean unlockAfterStartup = VaultSettings.DEFAULT_UNLOCK_AFTER_STARTUP;
|
||||
boolean revealAfterMount = VaultSettings.DEFAULT_REVEAL_AFTER_MOUNT;
|
||||
boolean usesReadOnlyMode = VaultSettings.DEFAULT_USES_READONLY_MODE;
|
||||
String mountFlags = VaultSettings.DEFAULT_MOUNT_FLAGS;
|
||||
Path mountPoint = null;
|
||||
int maxCleartextFilenameLength = VaultSettings.DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH;
|
||||
WhenUnlocked actionAfterUnlock = VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK;
|
||||
boolean autoLockWhenIdle = VaultSettings.DEFAULT_AUTOLOCK_WHEN_IDLE;
|
||||
int autoLockIdleSeconds = VaultSettings.DEFAULT_AUTOLOCK_IDLE_SECONDS;
|
||||
|
||||
//legacy from 1.6.x
|
||||
boolean useCustomMountPath = false;
|
||||
String customMountPath = "";
|
||||
String winDriveLetter = "";
|
||||
//legacy end
|
||||
|
||||
in.beginObject();
|
||||
while (in.hasNext()) {
|
||||
String name = in.nextName();
|
||||
switch (name) {
|
||||
case "id" -> id = in.nextString();
|
||||
case "path" -> path = in.nextString();
|
||||
case "mountName" -> mountName = in.nextString(); //see https://github.com/cryptomator/cryptomator/pull/1318
|
||||
case "displayName" -> displayName = in.nextString();
|
||||
case "unlockAfterStartup" -> unlockAfterStartup = in.nextBoolean();
|
||||
case "revealAfterMount" -> revealAfterMount = in.nextBoolean();
|
||||
case "usesReadOnlyMode" -> usesReadOnlyMode = in.nextBoolean();
|
||||
case "mountFlags" -> mountFlags = in.nextString();
|
||||
case "mountPoint" -> {
|
||||
if (JsonToken.NULL == in.peek()) {
|
||||
in.nextNull();
|
||||
} else {
|
||||
mountPoint = parseMountPoint(in.nextString());
|
||||
}
|
||||
}
|
||||
case "maxCleartextFilenameLength" -> maxCleartextFilenameLength = in.nextInt();
|
||||
case "actionAfterUnlock" -> actionAfterUnlock = parseActionAfterUnlock(in.nextString());
|
||||
case "autoLockWhenIdle" -> autoLockWhenIdle = in.nextBoolean();
|
||||
case "autoLockIdleSeconds" -> autoLockIdleSeconds = in.nextInt();
|
||||
//legacy from 1.6.x
|
||||
case "winDriveLetter" -> winDriveLetter = in.nextString();
|
||||
case "usesIndividualMountPath", "useCustomMountPath" -> useCustomMountPath = in.nextBoolean();
|
||||
case "individualMountPath", "customMountPath" -> customMountPath = in.nextString();
|
||||
//legacy end
|
||||
default -> {
|
||||
LOG.warn("Unsupported vault setting found in JSON: {}", name);
|
||||
in.skipValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
in.endObject();
|
||||
|
||||
VaultSettings vaultSettings = (id == null) ? VaultSettings.withRandomId() : new VaultSettings(id);
|
||||
if (displayName != null) { //see https://github.com/cryptomator/cryptomator/pull/1318
|
||||
vaultSettings.displayName().set(displayName);
|
||||
} else {
|
||||
vaultSettings.displayName().set(mountName);
|
||||
}
|
||||
vaultSettings.path().set(Paths.get(path));
|
||||
vaultSettings.unlockAfterStartup().set(unlockAfterStartup);
|
||||
vaultSettings.revealAfterMount().set(revealAfterMount);
|
||||
vaultSettings.usesReadOnlyMode().set(usesReadOnlyMode);
|
||||
vaultSettings.mountFlags().set(mountFlags);
|
||||
vaultSettings.maxCleartextFilenameLength().set(maxCleartextFilenameLength);
|
||||
vaultSettings.actionAfterUnlock().set(actionAfterUnlock);
|
||||
vaultSettings.autoLockWhenIdle().set(autoLockWhenIdle);
|
||||
vaultSettings.autoLockIdleSeconds().set(autoLockIdleSeconds);
|
||||
vaultSettings.mountPoint().set(mountPoint);
|
||||
//legacy from 1.6.x
|
||||
if(useCustomMountPath && !customMountPath.isBlank()) {
|
||||
vaultSettings.mountPoint().set(parseMountPoint(customMountPath));
|
||||
} else if(!winDriveLetter.isBlank() ) {
|
||||
vaultSettings.mountPoint().set(parseMountPoint(winDriveLetter+":\\"));
|
||||
}
|
||||
//legacy end
|
||||
return vaultSettings;
|
||||
}
|
||||
|
||||
private Path parseMountPoint(String mountPoint) {
|
||||
try {
|
||||
return Path.of(mountPoint);
|
||||
} catch (InvalidPathException e) {
|
||||
LOG.warn("Invalid string as mount point. Defaulting to null.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private WhenUnlocked parseActionAfterUnlock(String actionAfterUnlockName) {
|
||||
try {
|
||||
return WhenUnlocked.valueOf(actionAfterUnlockName.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.warn("Invalid action after unlock {}. Defaulting to {}.", actionAfterUnlockName, VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK);
|
||||
return VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
// ******************************************************************************
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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())) //
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package org.cryptomator.ui.error;
|
||||
|
||||
import com.google.common.reflect.TypeToken;
|
||||
import com.google.gson.Gson;
|
||||
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;
|
||||
@@ -21,8 +23,8 @@ 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.io.InputStreamReader;
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.net.http.HttpClient;
|
||||
@@ -38,6 +40,8 @@ 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";
|
||||
@@ -137,11 +141,12 @@ public class ErrorController implements FxController {
|
||||
}
|
||||
|
||||
private void loadHttpResponse(HttpResponse<InputStream> response) {
|
||||
if (response.statusCode() == 200) {
|
||||
Map<String, ErrorDiscussion> errorDiscussionMap = new Gson().fromJson(//
|
||||
new InputStreamReader(response.body(), StandardCharsets.UTF_8),//
|
||||
new TypeToken<Map<String, ErrorDiscussion>>() {
|
||||
}.getType());
|
||||
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;
|
||||
@@ -155,6 +160,8 @@ public class ErrorController implements FxController {
|
||||
matchingErrorDiscussion.set(value.get());
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to load or parse JSON from " + response.uri(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
package org.cryptomator.ui.error;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ErrorDiscussion {
|
||||
|
||||
@JsonProperty
|
||||
int upvoteCount;
|
||||
@JsonProperty
|
||||
String title;
|
||||
@JsonProperty
|
||||
String url;
|
||||
@JsonProperty
|
||||
Answer answer;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
static class Answer {
|
||||
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ public class AutoUnlocker {
|
||||
|
||||
public void tryUnlockForTimespan(int timespan, TimeUnit timeUnit) {
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -38,7 +38,7 @@ public class UpdateChecker {
|
||||
}
|
||||
|
||||
public void automaticallyCheckForUpdatesIfEnabled() {
|
||||
if (settings.checkForUpdates().get()) {
|
||||
if (settings.checkForUpdates.get()) {
|
||||
startCheckingForUpdates(AUTOCHECK_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ public class UpdatesPreferencesController implements FxController {
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
checkForUpdatesCheckbox.selectedProperty().bindBidirectional(settings.checkForUpdates());
|
||||
checkForUpdatesCheckbox.selectedProperty().bindBidirectional(settings.checkForUpdates);
|
||||
}
|
||||
|
||||
@FXML
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -137,6 +137,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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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\""));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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\""))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user