mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-09-23 08:24:52 +00:00
Compare commits
88
Commits
1.6.14
...
1.7.0-alpha1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f42c1c9ce | ||
|
|
37f89fd235 | ||
|
|
668feb73f9 | ||
|
|
ba0569deb1 | ||
|
|
a70d8e1567 | ||
|
|
96b7a8df3f | ||
|
|
5143fdccbb | ||
|
|
3c5502955a | ||
|
|
2d3a035d9e | ||
|
|
4029f86a0d | ||
|
|
6dac00625c | ||
|
|
41b7bd54f1 | ||
|
|
f236614bd0 | ||
|
|
31c69f145e | ||
|
|
b5a00f5ebe | ||
|
|
40c4300012 | ||
|
|
5369ddb1f9 | ||
|
|
1b4117de38 | ||
|
|
3e5a7fadd0 | ||
|
|
eff2530e70 | ||
|
|
0f84d0c990 | ||
|
|
fbe33e21fb | ||
|
|
7091839f08 | ||
|
|
79bf1759c7 | ||
|
|
70e5f53009 | ||
|
|
c79cfe1c9d | ||
|
|
d619a0cbf4 | ||
|
|
517b5958b0 | ||
|
|
01a6475d5f | ||
|
|
69641ed3b6 | ||
|
|
1f01923169 | ||
|
|
687bed6984 | ||
|
|
01698d1393 | ||
|
|
6367cc2dfb | ||
|
|
bcfda68bef | ||
|
|
2399e29d75 | ||
|
|
322779ee88 | ||
|
|
fd6eb2139d | ||
|
|
26ecd67a79 | ||
|
|
e06240cbb9 | ||
|
|
b10d892b12 | ||
|
|
45c2dd0358 | ||
|
|
34d6e8c419 | ||
|
|
ae6a15b391 | ||
|
|
8040b71a51 | ||
|
|
76e7a0a7b8 | ||
|
|
910efc0b7b | ||
|
|
c805a820a4 | ||
|
|
3ed8316b60 | ||
|
|
b6e381f0bf | ||
|
|
aef3f4c6e2 | ||
|
|
7bc5a336e4 | ||
|
|
672b4aee2d | ||
|
|
d4ab17b38c | ||
|
|
91f49c5642 | ||
|
|
da2f97b1c4 | ||
|
|
5d647d20a5 | ||
|
|
ce96d1c65e | ||
|
|
f02a8e14ff | ||
|
|
dd31908f3a | ||
|
|
495a1884c3 | ||
|
|
49c3b1fa43 | ||
|
|
cde67966da | ||
|
|
d19e77432e | ||
|
|
cc24a4c9ae | ||
|
|
1101a7574c | ||
|
|
30de04e291 | ||
|
|
2e3d2e86e2 | ||
|
|
78cf082096 | ||
|
|
64c90f8107 | ||
|
|
b34bf6f161 | ||
|
|
17d3d7307d | ||
|
|
e6c7fed662 | ||
|
|
23fad16742 | ||
|
|
ec794cdca2 | ||
|
|
2def9216ec | ||
|
|
4f9759af58 | ||
|
|
382f6b7851 | ||
|
|
d60eb470ea | ||
|
|
97a61ef717 | ||
|
|
08fc5e9a77 | ||
|
|
4c96c285bf | ||
|
|
0021f2d37c | ||
|
|
31d32f2018 | ||
|
|
b9c087b48e | ||
|
|
797dbc0a57 | ||
|
|
46745d030b | ||
|
|
3cdf124e54 |
@@ -10,20 +10,20 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_VERSION: 17
|
JAVA_VERSION: 19
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build AppImage
|
name: Build AppImage
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v2
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin'
|
distribution: 'zulu'
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
cache: 'maven'
|
cache: 'maven'
|
||||||
- id: versions
|
- id: versions
|
||||||
|
|||||||
@@ -6,26 +6,25 @@ on:
|
|||||||
types: [labeled]
|
types: [labeled]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_VERSION: 17
|
JAVA_VERSION: 19
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
name: Compile and Test
|
name: Compile and Test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')"
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-java@v2
|
- uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin'
|
distribution: 'zulu'
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
cache: 'maven'
|
cache: 'maven'
|
||||||
- name: Cache SonarCloud packages
|
- name: Cache SonarCloud packages
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: ~/.sonar/cache
|
path: ~/.sonar/cache
|
||||||
key: ${{ runner.os }}-sonar
|
key: ${{ runner.os }}-sonar
|
||||||
|
|||||||
@@ -15,24 +15,25 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_VERSION: 17
|
JAVA_VERSION: 19
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build Debian Package
|
name: Build Debian Package
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Install build tools
|
- name: Install build tools
|
||||||
run: |
|
run: |
|
||||||
|
sudo add-apt-repository ppa:coffeelibs/openjdk
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install debhelper devscripts dput
|
sudo apt-get install debhelper devscripts dput coffeelibs-jdk-19
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v2
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin'
|
distribution: 'zulu'
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
cache: 'maven'
|
cache: 'maven'
|
||||||
- id: versions
|
- id: versions
|
||||||
|
|||||||
@@ -10,23 +10,36 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_VERSION: 17
|
JAVA_VERSION: 19
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build Cryptomator.app
|
name: Build Cryptomator.app for ${{ matrix.output-suffix }}
|
||||||
runs-on: macos-11
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: macos-11
|
||||||
|
architecture: x64
|
||||||
|
output-suffix: x64
|
||||||
|
xcode-path: '/Applications/Xcode_13.2.1.app'
|
||||||
|
- os: [self-hosted, macOS, ARM64]
|
||||||
|
architecture: aarch64
|
||||||
|
output-suffix: arm64
|
||||||
|
xcode-path: '/Applications/Xcode_13.2.1.app'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v2
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin'
|
distribution: 'zulu'
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
|
architecture: ${{ matrix.architecture }}
|
||||||
cache: 'maven'
|
cache: 'maven'
|
||||||
- id: versions
|
- id: versions
|
||||||
name: Apply version information
|
name: Apply version information
|
||||||
run: |
|
run: |
|
||||||
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
||||||
@@ -186,7 +199,7 @@ jobs:
|
|||||||
--icon ".background" 128 758
|
--icon ".background" 128 758
|
||||||
--icon ".fseventsd" 320 758
|
--icon ".fseventsd" 320 758
|
||||||
--icon ".VolumeIcon.icns" 512 758
|
--icon ".VolumeIcon.icns" 512 758
|
||||||
Cryptomator-${VERSION_NO}.dmg dmg
|
Cryptomator-${VERSION_NO}-${{ matrix.output-suffix }}.dmg dmg
|
||||||
env:
|
env:
|
||||||
VERSION_NO: ${{ steps.versions.outputs.semVerNum }}
|
VERSION_NO: ${{ steps.versions.outputs.semVerNum }}
|
||||||
- name: Notarize .dmg
|
- name: Notarize .dmg
|
||||||
@@ -197,8 +210,9 @@ jobs:
|
|||||||
apple-id: ${{ secrets.MACOS_NOTARIZATION_APPLE_ID }}
|
apple-id: ${{ secrets.MACOS_NOTARIZATION_APPLE_ID }}
|
||||||
password: ${{ secrets.MACOS_NOTARIZATION_PW }}
|
password: ${{ secrets.MACOS_NOTARIZATION_PW }}
|
||||||
team-id: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }}
|
team-id: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }}
|
||||||
|
xcode-path: ${{ matrix.xcode-path }}
|
||||||
- name: Add possible alpha/beta tags to installer name
|
- name: Add possible alpha/beta tags to installer name
|
||||||
run: mv Cryptomator-*.dmg Cryptomator-${{ steps.versions.outputs.semVerStr }}.dmg
|
run: mv Cryptomator-*.dmg Cryptomator-${{ steps.versions.outputs.semVerStr }}-${{ matrix.output-suffix }}.dmg
|
||||||
- name: Create detached GPG signature with key 615D449FE6E6A235
|
- name: Create detached GPG signature with key 615D449FE6E6A235
|
||||||
run: |
|
run: |
|
||||||
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
|
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
|
||||||
@@ -213,7 +227,7 @@ jobs:
|
|||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: dmg
|
name: dmg-${{ matrix.output-suffix }}
|
||||||
path: Cryptomator-*.dmg
|
path: Cryptomator-*.dmg
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
- name: Publish dmg on GitHub Releases
|
- name: Publish dmg on GitHub Releases
|
||||||
@@ -225,5 +239,3 @@ jobs:
|
|||||||
files: |
|
files: |
|
||||||
Cryptomator-*.dmg
|
Cryptomator-*.dmg
|
||||||
Cryptomator-*.asc
|
Cryptomator-*.asc
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_VERSION: 17
|
JAVA_VERSION: 19
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
@@ -16,10 +16,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')"
|
if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-java@v2
|
- uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin'
|
distribution: 'zulu'
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
cache: 'maven'
|
cache: 'maven'
|
||||||
- name: Build and Test
|
- name: Build and Test
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
name: Release Check
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'release/**'
|
||||||
|
- 'hotfix/**'
|
||||||
|
|
||||||
|
env:
|
||||||
|
JAVA_VERSION: 19
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release-check-precondition:
|
||||||
|
name: Validate commits pushed to release/hotfix branch to fulfill release requirements
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- id: validate-pom-version
|
||||||
|
name: Validate POM version
|
||||||
|
run: |
|
||||||
|
if [[ $GITHUB_REF =~ refs/heads/(hotfix|release)/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
||||||
|
SEM_VER_STR=${GITHUB_REF##*/}
|
||||||
|
else
|
||||||
|
echo "Failed to parse version"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ${SEM_VER_STR} == `mvn help:evaluate -Dexpression=project.version -q -DforceStdout` ]]; then
|
||||||
|
echo "::set-output name=semVerStr::${SEM_VER_STR}"
|
||||||
|
else
|
||||||
|
echo "Version not set in POM"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
- name: Validate release in org.cryptomator.Cryptomator.metainfo.xml file
|
||||||
|
run: |
|
||||||
|
if ! grep -q "<release date=\".*\" version=\"${{ steps.validate-pom-version.outputs.semVerStr }}\"/>" dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml; then
|
||||||
|
echo "Release not set in dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -8,14 +8,11 @@ on:
|
|||||||
version:
|
version:
|
||||||
description: 'Version'
|
description: 'Version'
|
||||||
required: false
|
required: false
|
||||||
winget-release:
|
|
||||||
description: 'Release artifacts to winget'
|
|
||||||
required: true
|
|
||||||
type: boolean
|
|
||||||
default: false
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_VERSION: 17
|
JAVA_VERSION: 19
|
||||||
|
JAVA_DIST: 'zulu'
|
||||||
|
JAVA_CACHE: 'maven'
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
@@ -26,15 +23,15 @@ jobs:
|
|||||||
name: Build .msi Installer
|
name: Build .msi Installer
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v2
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin'
|
distribution: ${{ env.JAVA_DIST }}
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
cache: 'maven'
|
cache: ${{ env.JAVA_CACHE }}
|
||||||
- id: versions
|
- id: versions
|
||||||
name: Apply version information
|
name: Apply version information
|
||||||
run: |
|
run: |
|
||||||
@@ -194,38 +191,32 @@ jobs:
|
|||||||
semVerStr: ${{ steps.versions.outputs.semVerStr }}
|
semVerStr: ${{ steps.versions.outputs.semVerStr }}
|
||||||
revNum: ${{ steps.versions.outputs.revNum }}
|
revNum: ${{ steps.versions.outputs.revNum }}
|
||||||
|
|
||||||
publish-winget:
|
call-winget-flow:
|
||||||
name: Publish on winget repo
|
|
||||||
runs-on: windows-latest
|
|
||||||
needs: [build-msi]
|
needs: [build-msi]
|
||||||
if: github.event.action == 'published' || inputs.winget-release
|
if: github.event.action == 'published'
|
||||||
steps:
|
uses: ./.github/workflows/winget.yml
|
||||||
- name: Submit package to Windows Package Manager Community Repository
|
with:
|
||||||
run: |
|
releaseTag: ${{ github.event.release.tag_name }}
|
||||||
iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe
|
|
||||||
$github = Get-Content '${{ github.event_path }}' | ConvertFrom-Json
|
|
||||||
$installerUrl = $github.release.assets | Where-Object -Property name -match 'Cryptomator-*.msi' | Select -ExpandProperty browser_download_url -First 1
|
|
||||||
.\wingetcreate.exe update Cryptomator.Cryptomator -s -v $github.release.tag_name -u $installerUrl -t ${{ secrets.CRYPTOBOT_WINGET_TOKEN }}
|
|
||||||
shell: pwsh
|
|
||||||
|
|
||||||
build-exe:
|
build-exe:
|
||||||
name: Build .exe installer
|
name: Build .exe installer
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
needs: [build-msi]
|
needs: [build-msi]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- name: Download .msi
|
- name: Download .msi
|
||||||
uses: actions/download-artifact@v2
|
uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: msi
|
name: msi
|
||||||
path: dist/win/bundle/resources
|
path: dist/win/bundle/resources
|
||||||
- name: Strip version info from msi file name
|
- name: Strip version info from msi file name
|
||||||
run: mv dist/win/bundle/resources/Cryptomator*.msi dist/win/bundle/resources/Cryptomator.msi
|
run: mv dist/win/bundle/resources/Cryptomator*.msi dist/win/bundle/resources/Cryptomator.msi
|
||||||
- uses: actions/setup-java@v2
|
- uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin'
|
distribution: ${{ env.JAVA_DIST }}
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
cache: 'maven'
|
cache: ${{ env.JAVA_CACHE }}
|
||||||
- name: Generate license for exe
|
- name: Generate license for exe
|
||||||
run: >
|
run: >
|
||||||
mvn -B license:add-third-party
|
mvn -B license:add-third-party
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
name: Release to Winget
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
releaseTag:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
releaseTag:
|
||||||
|
description: 'Release tag name'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish-winget:
|
||||||
|
name: Publish on winget repo
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- name: Get download url for msi artifacts
|
||||||
|
id: get-release-assets
|
||||||
|
uses: actions/github-script@v6
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const query =`query($tag:String!) {
|
||||||
|
repository(owner:"cryptomator", name:"cryptomator"){
|
||||||
|
release(tagName: $tag) {
|
||||||
|
releaseAssets(first:20) {
|
||||||
|
nodes {
|
||||||
|
name
|
||||||
|
downloadUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
const variables = {
|
||||||
|
tag: "${{ inputs.releaseTag }}"
|
||||||
|
}
|
||||||
|
return await github.graphql(query, variables)
|
||||||
|
- name: Submit package to Windows Package Manager Community Repository
|
||||||
|
id: submit-winget
|
||||||
|
run: |
|
||||||
|
iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe
|
||||||
|
$releaseAssets = (ConvertFrom-Json '${{ steps.get-release-assets.outputs.result }}').repository.release.releaseAssets.nodes
|
||||||
|
$installerUrl = $releaseAssets | Where-Object -Property name -match '^Cryptomator-.*\.msi$' | Select -ExpandProperty downloadUrl -First 1
|
||||||
|
.\wingetcreate.exe update Cryptomator.Cryptomator -s -v "${{ inputs.releaseTag }}" -u "$installerUrl" -t ${{ secrets.CRYPTOBOT_WINGET_TOKEN }}
|
||||||
|
shell: pwsh
|
||||||
Generated
+11
-11
@@ -14,10 +14,10 @@
|
|||||||
<option name="dagger.fastInit" value="enabled" />
|
<option name="dagger.fastInit" value="enabled" />
|
||||||
<option name="dagger.formatGeneratedSource" value="enabled" />
|
<option name="dagger.formatGeneratedSource" value="enabled" />
|
||||||
<processorPath useClasspath="false">
|
<processorPath useClasspath="false">
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.41/dagger-compiler-2.41.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.44/dagger-compiler-2.44.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.41/dagger-2.41.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.44/dagger-2.44.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar" />
|
<entry name="$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-producers/2.41/dagger-producers-2.41.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-producers/2.44/dagger-producers-2.44.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/guava/guava/31.0.1-jre/guava-31.0.1-jre.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/guava/guava/31.0.1-jre/guava-31.0.1-jre.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" />
|
||||||
@@ -26,18 +26,18 @@
|
|||||||
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.7.1/error_prone_annotations-2.7.1.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.7.1/error_prone_annotations-2.7.1.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-compat-qual/2.5.5/checker-compat-qual-2.5.5.jar" />
|
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-compat-qual/2.5.5/checker-compat-qual-2.5.5.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.41/dagger-spi-2.41.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.44/dagger-spi-2.44.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/devtools/ksp/symbol-processing-api/1.5.30-1.0.0/symbol-processing-api-1.5.30-1.0.0.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/devtools/ksp/symbol-processing-api/1.7.0-1.0.6/symbol-processing-api-1.7.0-1.0.6.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.5.32/kotlin-stdlib-jdk8-1.5.32.jar" />
|
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.7.0/kotlin-stdlib-1.7.0.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.5.32/kotlin-stdlib-1.5.32.jar" />
|
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.7.0/kotlin-stdlib-common-1.7.0.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" />
|
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.5.32/kotlin-stdlib-common-1.5.32.jar" />
|
|
||||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.5.32/kotlin-stdlib-jdk7-1.5.32.jar" />
|
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/squareup/javapoet/1.13.0/javapoet-1.13.0.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/squareup/javapoet/1.13.0/javapoet-1.13.0.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.7.0/kotlin-stdlib-jdk8-1.7.0.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.7.0/kotlin-stdlib-jdk7-1.7.0.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/googlejavaformat/google-java-format/1.5/google-java-format-1.5.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/googlejavaformat/google-java-format/1.5/google-java-format-1.5.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/javac-shaded/9-dev-r4023-3/javac-shaded-9-dev-r4023-3.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/javac-shaded/9-dev-r4023-3/javac-shaded-9-dev-r4023-3.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/net/ltgt/gradle/incap/incap/0.2/incap-0.2.jar" />
|
<entry name="$MAVEN_REPOSITORY$/net/ltgt/gradle/incap/incap/0.2/incap-0.2.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-metadata-jvm/0.3.0/kotlinx-metadata-jvm-0.3.0.jar" />
|
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-metadata-jvm/0.5.0/kotlinx-metadata-jvm-0.5.0.jar" />
|
||||||
</processorPath>
|
</processorPath>
|
||||||
<module name="cryptomator" />
|
<module name="cryptomator" />
|
||||||
</profile>
|
</profile>
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
</component>
|
</component>
|
||||||
<component name="JavacSettings">
|
<component name="JavacSettings">
|
||||||
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
|
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
|
||||||
<module name="cryptomator" options="-Adagger.fastInit=enabled -Adagger.formatGeneratedSource=enabled" />
|
<module name="cryptomator" options="-Adagger.fastInit=enabled -Adagger.formatGeneratedSource=enabled --enable-preview" />
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
Generated
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
</list>
|
</list>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="17" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_19" default="true" project-jdk-name="19" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
<configuration default="false" name="Cryptomator Linux" type="Application" factoryName="Application">
|
<configuration default="false" name="Cryptomator Linux" type="Application" factoryName="Application">
|
||||||
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
||||||
<module name="cryptomator" />
|
<module name="cryptomator" />
|
||||||
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath="~/.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" />
|
<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" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Make" enabled="true" />
|
<option name="Make" enabled="true" />
|
||||||
</method>
|
</method>
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
<configuration default="false" name="Cryptomator Linux Dev" type="Application" factoryName="Application">
|
<configuration default="false" name="Cryptomator Linux Dev" type="Application" factoryName="Application">
|
||||||
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
||||||
<module name="cryptomator" />
|
<module name="cryptomator" />
|
||||||
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath="~/.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" />
|
<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" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Make" enabled="true" />
|
<option name="Make" enabled="true" />
|
||||||
</method>
|
</method>
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
<configuration default="false" name="Cryptomator Windows" type="Application" factoryName="Application">
|
<configuration default="false" name="Cryptomator Windows" type="Application" factoryName="Application">
|
||||||
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
||||||
<module name="cryptomator" />
|
<module name="cryptomator" />
|
||||||
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath="~/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" />
|
<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" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Make" enabled="true" />
|
<option name="Make" enabled="true" />
|
||||||
</method>
|
</method>
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
<configuration default="false" name="Cryptomator Windows Dev" type="Application" factoryName="Application">
|
<configuration default="false" name="Cryptomator Windows Dev" type="Application" factoryName="Application">
|
||||||
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
||||||
<module name="cryptomator" />
|
<module name="cryptomator" />
|
||||||
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath="~/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" />
|
<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" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Make" enabled="true" />
|
<option name="Make" enabled="true" />
|
||||||
</method>
|
</method>
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
</envs>
|
</envs>
|
||||||
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
||||||
<module name="cryptomator" />
|
<module name="cryptomator" />
|
||||||
<option name="VM_PARAMETERS" value="-Dapple.awt.enableTemplateImages=true -Dcryptomator.settingsPath="~/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.showTrayIcon=true -Dcryptomator.integrationsMac.keychainServiceName=Cryptomator -Xss2m -Xmx512m -ea" />
|
<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" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Make" enabled="true" />
|
<option name="Make" enabled="true" />
|
||||||
</method>
|
</method>
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
</envs>
|
</envs>
|
||||||
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
||||||
<module name="cryptomator" />
|
<module name="cryptomator" />
|
||||||
<option name="VM_PARAMETERS" value="-Dapple.awt.enableTemplateImages=true -Dcryptomator.settingsPath="~/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.showTrayIcon=true -Dcryptomator.integrationsMac.keychainServiceName=Cryptomator -Xss2m -Xmx512m -ea" />
|
<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" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Make" enabled="true" />
|
<option name="Make" enabled="true" />
|
||||||
</method>
|
</method>
|
||||||
|
|||||||
@@ -37,6 +37,12 @@ Cryptomator is provided free of charge as an open-source project despite the hig
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
### Special Shoutout
|
||||||
|
|
||||||
|
Continuous integration hosting for ARM64 builds is provided by [MacStadium](https://www.macstadium.com/opensource).
|
||||||
|
|
||||||
|
<a href="https://www.macstadium.com/opensource"><img src="https://uploads-ssl.webflow.com/5ac3c046c82724970fc60918/5c019d917bba312af7553b49_MacStadium-developerlogo.png" alt="MacStadium" height="100"></a>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
@@ -86,7 +92,7 @@ For more information on the security details visit [cryptomator.org](https://doc
|
|||||||
|
|
||||||
```
|
```
|
||||||
mvn clean install
|
mvn clean install
|
||||||
# or mvn clean install -Pwindows
|
# or mvn clean install -Pwin
|
||||||
# or mvn clean install -Pmac
|
# or mvn clean install -Pmac
|
||||||
# or mvn clean install -Plinux
|
# or mvn clean install -Plinux
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -66,6 +66,7 @@
|
|||||||
</content_rating>
|
</content_rating>
|
||||||
|
|
||||||
<releases>
|
<releases>
|
||||||
|
<release date="2022-10-06" version="1.6.15"/>
|
||||||
<release date="2022-08-31" version="1.6.14"/>
|
<release date="2022-08-31" version="1.6.14"/>
|
||||||
<release date="2022-07-27" version="1.6.12"/>
|
<release date="2022-07-27" version="1.6.12"/>
|
||||||
<release date="2022-07-26" version="1.6.11"/>
|
<release date="2022-07-26" version="1.6.11"/>
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
cryptomator (${PPA_VERSION}) bionic; urgency=low
|
cryptomator (${PPA_VERSION}) focal; urgency=low
|
||||||
|
|
||||||
* Full changelog can be found on https://github.com/cryptomator/cryptomator/releases
|
* Full changelog can be found on https://github.com/cryptomator/cryptomator/releases
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
-1
@@ -2,7 +2,7 @@ Source: cryptomator
|
|||||||
Maintainer: Cryptobot <releases@cryptomator.org>
|
Maintainer: Cryptobot <releases@cryptomator.org>
|
||||||
Section: utils
|
Section: utils
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Build-Depends: debhelper (>=10), openjdk-17-jdk
|
Build-Depends: debhelper (>=10), coffeelibs-jdk-19
|
||||||
Standards-Version: 4.5.0
|
Standards-Version: 4.5.0
|
||||||
Homepage: https://cryptomator.org
|
Homepage: https://cryptomator.org
|
||||||
Vcs-Git: https://github.com/cryptomator/cryptomator.git
|
Vcs-Git: https://github.com/cryptomator/cryptomator.git
|
||||||
|
|||||||
Vendored
+4
-2
@@ -4,6 +4,8 @@
|
|||||||
# Uncomment this to turn on verbose mode.
|
# Uncomment this to turn on verbose mode.
|
||||||
#export DH_VERBOSE=1
|
#export DH_VERBOSE=1
|
||||||
|
|
||||||
|
JAVA_HOME = /usr/lib/jvm/java-19-coffeelibs
|
||||||
|
|
||||||
%:
|
%:
|
||||||
dh $@
|
dh $@
|
||||||
|
|
||||||
@@ -16,7 +18,7 @@ override_dh_auto_clean:
|
|||||||
override_dh_auto_build:
|
override_dh_auto_build:
|
||||||
mkdir resources
|
mkdir resources
|
||||||
ln -s ../common/org.cryptomator.Cryptomator512.png resources/cryptomator.png
|
ln -s ../common/org.cryptomator.Cryptomator512.png resources/cryptomator.png
|
||||||
jlink \
|
$(JAVA_HOME)/bin/jlink \
|
||||||
--output runtime \
|
--output runtime \
|
||||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \
|
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \
|
||||||
--strip-native-commands \
|
--strip-native-commands \
|
||||||
@@ -24,7 +26,7 @@ override_dh_auto_build:
|
|||||||
--no-man-pages \
|
--no-man-pages \
|
||||||
--strip-debug \
|
--strip-debug \
|
||||||
--compress=2
|
--compress=2
|
||||||
jpackage \
|
$(JAVA_HOME)/bin/jpackage \
|
||||||
--type app-image \
|
--type app-image \
|
||||||
--runtime-image runtime \
|
--runtime-image runtime \
|
||||||
--input libs \
|
--input libs \
|
||||||
|
|||||||
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
[InternetShortcut]
|
[InternetShortcut]
|
||||||
URL=https://github.com/winfsp/winfsp/releases/download/v1.11/winfsp-1.11.22176.msi
|
URL=https://github.com/winfsp/winfsp/releases/download/v1.12/winfsp-1.12.22301.msi
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>cryptomator</artifactId>
|
<artifactId>cryptomator</artifactId>
|
||||||
<version>1.6.14</version>
|
<version>1.7.0</version>
|
||||||
<name>Cryptomator Desktop App</name>
|
<name>Cryptomator Desktop App</name>
|
||||||
|
|
||||||
<organization>
|
<organization>
|
||||||
@@ -21,45 +21,46 @@
|
|||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<project.jdk.version>17</project.jdk.version>
|
<project.jdk.version>19</project.jdk.version>
|
||||||
|
|
||||||
<!-- Group IDs of jars that need to stay on the class path for now -->
|
<!-- Group IDs of jars that need to stay on the class path for now -->
|
||||||
<nonModularGroupIds>com.github.serceman,com.github.jnr,org.ow2.asm,net.java.dev.jna,org.apache.jackrabbit,org.apache.httpcomponents,de.swiesend,org.purejava,com.github.hypfvieh</nonModularGroupIds>
|
<!-- Check progress on https://github.com/swiesend/secret-service/issues/31 to remove hypfvieh, swiesend and jnr -->
|
||||||
|
<nonModularGroupIds>com.github.jnr,org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents,de.swiesend,org.purejava,com.github.hypfvieh</nonModularGroupIds>
|
||||||
|
|
||||||
<!-- cryptomator dependencies -->
|
<!-- cryptomator dependencies -->
|
||||||
<cryptomator.cryptolib.version>2.1.0-beta3</cryptomator.cryptolib.version>
|
<cryptomator.cryptolib.version>2.1.0-rc1</cryptomator.cryptolib.version>
|
||||||
<cryptomator.cryptofs.version>2.4.2</cryptomator.cryptofs.version>
|
<cryptomator.cryptofs.version>2.4.5</cryptomator.cryptofs.version>
|
||||||
<cryptomator.integrations.version>1.1.0</cryptomator.integrations.version>
|
<cryptomator.integrations.version>1.2.0-beta2</cryptomator.integrations.version>
|
||||||
<cryptomator.integrations.win.version>1.1.2</cryptomator.integrations.win.version>
|
<cryptomator.integrations.win.version>1.1.2</cryptomator.integrations.win.version>
|
||||||
<cryptomator.integrations.mac.version>1.1.2</cryptomator.integrations.mac.version>
|
<cryptomator.integrations.mac.version>1.1.2</cryptomator.integrations.mac.version>
|
||||||
<cryptomator.integrations.linux.version>1.1.0</cryptomator.integrations.linux.version>
|
<cryptomator.integrations.linux.version>1.1.0</cryptomator.integrations.linux.version>
|
||||||
<cryptomator.fuse.version>1.3.4</cryptomator.fuse.version>
|
<cryptomator.fuse.version>2.0.0-beta1</cryptomator.fuse.version>
|
||||||
<cryptomator.dokany.version>1.3.3</cryptomator.dokany.version>
|
<cryptomator.dokany.version>2.0.0-beta1</cryptomator.dokany.version>
|
||||||
<cryptomator.webdav.version>1.2.8</cryptomator.webdav.version>
|
<cryptomator.webdav.version>2.0.0-beta1</cryptomator.webdav.version>
|
||||||
|
|
||||||
<!-- 3rd party dependencies -->
|
<!-- 3rd party dependencies -->
|
||||||
<commons-lang3.version>3.12.0</commons-lang3.version>
|
<commons-lang3.version>3.12.0</commons-lang3.version>
|
||||||
<dagger.version>2.41</dagger.version>
|
<dagger.version>2.44</dagger.version>
|
||||||
<easybind.version>2.2</easybind.version>
|
<easybind.version>2.2</easybind.version>
|
||||||
<guava.version>31.1-jre</guava.version>
|
<guava.version>31.1-jre</guava.version>
|
||||||
<gson.version>2.9.0</gson.version>
|
<gson.version>2.9.1</gson.version>
|
||||||
<javafx.version>18.0.1</javafx.version>
|
<javafx.version>19</javafx.version>
|
||||||
<jwt.version>4.0.0</jwt.version>
|
<jwt.version>4.2.1</jwt.version>
|
||||||
<nimbus-jose.version>9.23</nimbus-jose.version>
|
<nimbus-jose.version>9.25.4</nimbus-jose.version>
|
||||||
<logback.version>1.4.0</logback.version>
|
<logback.version>1.4.4</logback.version>
|
||||||
<slf4j.version>2.0.0</slf4j.version>
|
<slf4j.version>2.0.3</slf4j.version>
|
||||||
<tinyoauth2.version>0.5.1</tinyoauth2.version>
|
<tinyoauth2.version>0.5.1</tinyoauth2.version>
|
||||||
<zxcvbn.version>1.7.0</zxcvbn.version>
|
<zxcvbn.version>1.7.0</zxcvbn.version>
|
||||||
|
|
||||||
<!-- test dependencies -->
|
<!-- test dependencies -->
|
||||||
<junit.jupiter.version>5.8.1</junit.jupiter.version>
|
<junit.jupiter.version>5.9.1</junit.jupiter.version>
|
||||||
<mockito.version>4.4.0</mockito.version>
|
<mockito.version>4.8.0</mockito.version>
|
||||||
<hamcrest.version>2.2</hamcrest.version>
|
<hamcrest.version>2.2</hamcrest.version>
|
||||||
|
|
||||||
<!-- build-time dependencies -->
|
<!-- build-time dependencies -->
|
||||||
<jetbrains.annotations.version>23.0.0</jetbrains.annotations.version>
|
<jetbrains.annotations.version>23.0.0</jetbrains.annotations.version>
|
||||||
<dependency-check.version>7.1.0</dependency-check.version>
|
<dependency-check.version>7.2.1</dependency-check.version>
|
||||||
<jacoco.version>0.8.7</jacoco.version>
|
<jacoco.version>0.8.8</jacoco.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -263,7 +264,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-resources-plugin</artifactId>
|
<artifactId>maven-resources-plugin</artifactId>
|
||||||
<version>3.2.0</version>
|
<version>3.3.0</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
@@ -273,7 +274,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<version>3.0.0-M5</version>
|
<version>3.0.0-M7</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
@@ -283,7 +284,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-jar-plugin</artifactId>
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
<version>3.2.2</version>
|
<version>3.3.0</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.jacoco</groupId>
|
<groupId>org.jacoco</groupId>
|
||||||
@@ -313,13 +314,14 @@
|
|||||||
<compilerArgs>
|
<compilerArgs>
|
||||||
<arg>-Adagger.fastInit=enabled</arg>
|
<arg>-Adagger.fastInit=enabled</arg>
|
||||||
<arg>-Adagger.formatGeneratedSource=enabled</arg>
|
<arg>-Adagger.formatGeneratedSource=enabled</arg>
|
||||||
|
<arg>--enable-preview</arg>
|
||||||
</compilerArgs>
|
</compilerArgs>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
<artifactId>exec-maven-plugin</artifactId>
|
<artifactId>exec-maven-plugin</artifactId>
|
||||||
<version>3.0.0</version>
|
<version>3.1.0</version>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<id>compile-light-theme</id>
|
<id>compile-light-theme</id>
|
||||||
|
|||||||
@@ -10,19 +10,18 @@ import dagger.Module;
|
|||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
import org.cryptomator.common.keychain.KeychainModule;
|
import org.cryptomator.common.keychain.KeychainModule;
|
||||||
|
import org.cryptomator.common.mount.MountModule;
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.common.settings.SettingsProvider;
|
import org.cryptomator.common.settings.SettingsProvider;
|
||||||
import org.cryptomator.common.vaults.VaultComponent;
|
import org.cryptomator.common.vaults.VaultComponent;
|
||||||
import org.cryptomator.common.vaults.VaultListModule;
|
import org.cryptomator.common.vaults.VaultListModule;
|
||||||
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
||||||
import org.cryptomator.frontend.webdav.WebDavServer;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import javafx.beans.binding.Binding;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.beans.binding.Bindings;
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
@@ -33,7 +32,7 @@ import java.util.concurrent.SynchronousQueue;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
@Module(subcomponents = {VaultComponent.class}, includes = {VaultListModule.class, KeychainModule.class})
|
@Module(subcomponents = {VaultComponent.class}, includes = {VaultListModule.class, KeychainModule.class, MountModule.class})
|
||||||
public abstract class CommonsModule {
|
public abstract class CommonsModule {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(CommonsModule.class);
|
private static final Logger LOG = LoggerFactory.getLogger(CommonsModule.class);
|
||||||
@@ -130,20 +129,11 @@ public abstract class CommonsModule {
|
|||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
static Binding<InetSocketAddress> provideServerSocketAddressBinding(Settings settings) {
|
static ObservableValue<InetSocketAddress> provideServerSocketAddressBinding(Settings settings) {
|
||||||
return Bindings.createObjectBinding(() -> {
|
return settings.port().map(port -> {
|
||||||
String host = SystemUtils.IS_OS_WINDOWS ? "127.0.0.1" : "localhost";
|
String host = SystemUtils.IS_OS_WINDOWS ? "127.0.0.1" : "localhost";
|
||||||
return InetSocketAddress.createUnresolved(host, settings.port().intValue());
|
return InetSocketAddress.createUnresolved(host, settings.port().intValue());
|
||||||
}, settings.port());
|
});
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
static WebDavServer provideWebDavServer(Binding<InetSocketAddress> serverSocketAddressBinding) {
|
|
||||||
WebDavServer server = WebDavServer.create();
|
|
||||||
// no need to unsubscribe eventually, because server is a singleton
|
|
||||||
EasyBind.subscribe(serverSocketAddressBinding, server::bind);
|
|
||||||
return server;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import javafx.beans.binding.BooleanBinding;
|
|||||||
import javafx.beans.binding.StringBinding;
|
import javafx.beans.binding.StringBinding;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@@ -18,7 +19,7 @@ public class LicenseHolder {
|
|||||||
private final Settings settings;
|
private final Settings settings;
|
||||||
private final LicenseChecker licenseChecker;
|
private final LicenseChecker licenseChecker;
|
||||||
private final ObjectProperty<DecodedJWT> validJwtClaims;
|
private final ObjectProperty<DecodedJWT> validJwtClaims;
|
||||||
private final StringBinding licenseSubject;
|
private final ObservableValue<String> licenseSubject;
|
||||||
private final BooleanBinding validLicenseProperty;
|
private final BooleanBinding validLicenseProperty;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@@ -26,7 +27,7 @@ public class LicenseHolder {
|
|||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.licenseChecker = licenseChecker;
|
this.licenseChecker = licenseChecker;
|
||||||
this.validJwtClaims = new SimpleObjectProperty<>();
|
this.validJwtClaims = new SimpleObjectProperty<>();
|
||||||
this.licenseSubject = Bindings.createStringBinding(this::getLicenseSubject, validJwtClaims);
|
this.licenseSubject = validJwtClaims.map(DecodedJWT::getSubject);
|
||||||
this.validLicenseProperty = validJwtClaims.isNotNull();
|
this.validLicenseProperty = validJwtClaims.isNotNull();
|
||||||
|
|
||||||
Optional<DecodedJWT> claims = licenseChecker.check(settings.licenseKey().get());
|
Optional<DecodedJWT> claims = licenseChecker.check(settings.licenseKey().get());
|
||||||
@@ -55,17 +56,12 @@ public class LicenseHolder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringBinding licenseSubjectProperty() {
|
public ObservableValue<String> licenseSubjectProperty() {
|
||||||
return licenseSubject;
|
return licenseSubject;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getLicenseSubject() {
|
public String getLicenseSubject() {
|
||||||
DecodedJWT claims = validJwtClaims.get();
|
return licenseSubject.getValue();
|
||||||
if (claims != null) {
|
|
||||||
return claims.getSubject();
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanBinding validLicenseProperty() {
|
public BooleanBinding validLicenseProperty() {
|
||||||
|
|||||||
@@ -23,11 +23,14 @@ public class KeychainModule {
|
|||||||
@Singleton
|
@Singleton
|
||||||
static ObjectExpression<KeychainAccessProvider> provideKeychainAccessProvider(Settings settings, List<KeychainAccessProvider> providers) {
|
static ObjectExpression<KeychainAccessProvider> provideKeychainAccessProvider(Settings settings, List<KeychainAccessProvider> providers) {
|
||||||
return Bindings.createObjectBinding(() -> {
|
return Bindings.createObjectBinding(() -> {
|
||||||
|
if (!settings.useKeychain().get()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
var selectedProviderClass = settings.keychainProvider().get();
|
var selectedProviderClass = settings.keychainProvider().get();
|
||||||
var selectedProvider = providers.stream().filter(provider -> provider.getClass().getName().equals(selectedProviderClass)).findAny();
|
var selectedProvider = providers.stream().filter(provider -> provider.getClass().getName().equals(selectedProviderClass)).findAny();
|
||||||
var fallbackProvider = providers.stream().findFirst().orElse(null);
|
var fallbackProvider = providers.stream().findFirst().orElse(null);
|
||||||
return selectedProvider.orElse(fallbackProvider);
|
return selectedProvider.orElse(fallbackProvider);
|
||||||
}, settings.keychainProvider());
|
}, settings.keychainProvider(), settings.useKeychain());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package org.cryptomator.common.mount;
|
||||||
|
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
import org.cryptomator.common.settings.Settings;
|
||||||
|
import org.cryptomator.integrations.mount.MountService;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
public class MountModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
static List<MountService> provideSupportedMountServices() {
|
||||||
|
return MountService.get().toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
static ObservableValue<MountService> provideMountService(Settings settings, List<MountService> serviceImpls) {
|
||||||
|
return settings.mountService().map(desiredServiceImpl -> {
|
||||||
|
var fallbackProvider = serviceImpls.stream().findFirst().orElse(null);
|
||||||
|
return serviceImpls.stream().filter(serviceImpl -> serviceImpl.getClass().getName().equals(desiredServiceImpl)).findAny().orElse(fallbackProvider);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the accompanying LICENSE file.
|
||||||
|
*******************************************************************************/
|
||||||
|
package org.cryptomator.common.mount;
|
||||||
|
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.nio.file.FileSystems;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public final class WindowsDriveLetters {
|
||||||
|
|
||||||
|
private static final Set<Path> A_TO_Z;
|
||||||
|
|
||||||
|
static {
|
||||||
|
var sortedSet = new TreeSet<Path>();
|
||||||
|
IntStream.rangeClosed('A', 'Z').mapToObj(i -> Path.of((char) i + ":\\")).forEach(sortedSet::add);
|
||||||
|
A_TO_Z = Collections.unmodifiableSet(sortedSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public WindowsDriveLetters() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Path> getAll() {
|
||||||
|
return A_TO_Z;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Path> getOccupied() {
|
||||||
|
if (!SystemUtils.IS_OS_WINDOWS) {
|
||||||
|
return Set.of();
|
||||||
|
} else {
|
||||||
|
Iterable<Path> rootDirs = FileSystems.getDefault().getRootDirectories();
|
||||||
|
return StreamSupport.stream(rootDirs.spliterator(), false).collect(Collectors.toUnmodifiableSet());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Path> getAvailable() {
|
||||||
|
return Sets.difference(getAll(), getOccupied());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skips A and B and only returns them if all others are occupied.
|
||||||
|
*
|
||||||
|
* @return an Optional containing either the letter of a free drive letter or empty, if none is available
|
||||||
|
*/
|
||||||
|
public Optional<Path> getFirstDesiredAvailable() {
|
||||||
|
var availableDriveLetters = getAvailable();
|
||||||
|
var optString = availableDriveLetters.stream().filter(this::notAOrB).findFirst();
|
||||||
|
return optString.or(() -> availableDriveLetters.stream().findFirst());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean notAOrB(Path driveLetter) {
|
||||||
|
return !(Path.of("A:\\").equals(driveLetter) || Path.of("B:\\").equals(driveLetter));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
package org.cryptomator.common.mountpoint;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.cryptomator.common.vaults.Volume;
|
|
||||||
import org.cryptomator.common.vaults.WindowsDriveLetters;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
class AvailableDriveLetterChooser implements MountPointChooser {
|
|
||||||
|
|
||||||
private final WindowsDriveLetters windowsDriveLetters;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public AvailableDriveLetterChooser(WindowsDriveLetters windowsDriveLetters) {
|
|
||||||
this.windowsDriveLetters = windowsDriveLetters;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isApplicable(Volume caller) {
|
|
||||||
return SystemUtils.IS_OS_WINDOWS;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<Path> chooseMountPoint(Volume caller) {
|
|
||||||
return this.windowsDriveLetters.getDesiredAvailableDriveLetterPath();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package org.cryptomator.common.mountpoint;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
|
||||||
import org.cryptomator.common.vaults.Volume;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import java.nio.file.FileAlreadyExistsException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.LinkOption;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
class CustomDriveLetterChooser implements MountPointChooser {
|
|
||||||
|
|
||||||
private final VaultSettings vaultSettings;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public CustomDriveLetterChooser(VaultSettings vaultSettings) {
|
|
||||||
this.vaultSettings = vaultSettings;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isApplicable(Volume caller) {
|
|
||||||
return SystemUtils.IS_OS_WINDOWS;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<Path> chooseMountPoint(Volume caller) {
|
|
||||||
return this.vaultSettings.getWinDriveLetter().map(letter -> letter.charAt(0) + ":\\").map(Paths::get);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean prepare(Volume caller, Path driveLetter) throws InvalidMountPointException {
|
|
||||||
if (!Files.notExists(driveLetter, LinkOption.NOFOLLOW_LINKS)) {
|
|
||||||
//Drive already exists OR can't be determined
|
|
||||||
throw new InvalidMountPointException(new FileAlreadyExistsException(driveLetter.toString()));
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
package org.cryptomator.common.mountpoint;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.cryptomator.common.Environment;
|
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
|
||||||
import org.cryptomator.common.settings.VolumeImpl;
|
|
||||||
import org.cryptomator.common.vaults.MountPointRequirement;
|
|
||||||
import org.cryptomator.common.vaults.Volume;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.DirectoryNotEmptyException;
|
|
||||||
import java.nio.file.FileAlreadyExistsException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.LinkOption;
|
|
||||||
import java.nio.file.NoSuchFileException;
|
|
||||||
import java.nio.file.NotDirectoryException;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
class CustomMountPointChooser implements MountPointChooser {
|
|
||||||
|
|
||||||
private static final String HIDEAWAY_PREFIX = ".~$";
|
|
||||||
private static final String HIDEAWAY_SUFFIX = ".tmp";
|
|
||||||
private static final String WIN_HIDDEN = "dos:hidden";
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(CustomMountPointChooser.class);
|
|
||||||
|
|
||||||
private final VaultSettings vaultSettings;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public CustomMountPointChooser(VaultSettings vaultSettings) {
|
|
||||||
this.vaultSettings = vaultSettings;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isApplicable(Volume caller) {
|
|
||||||
return caller.getImplementationType() != VolumeImpl.WEBDAV;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<Path> chooseMountPoint(Volume caller) {
|
|
||||||
//VaultSettings#getCustomMountPath already checks whether the saved custom mountpoint should be used
|
|
||||||
return this.vaultSettings.getCustomMountPath().map(Paths::get);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean prepare(Volume caller, Path mountPoint) throws InvalidMountPointException {
|
|
||||||
return switch (caller.getMountPointRequirement()) {
|
|
||||||
case PARENT_NO_MOUNT_POINT -> {
|
|
||||||
prepareParentNoMountPoint(mountPoint);
|
|
||||||
LOG.debug("Successfully checked custom mount point: {}", mountPoint);
|
|
||||||
yield true;
|
|
||||||
}
|
|
||||||
case EMPTY_MOUNT_POINT -> {
|
|
||||||
prepareEmptyMountPoint(mountPoint);
|
|
||||||
LOG.debug("Successfully checked custom mount point: {}", mountPoint);
|
|
||||||
yield false;
|
|
||||||
}
|
|
||||||
case NONE, UNUSED_ROOT_DIR, PARENT_OPT_MOUNT_POINT -> {
|
|
||||||
throw new InvalidMountPointException(new IllegalStateException("Illegal MountPointRequirement"));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
//This is case on Windows when using FUSE
|
|
||||||
//See https://github.com/billziss-gh/winfsp/issues/320
|
|
||||||
void prepareParentNoMountPoint(Path mountPoint) throws InvalidMountPointException {
|
|
||||||
Path hideaway = getHideaway(mountPoint);
|
|
||||||
var mpExists = Files.exists(mountPoint, LinkOption.NOFOLLOW_LINKS);
|
|
||||||
var hideExists = Files.exists(hideaway, LinkOption.NOFOLLOW_LINKS);
|
|
||||||
|
|
||||||
//TODO: possible improvement by just deleting an _empty_ hideaway
|
|
||||||
if (mpExists && hideExists) { //both resources exist (whatever type)
|
|
||||||
throw new InvalidMountPointException(new FileAlreadyExistsException(hideaway.toString()));
|
|
||||||
} else if (!mpExists && !hideExists) { //neither mountpoint nor hideaway exist
|
|
||||||
throw new InvalidMountPointException(new NoSuchFileException(mountPoint.toString()));
|
|
||||||
} else if (!mpExists) { //only hideaway exists
|
|
||||||
checkIsDirectory(hideaway);
|
|
||||||
LOG.info("Mountpoint {} for winfsp mount seems to be not properly cleaned up. Will be fixed on unmount.", mountPoint);
|
|
||||||
try {
|
|
||||||
if (SystemUtils.IS_OS_WINDOWS) {
|
|
||||||
Files.setAttribute(hideaway, WIN_HIDDEN, true, LinkOption.NOFOLLOW_LINKS);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new InvalidMountPointException(e);
|
|
||||||
}
|
|
||||||
} else { //only mountpoint exists
|
|
||||||
try {
|
|
||||||
checkIsDirectory(mountPoint);
|
|
||||||
checkIsEmpty(mountPoint);
|
|
||||||
|
|
||||||
Files.move(mountPoint, hideaway);
|
|
||||||
if (SystemUtils.IS_OS_WINDOWS) {
|
|
||||||
Files.setAttribute(hideaway, WIN_HIDDEN, true, LinkOption.NOFOLLOW_LINKS);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new InvalidMountPointException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void prepareEmptyMountPoint(Path mountPoint) throws InvalidMountPointException {
|
|
||||||
//This is the case for Windows when using Dokany and for Linux and Mac
|
|
||||||
checkIsDirectory(mountPoint);
|
|
||||||
try {
|
|
||||||
checkIsEmpty(mountPoint);
|
|
||||||
} catch (IOException exception) {
|
|
||||||
throw new InvalidMountPointException("IOException while checking folder content", exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void cleanup(Volume caller, Path mountPoint) {
|
|
||||||
if (caller.getMountPointRequirement() == MountPointRequirement.PARENT_NO_MOUNT_POINT) {
|
|
||||||
Path hideaway = getHideaway(mountPoint);
|
|
||||||
try {
|
|
||||||
Files.move(hideaway, mountPoint);
|
|
||||||
if (SystemUtils.IS_OS_WINDOWS) {
|
|
||||||
Files.setAttribute(mountPoint, WIN_HIDDEN, false);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.error("Unable to clean up mountpoint {} for Winfsp mounting.", mountPoint, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkIsDirectory(Path toCheck) throws InvalidMountPointException {
|
|
||||||
if (!Files.isDirectory(toCheck, LinkOption.NOFOLLOW_LINKS)) {
|
|
||||||
throw new InvalidMountPointException(new NotDirectoryException(toCheck.toString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkIsEmpty(Path toCheck) throws InvalidMountPointException, IOException {
|
|
||||||
try (var dirStream = Files.list(toCheck)) {
|
|
||||||
if (dirStream.findFirst().isPresent()) {
|
|
||||||
throw new InvalidMountPointException(new DirectoryNotEmptyException(toCheck.toString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//visible for testing
|
|
||||||
Path getHideaway(Path mountPoint) {
|
|
||||||
return mountPoint.resolveSibling(HIDEAWAY_PREFIX + mountPoint.getFileName().toString() + HIDEAWAY_SUFFIX);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package org.cryptomator.common.mountpoint;
|
|
||||||
|
|
||||||
public class InvalidMountPointException extends Exception {
|
|
||||||
|
|
||||||
public InvalidMountPointException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public InvalidMountPointException(Throwable cause) {
|
|
||||||
super(cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public InvalidMountPointException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package org.cryptomator.common.mountpoint;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
|
||||||
import org.cryptomator.common.vaults.Volume;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
class MacVolumeMountChooser implements MountPointChooser {
|
|
||||||
|
|
||||||
private static final Path VOLUME_PATH = Path.of("/Volumes");
|
|
||||||
|
|
||||||
private final VaultSettings vaultSettings;
|
|
||||||
private final MountPointHelper helper;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public MacVolumeMountChooser(VaultSettings vaultSettings, MountPointHelper helper) {
|
|
||||||
this.vaultSettings = vaultSettings;
|
|
||||||
this.helper = helper;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isApplicable(Volume caller) {
|
|
||||||
return SystemUtils.IS_OS_MAC;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<Path> chooseMountPoint(Volume caller) {
|
|
||||||
return Optional.of(helper.chooseTemporaryMountPoint(vaultSettings, VOLUME_PATH));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean prepare(Volume caller, Path mountPoint) {
|
|
||||||
// https://github.com/osxfuse/osxfuse/issues/306#issuecomment-245114592:
|
|
||||||
// In order to allow non-admin users to mount FUSE volumes in `/Volumes`,
|
|
||||||
// starting with version 3.5.0, FUSE will create non-existent mount points automatically.
|
|
||||||
// Therefore we don't need to prepare anything.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
package org.cryptomator.common.mountpoint;
|
|
||||||
|
|
||||||
import dagger.multibindings.IntKey;
|
|
||||||
import org.cryptomator.common.vaults.Volume;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.SortedSet;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base interface for the Mountpoint-Choosing-Operation that results in the choice and
|
|
||||||
* preparation of a mountpoint or an exception otherwise.<br>
|
|
||||||
* <p>All <i>MountPointChoosers (MPCs)</i> need to implement this class and must be added to
|
|
||||||
* the pool of possible MPCs by the {@link MountPointChooserModule MountPointChooserModule.}
|
|
||||||
* The MountPointChooserModule will sort them according to their {@link IntKey IntKey priority.}
|
|
||||||
* The priority must be defined by the developer to reflect a useful execution order.<br>
|
|
||||||
* A specific priority <b>must not</b> be assigned to more than one MPC at a time;
|
|
||||||
* the result of having two MPCs with equal priority is undefined.
|
|
||||||
*
|
|
||||||
* <p>MPCs are executed by a {@link Volume} in descending order of their priority
|
|
||||||
* (higher priorities are tried first) to find and prepare a suitable mountpoint for the volume.
|
|
||||||
* The volume has access to a {@link SortedSet} of MPCs in this specific order,
|
|
||||||
* that is provided by the Module. The Set contains all available Choosers, even if they
|
|
||||||
* are not {@link #isApplicable(Volume) applicable} for the Vault/Volume. The Volume must
|
|
||||||
* check whether a MPC is applicable by invoking {@code #isApplicable(Volume)} on it
|
|
||||||
* <i>before</i> calling {@code #chooseMountPoint(Volume)}.
|
|
||||||
*
|
|
||||||
* <p>At execution of a MPC {@link #chooseMountPoint(Volume)} is called to choose a mountpoint
|
|
||||||
* according to the MPC's <i>strategy.</i> The <i>strategy</i> can involve reading configs,
|
|
||||||
* searching the filesystem, processing user-input or similar operations.
|
|
||||||
* If {@code #chooseMountPoint(Volume)} returns a non-null path (everything but
|
|
||||||
* {@linkplain Optional#empty()}) the MPC's {@link #prepare(Volume, Path)} method is called and the
|
|
||||||
* MountPoint is verified and/or prepared. In this case <i>no other MPC's will be called for
|
|
||||||
* this volume, even if {@code #prepare(Volume, Path)} fails.</i>
|
|
||||||
*
|
|
||||||
* <p>If {@code #chooseMountPoint(Volume)} yields no result, the next MPC is executed
|
|
||||||
* <i>without</i> first calling the {@code #prepare(Volume, Path)} method of the current MPC.
|
|
||||||
* This is repeated until<br>
|
|
||||||
* <ul>
|
|
||||||
* <li><b>either</b> a mountpoint is returned by {@code #chooseMountPoint(Volume)}
|
|
||||||
* and {@code #prepare(Volume, Path)} succeeds or fails, ending the entire operation</li>
|
|
||||||
* <li><b>or</b> no MPC remains and an {@link InvalidMountPointException} is thrown.</li>
|
|
||||||
* </ul>
|
|
||||||
* If the {@code #prepare(Volume, Path)} method of a MPC fails, the entire
|
|
||||||
* Mountpoint-Choosing-Operation is aborted and the method should do all necessary cleanup
|
|
||||||
* before throwing the exception.
|
|
||||||
* If the preparation succeeds {@link #cleanup(Volume, Path)} can be used after unmount to do any
|
|
||||||
* remaining cleanup.
|
|
||||||
*/
|
|
||||||
public interface MountPointChooser {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by the {@link Volume} to determine whether this MountPointChooser is
|
|
||||||
* applicable for mounting the Vault/Volume, especially with regard to the
|
|
||||||
* current system configuration and particularities of the Volume type.
|
|
||||||
*
|
|
||||||
* <p>Developers should override this method to check for system configurations
|
|
||||||
* that are unsuitable for this MPC.
|
|
||||||
*
|
|
||||||
* @param caller The Volume that is calling the method to determine applicability of the MPC
|
|
||||||
* @return a boolean flag; true if applicable, else false.
|
|
||||||
* @see #chooseMountPoint(Volume)
|
|
||||||
*/
|
|
||||||
boolean isApplicable(Volume caller);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by a {@link Volume} to choose a mountpoint according to the
|
|
||||||
* MountPointChoosers strategy.
|
|
||||||
*
|
|
||||||
* <p>This method must only be called for MPCs that were deemed
|
|
||||||
* {@link #isApplicable(Volume) applicable} by the {@link Volume Volume.}
|
|
||||||
* Developers should override this method to find or extract a mountpoint for
|
|
||||||
* the volume <b>without</b> preparing it. Preparation should be done by
|
|
||||||
* {@link #prepare(Volume, Path)} instead.
|
|
||||||
* Exceptions in this method should be handled gracefully and result in returning
|
|
||||||
* {@link Optional#empty()} instead of throwing an exception.
|
|
||||||
*
|
|
||||||
* @param caller The Volume that is calling the method to choose a mountpoint
|
|
||||||
* @return the chosen path or {@link Optional#empty()} if an exception occurred
|
|
||||||
* or no mountpoint could be found.
|
|
||||||
* @see #isApplicable(Volume)
|
|
||||||
* @see #prepare(Volume, Path)
|
|
||||||
*/
|
|
||||||
Optional<Path> chooseMountPoint(Volume caller);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by a {@link Volume} to prepare and/or verify the chosen mountpoint.<br>
|
|
||||||
* This method is only called if the {@link #chooseMountPoint(Volume)} method
|
|
||||||
* of the same MountPointChooser returned a path.
|
|
||||||
*
|
|
||||||
* <p>Developers should override this method to prepare the mountpoint for
|
|
||||||
* the volume and check for any obstacles that could hinder the mount operation.
|
|
||||||
* The mountpoint is deemed "prepared" if it can be used to mount a volume
|
|
||||||
* without any further filesystem actions or user interaction. If this is not possible,
|
|
||||||
* this method should fail. In other words: This method should not return without
|
|
||||||
* either failing or finalizing the preparation of the mountpoint.
|
|
||||||
* Generally speaking exceptions should be wrapped as
|
|
||||||
* {@link InvalidMountPointException} to allow efficient handling by the caller.
|
|
||||||
*
|
|
||||||
* <p>Often the preparation of a mountpoint involves creating files or others
|
|
||||||
* actions that require cleaning up after the volume is unmounted.
|
|
||||||
* In this case developers should override the {@link #cleanup(Volume, Path)}
|
|
||||||
* method and return {@code true} to the volume to indicate that the
|
|
||||||
* {@code #cleanup} method of this MPC should be called after unmount.
|
|
||||||
*
|
|
||||||
* <p><b>Please note:</b> If this method fails the entire
|
|
||||||
* Mountpoint-Choosing-Operation is aborted without calling
|
|
||||||
* {@link #cleanup(Volume, Path)} or any other MPCs. Therefore this method should
|
|
||||||
* do all necessary cleanup before throwing the exception.
|
|
||||||
*
|
|
||||||
* @param caller The Volume that is calling the method to prepare a mountpoint
|
|
||||||
* @param mountPoint the mountpoint chosen by {@link #chooseMountPoint(Volume)}
|
|
||||||
* @return a boolean flag; true if cleanup is needed, false otherwise
|
|
||||||
* @throws InvalidMountPointException if the preparation fails
|
|
||||||
* @see #chooseMountPoint(Volume)
|
|
||||||
* @see #cleanup(Volume, Path)
|
|
||||||
*/
|
|
||||||
default boolean prepare(Volume caller, Path mountPoint) throws InvalidMountPointException {
|
|
||||||
return false; //NO-OP
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by a {@link Volume} to do any cleanup needed after unmount.
|
|
||||||
*
|
|
||||||
* <p>This method is only called if the {@link #prepare(Volume, Path)} method
|
|
||||||
* of the same MountPointChooser returned {@code true}. Typically developers want to
|
|
||||||
* delete any files created prior to mount or do similar tasks.<br>
|
|
||||||
* Exceptions in this method should be handled gracefully.
|
|
||||||
*
|
|
||||||
* @param caller The Volume that is calling the method to cleanup the prepared mountpoint
|
|
||||||
* @param mountPoint the mountpoint that was prepared by {@link #prepare(Volume, Path)}
|
|
||||||
* @see #prepare(Volume, Path)
|
|
||||||
*/
|
|
||||||
default void cleanup(Volume caller, Path mountPoint) {
|
|
||||||
//NO-OP
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
package org.cryptomator.common.mountpoint;
|
|
||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
|
||||||
import dagger.Binds;
|
|
||||||
import dagger.Module;
|
|
||||||
import dagger.Provides;
|
|
||||||
import dagger.multibindings.IntKey;
|
|
||||||
import dagger.multibindings.IntoMap;
|
|
||||||
import org.cryptomator.common.vaults.PerVault;
|
|
||||||
|
|
||||||
import javax.inject.Named;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.SortedMap;
|
|
||||||
import java.util.TreeMap;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dagger-Module for {@link MountPointChooser MountPointChoosers.}<br>
|
|
||||||
* See there for additional information.
|
|
||||||
*
|
|
||||||
* @see MountPointChooser
|
|
||||||
*/
|
|
||||||
@Module
|
|
||||||
public abstract class MountPointChooserModule {
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
@IntoMap
|
|
||||||
@IntKey(1000)
|
|
||||||
@PerVault
|
|
||||||
public abstract MountPointChooser bindCustomMountPointChooser(CustomMountPointChooser chooser);
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
@IntoMap
|
|
||||||
@IntKey(900)
|
|
||||||
@PerVault
|
|
||||||
public abstract MountPointChooser bindCustomDriveLetterChooser(CustomDriveLetterChooser chooser);
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
@IntoMap
|
|
||||||
@IntKey(800)
|
|
||||||
@PerVault
|
|
||||||
public abstract MountPointChooser bindAvailableDriveLetterChooser(AvailableDriveLetterChooser chooser);
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
@IntoMap
|
|
||||||
@IntKey(101)
|
|
||||||
@PerVault
|
|
||||||
public abstract MountPointChooser bindMacVolumeMountChooser(MacVolumeMountChooser chooser);
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
@IntoMap
|
|
||||||
@IntKey(100)
|
|
||||||
@PerVault
|
|
||||||
public abstract MountPointChooser bindTemporaryMountPointChooser(TemporaryMountPointChooser chooser);
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@PerVault
|
|
||||||
@Named("orderedMountPointChoosers")
|
|
||||||
public static Iterable<MountPointChooser> provideOrderedMountPointChoosers(Map<Integer, MountPointChooser> choosers) {
|
|
||||||
SortedMap<Integer, MountPointChooser> sortedChoosers = new TreeMap<>(Comparator.reverseOrder());
|
|
||||||
sortedChoosers.putAll(choosers);
|
|
||||||
return Iterables.unmodifiableIterable(sortedChoosers.values());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
package org.cryptomator.common.mountpoint;
|
|
||||||
|
|
||||||
import org.cryptomator.common.Environment;
|
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.DirectoryNotEmptyException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.LinkOption;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
class MountPointHelper {
|
|
||||||
|
|
||||||
public static Logger LOG = LoggerFactory.getLogger(MountPointHelper.class);
|
|
||||||
private static final int MAX_TMPMOUNTPOINT_CREATION_RETRIES = 10;
|
|
||||||
|
|
||||||
private final Optional<Path> tmpMountPointDir;
|
|
||||||
private volatile boolean unmountDebrisCleared = false;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public MountPointHelper(Environment env) {
|
|
||||||
this.tmpMountPointDir = env.getMountPointsDir();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Path chooseTemporaryMountPoint(VaultSettings vaultSettings, Path parentDir) {
|
|
||||||
String basename = vaultSettings.mountName().get();
|
|
||||||
//regular
|
|
||||||
Path mountPoint = parentDir.resolve(basename);
|
|
||||||
if (Files.notExists(mountPoint)) {
|
|
||||||
return mountPoint;
|
|
||||||
}
|
|
||||||
//with id
|
|
||||||
mountPoint = parentDir.resolve(basename + " (" + vaultSettings.getId() + ")");
|
|
||||||
if (Files.notExists(mountPoint)) {
|
|
||||||
return mountPoint;
|
|
||||||
}
|
|
||||||
//with id and count
|
|
||||||
for (int i = 1; i < MAX_TMPMOUNTPOINT_CREATION_RETRIES; i++) {
|
|
||||||
mountPoint = parentDir.resolve(basename + "_(" + vaultSettings.getId() + ")_" + i);
|
|
||||||
if (Files.notExists(mountPoint)) {
|
|
||||||
return mountPoint;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LOG.error("Failed to find feasible mountpoint at {}{}{}_x. Giving up after {} attempts.", parentDir, File.separator, basename, MAX_TMPMOUNTPOINT_CREATION_RETRIES);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void clearIrregularUnmountDebrisIfNeeded() {
|
|
||||||
if (unmountDebrisCleared || tmpMountPointDir.isEmpty()) {
|
|
||||||
return; // nothing to do
|
|
||||||
}
|
|
||||||
if (Files.exists(tmpMountPointDir.get(), LinkOption.NOFOLLOW_LINKS)) {
|
|
||||||
clearIrregularUnmountDebris(tmpMountPointDir.get());
|
|
||||||
}
|
|
||||||
unmountDebrisCleared = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void clearIrregularUnmountDebris(Path dirContainingMountPoints) {
|
|
||||||
IOException cleanupFailed = new IOException("Cleanup failed");
|
|
||||||
|
|
||||||
try (var ds = Files.newDirectoryStream(dirContainingMountPoints)) {
|
|
||||||
LOG.debug("Performing cleanup of mountpoint dir {}.", dirContainingMountPoints);
|
|
||||||
for (Path p : ds) {
|
|
||||||
try {
|
|
||||||
var attr = Files.readAttributes(p, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
|
|
||||||
if (attr.isOther() && attr.isDirectory()) { // yes, this is possible with windows junction points -.-
|
|
||||||
Files.delete(p);
|
|
||||||
} else if (attr.isDirectory()) {
|
|
||||||
deleteEmptyDir(p);
|
|
||||||
} else if (attr.isSymbolicLink()) {
|
|
||||||
deleteDeadLink(p);
|
|
||||||
} else {
|
|
||||||
LOG.debug("Found non-directory element in mountpoint dir: {}", p);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
cleanupFailed.addSuppressed(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cleanupFailed.getSuppressed().length > 0) {
|
|
||||||
throw cleanupFailed;
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.warn("Unable to perform cleanup of mountpoint dir {}.", dirContainingMountPoints, e);
|
|
||||||
} finally {
|
|
||||||
unmountDebrisCleared = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deleteEmptyDir(Path dir) throws IOException {
|
|
||||||
assert Files.isDirectory(dir, LinkOption.NOFOLLOW_LINKS);
|
|
||||||
try {
|
|
||||||
ensureIsEmpty(dir);
|
|
||||||
Files.delete(dir); // attempt to delete dir non-recursively (will fail, if there are contents)
|
|
||||||
} catch (DirectoryNotEmptyException e) {
|
|
||||||
LOG.info("Found non-empty directory in mountpoint dir: {}", dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deleteDeadLink(Path symlink) throws IOException {
|
|
||||||
assert Files.isSymbolicLink(symlink);
|
|
||||||
if (Files.notExists(symlink)) { // following link: target does not exist
|
|
||||||
Files.delete(symlink);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ensureIsEmpty(Path dir) throws IOException {
|
|
||||||
try (var ds = Files.newDirectoryStream(dir)) {
|
|
||||||
if (ds.iterator().hasNext()){
|
|
||||||
throw new DirectoryNotEmptyException(dir.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
package org.cryptomator.common.mountpoint;
|
|
||||||
|
|
||||||
import org.cryptomator.common.Environment;
|
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
|
||||||
import org.cryptomator.common.vaults.Volume;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
class TemporaryMountPointChooser implements MountPointChooser {
|
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(TemporaryMountPointChooser.class);
|
|
||||||
|
|
||||||
private final VaultSettings vaultSettings;
|
|
||||||
private final Environment environment;
|
|
||||||
private final MountPointHelper helper;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public TemporaryMountPointChooser(VaultSettings vaultSettings, Environment environment, MountPointHelper helper) {
|
|
||||||
this.vaultSettings = vaultSettings;
|
|
||||||
this.environment = environment;
|
|
||||||
this.helper = helper;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isApplicable(Volume caller) {
|
|
||||||
if (this.environment.getMountPointsDir().isEmpty()) {
|
|
||||||
LOG.warn("\"cryptomator.mountPointsDir\" is not set to a valid path!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<Path> chooseMountPoint(Volume caller) {
|
|
||||||
assert environment.getMountPointsDir().isPresent();
|
|
||||||
//clean leftovers of not-regularly unmounted vaults
|
|
||||||
//see https://github.com/cryptomator/cryptomator/issues/1013 and https://github.com/cryptomator/cryptomator/issues/1061
|
|
||||||
helper.clearIrregularUnmountDebrisIfNeeded();
|
|
||||||
return this.environment.getMountPointsDir().map(dir -> this.helper.chooseTemporaryMountPoint(this.vaultSettings, dir));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean prepare(Volume caller, Path mountPoint) throws InvalidMountPointException {
|
|
||||||
try {
|
|
||||||
switch (caller.getMountPointRequirement()) {
|
|
||||||
case PARENT_NO_MOUNT_POINT -> {
|
|
||||||
Files.createDirectories(mountPoint.getParent());
|
|
||||||
LOG.debug("Successfully created folder for mount point: {}", mountPoint);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
case EMPTY_MOUNT_POINT -> {
|
|
||||||
Files.createDirectories(mountPoint);
|
|
||||||
LOG.debug("Successfully created mount point: {}", mountPoint);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
case NONE -> {
|
|
||||||
//Requirement "NONE" doesn't make any sense here.
|
|
||||||
//No need to prepare/verify a Mountpoint without requiring one...
|
|
||||||
throw new InvalidMountPointException(new IllegalStateException("Illegal MountPointRequirement"));
|
|
||||||
}
|
|
||||||
default -> {
|
|
||||||
//Currently the case for "UNUSED_ROOT_DIR, PARENT_OPT_MOUNT_POINT"
|
|
||||||
throw new InvalidMountPointException(new IllegalStateException("Not implemented"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException exception) {
|
|
||||||
throw new InvalidMountPointException("IOException while preparing mountpoint", exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void cleanup(Volume caller, Path mountPoint) {
|
|
||||||
try {
|
|
||||||
Files.delete(mountPoint);
|
|
||||||
LOG.debug("Successfully deleted mount point: {}", mountPoint);
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.warn("Could not delete mount point: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -33,11 +33,10 @@ public class Settings {
|
|||||||
public static final boolean DEFAULT_CHECK_FOR_UPDATES = false;
|
public static final boolean DEFAULT_CHECK_FOR_UPDATES = false;
|
||||||
public static final boolean DEFAULT_START_HIDDEN = false;
|
public static final boolean DEFAULT_START_HIDDEN = false;
|
||||||
public static final boolean DEFAULT_AUTO_CLOSE_VAULTS = 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_PORT = 42427;
|
||||||
public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
|
public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
|
||||||
public static final WebDavUrlScheme DEFAULT_GVFS_SCHEME = WebDavUrlScheme.DAV;
|
|
||||||
public static final boolean DEFAULT_DEBUG_MODE = false;
|
public static final boolean DEFAULT_DEBUG_MODE = false;
|
||||||
public static final VolumeImpl DEFAULT_PREFERRED_VOLUME_IMPL = VolumeImpl.FUSE;
|
|
||||||
public static final UiTheme DEFAULT_THEME = UiTheme.LIGHT;
|
public static final UiTheme DEFAULT_THEME = UiTheme.LIGHT;
|
||||||
@Deprecated // to be changed to "whatever is available" eventually
|
@Deprecated // to be changed to "whatever is available" eventually
|
||||||
public static final String DEFAULT_KEYCHAIN_PROVIDER = SystemUtils.IS_OS_WINDOWS ? "org.cryptomator.windows.keychain.WindowsProtectedKeychainAccess" : SystemUtils.IS_OS_MAC ? "org.cryptomator.macos.keychain.MacSystemKeychainAccess" : "org.cryptomator.linux.keychain.SecretServiceKeychainAccess";
|
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";
|
||||||
@@ -53,11 +52,10 @@ public class Settings {
|
|||||||
private final BooleanProperty checkForUpdates = new SimpleBooleanProperty(DEFAULT_CHECK_FOR_UPDATES);
|
private final BooleanProperty checkForUpdates = new SimpleBooleanProperty(DEFAULT_CHECK_FOR_UPDATES);
|
||||||
private final BooleanProperty startHidden = new SimpleBooleanProperty(DEFAULT_START_HIDDEN);
|
private final BooleanProperty startHidden = new SimpleBooleanProperty(DEFAULT_START_HIDDEN);
|
||||||
private final BooleanProperty autoCloseVaults = new SimpleBooleanProperty(DEFAULT_AUTO_CLOSE_VAULTS);
|
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 port = new SimpleIntegerProperty(DEFAULT_PORT);
|
||||||
private final IntegerProperty numTrayNotifications = new SimpleIntegerProperty(DEFAULT_NUM_TRAY_NOTIFICATIONS);
|
private final IntegerProperty numTrayNotifications = new SimpleIntegerProperty(DEFAULT_NUM_TRAY_NOTIFICATIONS);
|
||||||
private final ObjectProperty<WebDavUrlScheme> preferredGvfsScheme = new SimpleObjectProperty<>(DEFAULT_GVFS_SCHEME);
|
|
||||||
private final BooleanProperty debugMode = new SimpleBooleanProperty(DEFAULT_DEBUG_MODE);
|
private final BooleanProperty debugMode = new SimpleBooleanProperty(DEFAULT_DEBUG_MODE);
|
||||||
private final ObjectProperty<VolumeImpl> preferredVolumeImpl = new SimpleObjectProperty<>(DEFAULT_PREFERRED_VOLUME_IMPL);
|
|
||||||
private final ObjectProperty<UiTheme> theme = new SimpleObjectProperty<>(DEFAULT_THEME);
|
private final ObjectProperty<UiTheme> theme = new SimpleObjectProperty<>(DEFAULT_THEME);
|
||||||
private final ObjectProperty<String> keychainProvider = new SimpleObjectProperty<>(DEFAULT_KEYCHAIN_PROVIDER);
|
private final ObjectProperty<String> keychainProvider = new SimpleObjectProperty<>(DEFAULT_KEYCHAIN_PROVIDER);
|
||||||
private final ObjectProperty<NodeOrientation> userInterfaceOrientation = new SimpleObjectProperty<>(DEFAULT_USER_INTERFACE_ORIENTATION);
|
private final ObjectProperty<NodeOrientation> userInterfaceOrientation = new SimpleObjectProperty<>(DEFAULT_USER_INTERFACE_ORIENTATION);
|
||||||
@@ -72,6 +70,9 @@ public class Settings {
|
|||||||
private final StringProperty language = new SimpleStringProperty(DEFAULT_LANGUAGE);
|
private final StringProperty language = new SimpleStringProperty(DEFAULT_LANGUAGE);
|
||||||
|
|
||||||
|
|
||||||
|
private final StringProperty mountService = new SimpleStringProperty();
|
||||||
|
|
||||||
|
|
||||||
private Consumer<Settings> saveCmd;
|
private Consumer<Settings> saveCmd;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -85,11 +86,10 @@ public class Settings {
|
|||||||
checkForUpdates.addListener(this::somethingChanged);
|
checkForUpdates.addListener(this::somethingChanged);
|
||||||
startHidden.addListener(this::somethingChanged);
|
startHidden.addListener(this::somethingChanged);
|
||||||
autoCloseVaults.addListener(this::somethingChanged);
|
autoCloseVaults.addListener(this::somethingChanged);
|
||||||
|
useKeychain.addListener(this::somethingChanged);
|
||||||
port.addListener(this::somethingChanged);
|
port.addListener(this::somethingChanged);
|
||||||
numTrayNotifications.addListener(this::somethingChanged);
|
numTrayNotifications.addListener(this::somethingChanged);
|
||||||
preferredGvfsScheme.addListener(this::somethingChanged);
|
|
||||||
debugMode.addListener(this::somethingChanged);
|
debugMode.addListener(this::somethingChanged);
|
||||||
preferredVolumeImpl.addListener(this::somethingChanged);
|
|
||||||
theme.addListener(this::somethingChanged);
|
theme.addListener(this::somethingChanged);
|
||||||
keychainProvider.addListener(this::somethingChanged);
|
keychainProvider.addListener(this::somethingChanged);
|
||||||
userInterfaceOrientation.addListener(this::somethingChanged);
|
userInterfaceOrientation.addListener(this::somethingChanged);
|
||||||
@@ -102,6 +102,7 @@ public class Settings {
|
|||||||
windowHeight.addListener(this::somethingChanged);
|
windowHeight.addListener(this::somethingChanged);
|
||||||
displayConfiguration.addListener(this::somethingChanged);
|
displayConfiguration.addListener(this::somethingChanged);
|
||||||
language.addListener(this::somethingChanged);
|
language.addListener(this::somethingChanged);
|
||||||
|
mountService.addListener(this::somethingChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSaveCmd(Consumer<Settings> saveCmd) {
|
void setSaveCmd(Consumer<Settings> saveCmd) {
|
||||||
@@ -140,6 +141,8 @@ public class Settings {
|
|||||||
return autoCloseVaults;
|
return autoCloseVaults;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BooleanProperty useKeychain() { return useKeychain; }
|
||||||
|
|
||||||
public IntegerProperty port() {
|
public IntegerProperty port() {
|
||||||
return port;
|
return port;
|
||||||
}
|
}
|
||||||
@@ -148,16 +151,12 @@ public class Settings {
|
|||||||
return numTrayNotifications;
|
return numTrayNotifications;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObjectProperty<WebDavUrlScheme> preferredGvfsScheme() {
|
|
||||||
return preferredGvfsScheme;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BooleanProperty debugMode() {
|
public BooleanProperty debugMode() {
|
||||||
return debugMode;
|
return debugMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObjectProperty<VolumeImpl> preferredVolumeImpl() {
|
public StringProperty mountService() {
|
||||||
return preferredVolumeImpl;
|
return mountService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObjectProperty<UiTheme> theme() {
|
public ObjectProperty<UiTheme> theme() {
|
||||||
@@ -205,4 +204,5 @@ public class Settings {
|
|||||||
public StringProperty languageProperty() {
|
public StringProperty languageProperty() {
|
||||||
return language;
|
return language;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,12 +44,11 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
out.name("autoCloseVaults").value(value.autoCloseVaults().get());
|
out.name("autoCloseVaults").value(value.autoCloseVaults().get());
|
||||||
out.name("port").value(value.port().get());
|
out.name("port").value(value.port().get());
|
||||||
out.name("numTrayNotifications").value(value.numTrayNotifications().get());
|
out.name("numTrayNotifications").value(value.numTrayNotifications().get());
|
||||||
out.name("preferredGvfsScheme").value(value.preferredGvfsScheme().get().name());
|
|
||||||
out.name("debugMode").value(value.debugMode().get());
|
out.name("debugMode").value(value.debugMode().get());
|
||||||
out.name("preferredVolumeImpl").value(value.preferredVolumeImpl().get().name());
|
|
||||||
out.name("theme").value(value.theme().get().name());
|
out.name("theme").value(value.theme().get().name());
|
||||||
out.name("uiOrientation").value(value.userInterfaceOrientation().get().name());
|
out.name("uiOrientation").value(value.userInterfaceOrientation().get().name());
|
||||||
out.name("keychainProvider").value(value.keychainProvider().get());
|
out.name("keychainProvider").value(value.keychainProvider().get());
|
||||||
|
out.name("useKeychain").value(value.useKeychain().get());
|
||||||
out.name("licenseKey").value(value.licenseKey().get());
|
out.name("licenseKey").value(value.licenseKey().get());
|
||||||
out.name("showMinimizeButton").value(value.showMinimizeButton().get());
|
out.name("showMinimizeButton").value(value.showMinimizeButton().get());
|
||||||
out.name("showTrayIcon").value(value.showTrayIcon().get());
|
out.name("showTrayIcon").value(value.showTrayIcon().get());
|
||||||
@@ -59,7 +58,7 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
out.name("windowHeight").value((value.windowHeightProperty().get()));
|
out.name("windowHeight").value((value.windowHeightProperty().get()));
|
||||||
out.name("displayConfiguration").value((value.displayConfigurationProperty().get()));
|
out.name("displayConfiguration").value((value.displayConfigurationProperty().get()));
|
||||||
out.name("language").value((value.languageProperty().get()));
|
out.name("language").value((value.languageProperty().get()));
|
||||||
|
out.name("mountService").value(value.mountService().get());
|
||||||
out.endObject();
|
out.endObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,12 +85,11 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
case "autoCloseVaults" -> settings.autoCloseVaults().set(in.nextBoolean());
|
case "autoCloseVaults" -> settings.autoCloseVaults().set(in.nextBoolean());
|
||||||
case "port" -> settings.port().set(in.nextInt());
|
case "port" -> settings.port().set(in.nextInt());
|
||||||
case "numTrayNotifications" -> settings.numTrayNotifications().set(in.nextInt());
|
case "numTrayNotifications" -> settings.numTrayNotifications().set(in.nextInt());
|
||||||
case "preferredGvfsScheme" -> settings.preferredGvfsScheme().set(parseWebDavUrlSchemePrefix(in.nextString()));
|
|
||||||
case "debugMode" -> settings.debugMode().set(in.nextBoolean());
|
case "debugMode" -> settings.debugMode().set(in.nextBoolean());
|
||||||
case "preferredVolumeImpl" -> settings.preferredVolumeImpl().set(parsePreferredVolumeImplName(in.nextString()));
|
|
||||||
case "theme" -> settings.theme().set(parseUiTheme(in.nextString()));
|
case "theme" -> settings.theme().set(parseUiTheme(in.nextString()));
|
||||||
case "uiOrientation" -> settings.userInterfaceOrientation().set(parseUiOrientation(in.nextString()));
|
case "uiOrientation" -> settings.userInterfaceOrientation().set(parseUiOrientation(in.nextString()));
|
||||||
case "keychainProvider" -> settings.keychainProvider().set(in.nextString());
|
case "keychainProvider" -> settings.keychainProvider().set(in.nextString());
|
||||||
|
case "useKeychain" -> settings.useKeychain().set(in.nextBoolean());
|
||||||
case "licenseKey" -> settings.licenseKey().set(in.nextString());
|
case "licenseKey" -> settings.licenseKey().set(in.nextString());
|
||||||
case "showMinimizeButton" -> settings.showMinimizeButton().set(in.nextBoolean());
|
case "showMinimizeButton" -> settings.showMinimizeButton().set(in.nextBoolean());
|
||||||
case "showTrayIcon" -> settings.showTrayIcon().set(in.nextBoolean());
|
case "showTrayIcon" -> settings.showTrayIcon().set(in.nextBoolean());
|
||||||
@@ -101,6 +99,12 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
case "windowHeight" -> settings.windowHeightProperty().set(in.nextInt());
|
case "windowHeight" -> settings.windowHeightProperty().set(in.nextInt());
|
||||||
case "displayConfiguration" -> settings.displayConfigurationProperty().set(in.nextString());
|
case "displayConfiguration" -> settings.displayConfigurationProperty().set(in.nextString());
|
||||||
case "language" -> settings.languageProperty().set(in.nextString());
|
case "language" -> settings.languageProperty().set(in.nextString());
|
||||||
|
case "mountService" -> {
|
||||||
|
var token = in.peek();
|
||||||
|
if (JsonToken.STRING == token) {
|
||||||
|
settings.mountService().set(in.nextString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
default -> {
|
default -> {
|
||||||
LOG.warn("Unsupported vault setting found in JSON: {}", name);
|
LOG.warn("Unsupported vault setting found in JSON: {}", name);
|
||||||
@@ -113,24 +117,6 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
private VolumeImpl parsePreferredVolumeImplName(String nioAdapterName) {
|
|
||||||
try {
|
|
||||||
return VolumeImpl.valueOf(nioAdapterName.toUpperCase());
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
LOG.warn("Invalid volume type {}. Defaulting to {}.", nioAdapterName, Settings.DEFAULT_PREFERRED_VOLUME_IMPL);
|
|
||||||
return Settings.DEFAULT_PREFERRED_VOLUME_IMPL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private WebDavUrlScheme parseWebDavUrlSchemePrefix(String webDavUrlSchemeName) {
|
|
||||||
try {
|
|
||||||
return WebDavUrlScheme.valueOf(webDavUrlSchemeName.toUpperCase());
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
LOG.warn("Invalid WebDAV url scheme {}. Defaulting to {}.", webDavUrlSchemeName, Settings.DEFAULT_GVFS_SCHEME);
|
|
||||||
return Settings.DEFAULT_GVFS_SCHEME;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private UiTheme parseUiTheme(String uiThemeName) {
|
private UiTheme parseUiTheme(String uiThemeName) {
|
||||||
try {
|
try {
|
||||||
return UiTheme.valueOf(uiThemeName.toUpperCase());
|
return UiTheme.valueOf(uiThemeName.toUpperCase());
|
||||||
|
|||||||
@@ -6,12 +6,11 @@
|
|||||||
package org.cryptomator.common.settings;
|
package org.cryptomator.common.settings;
|
||||||
|
|
||||||
import com.google.common.base.CharMatcher;
|
import com.google.common.base.CharMatcher;
|
||||||
import com.google.common.base.Strings;
|
|
||||||
import com.google.common.io.BaseEncoding;
|
import com.google.common.io.BaseEncoding;
|
||||||
|
|
||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.StringBinding;
|
import javafx.beans.binding.StringExpression;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.IntegerProperty;
|
import javafx.beans.property.IntegerProperty;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
@@ -22,7 +21,6 @@ import javafx.beans.property.SimpleStringProperty;
|
|||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,7 +30,6 @@ public class VaultSettings {
|
|||||||
|
|
||||||
public static final boolean DEFAULT_UNLOCK_AFTER_STARTUP = false;
|
public static final boolean DEFAULT_UNLOCK_AFTER_STARTUP = false;
|
||||||
public static final boolean DEFAULT_REVEAL_AFTER_MOUNT = true;
|
public static final boolean DEFAULT_REVEAL_AFTER_MOUNT = true;
|
||||||
public static final boolean DEFAULT_USES_INDIVIDUAL_MOUNTPATH = false;
|
|
||||||
public static final boolean DEFAULT_USES_READONLY_MODE = false;
|
public static final boolean DEFAULT_USES_READONLY_MODE = false;
|
||||||
public static final String DEFAULT_MOUNT_FLAGS = "";
|
public static final String DEFAULT_MOUNT_FLAGS = "";
|
||||||
public static final int DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH = -1;
|
public static final int DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH = -1;
|
||||||
@@ -45,26 +42,32 @@ public class VaultSettings {
|
|||||||
private final String id;
|
private final String id;
|
||||||
private final ObjectProperty<Path> path = new SimpleObjectProperty<>();
|
private final ObjectProperty<Path> path = new SimpleObjectProperty<>();
|
||||||
private final StringProperty displayName = new SimpleStringProperty();
|
private final StringProperty displayName = new SimpleStringProperty();
|
||||||
private final StringProperty winDriveLetter = new SimpleStringProperty();
|
|
||||||
private final BooleanProperty unlockAfterStartup = new SimpleBooleanProperty(DEFAULT_UNLOCK_AFTER_STARTUP);
|
private final BooleanProperty unlockAfterStartup = new SimpleBooleanProperty(DEFAULT_UNLOCK_AFTER_STARTUP);
|
||||||
private final BooleanProperty revealAfterMount = new SimpleBooleanProperty(DEFAULT_REVEAL_AFTER_MOUNT);
|
private final BooleanProperty revealAfterMount = new SimpleBooleanProperty(DEFAULT_REVEAL_AFTER_MOUNT);
|
||||||
private final BooleanProperty useCustomMountPath = new SimpleBooleanProperty(DEFAULT_USES_INDIVIDUAL_MOUNTPATH);
|
|
||||||
private final StringProperty customMountPath = new SimpleStringProperty();
|
|
||||||
private final BooleanProperty usesReadOnlyMode = new SimpleBooleanProperty(DEFAULT_USES_READONLY_MODE);
|
private final BooleanProperty usesReadOnlyMode = new SimpleBooleanProperty(DEFAULT_USES_READONLY_MODE);
|
||||||
private final StringProperty mountFlags = new SimpleStringProperty(DEFAULT_MOUNT_FLAGS);
|
private final StringProperty mountFlags = new SimpleStringProperty(DEFAULT_MOUNT_FLAGS);
|
||||||
private final IntegerProperty maxCleartextFilenameLength = new SimpleIntegerProperty(DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH);
|
private final IntegerProperty maxCleartextFilenameLength = new SimpleIntegerProperty(DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH);
|
||||||
private final ObjectProperty<WhenUnlocked> actionAfterUnlock = new SimpleObjectProperty<>(DEFAULT_ACTION_AFTER_UNLOCK);
|
private final ObjectProperty<WhenUnlocked> actionAfterUnlock = new SimpleObjectProperty<>(DEFAULT_ACTION_AFTER_UNLOCK);
|
||||||
private final BooleanProperty autoLockWhenIdle = new SimpleBooleanProperty(DEFAULT_AUTOLOCK_WHEN_IDLE);
|
private final BooleanProperty autoLockWhenIdle = new SimpleBooleanProperty(DEFAULT_AUTOLOCK_WHEN_IDLE);
|
||||||
private final IntegerProperty autoLockIdleSeconds = new SimpleIntegerProperty(DEFAULT_AUTOLOCK_IDLE_SECONDS);
|
private final IntegerProperty autoLockIdleSeconds = new SimpleIntegerProperty(DEFAULT_AUTOLOCK_IDLE_SECONDS);
|
||||||
private final StringBinding mountName;
|
private final StringExpression mountName;
|
||||||
|
private final ObjectProperty<Path> mountPoint = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
public VaultSettings(String id) {
|
public VaultSettings(String id) {
|
||||||
this.id = Objects.requireNonNull(id);
|
this.id = Objects.requireNonNull(id);
|
||||||
this.mountName = Bindings.createStringBinding(this::normalizeDisplayName, displayName);
|
this.mountName = StringExpression.stringExpression(Bindings.createStringBinding(() -> {
|
||||||
|
final String name;
|
||||||
|
if (displayName.isEmpty().get()) {
|
||||||
|
name = path.get().getFileName().toString();
|
||||||
|
} else {
|
||||||
|
name = displayName.get();
|
||||||
|
}
|
||||||
|
return normalizeDisplayName(name);
|
||||||
|
}, displayName, path));
|
||||||
}
|
}
|
||||||
|
|
||||||
Observable[] observables() {
|
Observable[] observables() {
|
||||||
return new Observable[]{path, displayName, winDriveLetter, unlockAfterStartup, revealAfterMount, useCustomMountPath, customMountPath, usesReadOnlyMode, mountFlags, maxCleartextFilenameLength, actionAfterUnlock, autoLockWhenIdle, autoLockIdleSeconds};
|
return new Observable[]{actionAfterUnlock, autoLockIdleSeconds, autoLockWhenIdle, displayName, maxCleartextFilenameLength, mountFlags, mountPoint, path, revealAfterMount, unlockAfterStartup, usesReadOnlyMode};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static VaultSettings withRandomId() {
|
public static VaultSettings withRandomId() {
|
||||||
@@ -78,8 +81,7 @@ public class VaultSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//visible for testing
|
//visible for testing
|
||||||
String normalizeDisplayName() {
|
static String normalizeDisplayName(String original) {
|
||||||
var original = displayName.getValueSafe();
|
|
||||||
if (original.isBlank() || ".".equals(original) || "..".equals(original)) {
|
if (original.isBlank() || ".".equals(original) || "..".equals(original)) {
|
||||||
return "_";
|
return "_";
|
||||||
}
|
}
|
||||||
@@ -105,22 +107,10 @@ public class VaultSettings {
|
|||||||
return displayName;
|
return displayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringBinding mountName() {
|
public StringExpression mountName() {
|
||||||
return mountName;
|
return mountName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringProperty winDriveLetter() {
|
|
||||||
return winDriveLetter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<String> getWinDriveLetter() {
|
|
||||||
String current = this.winDriveLetter.get();
|
|
||||||
if (!Strings.isNullOrEmpty(current)) {
|
|
||||||
return Optional.of(current);
|
|
||||||
}
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public BooleanProperty unlockAfterStartup() {
|
public BooleanProperty unlockAfterStartup() {
|
||||||
return unlockAfterStartup;
|
return unlockAfterStartup;
|
||||||
}
|
}
|
||||||
@@ -129,20 +119,12 @@ public class VaultSettings {
|
|||||||
return revealAfterMount;
|
return revealAfterMount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanProperty useCustomMountPath() {
|
public Path getMountPoint() {
|
||||||
return useCustomMountPath;
|
return mountPoint.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringProperty customMountPath() {
|
public ObjectProperty<Path> mountPoint() {
|
||||||
return customMountPath;
|
return mountPoint;
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<String> getCustomMountPath() {
|
|
||||||
if (useCustomMountPath.get()) {
|
|
||||||
return Optional.ofNullable(Strings.emptyToNull(customMountPath.get()));
|
|
||||||
} else {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanProperty usesReadOnlyMode() {
|
public BooleanProperty usesReadOnlyMode() {
|
||||||
@@ -188,5 +170,4 @@ public class VaultSettings {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,14 @@
|
|||||||
package org.cryptomator.common.settings;
|
package org.cryptomator.common.settings;
|
||||||
|
|
||||||
import com.google.gson.stream.JsonReader;
|
import com.google.gson.stream.JsonReader;
|
||||||
|
import com.google.gson.stream.JsonToken;
|
||||||
import com.google.gson.stream.JsonWriter;
|
import com.google.gson.stream.JsonWriter;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.file.InvalidPathException;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
class VaultSettingsJsonAdapter {
|
class VaultSettingsJsonAdapter {
|
||||||
@@ -22,11 +25,10 @@ class VaultSettingsJsonAdapter {
|
|||||||
out.name("id").value(value.getId());
|
out.name("id").value(value.getId());
|
||||||
out.name("path").value(value.path().get().toString());
|
out.name("path").value(value.path().get().toString());
|
||||||
out.name("displayName").value(value.displayName().get());
|
out.name("displayName").value(value.displayName().get());
|
||||||
out.name("winDriveLetter").value(value.winDriveLetter().get());
|
|
||||||
out.name("unlockAfterStartup").value(value.unlockAfterStartup().get());
|
out.name("unlockAfterStartup").value(value.unlockAfterStartup().get());
|
||||||
out.name("revealAfterMount").value(value.revealAfterMount().get());
|
out.name("revealAfterMount").value(value.revealAfterMount().get());
|
||||||
out.name("useCustomMountPath").value(value.useCustomMountPath().get());
|
var mountPoint = value.mountPoint().get();
|
||||||
out.name("customMountPath").value(value.customMountPath().get());
|
out.name("mountPoint").value(mountPoint != null ? mountPoint.toAbsolutePath().toString() : null);
|
||||||
out.name("usesReadOnlyMode").value(value.usesReadOnlyMode().get());
|
out.name("usesReadOnlyMode").value(value.usesReadOnlyMode().get());
|
||||||
out.name("mountFlags").value(value.mountFlags().get());
|
out.name("mountFlags").value(value.mountFlags().get());
|
||||||
out.name("maxCleartextFilenameLength").value(value.maxCleartextFilenameLength().get());
|
out.name("maxCleartextFilenameLength").value(value.maxCleartextFilenameLength().get());
|
||||||
@@ -36,18 +38,18 @@ class VaultSettingsJsonAdapter {
|
|||||||
out.endObject();
|
out.endObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO: usesCustomMountPath, customMountPath and winDriveLetter removed
|
||||||
|
// -> migration required
|
||||||
public VaultSettings read(JsonReader in) throws IOException {
|
public VaultSettings read(JsonReader in) throws IOException {
|
||||||
String id = null;
|
String id = null;
|
||||||
String path = null;
|
String path = null;
|
||||||
String mountName = null; //see https://github.com/cryptomator/cryptomator/pull/1318
|
String mountName = null; //see https://github.com/cryptomator/cryptomator/pull/1318
|
||||||
String displayName = null;
|
String displayName = null;
|
||||||
String customMountPath = null;
|
|
||||||
String winDriveLetter = null;
|
|
||||||
boolean unlockAfterStartup = VaultSettings.DEFAULT_UNLOCK_AFTER_STARTUP;
|
boolean unlockAfterStartup = VaultSettings.DEFAULT_UNLOCK_AFTER_STARTUP;
|
||||||
boolean revealAfterMount = VaultSettings.DEFAULT_REVEAL_AFTER_MOUNT;
|
boolean revealAfterMount = VaultSettings.DEFAULT_REVEAL_AFTER_MOUNT;
|
||||||
boolean useCustomMountPath = VaultSettings.DEFAULT_USES_INDIVIDUAL_MOUNTPATH;
|
|
||||||
boolean usesReadOnlyMode = VaultSettings.DEFAULT_USES_READONLY_MODE;
|
boolean usesReadOnlyMode = VaultSettings.DEFAULT_USES_READONLY_MODE;
|
||||||
String mountFlags = VaultSettings.DEFAULT_MOUNT_FLAGS;
|
String mountFlags = VaultSettings.DEFAULT_MOUNT_FLAGS;
|
||||||
|
Path mountPoint = null;
|
||||||
int maxCleartextFilenameLength = VaultSettings.DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH;
|
int maxCleartextFilenameLength = VaultSettings.DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH;
|
||||||
WhenUnlocked actionAfterUnlock = VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK;
|
WhenUnlocked actionAfterUnlock = VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK;
|
||||||
boolean autoLockWhenIdle = VaultSettings.DEFAULT_AUTOLOCK_WHEN_IDLE;
|
boolean autoLockWhenIdle = VaultSettings.DEFAULT_AUTOLOCK_WHEN_IDLE;
|
||||||
@@ -61,13 +63,17 @@ class VaultSettingsJsonAdapter {
|
|||||||
case "path" -> path = in.nextString();
|
case "path" -> path = in.nextString();
|
||||||
case "mountName" -> mountName = in.nextString(); //see https://github.com/cryptomator/cryptomator/pull/1318
|
case "mountName" -> mountName = in.nextString(); //see https://github.com/cryptomator/cryptomator/pull/1318
|
||||||
case "displayName" -> displayName = in.nextString();
|
case "displayName" -> displayName = in.nextString();
|
||||||
case "winDriveLetter" -> winDriveLetter = in.nextString();
|
|
||||||
case "unlockAfterStartup" -> unlockAfterStartup = in.nextBoolean();
|
case "unlockAfterStartup" -> unlockAfterStartup = in.nextBoolean();
|
||||||
case "revealAfterMount" -> revealAfterMount = in.nextBoolean();
|
case "revealAfterMount" -> revealAfterMount = in.nextBoolean();
|
||||||
case "usesIndividualMountPath", "useCustomMountPath" -> useCustomMountPath = in.nextBoolean();
|
|
||||||
case "individualMountPath", "customMountPath" -> customMountPath = in.nextString();
|
|
||||||
case "usesReadOnlyMode" -> usesReadOnlyMode = in.nextBoolean();
|
case "usesReadOnlyMode" -> usesReadOnlyMode = in.nextBoolean();
|
||||||
case "mountFlags" -> mountFlags = in.nextString();
|
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 "maxCleartextFilenameLength" -> maxCleartextFilenameLength = in.nextInt();
|
||||||
case "actionAfterUnlock" -> actionAfterUnlock = parseActionAfterUnlock(in.nextString());
|
case "actionAfterUnlock" -> actionAfterUnlock = parseActionAfterUnlock(in.nextString());
|
||||||
case "autoLockWhenIdle" -> autoLockWhenIdle = in.nextBoolean();
|
case "autoLockWhenIdle" -> autoLockWhenIdle = in.nextBoolean();
|
||||||
@@ -87,20 +93,27 @@ class VaultSettingsJsonAdapter {
|
|||||||
vaultSettings.displayName().set(mountName);
|
vaultSettings.displayName().set(mountName);
|
||||||
}
|
}
|
||||||
vaultSettings.path().set(Paths.get(path));
|
vaultSettings.path().set(Paths.get(path));
|
||||||
vaultSettings.winDriveLetter().set(winDriveLetter);
|
|
||||||
vaultSettings.unlockAfterStartup().set(unlockAfterStartup);
|
vaultSettings.unlockAfterStartup().set(unlockAfterStartup);
|
||||||
vaultSettings.revealAfterMount().set(revealAfterMount);
|
vaultSettings.revealAfterMount().set(revealAfterMount);
|
||||||
vaultSettings.useCustomMountPath().set(useCustomMountPath);
|
|
||||||
vaultSettings.customMountPath().set(customMountPath);
|
|
||||||
vaultSettings.usesReadOnlyMode().set(usesReadOnlyMode);
|
vaultSettings.usesReadOnlyMode().set(usesReadOnlyMode);
|
||||||
vaultSettings.mountFlags().set(mountFlags);
|
vaultSettings.mountFlags().set(mountFlags);
|
||||||
vaultSettings.maxCleartextFilenameLength().set(maxCleartextFilenameLength);
|
vaultSettings.maxCleartextFilenameLength().set(maxCleartextFilenameLength);
|
||||||
vaultSettings.actionAfterUnlock().set(actionAfterUnlock);
|
vaultSettings.actionAfterUnlock().set(actionAfterUnlock);
|
||||||
vaultSettings.autoLockWhenIdle().set(autoLockWhenIdle);
|
vaultSettings.autoLockWhenIdle().set(autoLockWhenIdle);
|
||||||
vaultSettings.autoLockIdleSeconds().set(autoLockIdleSeconds);
|
vaultSettings.autoLockIdleSeconds().set(autoLockIdleSeconds);
|
||||||
|
vaultSettings.mountPoint().set(mountPoint);
|
||||||
return vaultSettings;
|
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) {
|
private WhenUnlocked parseActionAfterUnlock(String actionAfterUnlockName) {
|
||||||
try {
|
try {
|
||||||
return WhenUnlocked.valueOf(actionAfterUnlockName.toUpperCase());
|
return WhenUnlocked.valueOf(actionAfterUnlockName.toUpperCase());
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
package org.cryptomator.common.settings;
|
|
||||||
|
|
||||||
public enum WebDavUrlScheme {
|
|
||||||
DAV("dav", "dav:// (Gnome, Nautilus, ...)"),
|
|
||||||
WEBDAV("webdav", "webdav:// (KDE, Dolphin, ...)");
|
|
||||||
|
|
||||||
private final String prefix;
|
|
||||||
private final String displayName;
|
|
||||||
|
|
||||||
WebDavUrlScheme(String prefix, String displayName) {
|
|
||||||
this.prefix = prefix;
|
|
||||||
this.displayName = displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPrefix() {
|
|
||||||
return prefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDisplayName() {
|
|
||||||
return displayName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
package org.cryptomator.common.vaults;
|
|
||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
|
||||||
import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
|
||||||
import org.cryptomator.common.mountpoint.MountPointChooser;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public abstract class AbstractVolume implements Volume {
|
|
||||||
|
|
||||||
private final Iterable<MountPointChooser> choosers;
|
|
||||||
|
|
||||||
protected Path mountPoint;
|
|
||||||
private boolean cleanupRequired;
|
|
||||||
private MountPointChooser usedChooser;
|
|
||||||
|
|
||||||
public AbstractVolume(Iterable<MountPointChooser> choosers) {
|
|
||||||
this.choosers = choosers;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Path determineMountPoint() throws InvalidMountPointException {
|
|
||||||
var applicableChoosers = Iterables.filter(choosers, c -> c.isApplicable(this));
|
|
||||||
for (var chooser : applicableChoosers) {
|
|
||||||
Optional<Path> chosenPath = chooser.chooseMountPoint(this);
|
|
||||||
if (chosenPath.isEmpty()) { // chooser couldn't find a feasible mountpoint
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
this.cleanupRequired = chooser.prepare(this, chosenPath.get());
|
|
||||||
this.usedChooser = chooser;
|
|
||||||
return chosenPath.get();
|
|
||||||
}
|
|
||||||
throw new InvalidMountPointException(String.format("No feasible MountPoint found by choosers: %s", applicableChoosers));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void cleanupMountPoint() {
|
|
||||||
if (this.cleanupRequired) {
|
|
||||||
this.usedChooser.cleanup(this, this.mountPoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<Path> getMountPoint() {
|
|
||||||
return Optional.ofNullable(mountPoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
package org.cryptomator.common.vaults;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
|
import org.cryptomator.integrations.mount.UnmountFailedException;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
|
import java.io.IOException;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@@ -39,7 +41,7 @@ public class AutoLocker {
|
|||||||
try {
|
try {
|
||||||
vault.lock(false);
|
vault.lock(false);
|
||||||
LOG.info("Autolocked {} after idle timeout", vault.getDisplayName());
|
LOG.info("Autolocked {} after idle timeout", vault.getDisplayName());
|
||||||
} catch (Volume.VolumeException | LockNotCompletedException e) {
|
} catch (UnmountFailedException | IOException e) {
|
||||||
LOG.error("Autolocking failed.", e);
|
LOG.error("Autolocking failed.", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
package org.cryptomator.common.vaults;
|
|
||||||
|
|
||||||
import javax.inject.Qualifier;
|
|
||||||
import java.lang.annotation.Documented;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
|
|
||||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|
||||||
|
|
||||||
@Qualifier
|
|
||||||
@Documented
|
|
||||||
@Retention(RUNTIME)
|
|
||||||
@interface DefaultMountFlags {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
package org.cryptomator.common.vaults;
|
|
||||||
|
|
||||||
import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
|
||||||
import org.cryptomator.common.mountpoint.MountPointChooser;
|
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
|
||||||
import org.cryptomator.common.settings.VolumeImpl;
|
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
|
||||||
import org.cryptomator.frontend.dokany.DokanyMountFailedException;
|
|
||||||
import org.cryptomator.frontend.dokany.Mount;
|
|
||||||
import org.cryptomator.frontend.dokany.MountFactory;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
public class DokanyVolume extends AbstractVolume {
|
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(DokanyVolume.class);
|
|
||||||
|
|
||||||
private static final String FS_TYPE_NAME = "CryptomatorFS";
|
|
||||||
|
|
||||||
private final VaultSettings vaultSettings;
|
|
||||||
|
|
||||||
private Mount mount;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public DokanyVolume(VaultSettings vaultSettings, @Named("orderedMountPointChoosers") Iterable<MountPointChooser> choosers) {
|
|
||||||
super(choosers);
|
|
||||||
this.vaultSettings = vaultSettings;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public VolumeImpl getImplementationType() {
|
|
||||||
return VolumeImpl.DOKANY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mount(CryptoFileSystem fs, String mountFlags, Consumer<Throwable> onExitAction) throws InvalidMountPointException, VolumeException {
|
|
||||||
this.mountPoint = determineMountPoint();
|
|
||||||
try {
|
|
||||||
this.mount = MountFactory.mount(fs.getPath("/"), mountPoint, vaultSettings.mountName().get(), FS_TYPE_NAME, mountFlags.strip(), onExitAction);
|
|
||||||
} catch (DokanyMountFailedException e) {
|
|
||||||
if (vaultSettings.getCustomMountPath().isPresent()) {
|
|
||||||
LOG.warn("Failed to mount vault into {}. Is this directory currently accessed by another process (e.g. Windows Explorer)?", mountPoint);
|
|
||||||
}
|
|
||||||
throw new VolumeException("Unable to mount Filesystem", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reveal(Revealer revealer) throws VolumeException {
|
|
||||||
try {
|
|
||||||
mount.reveal(revealer::reveal);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new VolumeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unmount() throws VolumeException {
|
|
||||||
try {
|
|
||||||
mount.unmount();
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
throw new VolumeException("Unmount Failed.", e);
|
|
||||||
}
|
|
||||||
cleanupMountPoint();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unmountForced() {
|
|
||||||
mount.unmountForced();
|
|
||||||
cleanupMountPoint();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsForcedUnmount() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSupported() {
|
|
||||||
return DokanyVolume.isSupportedStatic();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MountPointRequirement getMountPointRequirement() {
|
|
||||||
return this.vaultSettings.getWinDriveLetter().isPresent() ? MountPointRequirement.UNUSED_ROOT_DIR : MountPointRequirement.EMPTY_MOUNT_POINT;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isSupportedStatic() {
|
|
||||||
return MountFactory.isApplicable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
package org.cryptomator.common.vaults;
|
|
||||||
|
|
||||||
import com.google.common.collect.Iterators;
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
|
||||||
import org.cryptomator.common.mountpoint.MountPointChooser;
|
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
|
||||||
import org.cryptomator.common.settings.VolumeImpl;
|
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
|
||||||
import org.cryptomator.frontend.fuse.mount.EnvironmentVariables;
|
|
||||||
import org.cryptomator.frontend.fuse.mount.FuseMountException;
|
|
||||||
import org.cryptomator.frontend.fuse.mount.FuseMountFactory;
|
|
||||||
import org.cryptomator.frontend.fuse.mount.FuseNotSupportedException;
|
|
||||||
import org.cryptomator.frontend.fuse.mount.Mount;
|
|
||||||
import org.cryptomator.frontend.fuse.mount.Mounter;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
public class FuseVolume extends AbstractVolume {
|
|
||||||
|
|
||||||
private static final Pattern NON_WHITESPACE_OR_QUOTED = Pattern.compile("[^\\s\"']+|\"([^\"]*)\"|'([^']*)'"); // Thanks to https://stackoverflow.com/a/366532
|
|
||||||
|
|
||||||
private final VaultSettings vaultSettings;
|
|
||||||
|
|
||||||
private Mount mount;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public FuseVolume(VaultSettings vaultSettings, @Named("orderedMountPointChoosers") Iterable<MountPointChooser> choosers) {
|
|
||||||
super(choosers);
|
|
||||||
this.vaultSettings = vaultSettings;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mount(CryptoFileSystem fs, String mountFlags, Consumer<Throwable> onExitAction) throws InvalidMountPointException, VolumeException {
|
|
||||||
this.mountPoint = determineMountPoint();
|
|
||||||
mount(fs.getPath("/"), mountFlags, onExitAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void mount(Path root, String mountFlags, Consumer<Throwable> onExitAction) throws VolumeException {
|
|
||||||
try {
|
|
||||||
Mounter mounter = FuseMountFactory.getMounter();
|
|
||||||
EnvironmentVariables envVars = EnvironmentVariables.create() //
|
|
||||||
.withFlags(splitFlags(mountFlags)) //
|
|
||||||
.withMountPoint(mountPoint) //
|
|
||||||
.withFileNameTranscoder(mounter.defaultFileNameTranscoder()) //
|
|
||||||
.build();
|
|
||||||
this.mount = mounter.mount(root, envVars, onExitAction);
|
|
||||||
} catch (FuseMountException | FuseNotSupportedException e) {
|
|
||||||
throw new VolumeException("Unable to mount Filesystem", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String[] splitFlags(String str) {
|
|
||||||
List<String> flags = new ArrayList<>();
|
|
||||||
var matches = Iterators.peekingIterator(NON_WHITESPACE_OR_QUOTED.matcher(str).results().iterator());
|
|
||||||
while (matches.hasNext()) {
|
|
||||||
String flag = matches.next().group();
|
|
||||||
// check if flag is missing its argument:
|
|
||||||
if (flag.endsWith("=") && matches.hasNext() && matches.peek().group(1) != null) { // next is "double quoted"
|
|
||||||
// next is "double quoted" and flag is missing its argument
|
|
||||||
flag += matches.next().group(1);
|
|
||||||
} else if (flag.endsWith("=") && matches.hasNext() && matches.peek().group(2) != null) {
|
|
||||||
// next is 'single quoted' and flag is missing its argument
|
|
||||||
flag += matches.next().group(2);
|
|
||||||
}
|
|
||||||
flags.add(flag);
|
|
||||||
}
|
|
||||||
return flags.toArray(String[]::new);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reveal(Revealer revealer) throws VolumeException {
|
|
||||||
try {
|
|
||||||
mount.reveal(revealer::reveal);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new VolumeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsForcedUnmount() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void unmountForced() throws VolumeException {
|
|
||||||
try {
|
|
||||||
mount.unmountForced();
|
|
||||||
} catch (FuseMountException e) {
|
|
||||||
throw new VolumeException(e);
|
|
||||||
}
|
|
||||||
cleanupMountPoint();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void unmount() throws VolumeException {
|
|
||||||
try {
|
|
||||||
mount.unmount();
|
|
||||||
} catch (FuseMountException e) {
|
|
||||||
throw new VolumeException(e);
|
|
||||||
}
|
|
||||||
cleanupMountPoint();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSupported() {
|
|
||||||
return FuseVolume.isSupportedStatic();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public VolumeImpl getImplementationType() {
|
|
||||||
return VolumeImpl.FUSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MountPointRequirement getMountPointRequirement() {
|
|
||||||
if (!SystemUtils.IS_OS_WINDOWS) {
|
|
||||||
return MountPointRequirement.EMPTY_MOUNT_POINT;
|
|
||||||
}
|
|
||||||
return this.vaultSettings.getWinDriveLetter().isPresent() ? MountPointRequirement.UNUSED_ROOT_DIR : MountPointRequirement.PARENT_NO_MOUNT_POINT;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isSupportedStatic() {
|
|
||||||
return FuseMountFactory.isFuseSupported();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package org.cryptomator.common.vaults;
|
|
||||||
|
|
||||||
public class LockNotCompletedException extends Exception {
|
|
||||||
|
|
||||||
public LockNotCompletedException(String reason) {
|
|
||||||
super(reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LockNotCompletedException(Throwable cause) {
|
|
||||||
super(cause);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package org.cryptomator.common.vaults;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enumeration used to indicate the requirements for mounting a vault
|
|
||||||
* using a specific {@link Volume VolumeProvider}, e.g. {@link FuseVolume}.
|
|
||||||
*/
|
|
||||||
public enum MountPointRequirement {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Mountpoint needs to be a filesystem root and must not exist.
|
|
||||||
*/
|
|
||||||
UNUSED_ROOT_DIR,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* No Mountpoint on the local filesystem required. (e.g. WebDAV)
|
|
||||||
*/
|
|
||||||
NONE,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A parent folder is required, but the actual Mountpoint must not exist.
|
|
||||||
*/
|
|
||||||
PARENT_NO_MOUNT_POINT,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A parent folder is required, but the actual Mountpoint may exist.
|
|
||||||
*/
|
|
||||||
PARENT_OPT_MOUNT_POINT,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The actual Mountpoint must exist and must be empty.
|
|
||||||
*/
|
|
||||||
EMPTY_MOUNT_POINT;
|
|
||||||
}
|
|
||||||
@@ -11,9 +11,10 @@ package org.cryptomator.common.vaults;
|
|||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
import org.cryptomator.common.Constants;
|
import org.cryptomator.common.Constants;
|
||||||
import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
import org.cryptomator.common.Environment;
|
||||||
|
import org.cryptomator.common.mount.WindowsDriveLetters;
|
||||||
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
import org.cryptomator.common.vaults.Volume.VolumeException;
|
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags;
|
import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags;
|
||||||
@@ -22,29 +23,42 @@ import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
|
|||||||
import org.cryptomator.cryptolib.api.CryptoException;
|
import org.cryptomator.cryptolib.api.CryptoException;
|
||||||
import org.cryptomator.cryptolib.api.MasterkeyLoader;
|
import org.cryptomator.cryptolib.api.MasterkeyLoader;
|
||||||
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
|
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
|
||||||
|
import org.cryptomator.integrations.mount.Mount;
|
||||||
|
import org.cryptomator.integrations.mount.MountBuilder;
|
||||||
|
import org.cryptomator.integrations.mount.MountCapability;
|
||||||
|
import org.cryptomator.integrations.mount.MountFailedException;
|
||||||
|
import org.cryptomator.integrations.mount.MountService;
|
||||||
|
import org.cryptomator.integrations.mount.Mountpoint;
|
||||||
|
import org.cryptomator.integrations.mount.UnmountFailedException;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.inject.Provider;
|
|
||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.BooleanBinding;
|
import javafx.beans.binding.BooleanBinding;
|
||||||
|
import javafx.beans.binding.ObjectBinding;
|
||||||
import javafx.beans.binding.StringBinding;
|
import javafx.beans.binding.StringBinding;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.ReadOnlyStringProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_AS_DRIVE_LETTER;
|
||||||
|
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_TO_EXISTING_DIR;
|
||||||
|
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_TO_SYSTEM_CHOSEN_PATH;
|
||||||
|
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_WITHIN_EXISTING_PARENT;
|
||||||
|
|
||||||
@PerVault
|
@PerVault
|
||||||
public class Vault {
|
public class Vault {
|
||||||
|
|
||||||
@@ -52,15 +66,16 @@ public class Vault {
|
|||||||
private static final Path HOME_DIR = Paths.get(SystemUtils.USER_HOME);
|
private static final Path HOME_DIR = Paths.get(SystemUtils.USER_HOME);
|
||||||
private static final int UNLIMITED_FILENAME_LENGTH = Integer.MAX_VALUE;
|
private static final int UNLIMITED_FILENAME_LENGTH = Integer.MAX_VALUE;
|
||||||
|
|
||||||
|
private final Environment env;
|
||||||
|
private final Settings settings;
|
||||||
private final VaultSettings vaultSettings;
|
private final VaultSettings vaultSettings;
|
||||||
private final Provider<Volume> volumeProvider;
|
|
||||||
private final StringBinding defaultMountFlags;
|
|
||||||
private final AtomicReference<CryptoFileSystem> cryptoFileSystem;
|
private final AtomicReference<CryptoFileSystem> cryptoFileSystem;
|
||||||
private final VaultState state;
|
private final VaultState state;
|
||||||
private final ObjectProperty<Exception> lastKnownException;
|
private final ObjectProperty<Exception> lastKnownException;
|
||||||
|
private final ObservableValue<MountService> mountService;
|
||||||
|
private final ObservableValue<String> defaultMountFlags;
|
||||||
private final VaultConfigCache configCache;
|
private final VaultConfigCache configCache;
|
||||||
private final VaultStats stats;
|
private final VaultStats stats;
|
||||||
private final StringBinding displayName;
|
|
||||||
private final StringBinding displayablePath;
|
private final StringBinding displayablePath;
|
||||||
private final BooleanBinding locked;
|
private final BooleanBinding locked;
|
||||||
private final BooleanBinding processing;
|
private final BooleanBinding processing;
|
||||||
@@ -68,23 +83,24 @@ public class Vault {
|
|||||||
private final BooleanBinding missing;
|
private final BooleanBinding missing;
|
||||||
private final BooleanBinding needsMigration;
|
private final BooleanBinding needsMigration;
|
||||||
private final BooleanBinding unknownError;
|
private final BooleanBinding unknownError;
|
||||||
private final StringBinding accessPoint;
|
private final ObjectBinding<Mountpoint> mountPoint;
|
||||||
private final BooleanBinding accessPointPresent;
|
private final WindowsDriveLetters windowsDriveLetters;
|
||||||
private final BooleanProperty showingStats;
|
private final BooleanProperty showingStats;
|
||||||
|
|
||||||
private volatile Volume volume;
|
private AtomicReference<MountHandle> mountHandle = new AtomicReference<>(null);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
Vault(VaultSettings vaultSettings, VaultConfigCache configCache, Provider<Volume> volumeProvider, @DefaultMountFlags StringBinding defaultMountFlags, AtomicReference<CryptoFileSystem> cryptoFileSystem, VaultState state, @Named("lastKnownException") ObjectProperty<Exception> lastKnownException, VaultStats stats) {
|
Vault(Environment env, Settings settings, VaultSettings vaultSettings, VaultConfigCache configCache, AtomicReference<CryptoFileSystem> cryptoFileSystem, VaultState state, @Named("lastKnownException") ObjectProperty<Exception> lastKnownException, ObservableValue<MountService> mountService, VaultStats stats, WindowsDriveLetters windowsDriveLetters) {
|
||||||
|
this.env = env;
|
||||||
|
this.settings = settings;
|
||||||
this.vaultSettings = vaultSettings;
|
this.vaultSettings = vaultSettings;
|
||||||
this.configCache = configCache;
|
this.configCache = configCache;
|
||||||
this.volumeProvider = volumeProvider;
|
|
||||||
this.defaultMountFlags = defaultMountFlags;
|
|
||||||
this.cryptoFileSystem = cryptoFileSystem;
|
this.cryptoFileSystem = cryptoFileSystem;
|
||||||
this.state = state;
|
this.state = state;
|
||||||
this.lastKnownException = lastKnownException;
|
this.lastKnownException = lastKnownException;
|
||||||
|
this.mountService = mountService;
|
||||||
|
this.defaultMountFlags = mountService.map(MountService::getDefaultMountFlags);
|
||||||
this.stats = stats;
|
this.stats = stats;
|
||||||
this.displayName = Bindings.createStringBinding(this::getDisplayName, vaultSettings.displayName());
|
|
||||||
this.displayablePath = Bindings.createStringBinding(this::getDisplayablePath, vaultSettings.path());
|
this.displayablePath = Bindings.createStringBinding(this::getDisplayablePath, vaultSettings.path());
|
||||||
this.locked = Bindings.createBooleanBinding(this::isLocked, state);
|
this.locked = Bindings.createBooleanBinding(this::isLocked, state);
|
||||||
this.processing = Bindings.createBooleanBinding(this::isProcessing, state);
|
this.processing = Bindings.createBooleanBinding(this::isProcessing, state);
|
||||||
@@ -92,8 +108,8 @@ public class Vault {
|
|||||||
this.missing = Bindings.createBooleanBinding(this::isMissing, state);
|
this.missing = Bindings.createBooleanBinding(this::isMissing, state);
|
||||||
this.needsMigration = Bindings.createBooleanBinding(this::isNeedsMigration, state);
|
this.needsMigration = Bindings.createBooleanBinding(this::isNeedsMigration, state);
|
||||||
this.unknownError = Bindings.createBooleanBinding(this::isUnknownError, state);
|
this.unknownError = Bindings.createBooleanBinding(this::isUnknownError, state);
|
||||||
this.accessPoint = Bindings.createStringBinding(this::getAccessPoint, state);
|
this.mountPoint = Bindings.createObjectBinding(this::getMountPoint, state);
|
||||||
this.accessPointPresent = this.accessPoint.isNotEmpty();
|
this.windowsDriveLetters = windowsDriveLetters;
|
||||||
this.showingStats = new SimpleBooleanProperty(false);
|
this.showingStats = new SimpleBooleanProperty(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +159,46 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void unlock(MasterkeyLoader keyLoader) throws CryptoException, IOException, VolumeException, InvalidMountPointException {
|
private MountBuilder prepareMount(Path cryptoRoot) throws IOException {
|
||||||
|
var mountProvider = mountService.getValue();
|
||||||
|
var builder = mountProvider.forFileSystem(cryptoRoot);
|
||||||
|
|
||||||
|
for (var capability : mountProvider.capabilities()) {
|
||||||
|
switch (capability) {
|
||||||
|
case FILE_SYSTEM_NAME -> builder.setFileSystemName("crypto");
|
||||||
|
case LOOPBACK_PORT -> builder.setLoopbackPort(settings.port().get()); //TODO: move port from settings to vaultsettings?
|
||||||
|
case LOOPBACK_HOST_NAME -> builder.setLoopbackHostName("cryptomator-vault"); //TODO: Read from system property
|
||||||
|
case READ_ONLY -> builder.setReadOnly(vaultSettings.usesReadOnlyMode().get());
|
||||||
|
case MOUNT_FLAGS -> builder.setMountFlags(defaultMountFlags.getValue()); // TODO use custom mount flags (pre-populated with default mount flags)
|
||||||
|
case VOLUME_ID -> builder.setVolumeId(vaultSettings.getId());
|
||||||
|
case VOLUME_NAME -> builder.setVolumeName(vaultSettings.mountName().get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var userChosenMountPoint = vaultSettings.getMountPoint();
|
||||||
|
var defaultMountPointBase = env.getMountPointsDir().orElseThrow();
|
||||||
|
if (userChosenMountPoint == null) {
|
||||||
|
if (mountProvider.hasCapability(MOUNT_TO_SYSTEM_CHOSEN_PATH)) {
|
||||||
|
// no need to set a mount point
|
||||||
|
} else if (mountProvider.hasCapability(MOUNT_AS_DRIVE_LETTER)) {
|
||||||
|
builder.setMountpoint(windowsDriveLetters.getFirstDesiredAvailable().orElseThrow());
|
||||||
|
} else if (mountProvider.hasCapability(MOUNT_WITHIN_EXISTING_PARENT)) {
|
||||||
|
Files.createDirectories(defaultMountPointBase);
|
||||||
|
builder.setMountpoint(defaultMountPointBase);
|
||||||
|
} else if (mountProvider.hasCapability(MOUNT_TO_EXISTING_DIR) ) {
|
||||||
|
var mountPoint = defaultMountPointBase.resolve(vaultSettings.mountName().get());
|
||||||
|
Files.createDirectories(mountPoint);
|
||||||
|
builder.setMountpoint(mountPoint);
|
||||||
|
}
|
||||||
|
} else if (mountProvider.hasCapability(MOUNT_TO_EXISTING_DIR) || mountProvider.hasCapability(MOUNT_WITHIN_EXISTING_PARENT) || mountProvider.hasCapability(MOUNT_AS_DRIVE_LETTER)) {
|
||||||
|
// TODO: move the mount point away in case of MOUNT_WITHIN_EXISTING_PARENT?
|
||||||
|
builder.setMountpoint(userChosenMountPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void unlock(MasterkeyLoader keyLoader) throws CryptoException, IOException, MountFailedException {
|
||||||
if (cryptoFileSystem.get() != null) {
|
if (cryptoFileSystem.get() != null) {
|
||||||
throw new IllegalStateException("Already unlocked.");
|
throw new IllegalStateException("Already unlocked.");
|
||||||
}
|
}
|
||||||
@@ -151,9 +206,10 @@ public class Vault {
|
|||||||
boolean success = false;
|
boolean success = false;
|
||||||
try {
|
try {
|
||||||
cryptoFileSystem.set(fs);
|
cryptoFileSystem.set(fs);
|
||||||
volume = volumeProvider.get();
|
var rootPath = fs.getRootDirectories().iterator().next();
|
||||||
volume.mount(fs, getEffectiveMountFlags(), this::lockOnVolumeExit);
|
var supportsForcedUnmount = mountService.getValue().hasCapability(MountCapability.UNMOUNT_FORCED);
|
||||||
success = true;
|
var mountHandle = new MountHandle(prepareMount(rootPath).mount(), supportsForcedUnmount);
|
||||||
|
success = this.mountHandle.compareAndSet(null, mountHandle);
|
||||||
} finally {
|
} finally {
|
||||||
if (!success) {
|
if (!success) {
|
||||||
destroyCryptoFileSystem();
|
destroyCryptoFileSystem();
|
||||||
@@ -161,37 +217,28 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void lockOnVolumeExit(Throwable t) {
|
|
||||||
LOG.info("Unmounted vault '{}'", getDisplayName());
|
|
||||||
destroyCryptoFileSystem();
|
|
||||||
state.set(VaultState.Value.LOCKED);
|
|
||||||
if (t != null) {
|
|
||||||
LOG.warn("Unexpected unmount and lock of vault " + getDisplayName(), t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void lock(boolean forced) throws VolumeException, LockNotCompletedException {
|
public synchronized void lock(boolean forced) throws UnmountFailedException, IOException {
|
||||||
//initiate unmount
|
var mountHandle = this.mountHandle.get();
|
||||||
if (forced && volume.supportsForcedUnmount()) {
|
if (mountHandle == null) {
|
||||||
volume.unmountForced();
|
//TODO: noop or InvalidStateException?
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (forced && mountHandle.supportsUnmountForced) {
|
||||||
|
mountHandle.mount.unmountForced();
|
||||||
} else {
|
} else {
|
||||||
volume.unmount();
|
mountHandle.mount.unmount();
|
||||||
}
|
}
|
||||||
|
|
||||||
//wait for lockOnVolumeExit to be executed
|
|
||||||
try {
|
try {
|
||||||
boolean locked = state.awaitState(VaultState.Value.LOCKED, 3000, TimeUnit.MILLISECONDS);
|
mountHandle.mount.close();
|
||||||
if (!locked) {
|
} finally {
|
||||||
throw new LockNotCompletedException("Locking of vault " + this.getDisplayName() + " still in progress.");
|
destroyCryptoFileSystem();
|
||||||
}
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
throw new LockNotCompletedException(e);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void reveal(Volume.Revealer vaultRevealer) throws VolumeException {
|
this.mountHandle.set(null);
|
||||||
volume.reveal(vaultRevealer);
|
LOG.info("Locked vault '{}'", getDisplayName());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ******************************************************************************
|
// ******************************************************************************
|
||||||
@@ -266,33 +313,21 @@ public class Vault {
|
|||||||
return state.get() == VaultState.Value.ERROR;
|
return state.get() == VaultState.Value.ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringBinding displayNameProperty() {
|
public ReadOnlyStringProperty displayNameProperty() {
|
||||||
return displayName;
|
return vaultSettings.displayName();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDisplayName() {
|
public String getDisplayName() {
|
||||||
return vaultSettings.displayName().get();
|
return vaultSettings.displayName().get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringBinding accessPointProperty() {
|
public ObjectBinding<Mountpoint> mountPointProperty() {
|
||||||
return accessPoint;
|
return mountPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAccessPoint() {
|
public Mountpoint getMountPoint() {
|
||||||
if (state.getValue() == VaultState.Value.UNLOCKED) {
|
var handle = mountHandle.get();
|
||||||
assert volume != null;
|
return handle == null ? null : handle.mount.getMountpoint();
|
||||||
return volume.getMountPoint().orElse(Path.of("")).toString();
|
|
||||||
} else {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public BooleanBinding accessPointPresentProperty() {
|
|
||||||
return accessPointPresent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isAccessPointPresent() {
|
|
||||||
return accessPointPresent.get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringBinding displayablePathProperty() {
|
public StringBinding displayablePathProperty() {
|
||||||
@@ -315,7 +350,7 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isShowingStats() {
|
public boolean isShowingStats() {
|
||||||
return accessPointPresent.get();
|
return mountHandle.get() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -344,18 +379,18 @@ public class Vault {
|
|||||||
return !Strings.isNullOrEmpty(vaultSettings.mountFlags().get());
|
return !Strings.isNullOrEmpty(vaultSettings.mountFlags().get());
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringBinding defaultMountFlagsProperty() {
|
public ObservableValue<String> defaultMountFlagsProperty() {
|
||||||
return defaultMountFlags;
|
return defaultMountFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDefaultMountFlags() {
|
public String getDefaultMountFlags() {
|
||||||
return defaultMountFlags.get();
|
return defaultMountFlags.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getEffectiveMountFlags() {
|
public String getEffectiveMountFlags() {
|
||||||
String mountFlags = vaultSettings.mountFlags().get();
|
String mountFlags = vaultSettings.mountFlags().get();
|
||||||
if (Strings.isNullOrEmpty(mountFlags)) {
|
if (Strings.isNullOrEmpty(mountFlags)) {
|
||||||
return getDefaultMountFlags();
|
return ""; //TODO: should the provider provide dem defaults??
|
||||||
} else {
|
} else {
|
||||||
return mountFlags;
|
return mountFlags;
|
||||||
}
|
}
|
||||||
@@ -373,10 +408,6 @@ public class Vault {
|
|||||||
return vaultSettings.getId();
|
return vaultSettings.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<Volume> getVolume() {
|
|
||||||
return Optional.ofNullable(this.volume);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ******************************************************************************
|
// ******************************************************************************
|
||||||
// Hashcode / Equals
|
// Hashcode / Equals
|
||||||
// *******************************************************************************/
|
// *******************************************************************************/
|
||||||
@@ -395,7 +426,15 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* TODO: reactivate/ needed at all?
|
||||||
public boolean supportsForcedUnmount() {
|
public boolean supportsForcedUnmount() {
|
||||||
return volume.supportsForcedUnmount();
|
return volume.supportsForcedUnmount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
private record MountHandle(Mount mount, boolean supportsUnmountForced) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -8,13 +8,12 @@ package org.cryptomator.common.vaults;
|
|||||||
import dagger.BindsInstance;
|
import dagger.BindsInstance;
|
||||||
import dagger.Subcomponent;
|
import dagger.Subcomponent;
|
||||||
import org.cryptomator.common.Nullable;
|
import org.cryptomator.common.Nullable;
|
||||||
import org.cryptomator.common.mountpoint.MountPointChooserModule;
|
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
|
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
|
||||||
@PerVault
|
@PerVault
|
||||||
@Subcomponent(modules = {VaultModule.class, MountPointChooserModule.class})
|
@Subcomponent(modules = {VaultModule.class})
|
||||||
public interface VaultComponent {
|
public interface VaultComponent {
|
||||||
|
|
||||||
Vault vault();
|
Vault vault();
|
||||||
|
|||||||
@@ -7,26 +7,14 @@ package org.cryptomator.common.vaults;
|
|||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.cryptomator.common.Nullable;
|
import org.cryptomator.common.Nullable;
|
||||||
import org.cryptomator.common.settings.Settings;
|
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
|
||||||
import org.cryptomator.common.settings.VolumeImpl;
|
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javafx.beans.binding.Bindings;
|
|
||||||
import javafx.beans.binding.StringBinding;
|
|
||||||
import javafx.beans.property.BooleanProperty;
|
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@@ -47,130 +35,4 @@ public class VaultModule {
|
|||||||
return new SimpleObjectProperty<>(initialErrorCause);
|
return new SimpleObjectProperty<>(initialErrorCause);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
public Volume provideVolume(Settings settings, WebDavVolume webDavVolume, FuseVolume fuseVolume, DokanyVolume dokanyVolume) {
|
|
||||||
VolumeImpl preferredImpl = settings.preferredVolumeImpl().get();
|
|
||||||
if (VolumeImpl.DOKANY == preferredImpl && dokanyVolume.isSupported()) {
|
|
||||||
return dokanyVolume;
|
|
||||||
} else if (VolumeImpl.FUSE == preferredImpl && fuseVolume.isSupported()) {
|
|
||||||
return fuseVolume;
|
|
||||||
} else {
|
|
||||||
if (VolumeImpl.WEBDAV != preferredImpl) {
|
|
||||||
LOG.warn("Using WebDAV, because {} is not supported.", preferredImpl.getDisplayName());
|
|
||||||
}
|
|
||||||
assert webDavVolume.isSupported();
|
|
||||||
return webDavVolume;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@PerVault
|
|
||||||
@DefaultMountFlags
|
|
||||||
public StringBinding provideDefaultMountFlags(Settings settings, VaultSettings vaultSettings) {
|
|
||||||
ObjectProperty<VolumeImpl> preferredVolumeImpl = settings.preferredVolumeImpl();
|
|
||||||
StringBinding mountName = vaultSettings.mountName();
|
|
||||||
BooleanProperty readOnly = vaultSettings.usesReadOnlyMode();
|
|
||||||
|
|
||||||
return Bindings.createStringBinding(() -> {
|
|
||||||
VolumeImpl v = preferredVolumeImpl.get();
|
|
||||||
if (v == VolumeImpl.FUSE && SystemUtils.IS_OS_MAC) {
|
|
||||||
return getMacFuseDefaultMountFlags(mountName, readOnly);
|
|
||||||
} else if (v == VolumeImpl.FUSE && SystemUtils.IS_OS_LINUX) {
|
|
||||||
return getLinuxFuseDefaultMountFlags(readOnly);
|
|
||||||
} else if (v == VolumeImpl.FUSE && SystemUtils.IS_OS_WINDOWS) {
|
|
||||||
return getWindowsFuseDefaultMountFlags(mountName, readOnly);
|
|
||||||
} else if (v == VolumeImpl.DOKANY && SystemUtils.IS_OS_WINDOWS) {
|
|
||||||
return getDokanyDefaultMountFlags(readOnly);
|
|
||||||
} else {
|
|
||||||
return "--flags-supported-on-FUSE-or-DOKANY-only";
|
|
||||||
}
|
|
||||||
}, mountName, readOnly, preferredVolumeImpl);
|
|
||||||
}
|
|
||||||
|
|
||||||
// see: https://github.com/osxfuse/osxfuse/wiki/Mount-options
|
|
||||||
private String getMacFuseDefaultMountFlags(StringBinding mountName, ReadOnlyBooleanProperty readOnly) {
|
|
||||||
assert SystemUtils.IS_OS_MAC_OSX;
|
|
||||||
StringBuilder flags = new StringBuilder();
|
|
||||||
if (readOnly.get()) {
|
|
||||||
flags.append(" -ordonly");
|
|
||||||
}
|
|
||||||
flags.append(" -ovolname=").append('"').append(mountName.get()).append('"');
|
|
||||||
flags.append(" -oatomic_o_trunc");
|
|
||||||
flags.append(" -oauto_xattr");
|
|
||||||
flags.append(" -oauto_cache");
|
|
||||||
flags.append(" -onoappledouble"); // vastly impacts performance for some reason...
|
|
||||||
flags.append(" -odefault_permissions"); // let the kernel assume permissions based on file attributes etc
|
|
||||||
|
|
||||||
try {
|
|
||||||
Path userHome = Paths.get(System.getProperty("user.home"));
|
|
||||||
int uid = (int) Files.getAttribute(userHome, "unix:uid");
|
|
||||||
int gid = (int) Files.getAttribute(userHome, "unix:gid");
|
|
||||||
flags.append(" -ouid=").append(uid);
|
|
||||||
flags.append(" -ogid=").append(gid);
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.error("Could not read uid/gid from USER_HOME", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return flags.toString().strip();
|
|
||||||
}
|
|
||||||
|
|
||||||
// see https://manpages.debian.org/testing/fuse/mount.fuse.8.en.html
|
|
||||||
private String getLinuxFuseDefaultMountFlags(ReadOnlyBooleanProperty readOnly) {
|
|
||||||
assert SystemUtils.IS_OS_LINUX;
|
|
||||||
StringBuilder flags = new StringBuilder();
|
|
||||||
if (readOnly.get()) {
|
|
||||||
flags.append(" -oro");
|
|
||||||
}
|
|
||||||
flags.append(" -oauto_unmount");
|
|
||||||
|
|
||||||
try {
|
|
||||||
Path userHome = Paths.get(System.getProperty("user.home"));
|
|
||||||
int uid = (int) Files.getAttribute(userHome, "unix:uid");
|
|
||||||
int gid = (int) Files.getAttribute(userHome, "unix:gid");
|
|
||||||
flags.append(" -ouid=").append(uid);
|
|
||||||
flags.append(" -ogid=").append(gid);
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.error("Could not read uid/gid from USER_HOME", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return flags.toString().strip();
|
|
||||||
}
|
|
||||||
|
|
||||||
// see https://github.com/billziss-gh/winfsp/blob/5d0b10d0b643652c00ebb4704dc2bb28e7244973/src/dll/fuse/fuse_main.c#L53-L62 for syntax guide
|
|
||||||
// see https://github.com/billziss-gh/winfsp/blob/5d0b10d0b643652c00ebb4704dc2bb28e7244973/src/dll/fuse/fuse.c#L295-L319 for options (-o <...>)
|
|
||||||
// see https://github.com/billziss-gh/winfsp/wiki/Frequently-Asked-Questions/5ba00e4be4f5e938eaae6ef1500b331de12dee77 (FUSE 4.) on why the given defaults were chosen
|
|
||||||
private String getWindowsFuseDefaultMountFlags(StringBinding mountName, ReadOnlyBooleanProperty readOnly) {
|
|
||||||
assert SystemUtils.IS_OS_WINDOWS;
|
|
||||||
StringBuilder flags = new StringBuilder();
|
|
||||||
|
|
||||||
//WinFSP has no explicit "readonly"-option, nut not setting the group/user-id has the same effect, tho.
|
|
||||||
//So for the time being not setting them is the way to go...
|
|
||||||
//See: https://github.com/billziss-gh/winfsp/issues/319
|
|
||||||
if (!readOnly.get()) {
|
|
||||||
flags.append(" -ouid=-1");
|
|
||||||
flags.append(" -ogid=11");
|
|
||||||
}
|
|
||||||
flags.append(" -ovolname=").append('"').append(mountName.get()).append('"');
|
|
||||||
//Dokany requires this option to be set, WinFSP doesn't seem to share this peculiarity,
|
|
||||||
//but the option exists. Let's keep this here in case we need it.
|
|
||||||
// flags.append(" -oThreadCount=").append(5);
|
|
||||||
|
|
||||||
return flags.toString().strip();
|
|
||||||
}
|
|
||||||
|
|
||||||
// see https://github.com/cryptomator/dokany-nio-adapter/blob/develop/src/main/java/org/cryptomator/frontend/dokany/MountUtil.java#L30-L34
|
|
||||||
private String getDokanyDefaultMountFlags(ReadOnlyBooleanProperty readOnly) {
|
|
||||||
assert SystemUtils.IS_OS_WINDOWS;
|
|
||||||
StringBuilder flags = new StringBuilder();
|
|
||||||
flags.append(" --options CURRENT_SESSION");
|
|
||||||
if (readOnly.get()) {
|
|
||||||
flags.append(",WRITE_PROTECTION");
|
|
||||||
}
|
|
||||||
flags.append(" --thread-count 5");
|
|
||||||
flags.append(" --timeout 10000");
|
|
||||||
flags.append(" --allocation-unit-size 4096");
|
|
||||||
flags.append(" --sector-size 4096");
|
|
||||||
return flags.toString().strip();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,102 +0,0 @@
|
|||||||
package org.cryptomator.common.vaults;
|
|
||||||
|
|
||||||
import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
|
||||||
import org.cryptomator.common.settings.VolumeImpl;
|
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.CompletionStage;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Takes a Volume and uses it to mount an unlocked vault
|
|
||||||
*/
|
|
||||||
public interface Volume {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks in constant time whether this volume type is supported on the system running Cryptomator.
|
|
||||||
*
|
|
||||||
* @return true if this volume can be mounted
|
|
||||||
*/
|
|
||||||
boolean isSupported();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the corresponding enum type of the {@link VolumeImpl volume implementation ("VolumeImpl")} that is implemented by this Volume.
|
|
||||||
*
|
|
||||||
* @return the type of implementation as defined by the {@link VolumeImpl VolumeImpl enum}
|
|
||||||
*/
|
|
||||||
VolumeImpl getImplementationType();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param fs
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
void mount(CryptoFileSystem fs, String mountFlags, Consumer<Throwable> onExitAction) throws IOException, VolumeException, InvalidMountPointException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reveals the mounted volume.
|
|
||||||
* <p>
|
|
||||||
* The given {@code revealer} might be used to do it, but not necessarily.
|
|
||||||
*
|
|
||||||
* @param revealer An object capable of revealing the location of the mounted vault to view the content (e.g. in the default file browser).
|
|
||||||
* @throws VolumeException
|
|
||||||
*/
|
|
||||||
void reveal(Revealer revealer) throws VolumeException;
|
|
||||||
|
|
||||||
void unmount() throws VolumeException;
|
|
||||||
|
|
||||||
Optional<Path> getMountPoint();
|
|
||||||
|
|
||||||
MountPointRequirement getMountPointRequirement();
|
|
||||||
|
|
||||||
// optional forced unmounting:
|
|
||||||
|
|
||||||
default boolean supportsForcedUnmount() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
default void unmountForced() throws VolumeException {
|
|
||||||
throw new VolumeException("Operation not supported.");
|
|
||||||
}
|
|
||||||
|
|
||||||
static VolumeImpl[] getCurrentSupportedAdapters() {
|
|
||||||
return Stream.of(VolumeImpl.values()).filter(impl -> switch (impl) {
|
|
||||||
case WEBDAV -> WebDavVolume.isSupportedStatic();
|
|
||||||
case DOKANY -> DokanyVolume.isSupportedStatic();
|
|
||||||
case FUSE -> FuseVolume.isSupportedStatic();
|
|
||||||
}).toArray(VolumeImpl[]::new);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exception thrown when a volume-specific command such as mount/unmount/reveal failed.
|
|
||||||
*/
|
|
||||||
class VolumeException extends Exception {
|
|
||||||
|
|
||||||
public VolumeException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public VolumeException(Throwable cause) {
|
|
||||||
super(cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public VolumeException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hides and unifies the different Revealer implementations in the different nio-adapters.
|
|
||||||
*/
|
|
||||||
@FunctionalInterface
|
|
||||||
interface Revealer {
|
|
||||||
|
|
||||||
void reveal(Path p) throws VolumeException;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
package org.cryptomator.common.vaults;
|
|
||||||
|
|
||||||
|
|
||||||
import com.google.common.base.CharMatcher;
|
|
||||||
import org.cryptomator.common.settings.Settings;
|
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
|
||||||
import org.cryptomator.common.settings.VolumeImpl;
|
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
|
||||||
import org.cryptomator.frontend.webdav.WebDavServer;
|
|
||||||
import org.cryptomator.frontend.webdav.mount.MountParams;
|
|
||||||
import org.cryptomator.frontend.webdav.mount.Mounter;
|
|
||||||
import org.cryptomator.frontend.webdav.servlet.WebDavServletController;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Provider;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
public class WebDavVolume implements Volume {
|
|
||||||
|
|
||||||
private static final String LOCALHOST_ALIAS = "cryptomator-vault";
|
|
||||||
|
|
||||||
private final Provider<WebDavServer> serverProvider;
|
|
||||||
private final VaultSettings vaultSettings;
|
|
||||||
private final Settings settings;
|
|
||||||
private final WindowsDriveLetters windowsDriveLetters;
|
|
||||||
|
|
||||||
private WebDavServer server;
|
|
||||||
private WebDavServletController servlet;
|
|
||||||
private Mounter.Mount mount;
|
|
||||||
private Consumer<Throwable> onExitAction;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public WebDavVolume(Provider<WebDavServer> serverProvider, VaultSettings vaultSettings, Settings settings, WindowsDriveLetters windowsDriveLetters) {
|
|
||||||
this.serverProvider = serverProvider;
|
|
||||||
this.vaultSettings = vaultSettings;
|
|
||||||
this.settings = settings;
|
|
||||||
this.windowsDriveLetters = windowsDriveLetters;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mount(CryptoFileSystem fs, String mountFlags, Consumer<Throwable> onExitAction) throws VolumeException {
|
|
||||||
startServlet(fs);
|
|
||||||
mountServlet();
|
|
||||||
this.onExitAction = onExitAction;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startServlet(CryptoFileSystem fs) {
|
|
||||||
if (server == null) {
|
|
||||||
server = serverProvider.get();
|
|
||||||
}
|
|
||||||
if (!server.isRunning()) {
|
|
||||||
server.start();
|
|
||||||
}
|
|
||||||
CharMatcher acceptable = CharMatcher.inRange('0', '9').or(CharMatcher.inRange('A', 'Z')).or(CharMatcher.inRange('a', 'z'));
|
|
||||||
String urlConformMountName = acceptable.negate().collapseFrom(vaultSettings.mountName().get(), '_');
|
|
||||||
servlet = server.createWebDavServlet(fs.getPath("/"), vaultSettings.getId() + "/" + urlConformMountName);
|
|
||||||
servlet.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void mountServlet() throws VolumeException {
|
|
||||||
if (servlet == null) {
|
|
||||||
throw new IllegalStateException("Mounting requires unlocked WebDAV servlet.");
|
|
||||||
}
|
|
||||||
|
|
||||||
//on windows, prevent an automatic drive letter selection in the upstream library. Either we choose already a specific one or there is no free.
|
|
||||||
Supplier<String> driveLetterSupplier;
|
|
||||||
if (System.getProperty("os.name").toLowerCase().contains("windows") && vaultSettings.winDriveLetter().isEmpty().get()) {
|
|
||||||
driveLetterSupplier = () -> windowsDriveLetters.getDesiredAvailableDriveLetter().orElse(null);
|
|
||||||
} else {
|
|
||||||
driveLetterSupplier = () -> vaultSettings.winDriveLetter().get();
|
|
||||||
}
|
|
||||||
|
|
||||||
MountParams mountParams = MountParams.create() //
|
|
||||||
.withWindowsDriveLetter(driveLetterSupplier.get()) //
|
|
||||||
.withPreferredGvfsScheme(settings.preferredGvfsScheme().get().getPrefix())//
|
|
||||||
.withWebdavHostname(getLocalhostAliasOrNull()) //
|
|
||||||
.build();
|
|
||||||
try {
|
|
||||||
this.mount = servlet.mount(mountParams); // might block this thread for a while
|
|
||||||
} catch (Mounter.CommandFailedException e) {
|
|
||||||
throw new VolumeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reveal(Revealer revealer) throws VolumeException {
|
|
||||||
try {
|
|
||||||
mount.reveal(revealer::reveal);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new VolumeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void unmount() throws VolumeException {
|
|
||||||
try {
|
|
||||||
mount.unmount();
|
|
||||||
} catch (Mounter.CommandFailedException e) {
|
|
||||||
throw new VolumeException(e);
|
|
||||||
}
|
|
||||||
cleanup();
|
|
||||||
onExitAction.accept(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void unmountForced() throws VolumeException {
|
|
||||||
try {
|
|
||||||
mount.forced().orElseThrow(IllegalStateException::new).unmount();
|
|
||||||
} catch (Mounter.CommandFailedException e) {
|
|
||||||
throw new VolumeException(e);
|
|
||||||
}
|
|
||||||
cleanup();
|
|
||||||
onExitAction.accept(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<Path> getMountPoint() {
|
|
||||||
return mount.getMountPoint();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MountPointRequirement getMountPointRequirement() {
|
|
||||||
return MountPointRequirement.NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getLocalhostAliasOrNull() {
|
|
||||||
try {
|
|
||||||
InetAddress alias = InetAddress.getByName(LOCALHOST_ALIAS);
|
|
||||||
if (alias.getHostAddress().equals("127.0.0.1")) {
|
|
||||||
return LOCALHOST_ALIAS;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void cleanup() {
|
|
||||||
if (servlet != null) {
|
|
||||||
servlet.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSupported() {
|
|
||||||
return WebDavVolume.isSupportedStatic();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public VolumeImpl getImplementationType() {
|
|
||||||
return VolumeImpl.WEBDAV;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsForcedUnmount() {
|
|
||||||
return mount != null && mount.forced().isPresent();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static boolean isSupportedStatic() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the accompanying LICENSE file.
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.cryptomator.common.vaults;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
|
||||||
import com.google.common.collect.Sets;
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
import java.nio.file.FileSystems;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.IntStream;
|
|
||||||
import java.util.stream.StreamSupport;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public final class WindowsDriveLetters {
|
|
||||||
|
|
||||||
private static final Set<String> A_TO_Z;
|
|
||||||
|
|
||||||
static {
|
|
||||||
try (IntStream stream = IntStream.rangeClosed('A', 'Z')) {
|
|
||||||
A_TO_Z = stream.mapToObj(i -> String.valueOf((char) i)).collect(ImmutableSet.toImmutableSet());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public WindowsDriveLetters() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<String> getAllDriveLetters() {
|
|
||||||
return A_TO_Z;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<String> getOccupiedDriveLetters() {
|
|
||||||
if (!SystemUtils.IS_OS_WINDOWS) {
|
|
||||||
return Set.of();
|
|
||||||
} else {
|
|
||||||
Iterable<Path> rootDirs = FileSystems.getDefault().getRootDirectories();
|
|
||||||
return StreamSupport.stream(rootDirs.spliterator(), false).map(p -> p.toString().substring(0, 1)).collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<String> getAvailableDriveLetters() {
|
|
||||||
return Sets.difference(getAllDriveLetters(), getOccupiedDriveLetters());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<String> getAvailableDriveLetter() {
|
|
||||||
return getAvailableDriveLetters().stream().findFirst();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<Path> getAvailableDriveLetterPath() {
|
|
||||||
return getAvailableDriveLetter().map(this::toPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Skips A and B and only returns them if all other are occupied.
|
|
||||||
*
|
|
||||||
* @return an Optional containing either the letter of a free drive letter or empty, if none is available
|
|
||||||
*/
|
|
||||||
public Optional<String> getDesiredAvailableDriveLetter() {
|
|
||||||
var availableDriveLetters = getAvailableDriveLetters();
|
|
||||||
var optString = availableDriveLetters.stream().filter(s -> !(s.equals("A") || s.equals("B"))).findFirst();
|
|
||||||
return optString.or(() -> availableDriveLetters.stream().findFirst());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Skips A and B and only returns them if all other are occupied.
|
|
||||||
*
|
|
||||||
* @return an Optional containing either the path to a free drive letter or empty, if none is available
|
|
||||||
*/
|
|
||||||
public Optional<Path> getDesiredAvailableDriveLetterPath() {
|
|
||||||
return getDesiredAvailableDriveLetter().map(this::toPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Path toPath(String driveLetter) {
|
|
||||||
return Path.of(driveLetter + ":\\");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -15,9 +15,9 @@ public class SupportedLanguages {
|
|||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(SupportedLanguages.class);
|
private static final Logger LOG = LoggerFactory.getLogger(SupportedLanguages.class);
|
||||||
// these are BCP 47 language codes, not ISO. Note the "-" instead of the "_":
|
// these are BCP 47 language codes, not ISO. Note the "-" instead of the "_":
|
||||||
public static final List<String> LANGUAGAE_TAGS = List.of("en", "ar", "bn", "bs", "ca", "cs", "de", "el", "es", "fil", "fr", "gl", "he", //
|
public static final List<String> LANGUAGAE_TAGS = List.of("en", "ar", "be", "bn", "bs", "ca", "cs", "da", "de", "el", "es", "fil", "fa", "fr", "gl", "he", //
|
||||||
"hi", "hr", "hu", "id", "it", "ja", "ko", "lv", "mk", "nb", "nl", "nn", "no", "pa", "pl", "pt", "pt-BR", "ro", "ru", "sk", "sr", //
|
"hi", "hr", "hu", "id", "it", "ja", "ko", "lv", "mk", "nb", "nl", "nn", "no", "pa", "pl", "pt", "pt-BR", "ro", "ru", "si", "sk", "sr", "sr-Latn", "sv", "sw", //
|
||||||
"sr-Latn", "sv", "sw", "ta", "te", "th", "tr", "uk", "zh", "zh-HK", "zh-TW");
|
"ta", "te", "th", "tr", "uk", "vi", "zh", "zh-HK", "zh-TW");
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private final String preferredLanguage;
|
private final String preferredLanguage;
|
||||||
|
|||||||
@@ -40,19 +40,15 @@ public class CreateNewVaultNameController implements FxController {
|
|||||||
this.chooseLocationScene = chooseLocationScene;
|
this.chooseLocationScene = chooseLocationScene;
|
||||||
this.vaultPath = vaultPath;
|
this.vaultPath = vaultPath;
|
||||||
this.vaultName = vaultName;
|
this.vaultName = vaultName;
|
||||||
this.validVaultName = Bindings.createBooleanBinding(this::isValidVaultNameInternal, vaultName);
|
this.validVaultName = Bindings.createBooleanBinding(this::isValidVaultName, vaultName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
vaultName.bind(textField.textProperty());
|
vaultName.bindBidirectional(textField.textProperty());
|
||||||
vaultName.addListener(this::vaultNameChanged);
|
vaultName.addListener(this::vaultNameChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isValidVaultNameInternal() {
|
|
||||||
return vaultName.get() != null && VALID_NAME_PATTERN.matcher(vaultName.get().trim()).matches();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void vaultNameChanged(@SuppressWarnings("unused") Observable observable) {
|
private void vaultNameChanged(@SuppressWarnings("unused") Observable observable) {
|
||||||
if (isValidVaultName()) {
|
if (isValidVaultName()) {
|
||||||
if (vaultPath.get() != null) {
|
if (vaultPath.get() != null) {
|
||||||
@@ -70,6 +66,7 @@ public class CreateNewVaultNameController implements FxController {
|
|||||||
@FXML
|
@FXML
|
||||||
public void next() {
|
public void next() {
|
||||||
window.setScene(chooseLocationScene.get());
|
window.setScene(chooseLocationScene.get());
|
||||||
|
vaultName.set(vaultName.get().trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Getter/Setter */
|
/* Getter/Setter */
|
||||||
@@ -79,7 +76,7 @@ public class CreateNewVaultNameController implements FxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isValidVaultName() {
|
public boolean isValidVaultName() {
|
||||||
return validVaultName.get();
|
return vaultName.get() != null && VALID_NAME_PATTERN.matcher(vaultName.get().trim()).matches();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -102,7 +102,7 @@ public class CreateNewVaultPasswordController implements FxController {
|
|||||||
this.masterkeyFileAccess = masterkeyFileAccess;
|
this.masterkeyFileAccess = masterkeyFileAccess;
|
||||||
this.processing = new SimpleBooleanProperty();
|
this.processing = new SimpleBooleanProperty();
|
||||||
this.readyToCreateVault = new SimpleBooleanProperty();
|
this.readyToCreateVault = new SimpleBooleanProperty();
|
||||||
this.createVaultButtonState = Bindings.createObjectBinding(this::getCreateVaultButtonState, processing);
|
this.createVaultButtonState = Bindings.when(processing).then(ContentDisplay.LEFT).otherwise(ContentDisplay.TEXT_ONLY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -231,6 +231,6 @@ public class CreateNewVaultPasswordController implements FxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ContentDisplay getCreateVaultButtonState() {
|
public ContentDisplay getCreateVaultButtonState() {
|
||||||
return processing.get() ? ContentDisplay.LEFT : ContentDisplay.TEXT_ONLY;
|
return createVaultButtonState.get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
package org.cryptomator.ui.common;
|
|
||||||
|
|
||||||
import dagger.Lazy;
|
|
||||||
import org.cryptomator.common.vaults.Volume;
|
|
||||||
import org.cryptomator.ui.fxapp.FxApplicationScoped;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javafx.application.Application;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
@FxApplicationScoped
|
|
||||||
public class HostServiceRevealer implements Volume.Revealer {
|
|
||||||
|
|
||||||
private final Lazy<Application> application;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public HostServiceRevealer(Lazy<Application> application) {
|
|
||||||
this.application = application;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reveal(Path p) throws Volume.VolumeException {
|
|
||||||
application.get().getHostServices().showDocument(p.toUri().toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.cryptomator.ui.common;
|
package org.cryptomator.ui.common;
|
||||||
|
|
||||||
import com.tobiasdiez.easybind.EasyBind;
|
|
||||||
import org.cryptomator.ui.controls.FontAwesome5IconView;
|
import org.cryptomator.ui.controls.FontAwesome5IconView;
|
||||||
import org.cryptomator.ui.controls.NiceSecurePasswordField;
|
import org.cryptomator.ui.controls.NiceSecurePasswordField;
|
||||||
|
|
||||||
@@ -42,7 +41,7 @@ public class NewPasswordController implements FxController {
|
|||||||
passwordStrength.bind(Bindings.createIntegerBinding(() -> strengthRater.computeRate(passwordField.getCharacters()), passwordField.textProperty()));
|
passwordStrength.bind(Bindings.createIntegerBinding(() -> strengthRater.computeRate(passwordField.getCharacters()), passwordField.textProperty()));
|
||||||
|
|
||||||
passwordStrengthLabel.graphicProperty().bind(Bindings.createObjectBinding(this::getIconViewForPasswordStrengthLabel, passwordField.textProperty(), passwordStrength));
|
passwordStrengthLabel.graphicProperty().bind(Bindings.createObjectBinding(this::getIconViewForPasswordStrengthLabel, passwordField.textProperty(), passwordStrength));
|
||||||
passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
|
passwordStrengthLabel.textProperty().bind(passwordStrength.map(strengthRater::getStrengthDescription));
|
||||||
|
|
||||||
BooleanBinding passwordsMatch = Bindings.createBooleanBinding(this::passwordFieldsMatch, passwordField.textProperty(), reenterField.textProperty());
|
BooleanBinding passwordsMatch = Bindings.createBooleanBinding(this::passwordFieldsMatch, passwordField.textProperty(), reenterField.textProperty());
|
||||||
BooleanBinding reenterFieldNotEmpty = reenterField.textProperty().isNotEmpty();
|
BooleanBinding reenterFieldNotEmpty = reenterField.textProperty().isNotEmpty();
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
package org.cryptomator.ui.common;
|
package org.cryptomator.ui.common;
|
||||||
|
|
||||||
import org.cryptomator.common.vaults.LockNotCompletedException;
|
import dagger.Lazy;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.common.vaults.VaultState;
|
import org.cryptomator.common.vaults.VaultState;
|
||||||
import org.cryptomator.common.vaults.Volume;
|
import org.cryptomator.integrations.mount.Mountpoint;
|
||||||
|
import org.cryptomator.integrations.mount.UnmountFailedException;
|
||||||
import org.cryptomator.ui.fxapp.FxApplicationScoped;
|
import org.cryptomator.ui.fxapp.FxApplicationScoped;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javafx.application.Application;
|
||||||
|
import javafx.application.HostServices;
|
||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
@@ -24,13 +28,13 @@ public class VaultService {
|
|||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(VaultService.class);
|
private static final Logger LOG = LoggerFactory.getLogger(VaultService.class);
|
||||||
|
|
||||||
|
private final Lazy<Application> application;
|
||||||
private final ExecutorService executorService;
|
private final ExecutorService executorService;
|
||||||
private final HostServiceRevealer vaultRevealer;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public VaultService(ExecutorService executorService, HostServiceRevealer vaultRevealer) {
|
public VaultService(Lazy<Application> application, ExecutorService executorService) {
|
||||||
|
this.application = application;
|
||||||
this.executorService = executorService;
|
this.executorService = executorService;
|
||||||
this.vaultRevealer = vaultRevealer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reveal(Vault vault) {
|
public void reveal(Vault vault) {
|
||||||
@@ -43,7 +47,7 @@ public class VaultService {
|
|||||||
* @param vault The vault to reveal
|
* @param vault The vault to reveal
|
||||||
*/
|
*/
|
||||||
public Task<Vault> createRevealTask(Vault vault) {
|
public Task<Vault> createRevealTask(Vault vault) {
|
||||||
Task<Vault> task = new RevealVaultTask(vault, vaultRevealer);
|
Task<Vault> task = new RevealVaultTask(vault, application.get().getHostServices());
|
||||||
task.setOnSucceeded(evt -> LOG.info("Revealed {}", vault.getDisplayName()));
|
task.setOnSucceeded(evt -> LOG.info("Revealed {}", vault.getDisplayName()));
|
||||||
task.setOnFailed(evt -> LOG.error("Failed to reveal " + vault.getDisplayName(), evt.getSource().getException()));
|
task.setOnFailed(evt -> LOG.error("Failed to reveal " + vault.getDisplayName(), evt.getSource().getException()));
|
||||||
return task;
|
return task;
|
||||||
@@ -106,22 +110,21 @@ public class VaultService {
|
|||||||
private static class RevealVaultTask extends Task<Vault> {
|
private static class RevealVaultTask extends Task<Vault> {
|
||||||
|
|
||||||
private final Vault vault;
|
private final Vault vault;
|
||||||
private final Volume.Revealer revealer;
|
private final HostServices hostServices;
|
||||||
|
|
||||||
/**
|
public RevealVaultTask(Vault vault, HostServices hostServices) {
|
||||||
* @param vault The vault to lock
|
|
||||||
* @param revealer The object to use to show the vault content to the user.
|
|
||||||
*/
|
|
||||||
public RevealVaultTask(Vault vault, Volume.Revealer revealer) {
|
|
||||||
this.vault = vault;
|
this.vault = vault;
|
||||||
this.revealer = revealer;
|
this.hostServices = hostServices;
|
||||||
|
|
||||||
setOnFailed(evt -> LOG.error("Failed to reveal " + vault.getDisplayName(), getException()));
|
setOnFailed(evt -> LOG.error("Failed to reveal " + vault.getDisplayName(), getException()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Vault call() throws Volume.VolumeException {
|
protected Vault call() {
|
||||||
vault.reveal(revealer);
|
switch (vault.getMountPoint()) {
|
||||||
|
case null -> LOG.warn("Not currently mounted");
|
||||||
|
case Mountpoint.WithPath m -> hostServices.showDocument(m.uri().toString());
|
||||||
|
case Mountpoint.WithUri m -> LOG.info("Vault mounted at {}", m.uri()); // TODO show in UI?
|
||||||
|
}
|
||||||
return vault;
|
return vault;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -180,7 +183,7 @@ public class VaultService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Vault call() throws Volume.VolumeException, LockNotCompletedException {
|
protected Vault call() throws UnmountFailedException, IOException {
|
||||||
vault.lock(forced);
|
vault.lock(forced);
|
||||||
return vault;
|
return vault;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ public class NiceSecurePasswordField extends StackPane {
|
|||||||
nonPrintableCharsIcon.managedProperty().bind(passwordField.containingNonPrintableCharsProperty());
|
nonPrintableCharsIcon.managedProperty().bind(passwordField.containingNonPrintableCharsProperty());
|
||||||
|
|
||||||
revealPasswordIcon.setGlyph(FontAwesome5Icon.EYE);
|
revealPasswordIcon.setGlyph(FontAwesome5Icon.EYE);
|
||||||
revealPasswordIcon.glyphProperty().bind(Bindings.createObjectBinding(this::getRevealPasswordGlyph, revealPasswordButton.selectedProperty()));
|
revealPasswordIcon.glyphProperty().bind(Bindings.when(revealPasswordButton.selectedProperty()).then(FontAwesome5Icon.EYE_SLASH).otherwise(FontAwesome5Icon.EYE));
|
||||||
revealPasswordIcon.setGlyphSize(ICON_SIZE);
|
revealPasswordIcon.setGlyphSize(ICON_SIZE);
|
||||||
|
|
||||||
revealPasswordButton.setContentDisplay(ContentDisplay.LEFT);
|
revealPasswordButton.setContentDisplay(ContentDisplay.LEFT);
|
||||||
@@ -61,10 +61,6 @@ public class NiceSecurePasswordField extends StackPane {
|
|||||||
disabledProperty().addListener(this::disabledChanged);
|
disabledProperty().addListener(this::disabledChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
private FontAwesome5Icon getRevealPasswordGlyph() {
|
|
||||||
return revealPasswordButton.isSelected() ? FontAwesome5Icon.EYE_SLASH : FontAwesome5Icon.EYE;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void disabledChanged(@SuppressWarnings("unused") Observable observable) {
|
private void disabledChanged(@SuppressWarnings("unused") Observable observable) {
|
||||||
revealPasswordButton.setSelected(false);
|
revealPasswordButton.setSelected(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,9 @@ package org.cryptomator.ui.fxapp;
|
|||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import org.cryptomator.common.ShutdownHook;
|
import org.cryptomator.common.ShutdownHook;
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.common.vaults.LockNotCompletedException;
|
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.common.vaults.VaultState;
|
import org.cryptomator.common.vaults.VaultState;
|
||||||
import org.cryptomator.common.vaults.Volume;
|
import org.cryptomator.integrations.mount.UnmountFailedException;
|
||||||
import org.cryptomator.ui.common.VaultService;
|
import org.cryptomator.ui.common.VaultService;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -18,6 +17,7 @@ import javafx.collections.ObservableList;
|
|||||||
import java.awt.Desktop;
|
import java.awt.Desktop;
|
||||||
import java.awt.desktop.QuitResponse;
|
import java.awt.desktop.QuitResponse;
|
||||||
import java.awt.desktop.QuitStrategy;
|
import java.awt.desktop.QuitStrategy;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.EventObject;
|
import java.util.EventObject;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -128,10 +128,8 @@ public class FxApplicationTerminator {
|
|||||||
if (vault.isUnlocked()) {
|
if (vault.isUnlocked()) {
|
||||||
try {
|
try {
|
||||||
vault.lock(true);
|
vault.lock(true);
|
||||||
} catch (Volume.VolumeException e) {
|
} catch (UnmountFailedException | IOException e) {
|
||||||
LOG.error("Failed to unmount vault " + vault.getPath(), e);
|
LOG.error("Failed to unmount vault " + vault.getPath(), e);
|
||||||
} catch (LockNotCompletedException e) {
|
|
||||||
LOG.error("Failed to lock vault " + vault.getPath(), e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ package org.cryptomator.ui.health;
|
|||||||
import com.tobiasdiez.easybind.EasyBind;
|
import com.tobiasdiez.easybind.EasyBind;
|
||||||
import com.tobiasdiez.easybind.EasyObservableList;
|
import com.tobiasdiez.easybind.EasyObservableList;
|
||||||
import com.tobiasdiez.easybind.Subscription;
|
import com.tobiasdiez.easybind.Subscription;
|
||||||
import com.tobiasdiez.easybind.optional.OptionalBinding;
|
|
||||||
import org.cryptomator.cryptofs.health.api.DiagnosticResult;
|
import org.cryptomator.cryptofs.health.api.DiagnosticResult;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javafx.beans.binding.Binding;
|
import javafx.beans.binding.Binding;
|
||||||
|
import javafx.beans.binding.BooleanExpression;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
@@ -22,15 +22,15 @@ public class CheckDetailController implements FxController {
|
|||||||
|
|
||||||
private final EasyObservableList<Result> results;
|
private final EasyObservableList<Result> results;
|
||||||
private final ObjectProperty<Check> check;
|
private final ObjectProperty<Check> check;
|
||||||
private final OptionalBinding<Check.CheckState> checkState;
|
private final ObservableValue<Check.CheckState> checkState;
|
||||||
private final Binding<String> checkName;
|
private final ObservableValue<String> checkName;
|
||||||
private final Binding<Boolean> checkRunning;
|
private final BooleanExpression checkRunning;
|
||||||
private final Binding<Boolean> checkScheduled;
|
private final BooleanExpression checkScheduled;
|
||||||
private final Binding<Boolean> checkFinished;
|
private final BooleanExpression checkFinished;
|
||||||
private final Binding<Boolean> checkSkipped;
|
private final BooleanExpression checkSkipped;
|
||||||
private final Binding<Boolean> checkSucceeded;
|
private final BooleanExpression checkSucceeded;
|
||||||
private final Binding<Boolean> checkFailed;
|
private final BooleanExpression checkFailed;
|
||||||
private final Binding<Boolean> checkCancelled;
|
private final BooleanExpression checkCancelled;
|
||||||
private final Binding<Number> countOfWarnSeverity;
|
private final Binding<Number> countOfWarnSeverity;
|
||||||
private final Binding<Number> countOfCritSeverity;
|
private final Binding<Number> countOfCritSeverity;
|
||||||
private final Binding<Boolean> warnOrCritsExist;
|
private final Binding<Boolean> warnOrCritsExist;
|
||||||
@@ -44,15 +44,15 @@ public class CheckDetailController implements FxController {
|
|||||||
this.resultListCellFactory = resultListCellFactory;
|
this.resultListCellFactory = resultListCellFactory;
|
||||||
this.results = EasyBind.wrapList(FXCollections.observableArrayList());
|
this.results = EasyBind.wrapList(FXCollections.observableArrayList());
|
||||||
this.check = selectedTask;
|
this.check = selectedTask;
|
||||||
this.checkState = EasyBind.wrapNullable(selectedTask).mapObservable(Check::stateProperty);
|
this.checkState = selectedTask.flatMap(Check::stateProperty);
|
||||||
this.checkName = EasyBind.wrapNullable(selectedTask).map(Check::getName).orElse("");
|
this.checkName = selectedTask.map(Check::getName).orElse("");
|
||||||
this.checkRunning = checkState.map(Check.CheckState.RUNNING::equals).orElse(false);
|
this.checkRunning = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.RUNNING::equals).orElse(false));
|
||||||
this.checkScheduled = checkState.map(Check.CheckState.SCHEDULED::equals).orElse(false);
|
this.checkScheduled = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.SCHEDULED::equals).orElse(false));
|
||||||
this.checkSkipped = checkState.map(Check.CheckState.SKIPPED::equals).orElse(false);
|
this.checkSkipped =BooleanExpression.booleanExpression(checkState.map(Check.CheckState.SKIPPED::equals).orElse(false));
|
||||||
this.checkSucceeded = checkState.map(Check.CheckState.SUCCEEDED::equals).orElse(false);
|
this.checkSucceeded = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.SUCCEEDED::equals).orElse(false));
|
||||||
this.checkFailed = checkState.map(Check.CheckState.ERROR::equals).orElse(false);
|
this.checkFailed = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.ERROR::equals).orElse(false));
|
||||||
this.checkCancelled = checkState.map(Check.CheckState.CANCELLED::equals).orElse(false);
|
this.checkCancelled = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.CANCELLED::equals).orElse(false));
|
||||||
this.checkFinished = EasyBind.combine(checkSucceeded, checkFailed, checkCancelled, (a, b, c) -> a || b || c);
|
this.checkFinished = checkSucceeded.or(checkFailed).or(checkCancelled);
|
||||||
this.countOfWarnSeverity = results.reduce(countSeverity(DiagnosticResult.Severity.WARN));
|
this.countOfWarnSeverity = results.reduce(countSeverity(DiagnosticResult.Severity.WARN));
|
||||||
this.countOfCritSeverity = results.reduce(countSeverity(DiagnosticResult.Severity.CRITICAL));
|
this.countOfCritSeverity = results.reduce(countSeverity(DiagnosticResult.Severity.CRITICAL));
|
||||||
this.warnOrCritsExist = EasyBind.combine(checkSucceeded, countOfWarnSeverity, countOfCritSeverity, (suceeded, warns, crits) -> suceeded && (warns.longValue() > 0 || crits.longValue() > 0) );
|
this.warnOrCritsExist = EasyBind.combine(checkSucceeded, countOfWarnSeverity, countOfCritSeverity, (suceeded, warns, crits) -> suceeded && (warns.longValue() > 0 || crits.longValue() > 0) );
|
||||||
@@ -84,7 +84,7 @@ public class CheckDetailController implements FxController {
|
|||||||
return checkName.getValue();
|
return checkName.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Binding<String> checkNameProperty() {
|
public ObservableValue<String> checkNameProperty() {
|
||||||
return checkName;
|
return checkName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ public class CheckDetailController implements FxController {
|
|||||||
return checkRunning.getValue();
|
return checkRunning.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Binding<Boolean> checkRunningProperty() {
|
public BooleanExpression checkRunningProperty() {
|
||||||
return checkRunning;
|
return checkRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ public class CheckDetailController implements FxController {
|
|||||||
return checkFinished.getValue();
|
return checkFinished.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Binding<Boolean> checkFinishedProperty() {
|
public BooleanExpression checkFinishedProperty() {
|
||||||
return checkFinished;
|
return checkFinished;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +124,7 @@ public class CheckDetailController implements FxController {
|
|||||||
return checkScheduled.getValue();
|
return checkScheduled.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Binding<Boolean> checkScheduledProperty() {
|
public BooleanExpression checkScheduledProperty() {
|
||||||
return checkScheduled;
|
return checkScheduled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +132,7 @@ public class CheckDetailController implements FxController {
|
|||||||
return checkSkipped.getValue();
|
return checkSkipped.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Binding<Boolean> checkSkippedProperty() {
|
public BooleanExpression checkSkippedProperty() {
|
||||||
return checkSkipped;
|
return checkSkipped;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,7 +140,7 @@ public class CheckDetailController implements FxController {
|
|||||||
return checkSucceeded.getValue();
|
return checkSucceeded.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Binding<Boolean> checkSucceededProperty() {
|
public BooleanExpression checkSucceededProperty() {
|
||||||
return checkSucceeded;
|
return checkSucceeded;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,7 +148,7 @@ public class CheckDetailController implements FxController {
|
|||||||
return checkFailed.getValue();
|
return checkFailed.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Binding<Boolean> checkFailedProperty() {
|
public BooleanExpression checkFailedProperty() {
|
||||||
return checkFailed;
|
return checkFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,7 +164,7 @@ public class CheckDetailController implements FxController {
|
|||||||
return warnOrCritsExist.getValue();
|
return warnOrCritsExist.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Binding<Boolean> checkCancelledProperty() {
|
public BooleanExpression checkCancelledProperty() {
|
||||||
return checkCancelled;
|
return checkCancelled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
package org.cryptomator.ui.health;
|
package org.cryptomator.ui.health;
|
||||||
|
|
||||||
import com.tobiasdiez.easybind.EasyBind;
|
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javafx.beans.binding.Binding;
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.scene.control.CheckBox;
|
import javafx.scene.control.CheckBox;
|
||||||
|
|
||||||
public class CheckListCellController implements FxController {
|
public class CheckListCellController implements FxController {
|
||||||
|
|
||||||
|
|
||||||
private final ObjectProperty<Check> check;
|
private final ObjectProperty<Check> check;
|
||||||
private final Binding<String> checkName;
|
private final ObservableValue<Boolean> checkRunnable;
|
||||||
private final Binding<Boolean> checkRunnable;
|
private final ObservableValue<String> checkName;
|
||||||
|
|
||||||
/* FXML */
|
/* FXML */
|
||||||
public CheckBox checkbox;
|
public CheckBox checkbox;
|
||||||
@@ -23,8 +22,8 @@ public class CheckListCellController implements FxController {
|
|||||||
@Inject
|
@Inject
|
||||||
public CheckListCellController() {
|
public CheckListCellController() {
|
||||||
check = new SimpleObjectProperty<>();
|
check = new SimpleObjectProperty<>();
|
||||||
checkRunnable = EasyBind.wrapNullable(check).mapObservable(Check::stateProperty).map(Check.CheckState.RUNNABLE::equals).orElse(false);
|
checkRunnable = check.flatMap(Check::stateProperty).map(Check.CheckState.RUNNABLE::equals).orElse(false);
|
||||||
checkName = EasyBind.wrapNullable(check).map(Check::getName).orElse("");
|
checkName = check.map(Check::getName).orElse("");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
@@ -50,7 +49,7 @@ public class CheckListCellController implements FxController {
|
|||||||
check.set(c);
|
check.set(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Binding<String> checkNameProperty() {
|
public ObservableValue<String> checkNameProperty() {
|
||||||
return checkName;
|
return checkName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +57,7 @@ public class CheckListCellController implements FxController {
|
|||||||
return checkName.getValue();
|
return checkName.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Binding<Boolean> checkRunnableProperty() {
|
public ObservableValue<Boolean> checkRunnableProperty() {
|
||||||
return checkRunnable;
|
return checkRunnable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,13 +13,12 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.binding.Binding;
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.BooleanBinding;
|
import javafx.beans.binding.BooleanBinding;
|
||||||
import javafx.beans.binding.ObjectBinding;
|
import javafx.beans.binding.ObjectBinding;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.value.ObservableObjectValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.Tooltip;
|
import javafx.scene.control.Tooltip;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
@@ -38,10 +37,10 @@ public class ResultListCellController implements FxController {
|
|||||||
private final Logger LOG = LoggerFactory.getLogger(ResultListCellController.class);
|
private final Logger LOG = LoggerFactory.getLogger(ResultListCellController.class);
|
||||||
|
|
||||||
private final ObjectProperty<Result> result;
|
private final ObjectProperty<Result> result;
|
||||||
private final ObservableObjectValue<DiagnosticResult.Severity> severity;
|
private final ObservableValue<DiagnosticResult.Severity> severity;
|
||||||
private final Binding<String> description;
|
private final ObservableValue<String> description;
|
||||||
private final ResultFixApplier fixApplier;
|
private final ResultFixApplier fixApplier;
|
||||||
private final ObservableObjectValue<Result.FixState> fixState;
|
private final ObservableValue<Result.FixState> fixState;
|
||||||
private final ObjectBinding<FontAwesome5Icon> severityGlyph;
|
private final ObjectBinding<FontAwesome5Icon> severityGlyph;
|
||||||
private final ObjectBinding<FontAwesome5Icon> fixGlyph;
|
private final ObjectBinding<FontAwesome5Icon> fixGlyph;
|
||||||
private final BooleanBinding fixable;
|
private final BooleanBinding fixable;
|
||||||
@@ -62,10 +61,10 @@ public class ResultListCellController implements FxController {
|
|||||||
@Inject
|
@Inject
|
||||||
public ResultListCellController(ResultFixApplier fixApplier, ResourceBundle resourceBundle) {
|
public ResultListCellController(ResultFixApplier fixApplier, ResourceBundle resourceBundle) {
|
||||||
this.result = new SimpleObjectProperty<>(null);
|
this.result = new SimpleObjectProperty<>(null);
|
||||||
this.severity = EasyBind.wrapNullable(result).map(r -> r.diagnosis().getSeverity()).asOrdinary();
|
this.severity = result.map(Result::diagnosis).map(DiagnosticResult::getSeverity);
|
||||||
this.description = EasyBind.wrapNullable(result).map(Result::getDescription).orElse("");
|
this.description = result.map(Result::getDescription).orElse("");
|
||||||
this.fixApplier = fixApplier;
|
this.fixApplier = fixApplier;
|
||||||
this.fixState = EasyBind.wrapNullable(result).mapObservable(Result::fixState).asOrdinary();
|
this.fixState = result.flatMap(Result::fixState);
|
||||||
this.severityGlyph = Bindings.createObjectBinding(this::getSeverityGlyph, result);
|
this.severityGlyph = Bindings.createObjectBinding(this::getSeverityGlyph, result);
|
||||||
this.fixGlyph = Bindings.createObjectBinding(this::getFixGlyph, fixState);
|
this.fixGlyph = Bindings.createObjectBinding(this::getFixGlyph, fixState);
|
||||||
this.fixable = Bindings.createBooleanBinding(this::isFixable, fixState);
|
this.fixable = Bindings.createBooleanBinding(this::isFixable, fixState);
|
||||||
@@ -83,14 +82,15 @@ public class ResultListCellController implements FxController {
|
|||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
// see getGlyph() for relevant glyphs:
|
// see getGlyph() for relevant glyphs:
|
||||||
subscriptions.addAll(List.of(EasyBind.includeWhen(severityView.getStyleClass(), "glyph-icon-muted", Bindings.equal(severity, DiagnosticResult.Severity.INFO)), //
|
subscriptions.addAll(List.of(EasyBind.includeWhen(severityView.getStyleClass(), "glyph-icon-muted", severity.map(DiagnosticResult.Severity.INFO::equals).orElse(false)), //
|
||||||
EasyBind.includeWhen(severityView.getStyleClass(), "glyph-icon-primary", Bindings.equal(severity, DiagnosticResult.Severity.GOOD)), //
|
EasyBind.includeWhen(severityView.getStyleClass(), "glyph-icon-primary", severity.map(DiagnosticResult.Severity.GOOD::equals).orElse(false)), //
|
||||||
EasyBind.includeWhen(severityView.getStyleClass(), "glyph-icon-orange", Bindings.equal(severity, DiagnosticResult.Severity.WARN)), //
|
EasyBind.includeWhen(severityView.getStyleClass(), "glyph-icon-orange", severity.map(DiagnosticResult.Severity.WARN::equals).orElse(false)), //
|
||||||
EasyBind.includeWhen(severityView.getStyleClass(), "glyph-icon-red", Bindings.equal(severity, DiagnosticResult.Severity.CRITICAL)) //
|
EasyBind.includeWhen(severityView.getStyleClass(), "glyph-icon-red", severity.map(DiagnosticResult.Severity.CRITICAL::equals).orElse(false)) //
|
||||||
));
|
));
|
||||||
|
|
||||||
var animation = Animations.createDiscrete360Rotation(fixView);
|
var animation = Animations.createDiscrete360Rotation(fixView);
|
||||||
this.fixRunningRotator = AutoAnimator.animate(animation) //
|
this.fixRunningRotator = AutoAnimator.animate(animation) //
|
||||||
.onCondition(Bindings.equal(fixState, Result.FixState.FIXING)) //
|
.onCondition(fixing) //
|
||||||
.afterStop(() -> fixView.setRotate(0)) //
|
.afterStop(() -> fixView.setRotate(0)) //
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
@@ -127,7 +127,7 @@ public class ResultListCellController implements FxController {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Binding<String> descriptionProperty() {
|
public ObservableValue<String> descriptionProperty() {
|
||||||
return description;
|
return description;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,7 +173,7 @@ public class ResultListCellController implements FxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFixable() {
|
public boolean isFixable() {
|
||||||
return Result.FixState.FIXABLE.equals(fixState.get());
|
return Result.FixState.FIXABLE.equals(fixState.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanBinding fixingProperty() {
|
public BooleanBinding fixingProperty() {
|
||||||
@@ -181,7 +181,7 @@ public class ResultListCellController implements FxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFixing() {
|
public boolean isFixing() {
|
||||||
return Result.FixState.FIXING.equals(fixState.get());
|
return Result.FixState.FIXING.equals(fixState.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanBinding fixedProperty() {
|
public BooleanBinding fixedProperty() {
|
||||||
@@ -189,7 +189,7 @@ public class ResultListCellController implements FxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFixed() {
|
public boolean isFixed() {
|
||||||
return Result.FixState.FIXED.equals(fixState.get());
|
return Result.FixState.FIXED.equals(fixState.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanBinding fixFailedProperty() {
|
public BooleanBinding fixFailedProperty() {
|
||||||
@@ -197,7 +197,7 @@ public class ResultListCellController implements FxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Boolean isFixFailed() {
|
public Boolean isFixFailed() {
|
||||||
return Result.FixState.FIX_FAILED.equals(fixState.get());
|
return Result.FixState.FIX_FAILED.equals(fixState.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanBinding fixRunningOrDoneProperty() {
|
public BooleanBinding fixRunningOrDoneProperty() {
|
||||||
|
|||||||
@@ -56,8 +56,10 @@ public abstract class HubKeyLoadingModule {
|
|||||||
@Named("deviceId")
|
@Named("deviceId")
|
||||||
static String provideDeviceId(DeviceKey deviceKey) {
|
static String provideDeviceId(DeviceKey deviceKey) {
|
||||||
var publicKey = Objects.requireNonNull(deviceKey.get()).getPublic().getEncoded();
|
var publicKey = Objects.requireNonNull(deviceKey.get()).getPublic().getEncoded();
|
||||||
var hashedKey = MessageDigestSupplier.SHA256.get().digest(publicKey);
|
try (var instance = MessageDigestSupplier.SHA256.instance()) {
|
||||||
return BaseEncoding.base16().encode(hashedKey);
|
var hashedKey = instance.get().digest(publicKey);
|
||||||
|
return BaseEncoding.base16().encode(hashedKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public class ReceiveKeyController implements FxController {
|
|||||||
this.vaultBaseUri = getVaultBaseUri(vault);
|
this.vaultBaseUri = getVaultBaseUri(vault);
|
||||||
this.licenseExceededScene = licenseExceededScene;
|
this.licenseExceededScene = licenseExceededScene;
|
||||||
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
|
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
|
||||||
this.httpClient = HttpClient.newBuilder().executor(executor).build();
|
this.httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).executor(executor).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ public class RegisterDeviceController implements FxController {
|
|||||||
this.registerFailedScene = registerFailedScene;
|
this.registerFailedScene = registerFailedScene;
|
||||||
this.jwt = JWT.decode(this.bearerToken);
|
this.jwt = JWT.decode(this.bearerToken);
|
||||||
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
|
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
|
||||||
this.httpClient = HttpClient.newBuilder().executor(executor).build();
|
this.httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).executor(executor).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
|
|||||||
+2
-2
@@ -50,7 +50,7 @@ public class PassphraseEntryController implements FxController {
|
|||||||
private final KeychainManager keychain;
|
private final KeychainManager keychain;
|
||||||
private final StringBinding vaultName;
|
private final StringBinding vaultName;
|
||||||
private final BooleanProperty unlockInProgress = new SimpleBooleanProperty();
|
private final BooleanProperty unlockInProgress = new SimpleBooleanProperty();
|
||||||
private final ObjectBinding<ContentDisplay> unlockButtonContentDisplay = Bindings.createObjectBinding(this::getUnlockButtonContentDisplay, unlockInProgress);
|
private final ObjectBinding<ContentDisplay> unlockButtonContentDisplay = Bindings.when(unlockInProgress).then(ContentDisplay.LEFT).otherwise(ContentDisplay.TEXT_ONLY);
|
||||||
private final BooleanProperty unlockButtonDisabled = new SimpleBooleanProperty();
|
private final BooleanProperty unlockButtonDisabled = new SimpleBooleanProperty();
|
||||||
|
|
||||||
/* FXML */
|
/* FXML */
|
||||||
@@ -186,7 +186,7 @@ public class PassphraseEntryController implements FxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ContentDisplay getUnlockButtonContentDisplay() {
|
public ContentDisplay getUnlockButtonContentDisplay() {
|
||||||
return unlockInProgress.get() ? ContentDisplay.LEFT : ContentDisplay.TEXT_ONLY;
|
return unlockButtonContentDisplay.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlyBooleanProperty userInteractionDisabledProperty() {
|
public ReadOnlyBooleanProperty userInteractionDisabledProperty() {
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ public class LockForcedController implements FxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isForceSupported() {
|
public boolean isForceSupported() {
|
||||||
return vault.supportsForcedUnmount();
|
return false;//vault.supportsForcedUnmount(); TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
package org.cryptomator.ui.lock;
|
package org.cryptomator.ui.lock;
|
||||||
|
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
import org.cryptomator.common.vaults.LockNotCompletedException;
|
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.common.vaults.VaultState;
|
import org.cryptomator.common.vaults.VaultState;
|
||||||
import org.cryptomator.common.vaults.Volume;
|
import org.cryptomator.integrations.mount.UnmountFailedException;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
import org.cryptomator.ui.common.FxmlScene;
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||||
@@ -17,6 +16,7 @@ import javafx.concurrent.Task;
|
|||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import javafx.stage.Window;
|
import javafx.stage.Window;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.concurrent.CancellationException;
|
import java.util.concurrent.CancellationException;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
@@ -53,21 +53,21 @@ public class LockWorkflow extends Task<Void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Void call() throws Volume.VolumeException, InterruptedException, LockNotCompletedException, ExecutionException {
|
protected Void call() throws InterruptedException, ExecutionException, IOException {
|
||||||
lock(false);
|
lock(false);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void lock(boolean forced) throws InterruptedException, ExecutionException {
|
private void lock(boolean forced) throws InterruptedException, ExecutionException, IOException { //TODO: catch or rethrow IOException?
|
||||||
try {
|
try {
|
||||||
vault.lock(forced);
|
vault.lock(forced);
|
||||||
} catch (Volume.VolumeException | LockNotCompletedException e) {
|
} catch (UnmountFailedException e) {
|
||||||
LOG.info("Locking {} failed (forced: {}).", vault.getDisplayName(), forced, e);
|
LOG.info("Locking {} failed (forced: {}).", vault.getDisplayName(), forced, e);
|
||||||
retryOrCancel();
|
retryOrCancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void retryOrCancel() throws ExecutionException, InterruptedException {
|
private void retryOrCancel() throws ExecutionException, InterruptedException, IOException {
|
||||||
try {
|
try {
|
||||||
boolean forced = askWhetherToUseTheForce().get();
|
boolean forced = askWhetherToUseTheForce().get();
|
||||||
lock(forced);
|
lock(forced);
|
||||||
@@ -105,7 +105,7 @@ public class LockWorkflow extends Task<Void> {
|
|||||||
final var throwable = super.getException();
|
final var throwable = super.getException();
|
||||||
LOG.warn("Lock of {} failed.", vault.getDisplayName(), throwable);
|
LOG.warn("Lock of {} failed.", vault.getDisplayName(), throwable);
|
||||||
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.UNLOCKED);
|
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.UNLOCKED);
|
||||||
if (throwable instanceof Volume.VolumeException) {
|
if (throwable instanceof UnmountFailedException) { //TODO: check if correct exception caught
|
||||||
lockWindow.setScene(lockFailedScene.get());
|
lockWindow.setScene(lockFailedScene.get());
|
||||||
lockWindow.show();
|
lockWindow.show();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ public class ResizeController implements FxController {
|
|||||||
ResizeController(@MainWindow Stage window, Settings settings) {
|
ResizeController(@MainWindow Stage window, Settings settings) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.showResizingArrows = Bindings.createBooleanBinding(this::isShowResizingArrows, window.fullScreenProperty());
|
this.showResizingArrows = window.fullScreenProperty().not();
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -181,8 +181,7 @@ public class ResizeController implements FxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isShowResizingArrows() {
|
public boolean isShowResizingArrows() {
|
||||||
//If in fullscreen resizing is not be possible;
|
return showResizingArrows.get();
|
||||||
return !window.isFullScreen();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.cryptomator.ui.mainwindow;
|
package org.cryptomator.ui.mainwindow;
|
||||||
|
|
||||||
import com.tobiasdiez.easybind.EasyBind;
|
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.common.vaults.VaultState;
|
import org.cryptomator.common.vaults.VaultState;
|
||||||
import org.cryptomator.ui.common.Animations;
|
import org.cryptomator.ui.common.Animations;
|
||||||
@@ -11,10 +10,10 @@ import org.cryptomator.ui.controls.FontAwesome5IconView;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
import javafx.beans.binding.Binding;
|
|
||||||
import javafx.beans.binding.BooleanBinding;
|
import javafx.beans.binding.BooleanBinding;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
|
|
||||||
@MainWindowScoped
|
@MainWindowScoped
|
||||||
@@ -22,7 +21,7 @@ public class VaultDetailController implements FxController {
|
|||||||
|
|
||||||
private final ReadOnlyObjectProperty<Vault> vault;
|
private final ReadOnlyObjectProperty<Vault> vault;
|
||||||
private final Application application;
|
private final Application application;
|
||||||
private final Binding<FontAwesome5Icon> glyph;
|
private final ObservableValue<FontAwesome5Icon> glyph;
|
||||||
private final BooleanBinding anyVaultSelected;
|
private final BooleanBinding anyVaultSelected;
|
||||||
|
|
||||||
private AutoAnimator spinAnimation;
|
private AutoAnimator spinAnimation;
|
||||||
@@ -35,15 +34,13 @@ public class VaultDetailController implements FxController {
|
|||||||
VaultDetailController(ObjectProperty<Vault> vault, Application application) {
|
VaultDetailController(ObjectProperty<Vault> vault, Application application) {
|
||||||
this.vault = vault;
|
this.vault = vault;
|
||||||
this.application = application;
|
this.application = application;
|
||||||
this.glyph = EasyBind.select(vault) //
|
this.glyph = vault.flatMap(Vault::stateProperty).map(this::getGlyphForVaultState);
|
||||||
.selectObject(Vault::stateProperty) //
|
|
||||||
.map(this::getGlyphForVaultState);
|
|
||||||
this.anyVaultSelected = vault.isNotNull();
|
this.anyVaultSelected = vault.isNotNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
this.spinAnimation = AutoAnimator.animate(Animations.createDiscrete360Rotation(vaultStateView)) //
|
this.spinAnimation = AutoAnimator.animate(Animations.createDiscrete360Rotation(vaultStateView)) //
|
||||||
.onCondition(EasyBind.select(vault).selectObject(Vault::stateProperty).map(VaultState.Value.PROCESSING::equals)) //
|
.onCondition(vault.flatMap(Vault::stateProperty).map(VaultState.Value.PROCESSING::equals).orElse(false)) //
|
||||||
.afterStop(() -> vaultStateView.setRotate(0)) //
|
.afterStop(() -> vaultStateView.setRotate(0)) //
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
@@ -77,7 +74,7 @@ public class VaultDetailController implements FxController {
|
|||||||
return vault.get();
|
return vault.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Binding<FontAwesome5Icon> glyphProperty() {
|
public ObservableValue<FontAwesome5Icon> glyphProperty() {
|
||||||
return glyph;
|
return glyph;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.cryptomator.ui.mainwindow;
|
package org.cryptomator.ui.mainwindow;
|
||||||
|
|
||||||
import com.tobiasdiez.easybind.EasyBind;
|
|
||||||
import org.cryptomator.common.keychain.KeychainManager;
|
import org.cryptomator.common.keychain.KeychainManager;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
@@ -9,10 +8,10 @@ import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab;
|
|||||||
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
|
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javafx.beans.binding.BooleanExpression;
|
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
@@ -24,7 +23,7 @@ public class VaultDetailLockedController implements FxController {
|
|||||||
private final VaultOptionsComponent.Factory vaultOptionsWindow;
|
private final VaultOptionsComponent.Factory vaultOptionsWindow;
|
||||||
private final KeychainManager keychain;
|
private final KeychainManager keychain;
|
||||||
private final Stage mainWindow;
|
private final Stage mainWindow;
|
||||||
private final BooleanExpression passwordSaved;
|
private final ObservableValue<Boolean> passwordSaved;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
VaultDetailLockedController(ObjectProperty<Vault> vault, FxApplicationWindows appWindows, VaultOptionsComponent.Factory vaultOptionsWindow, KeychainManager keychain, @MainWindow Stage mainWindow) {
|
VaultDetailLockedController(ObjectProperty<Vault> vault, FxApplicationWindows appWindows, VaultOptionsComponent.Factory vaultOptionsWindow, KeychainManager keychain, @MainWindow Stage mainWindow) {
|
||||||
@@ -34,7 +33,7 @@ public class VaultDetailLockedController implements FxController {
|
|||||||
this.keychain = keychain;
|
this.keychain = keychain;
|
||||||
this.mainWindow = mainWindow;
|
this.mainWindow = mainWindow;
|
||||||
if (keychain.isSupported() && !keychain.isLocked()) {
|
if (keychain.isSupported() && !keychain.isLocked()) {
|
||||||
this.passwordSaved = BooleanExpression.booleanExpression(EasyBind.select(vault).selectObject(v -> keychain.getPassphraseStoredProperty(v.getId())));
|
this.passwordSaved = vault.flatMap(v -> keychain.getPassphraseStoredProperty(v.getId())).orElse(false);
|
||||||
} else {
|
} else {
|
||||||
this.passwordSaved = new SimpleBooleanProperty(false);
|
this.passwordSaved = new SimpleBooleanProperty(false);
|
||||||
}
|
}
|
||||||
@@ -65,13 +64,11 @@ public class VaultDetailLockedController implements FxController {
|
|||||||
return vault.get();
|
return vault.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanExpression passwordSavedProperty() {
|
public ObservableValue<Boolean> passwordSavedProperty() {
|
||||||
return passwordSaved;
|
return passwordSaved;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPasswordSaved() {
|
public boolean isPasswordSaved() {
|
||||||
if (keychain.isSupported() && vault.get() != null) {
|
return passwordSaved.getValue();
|
||||||
return keychain.getPassphraseStoredProperty(vault.get().getId()).get();
|
|
||||||
} else return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,16 +4,24 @@ import com.google.common.cache.CacheBuilder;
|
|||||||
import com.google.common.cache.CacheLoader;
|
import com.google.common.cache.CacheLoader;
|
||||||
import com.google.common.cache.LoadingCache;
|
import com.google.common.cache.LoadingCache;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
|
import org.cryptomator.common.vaults.VaultState;
|
||||||
|
import org.cryptomator.integrations.mount.Mountpoint;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.VaultService;
|
import org.cryptomator.ui.common.VaultService;
|
||||||
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||||
import org.cryptomator.ui.stats.VaultStatisticsComponent;
|
import org.cryptomator.ui.stats.VaultStatisticsComponent;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.binding.BooleanBinding;
|
||||||
|
import javafx.beans.binding.StringBinding;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
@MainWindowScoped
|
@MainWindowScoped
|
||||||
public class VaultDetailUnlockedController implements FxController {
|
public class VaultDetailUnlockedController implements FxController {
|
||||||
@@ -24,6 +32,10 @@ public class VaultDetailUnlockedController implements FxController {
|
|||||||
private final Stage mainWindow;
|
private final Stage mainWindow;
|
||||||
private final LoadingCache<Vault, VaultStatisticsComponent> vaultStats;
|
private final LoadingCache<Vault, VaultStatisticsComponent> vaultStats;
|
||||||
private final VaultStatisticsComponent.Builder vaultStatsBuilder;
|
private final VaultStatisticsComponent.Builder vaultStatsBuilder;
|
||||||
|
private final ObservableValue<Mountpoint> mountPoint;
|
||||||
|
private final ObservableValue<Boolean> accessibleViaPath;
|
||||||
|
private final ObservableValue<Boolean> accessibleViaUri;
|
||||||
|
private final ObservableValue<String> mountUri;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public VaultDetailUnlockedController(ObjectProperty<Vault> vault, FxApplicationWindows appWindows, VaultService vaultService, VaultStatisticsComponent.Builder vaultStatsBuilder, @MainWindow Stage mainWindow) {
|
public VaultDetailUnlockedController(ObjectProperty<Vault> vault, FxApplicationWindows appWindows, VaultService vaultService, VaultStatisticsComponent.Builder vaultStatsBuilder, @MainWindow Stage mainWindow) {
|
||||||
@@ -33,6 +45,10 @@ public class VaultDetailUnlockedController implements FxController {
|
|||||||
this.mainWindow = mainWindow;
|
this.mainWindow = mainWindow;
|
||||||
this.vaultStats = CacheBuilder.newBuilder().weakValues().build(CacheLoader.from(this::buildVaultStats));
|
this.vaultStats = CacheBuilder.newBuilder().weakValues().build(CacheLoader.from(this::buildVaultStats));
|
||||||
this.vaultStatsBuilder = vaultStatsBuilder;
|
this.vaultStatsBuilder = vaultStatsBuilder;
|
||||||
|
this.mountPoint = vault.flatMap(Vault::mountPointProperty);
|
||||||
|
this.accessibleViaPath = mountPoint.map(m -> m instanceof Mountpoint.WithPath).orElse(false);
|
||||||
|
this.accessibleViaUri = mountPoint.map(m -> m instanceof Mountpoint.WithUri).orElse(false);
|
||||||
|
this.mountUri = mountPoint.map(Mountpoint::uri).map(URI::toASCIIString).orElse("");
|
||||||
}
|
}
|
||||||
|
|
||||||
private VaultStatisticsComponent buildVaultStats(Vault vault) {
|
private VaultStatisticsComponent buildVaultStats(Vault vault) {
|
||||||
@@ -44,6 +60,11 @@ public class VaultDetailUnlockedController implements FxController {
|
|||||||
vaultService.reveal(vault.get());
|
vaultService.reveal(vault.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void copyMountUri() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void lock() {
|
public void lock() {
|
||||||
appWindows.startLockWorkflow(vault.get(), mainWindow);
|
appWindows.startLockWorkflow(vault.get(), mainWindow);
|
||||||
@@ -64,4 +85,29 @@ public class VaultDetailUnlockedController implements FxController {
|
|||||||
return vault.get();
|
return vault.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ObservableValue<Boolean> accessibleViaPathProperty() {
|
||||||
|
return accessibleViaPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAccessibleViaPath() {
|
||||||
|
return accessibleViaPath.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableValue<Boolean> accessibleViaUriProperty() {
|
||||||
|
return accessibleViaUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAccessibleViaUri() {
|
||||||
|
return accessibleViaUri.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableValue<String> mountUriProperty() {
|
||||||
|
return mountUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMountUri() {
|
||||||
|
return mountUri.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.cryptomator.ui.mainwindow;
|
package org.cryptomator.ui.mainwindow;
|
||||||
|
|
||||||
import com.tobiasdiez.easybind.EasyBind;
|
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.common.vaults.VaultState;
|
import org.cryptomator.common.vaults.VaultState;
|
||||||
import org.cryptomator.ui.common.Animations;
|
import org.cryptomator.ui.common.Animations;
|
||||||
@@ -10,15 +9,15 @@ import org.cryptomator.ui.controls.FontAwesome5Icon;
|
|||||||
import org.cryptomator.ui.controls.FontAwesome5IconView;
|
import org.cryptomator.ui.controls.FontAwesome5IconView;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javafx.beans.binding.Binding;
|
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
|
|
||||||
// unscoped because each cell needs its own controller
|
// unscoped because each cell needs its own controller
|
||||||
public class VaultListCellController implements FxController {
|
public class VaultListCellController implements FxController {
|
||||||
|
|
||||||
private final ObjectProperty<Vault> vault = new SimpleObjectProperty<>();
|
private final ObjectProperty<Vault> vault = new SimpleObjectProperty<>();
|
||||||
private final Binding<FontAwesome5Icon> glyph;
|
private final ObservableValue<FontAwesome5Icon> glyph;
|
||||||
|
|
||||||
private AutoAnimator spinAnimation;
|
private AutoAnimator spinAnimation;
|
||||||
|
|
||||||
@@ -27,14 +26,12 @@ public class VaultListCellController implements FxController {
|
|||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
VaultListCellController() {
|
VaultListCellController() {
|
||||||
this.glyph = EasyBind.select(vault) //
|
this.glyph = vault.flatMap(Vault::stateProperty).map(this::getGlyphForVaultState);
|
||||||
.selectObject(Vault::stateProperty) //
|
|
||||||
.map(this::getGlyphForVaultState);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
this.spinAnimation = AutoAnimator.animate(Animations.createDiscrete360Rotation(vaultStateView)) //
|
this.spinAnimation = AutoAnimator.animate(Animations.createDiscrete360Rotation(vaultStateView)) //
|
||||||
.onCondition(EasyBind.select(vault).selectObject(Vault::stateProperty).map(VaultState.Value.PROCESSING::equals)) //
|
.onCondition(vault.flatMap(Vault::stateProperty).map(VaultState.Value.PROCESSING::equals).orElse(false)) //
|
||||||
.afterStop(() -> vaultStateView.setRotate(0)) //
|
.afterStop(() -> vaultStateView.setRotate(0)) //
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
@@ -55,7 +52,7 @@ public class VaultListCellController implements FxController {
|
|||||||
|
|
||||||
/* Getter/Setter */
|
/* Getter/Setter */
|
||||||
|
|
||||||
public Binding<FontAwesome5Icon> glyphProperty() {
|
public ObservableValue<FontAwesome5Icon> glyphProperty() {
|
||||||
return glyph;
|
return glyph;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
package org.cryptomator.ui.mainwindow;
|
package org.cryptomator.ui.mainwindow;
|
||||||
|
|
||||||
import com.tobiasdiez.easybind.EasyBind;
|
|
||||||
import com.tobiasdiez.easybind.optional.ObservableOptionalValue;
|
|
||||||
import com.tobiasdiez.easybind.optional.OptionalBinding;
|
|
||||||
import org.cryptomator.common.keychain.KeychainManager;
|
import org.cryptomator.common.keychain.KeychainManager;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.common.vaults.VaultState;
|
import org.cryptomator.common.vaults.VaultState;
|
||||||
@@ -14,33 +11,39 @@ import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab;
|
|||||||
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
|
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javafx.beans.binding.Binding;
|
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import static org.cryptomator.common.vaults.VaultState.Value.*;
|
import static org.cryptomator.common.vaults.VaultState.Value.ERROR;
|
||||||
|
import static org.cryptomator.common.vaults.VaultState.Value.LOCKED;
|
||||||
|
import static org.cryptomator.common.vaults.VaultState.Value.MISSING;
|
||||||
|
import static org.cryptomator.common.vaults.VaultState.Value.NEEDS_MIGRATION;
|
||||||
|
import static org.cryptomator.common.vaults.VaultState.Value.UNLOCKED;
|
||||||
|
|
||||||
@MainWindowScoped
|
@MainWindowScoped
|
||||||
public class VaultListContextMenuController implements FxController {
|
public class VaultListContextMenuController implements FxController {
|
||||||
|
|
||||||
private final ObservableOptionalValue<Vault> selectedVault;
|
private final ReadOnlyObjectProperty<Vault> selectedVault;
|
||||||
private final Stage mainWindow;
|
private final Stage mainWindow;
|
||||||
private final FxApplicationWindows appWindows;
|
private final FxApplicationWindows appWindows;
|
||||||
private final VaultService vaultService;
|
private final VaultService vaultService;
|
||||||
private final KeychainManager keychain;
|
private final KeychainManager keychain;
|
||||||
private final RemoveVaultComponent.Builder removeVault;
|
private final RemoveVaultComponent.Builder removeVault;
|
||||||
private final VaultOptionsComponent.Factory vaultOptionsWindow;
|
private final VaultOptionsComponent.Factory vaultOptionsWindow;
|
||||||
private final OptionalBinding<VaultState.Value> selectedVaultState;
|
private final ObservableValue<VaultState.Value> selectedVaultState;
|
||||||
private final Binding<Boolean> selectedVaultPassphraseStored;
|
private final ObservableValue<Boolean> selectedVaultPassphraseStored;
|
||||||
private final Binding<Boolean> selectedVaultRemovable;
|
private final ObservableValue<Boolean> selectedVaultRemovable;
|
||||||
private final Binding<Boolean> selectedVaultUnlockable;
|
private final ObservableValue<Boolean> selectedVaultUnlockable;
|
||||||
private final Binding<Boolean> selectedVaultLockable;
|
private final ObservableValue<Boolean> selectedVaultLockable;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
VaultListContextMenuController(ObjectProperty<Vault> selectedVault, @MainWindow Stage mainWindow, FxApplicationWindows appWindows, VaultService vaultService, KeychainManager keychain, RemoveVaultComponent.Builder removeVault, VaultOptionsComponent.Factory vaultOptionsWindow) {
|
VaultListContextMenuController(ObjectProperty<Vault> selectedVault, @MainWindow Stage mainWindow, FxApplicationWindows appWindows, VaultService vaultService, KeychainManager keychain, RemoveVaultComponent.Builder removeVault, VaultOptionsComponent.Factory vaultOptionsWindow) {
|
||||||
this.selectedVault = EasyBind.wrapNullable(selectedVault);
|
this.selectedVault = selectedVault;
|
||||||
this.mainWindow = mainWindow;
|
this.mainWindow = mainWindow;
|
||||||
this.appWindows = appWindows;
|
this.appWindows = appWindows;
|
||||||
this.vaultService = vaultService;
|
this.vaultService = vaultService;
|
||||||
@@ -48,8 +51,8 @@ public class VaultListContextMenuController implements FxController {
|
|||||||
this.removeVault = removeVault;
|
this.removeVault = removeVault;
|
||||||
this.vaultOptionsWindow = vaultOptionsWindow;
|
this.vaultOptionsWindow = vaultOptionsWindow;
|
||||||
|
|
||||||
this.selectedVaultState = this.selectedVault.mapObservable(Vault::stateProperty);
|
this.selectedVaultState = selectedVault.flatMap(Vault::stateProperty).orElse(null);
|
||||||
this.selectedVaultPassphraseStored = this.selectedVault.map(this::isPasswordStored).orElse(false);
|
this.selectedVaultPassphraseStored = selectedVault.map(this::isPasswordStored).orElse(false);
|
||||||
this.selectedVaultRemovable = selectedVaultState.map(EnumSet.of(LOCKED, MISSING, ERROR, NEEDS_MIGRATION)::contains).orElse(false);
|
this.selectedVaultRemovable = selectedVaultState.map(EnumSet.of(LOCKED, MISSING, ERROR, NEEDS_MIGRATION)::contains).orElse(false);
|
||||||
this.selectedVaultUnlockable = selectedVaultState.map(LOCKED::equals).orElse(false);
|
this.selectedVaultUnlockable = selectedVaultState.map(LOCKED::equals).orElse(false);
|
||||||
this.selectedVaultLockable = selectedVaultState.map(UNLOCKED::equals).orElse(false);
|
this.selectedVaultLockable = selectedVaultState.map(UNLOCKED::equals).orElse(false);
|
||||||
@@ -61,40 +64,37 @@ public class VaultListContextMenuController implements FxController {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void didClickRemoveVault() {
|
public void didClickRemoveVault() {
|
||||||
selectedVault.ifValuePresent(v -> {
|
var vault = Objects.requireNonNull(selectedVault.get());
|
||||||
removeVault.vault(v).build().showRemoveVault();
|
removeVault.vault(vault).build().showRemoveVault();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void didClickShowVaultOptions() {
|
public void didClickShowVaultOptions() {
|
||||||
selectedVault.ifValuePresent(v -> {
|
var vault = Objects.requireNonNull(selectedVault.get());
|
||||||
vaultOptionsWindow.create(v).showVaultOptionsWindow(SelectedVaultOptionsTab.ANY);
|
vaultOptionsWindow.create(vault).showVaultOptionsWindow(SelectedVaultOptionsTab.ANY);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void didClickUnlockVault() {
|
public void didClickUnlockVault() {
|
||||||
selectedVault.ifValuePresent(v -> {
|
var vault = Objects.requireNonNull(selectedVault.get());
|
||||||
appWindows.startUnlockWorkflow(v, mainWindow);
|
appWindows.startUnlockWorkflow(vault, mainWindow);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void didClickLockVault() {
|
public void didClickLockVault() {
|
||||||
selectedVault.ifValuePresent(v -> {
|
var vault = Objects.requireNonNull(selectedVault.get());
|
||||||
appWindows.startLockWorkflow(v, mainWindow);
|
appWindows.startLockWorkflow(vault, mainWindow);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void didClickRevealVault() {
|
public void didClickRevealVault() {
|
||||||
selectedVault.ifValuePresent(vaultService::reveal);
|
var vault = Objects.requireNonNull(selectedVault.get());
|
||||||
|
vaultService.reveal(vault);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getter and Setter
|
// Getter and Setter
|
||||||
|
|
||||||
public Binding<Boolean> selectedVaultUnlockableProperty() {
|
public ObservableValue<Boolean> selectedVaultUnlockableProperty() {
|
||||||
return selectedVaultUnlockable;
|
return selectedVaultUnlockable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ public class VaultListContextMenuController implements FxController {
|
|||||||
return selectedVaultUnlockable.getValue();
|
return selectedVaultUnlockable.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Binding<Boolean> selectedVaultLockableProperty() {
|
public ObservableValue<Boolean> selectedVaultLockableProperty() {
|
||||||
return selectedVaultLockable;
|
return selectedVaultLockable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ public class VaultListContextMenuController implements FxController {
|
|||||||
return selectedVaultLockable.getValue();
|
return selectedVaultLockable.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Binding<Boolean> selectedVaultRemovableProperty() {
|
public ObservableValue<Boolean> selectedVaultRemovableProperty() {
|
||||||
return selectedVaultRemovable;
|
return selectedVaultRemovable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ public class VaultListContextMenuController implements FxController {
|
|||||||
return selectedVaultRemovable.getValue();
|
return selectedVaultRemovable.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Binding<Boolean> selectedVaultPassphraseStoredProperty() {
|
public ObservableValue<Boolean> selectedVaultPassphraseStoredProperty() {
|
||||||
return selectedVaultPassphraseStored;
|
return selectedVaultPassphraseStored;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ public class MigrationRunController implements FxController {
|
|||||||
this.appWindows = appWindows;
|
this.appWindows = appWindows;
|
||||||
this.startScene = startScene;
|
this.startScene = startScene;
|
||||||
this.successScene = successScene;
|
this.successScene = successScene;
|
||||||
this.migrateButtonContentDisplay = Bindings.createObjectBinding(this::getMigrateButtonContentDisplay, vault.stateProperty());
|
this.migrateButtonContentDisplay = Bindings.when(vault.processingProperty()).then(ContentDisplay.LEFT).otherwise(ContentDisplay.TEXT_ONLY);
|
||||||
this.capabilityErrorScene = capabilityErrorScene;
|
this.capabilityErrorScene = capabilityErrorScene;
|
||||||
this.migrationButtonDisabled = new SimpleBooleanProperty();
|
this.migrationButtonDisabled = new SimpleBooleanProperty();
|
||||||
this.migrationProgress = new SimpleDoubleProperty(volatileMigrationProgress);
|
this.migrationProgress = new SimpleDoubleProperty(volatileMigrationProgress);
|
||||||
@@ -211,10 +211,7 @@ public class MigrationRunController implements FxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ContentDisplay getMigrateButtonContentDisplay() {
|
public ContentDisplay getMigrateButtonContentDisplay() {
|
||||||
return switch (vault.getState()) {
|
return migrateButtonContentDisplay.get();
|
||||||
case PROCESSING -> ContentDisplay.LEFT;
|
|
||||||
default -> ContentDisplay.TEXT_ONLY;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlyDoubleProperty migrationProgressProperty() {
|
public ReadOnlyDoubleProperty migrationProgressProperty() {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ public class GeneralPreferencesController implements FxController {
|
|||||||
private final Environment environment;
|
private final Environment environment;
|
||||||
private final List<KeychainAccessProvider> keychainAccessProviders;
|
private final List<KeychainAccessProvider> keychainAccessProviders;
|
||||||
private final FxApplicationWindows appWindows;
|
private final FxApplicationWindows appWindows;
|
||||||
|
public CheckBox useKeychainCheckbox;
|
||||||
public ChoiceBox<KeychainAccessProvider> keychainBackendChoiceBox;
|
public ChoiceBox<KeychainAccessProvider> keychainBackendChoiceBox;
|
||||||
public CheckBox startHiddenCheckbox;
|
public CheckBox startHiddenCheckbox;
|
||||||
public CheckBox autoCloseVaultsCheckbox;
|
public CheckBox autoCloseVaultsCheckbox;
|
||||||
@@ -64,6 +65,8 @@ public class GeneralPreferencesController implements FxController {
|
|||||||
keychainBackendChoiceBox.setValue(keychainSettingsConverter.fromString(settings.keychainProvider().get()));
|
keychainBackendChoiceBox.setValue(keychainSettingsConverter.fromString(settings.keychainProvider().get()));
|
||||||
keychainBackendChoiceBox.setConverter(new KeychainProviderDisplayNameConverter());
|
keychainBackendChoiceBox.setConverter(new KeychainProviderDisplayNameConverter());
|
||||||
Bindings.bindBidirectional(settings.keychainProvider(), keychainBackendChoiceBox.valueProperty(), keychainSettingsConverter);
|
Bindings.bindBidirectional(settings.keychainProvider(), keychainBackendChoiceBox.valueProperty(), keychainSettingsConverter);
|
||||||
|
useKeychainCheckbox.selectedProperty().bindBidirectional(settings.useKeychain());
|
||||||
|
keychainBackendChoiceBox.disableProperty().bind(useKeychainCheckbox.selectedProperty().not());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAutoStartSupported() {
|
public boolean isAutoStartSupported() {
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
package org.cryptomator.ui.preferences;
|
package org.cryptomator.ui.preferences;
|
||||||
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.common.settings.VolumeImpl;
|
import org.cryptomator.integrations.mount.MountCapability;
|
||||||
import org.cryptomator.common.settings.WebDavUrlScheme;
|
import org.cryptomator.integrations.mount.MountService;
|
||||||
import org.cryptomator.common.vaults.Volume;
|
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.BooleanBinding;
|
import javafx.beans.binding.BooleanExpression;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.ChoiceBox;
|
import javafx.scene.control.ChoiceBox;
|
||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
import javafx.util.StringConverter;
|
import javafx.util.StringConverter;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: if WebDAV is selected under Windows, show warning that specific mount options (like selecting a directory as mount point) are _not_ supported
|
* TODO: if WebDAV is selected under Windows, show warning that specific mount options (like selecting a directory as mount point) are _not_ supported
|
||||||
@@ -23,40 +22,36 @@ import javafx.util.StringConverter;
|
|||||||
public class VolumePreferencesController implements FxController {
|
public class VolumePreferencesController implements FxController {
|
||||||
|
|
||||||
private final Settings settings;
|
private final Settings settings;
|
||||||
private final BooleanBinding showWebDavSettings;
|
private final ObservableValue<MountService> selectedMountService;
|
||||||
private final BooleanBinding showWebDavScheme;
|
private final BooleanExpression loopbackPortSupported;
|
||||||
public ChoiceBox<VolumeImpl> volumeTypeChoiceBox;
|
private final List<MountService> mountProviders;
|
||||||
public TextField webDavPortField;
|
public ChoiceBox<MountService> volumeTypeChoiceBox;
|
||||||
public Button changeWebDavPortButton;
|
public TextField loopbackPortField;
|
||||||
public ChoiceBox<WebDavUrlScheme> webDavUrlSchemeChoiceBox;
|
public Button loopbackPortApplyButton;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
VolumePreferencesController(Settings settings) {
|
VolumePreferencesController(Settings settings, List<MountService> mountProviders, ObservableValue<MountService> selectedMountService) {
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.showWebDavSettings = Bindings.equal(settings.preferredVolumeImpl(), VolumeImpl.WEBDAV);
|
this.mountProviders = mountProviders;
|
||||||
this.showWebDavScheme = showWebDavSettings.and(new SimpleBooleanProperty(SystemUtils.IS_OS_LINUX)); //TODO: remove SystemUtils
|
this.selectedMountService = selectedMountService;
|
||||||
|
this.loopbackPortSupported = BooleanExpression.booleanExpression(selectedMountService.map(s -> s.hasCapability(MountCapability.LOOPBACK_PORT)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
volumeTypeChoiceBox.getItems().addAll(Volume.getCurrentSupportedAdapters());
|
volumeTypeChoiceBox.getItems().addAll(mountProviders);
|
||||||
if (!volumeTypeChoiceBox.getItems().contains(settings.preferredVolumeImpl().get())) {
|
volumeTypeChoiceBox.setConverter(new MountServiceConverter());
|
||||||
settings.preferredVolumeImpl().set(VolumeImpl.WEBDAV);
|
volumeTypeChoiceBox.getSelectionModel().select(selectedMountService.getValue());
|
||||||
}
|
volumeTypeChoiceBox.valueProperty().addListener((observableValue, oldProvide, newProvider) -> settings.mountService().set(newProvider.getClass().getName()));
|
||||||
volumeTypeChoiceBox.valueProperty().bindBidirectional(settings.preferredVolumeImpl());
|
|
||||||
volumeTypeChoiceBox.setConverter(new VolumeImplConverter());
|
|
||||||
|
|
||||||
webDavPortField.setText(String.valueOf(settings.port().get()));
|
loopbackPortField.setText(String.valueOf(settings.port().get()));
|
||||||
changeWebDavPortButton.visibleProperty().bind(settings.port().asString().isNotEqualTo(webDavPortField.textProperty()));
|
loopbackPortApplyButton.visibleProperty().bind(settings.port().asString().isNotEqualTo(loopbackPortField.textProperty()));
|
||||||
changeWebDavPortButton.disableProperty().bind(Bindings.createBooleanBinding(this::validateWebDavPort, webDavPortField.textProperty()).not());
|
loopbackPortApplyButton.disableProperty().bind(Bindings.createBooleanBinding(this::validateLoopbackPort, loopbackPortField.textProperty()).not());
|
||||||
|
|
||||||
webDavUrlSchemeChoiceBox.getItems().addAll(WebDavUrlScheme.values());
|
|
||||||
webDavUrlSchemeChoiceBox.valueProperty().bindBidirectional(settings.preferredGvfsScheme());
|
|
||||||
webDavUrlSchemeChoiceBox.setConverter(new WebDavUrlSchemeConverter());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean validateWebDavPort() {
|
private boolean validateLoopbackPort() {
|
||||||
try {
|
try {
|
||||||
int port = Integer.parseInt(webDavPortField.getText());
|
int port = Integer.parseInt(loopbackPortField.getText());
|
||||||
return port == 0 // choose port automatically
|
return port == 0 // choose port automatically
|
||||||
|| port >= Settings.MIN_PORT && port <= Settings.MAX_PORT; // port within range
|
|| port >= Settings.MIN_PORT && port <= Settings.MAX_PORT; // port within range
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
@@ -64,54 +59,36 @@ public class VolumePreferencesController implements FxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void doChangeWebDavPort() {
|
public void doChangeLoopbackPort() {
|
||||||
settings.port().set(Integer.parseInt(webDavPortField.getText()));
|
if (validateLoopbackPort()) {
|
||||||
|
settings.port().set(Integer.parseInt(loopbackPortField.getText()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Property Getters */
|
/* Property Getters */
|
||||||
|
|
||||||
public BooleanBinding showWebDavSettingsProperty() {
|
public BooleanExpression loopbackPortSupportedProperty() {
|
||||||
return showWebDavSettings;
|
return loopbackPortSupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Boolean getShowWebDavSettings() {
|
public boolean isLoopbackPortSupported() {
|
||||||
return showWebDavSettings.get();
|
return loopbackPortSupported.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanBinding showWebDavSchemeProperty() {
|
/* Helpers */
|
||||||
return showWebDavScheme;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getShowWebDavScheme() {
|
private static class MountServiceConverter extends StringConverter<MountService> {
|
||||||
return showWebDavScheme.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Helper classes */
|
|
||||||
|
|
||||||
private static class WebDavUrlSchemeConverter extends StringConverter<WebDavUrlScheme> {
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString(WebDavUrlScheme scheme) {
|
public String toString(MountService provider) {
|
||||||
return scheme.getDisplayName();
|
return provider== null? "None" : provider.displayName(); //TODO: adjust message
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WebDavUrlScheme fromString(String string) {
|
public MountService fromString(String string) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class VolumeImplConverter extends StringConverter<VolumeImpl> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString(VolumeImpl impl) {
|
|
||||||
return impl.getDisplayName();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public VolumeImpl fromString(String string) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,11 @@ public class AwtTrayMenuController implements TrayMenuController {
|
|||||||
addChildren(menu, items);
|
addChildren(menu, items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBeforeOpenMenu(Runnable runnable) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private void addChildren(Menu menu, List<TrayMenuItem> items) {
|
private void addChildren(Menu menu, List<TrayMenuItem> items) {
|
||||||
for (var item : items) {
|
for (var item : items) {
|
||||||
// TODO: use Pattern Matching for switch, once available
|
// TODO: use Pattern Matching for switch, once available
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ import java.util.ResourceBundle;
|
|||||||
public class TrayMenuBuilder {
|
public class TrayMenuBuilder {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(TrayMenuBuilder.class);
|
private static final Logger LOG = LoggerFactory.getLogger(TrayMenuBuilder.class);
|
||||||
private static final String TRAY_ICON_MAC = "/img/tray_icon_mac.png";
|
private static final String TRAY_ICON_MAC = "/img/tray_icon_mac@2x.png";
|
||||||
private static final String TRAY_ICON = "/img/tray_icon.png";
|
private static final String TRAY_ICON = "/img/window_icon_32.png";
|
||||||
|
|
||||||
private final ResourceBundle resourceBundle;
|
private final ResourceBundle resourceBundle;
|
||||||
private final VaultService vaultService;
|
private final VaultService vaultService;
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package org.cryptomator.ui.unlock;
|
package org.cryptomator.ui.unlock;
|
||||||
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.cryptomator.common.vaults.MountPointRequirement;
|
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
|
||||||
@@ -27,30 +25,4 @@ public class UnlockInvalidMountPointController implements FxController {
|
|||||||
window.close();
|
window.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Getter/Setter */
|
|
||||||
|
|
||||||
public String getMountPoint() {
|
|
||||||
return vault.getVaultSettings().getCustomMountPath().orElse("AUTO");
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getNotExisting() {
|
|
||||||
return getMountPointRequirement() == MountPointRequirement.EMPTY_MOUNT_POINT;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getExisting() {
|
|
||||||
return getMountPointRequirement() == MountPointRequirement.PARENT_NO_MOUNT_POINT;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getDriveLetterOccupied() {
|
|
||||||
return getMountPointRequirement() == MountPointRequirement.UNUSED_ROOT_DIR;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MountPointRequirement getMountPointRequirement() {
|
|
||||||
var requirement = vault.getVolume().orElseThrow(() -> new IllegalStateException("Invalid Mountpoint without a Volume?!")).getMountPointRequirement();
|
|
||||||
assert requirement != MountPointRequirement.NONE; //An invalid MountPoint with no required MountPoint doesn't seem sensible
|
|
||||||
assert requirement != MountPointRequirement.PARENT_OPT_MOUNT_POINT; //Not implemented anywhere (yet)
|
|
||||||
assert requirement != MountPointRequirement.UNUSED_ROOT_DIR || SystemUtils.IS_OS_WINDOWS; //Not implemented anywhere, but on Windows
|
|
||||||
|
|
||||||
return requirement;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -2,13 +2,10 @@ package org.cryptomator.ui.unlock;
|
|||||||
|
|
||||||
import com.google.common.base.Throwables;
|
import com.google.common.base.Throwables;
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
|
||||||
import org.cryptomator.common.vaults.MountPointRequirement;
|
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.common.vaults.VaultState;
|
import org.cryptomator.common.vaults.VaultState;
|
||||||
import org.cryptomator.common.vaults.Volume.VolumeException;
|
|
||||||
import org.cryptomator.cryptolib.api.CryptoException;
|
import org.cryptomator.cryptolib.api.CryptoException;
|
||||||
|
import org.cryptomator.integrations.mount.MountFailedException;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
import org.cryptomator.ui.common.FxmlScene;
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
import org.cryptomator.ui.common.VaultService;
|
import org.cryptomator.ui.common.VaultService;
|
||||||
@@ -23,9 +20,6 @@ import javafx.concurrent.Task;
|
|||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.DirectoryNotEmptyException;
|
|
||||||
import java.nio.file.FileAlreadyExistsException;
|
|
||||||
import java.nio.file.NotDirectoryException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A multi-step task that consists of background activities as well as user interaction.
|
* A multi-step task that consists of background activities as well as user interaction.
|
||||||
@@ -57,7 +51,7 @@ public class UnlockWorkflow extends Task<Boolean> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Boolean call() throws InterruptedException, IOException, VolumeException, InvalidMountPointException, CryptoException {
|
protected Boolean call() throws InterruptedException, IOException, CryptoException, MountFailedException {
|
||||||
try {
|
try {
|
||||||
attemptUnlock();
|
attemptUnlock();
|
||||||
return true;
|
return true;
|
||||||
@@ -67,48 +61,17 @@ public class UnlockWorkflow extends Task<Boolean> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void attemptUnlock() throws IOException, VolumeException, InvalidMountPointException, CryptoException {
|
private void attemptUnlock() throws IOException, CryptoException, MountFailedException {
|
||||||
try {
|
try {
|
||||||
keyLoadingStrategy.use(vault::unlock);
|
keyLoadingStrategy.use(vault::unlock);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Throwables.propagateIfPossible(e, IOException.class);
|
Throwables.propagateIfPossible(e, IOException.class);
|
||||||
Throwables.propagateIfPossible(e, VolumeException.class);
|
|
||||||
Throwables.propagateIfPossible(e, InvalidMountPointException.class);
|
|
||||||
Throwables.propagateIfPossible(e, CryptoException.class);
|
Throwables.propagateIfPossible(e, CryptoException.class);
|
||||||
|
Throwables.propagateIfPossible(e, MountFailedException.class);
|
||||||
throw new IllegalStateException("unexpected exception type", e);
|
throw new IllegalStateException("unexpected exception type", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleInvalidMountPoint(InvalidMountPointException impExc) {
|
|
||||||
var requirement = vault.getVolume().orElseThrow(() -> new IllegalStateException("Invalid Mountpoint without a Volume?!", impExc)).getMountPointRequirement();
|
|
||||||
assert requirement != MountPointRequirement.NONE; //An invalid MountPoint with no required MountPoint doesn't seem sensible
|
|
||||||
assert requirement != MountPointRequirement.PARENT_OPT_MOUNT_POINT; //Not implemented anywhere (yet)
|
|
||||||
assert requirement != MountPointRequirement.UNUSED_ROOT_DIR || SystemUtils.IS_OS_WINDOWS; //Not implemented anywhere, but on Windows
|
|
||||||
|
|
||||||
Throwable cause = impExc.getCause();
|
|
||||||
// TODO: apply https://openjdk.java.net/jeps/8213076 in future JDK versions
|
|
||||||
if (cause instanceof NotDirectoryException) {
|
|
||||||
if (requirement == MountPointRequirement.PARENT_NO_MOUNT_POINT) {
|
|
||||||
LOG.error("Unlock failed. Parent folder is missing: {}", cause.getMessage());
|
|
||||||
} else {
|
|
||||||
LOG.error("Unlock failed. Mountpoint doesn't exist (needs to be a folder): {}", cause.getMessage());
|
|
||||||
}
|
|
||||||
showInvalidMountPointScene();
|
|
||||||
} else if (cause instanceof FileAlreadyExistsException) {
|
|
||||||
if (requirement == MountPointRequirement.UNUSED_ROOT_DIR) {
|
|
||||||
LOG.error("Unlock failed. Drive Letter already in use: {}", cause.getMessage());
|
|
||||||
} else {
|
|
||||||
LOG.error("Unlock failed. Mountpoint already exists: {}", cause.getMessage());
|
|
||||||
}
|
|
||||||
showInvalidMountPointScene();
|
|
||||||
} else if (cause instanceof DirectoryNotEmptyException) {
|
|
||||||
LOG.error("Unlock failed. Mountpoint not an empty directory: {}", cause.getMessage());
|
|
||||||
showInvalidMountPointScene();
|
|
||||||
} else {
|
|
||||||
handleGenericError(impExc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showInvalidMountPointScene() {
|
private void showInvalidMountPointScene() {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
window.setScene(invalidMountPointScene.get());
|
window.setScene(invalidMountPointScene.get());
|
||||||
@@ -144,11 +107,7 @@ public class UnlockWorkflow extends Task<Boolean> {
|
|||||||
protected void failed() {
|
protected void failed() {
|
||||||
LOG.info("Unlock of '{}' failed.", vault.getDisplayName());
|
LOG.info("Unlock of '{}' failed.", vault.getDisplayName());
|
||||||
Throwable throwable = super.getException();
|
Throwable throwable = super.getException();
|
||||||
if (throwable instanceof InvalidMountPointException e) {
|
handleGenericError(throwable);
|
||||||
handleInvalidMountPoint(e);
|
|
||||||
} else {
|
|
||||||
handleGenericError(throwable);
|
|
||||||
}
|
|
||||||
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.LOCKED);
|
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.LOCKED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package org.cryptomator.ui.vaultoptions;
|
|||||||
|
|
||||||
import org.cryptomator.common.keychain.KeychainManager;
|
import org.cryptomator.common.keychain.KeychainManager;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.integrations.keychain.KeychainAccessException;
|
|
||||||
import org.cryptomator.ui.changepassword.ChangePasswordComponent;
|
import org.cryptomator.ui.changepassword.ChangePasswordComponent;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent;
|
import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent;
|
||||||
@@ -11,9 +10,8 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javafx.beans.binding.Bindings;
|
|
||||||
import javafx.beans.binding.BooleanExpression;
|
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
@@ -28,7 +26,7 @@ public class MasterkeyOptionsController implements FxController {
|
|||||||
private final RecoveryKeyComponent.Builder recoveryKeyWindow;
|
private final RecoveryKeyComponent.Builder recoveryKeyWindow;
|
||||||
private final ForgetPasswordComponent.Builder forgetPasswordWindow;
|
private final ForgetPasswordComponent.Builder forgetPasswordWindow;
|
||||||
private final KeychainManager keychain;
|
private final KeychainManager keychain;
|
||||||
private final BooleanExpression passwordSaved;
|
private final ObservableValue<Boolean> passwordSaved;
|
||||||
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@@ -40,7 +38,7 @@ public class MasterkeyOptionsController implements FxController {
|
|||||||
this.forgetPasswordWindow = forgetPasswordWindow;
|
this.forgetPasswordWindow = forgetPasswordWindow;
|
||||||
this.keychain = keychain;
|
this.keychain = keychain;
|
||||||
if (keychain.isSupported() && !keychain.isLocked()) {
|
if (keychain.isSupported() && !keychain.isLocked()) {
|
||||||
this.passwordSaved = Bindings.createBooleanBinding(this::isPasswordSaved, keychain.getPassphraseStoredProperty(vault.getId()));
|
this.passwordSaved = keychain.getPassphraseStoredProperty(vault.getId()).orElse(false);
|
||||||
} else {
|
} else {
|
||||||
this.passwordSaved = new SimpleBooleanProperty(false);
|
this.passwordSaved = new SimpleBooleanProperty(false);
|
||||||
}
|
}
|
||||||
@@ -67,13 +65,11 @@ public class MasterkeyOptionsController implements FxController {
|
|||||||
forgetPasswordWindow.vault(vault).owner(window).build().showForgetPassword();
|
forgetPasswordWindow.vault(vault).owner(window).build().showForgetPassword();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanExpression passwordSavedProperty() {
|
public ObservableValue<Boolean> passwordSavedProperty() {
|
||||||
return passwordSaved;
|
return passwordSaved;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPasswordSaved() {
|
public boolean isPasswordSaved() {
|
||||||
if (keychain.isSupported() && !keychain.isLocked() && vault != null) {
|
return passwordSaved.getValue();
|
||||||
return keychain.getPassphraseStoredProperty(vault.getId()).get();
|
|
||||||
} else return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
package org.cryptomator.ui.vaultoptions;
|
package org.cryptomator.ui.vaultoptions;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.cryptomator.common.Environment;
|
import org.cryptomator.common.Environment;
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.mount.WindowsDriveLetters;
|
||||||
import org.cryptomator.common.settings.VolumeImpl;
|
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.common.vaults.WindowsDriveLetters;
|
import org.cryptomator.integrations.mount.MountCapability;
|
||||||
|
import org.cryptomator.integrations.mount.MountService;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javafx.beans.binding.Bindings;
|
|
||||||
import javafx.beans.property.StringProperty;
|
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.CheckBox;
|
import javafx.scene.control.CheckBox;
|
||||||
@@ -35,97 +31,110 @@ public class MountOptionsController implements FxController {
|
|||||||
|
|
||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final Vault vault;
|
private final Vault vault;
|
||||||
private final VolumeImpl usedVolumeImpl;
|
|
||||||
private final WindowsDriveLetters windowsDriveLetters;
|
private final WindowsDriveLetters windowsDriveLetters;
|
||||||
private final ResourceBundle resourceBundle;
|
private final ResourceBundle resourceBundle;
|
||||||
|
|
||||||
|
private final ObservableValue<Boolean> mountpointDirSupported;
|
||||||
|
private final ObservableValue<Boolean> mountpointDriveLetterSupported;
|
||||||
|
private final ObservableValue<Boolean> readOnlySupported;
|
||||||
|
private final ObservableValue<Boolean> mountFlagsSupported;
|
||||||
|
private final ObservableValue<Path> driveLetter;
|
||||||
|
private final ObservableValue<String> directoryPath;
|
||||||
|
|
||||||
|
|
||||||
|
//-- FXML objects --
|
||||||
public CheckBox readOnlyCheckbox;
|
public CheckBox readOnlyCheckbox;
|
||||||
public CheckBox customMountFlagsCheckbox;
|
public CheckBox customMountFlagsCheckbox;
|
||||||
public TextField mountFlags;
|
public TextField mountFlagsField;
|
||||||
public ToggleGroup mountPoint;
|
public ToggleGroup mountPointToggleGroup;
|
||||||
public RadioButton mountPointAuto;
|
public RadioButton mountPointAutoBtn;
|
||||||
public RadioButton mountPointWinDriveLetter;
|
public RadioButton mountPointDriveLetterBtn;
|
||||||
public RadioButton mountPointCustomDir;
|
public RadioButton mountPointDirBtn;
|
||||||
public ChoiceBox<String> driveLetterSelection;
|
public TextField directoryPathField;
|
||||||
|
public ChoiceBox<Path> driveLetterSelection;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
MountOptionsController(@VaultOptionsWindow Stage window, @VaultOptionsWindow Vault vault, Settings settings, WindowsDriveLetters windowsDriveLetters, ResourceBundle resourceBundle, Environment environment) {
|
MountOptionsController(@VaultOptionsWindow Stage window, @VaultOptionsWindow Vault vault, ObservableValue<MountService> mountService, WindowsDriveLetters windowsDriveLetters, ResourceBundle resourceBundle, Environment environment) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.vault = vault;
|
this.vault = vault;
|
||||||
this.usedVolumeImpl = settings.preferredVolumeImpl().get();
|
|
||||||
this.windowsDriveLetters = windowsDriveLetters;
|
this.windowsDriveLetters = windowsDriveLetters;
|
||||||
this.resourceBundle = resourceBundle;
|
this.resourceBundle = resourceBundle;
|
||||||
|
this.mountpointDirSupported = mountService.map(s -> s.hasCapability(MountCapability.MOUNT_TO_EXISTING_DIR) || s.hasCapability(MountCapability.MOUNT_WITHIN_EXISTING_PARENT));
|
||||||
|
this.mountpointDriveLetterSupported = mountService.map(s -> s.hasCapability(MountCapability.MOUNT_AS_DRIVE_LETTER));
|
||||||
|
this.mountFlagsSupported = mountService.map(s -> s.hasCapability(MountCapability.MOUNT_FLAGS));
|
||||||
|
this.readOnlySupported = mountService.map(s -> s.hasCapability(MountCapability.READ_ONLY));
|
||||||
|
this.driveLetter = vault.getVaultSettings().mountPoint().map(p -> isDriveLetter(p) ? p : null);
|
||||||
|
this.directoryPath = vault.getVaultSettings().mountPoint().map(p -> isDriveLetter(p) ? null : p.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
|
|
||||||
// readonly:
|
// readonly:
|
||||||
readOnlyCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().usesReadOnlyMode());
|
readOnlyCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().usesReadOnlyMode());
|
||||||
//TODO: support this feature on Windows
|
|
||||||
if (usedVolumeImpl == VolumeImpl.FUSE && isOsWindows()) {
|
|
||||||
readOnlyCheckbox.setSelected(false); // to prevent invalid states
|
|
||||||
readOnlyCheckbox.setDisable(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// custom mount flags:
|
// custom mount flags:
|
||||||
mountFlags.disableProperty().bind(customMountFlagsCheckbox.selectedProperty().not());
|
mountFlagsField.disableProperty().bind(customMountFlagsCheckbox.selectedProperty().not());
|
||||||
customMountFlagsCheckbox.setSelected(vault.isHavingCustomMountFlags());
|
customMountFlagsCheckbox.setSelected(vault.isHavingCustomMountFlags());
|
||||||
if (vault.isHavingCustomMountFlags()) {
|
|
||||||
mountFlags.textProperty().bindBidirectional(vault.getVaultSettings().mountFlags());
|
|
||||||
readOnlyCheckbox.setSelected(false); // to prevent invalid states
|
|
||||||
} else {
|
|
||||||
mountFlags.textProperty().bind(vault.defaultMountFlagsProperty());
|
|
||||||
}
|
|
||||||
|
|
||||||
// mount point options:
|
//driveLetter choice box
|
||||||
mountPoint.selectedToggleProperty().addListener(this::toggleMountPoint);
|
driveLetterSelection.getItems().addAll(windowsDriveLetters.getAll());
|
||||||
driveLetterSelection.getItems().addAll(windowsDriveLetters.getAllDriveLetters());
|
|
||||||
driveLetterSelection.setConverter(new WinDriveLetterLabelConverter(windowsDriveLetters, resourceBundle));
|
driveLetterSelection.setConverter(new WinDriveLetterLabelConverter(windowsDriveLetters, resourceBundle));
|
||||||
driveLetterSelection.setValue(vault.getVaultSettings().winDriveLetter().get());
|
driveLetterSelection.setOnShowing(event -> driveLetterSelection.setConverter(new WinDriveLetterLabelConverter(windowsDriveLetters, resourceBundle))); //TODO: does this work?
|
||||||
|
|
||||||
if (vault.getVaultSettings().useCustomMountPath().get() && vault.getVaultSettings().getCustomMountPath().isPresent()) {
|
//mountPoint toggle group
|
||||||
mountPoint.selectToggle(mountPointCustomDir);
|
var mountPoint = vault.getVaultSettings().getMountPoint();
|
||||||
} else if (!Strings.isNullOrEmpty(vault.getVaultSettings().winDriveLetter().get())) {
|
if (mountPoint == null) {
|
||||||
mountPoint.selectToggle(mountPointWinDriveLetter);
|
//prepare and select auto
|
||||||
} else {
|
mountPointToggleGroup.selectToggle(mountPointAutoBtn);
|
||||||
mountPoint.selectToggle(mountPointAuto);
|
} else if (mountPoint.getParent() == null && isDriveLetter(mountPoint)) {
|
||||||
|
//prepare and select drive letter
|
||||||
|
mountPointToggleGroup.selectToggle(mountPointDriveLetterBtn);
|
||||||
|
} else if (driveLetterSelection.getValue() == null) {
|
||||||
|
//prepare and select dir
|
||||||
|
mountPointToggleGroup.selectToggle(mountPointDirBtn);
|
||||||
}
|
}
|
||||||
|
mountPointToggleGroup.selectedToggleProperty().addListener(this::selectedToggleChanged);
|
||||||
vault.getVaultSettings().useCustomMountPath().bind(mountPoint.selectedToggleProperty().isEqualTo(mountPointCustomDir));
|
|
||||||
vault.getVaultSettings().winDriveLetter().bind( //
|
|
||||||
Bindings.when(mountPoint.selectedToggleProperty().isEqualTo(mountPointWinDriveLetter)) //
|
|
||||||
.then(driveLetterSelection.getSelectionModel().selectedItemProperty()) //
|
|
||||||
.otherwise((String) null) //
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void toggleUseCustomMountFlags() {
|
public void toggleUseCustomMountFlags() {
|
||||||
if (customMountFlagsCheckbox.isSelected()) {
|
if (customMountFlagsCheckbox.isSelected()) {
|
||||||
readOnlyCheckbox.setSelected(false); // to prevent invalid states
|
readOnlyCheckbox.setSelected(false); // to prevent invalid states
|
||||||
mountFlags.textProperty().unbind();
|
mountFlagsField.textProperty().unbind();
|
||||||
vault.setCustomMountFlags(vault.defaultMountFlagsProperty().get());
|
vault.setCustomMountFlags(vault.defaultMountFlagsProperty().getValue());
|
||||||
mountFlags.textProperty().bindBidirectional(vault.getVaultSettings().mountFlags());
|
mountFlagsField.textProperty().bindBidirectional(vault.getVaultSettings().mountFlags());
|
||||||
} else {
|
} else {
|
||||||
mountFlags.textProperty().unbindBidirectional(vault.getVaultSettings().mountFlags());
|
mountFlagsField.textProperty().unbindBidirectional(vault.getVaultSettings().mountFlags());
|
||||||
vault.setCustomMountFlags(null);
|
vault.setCustomMountFlags(null);
|
||||||
mountFlags.textProperty().bind(vault.defaultMountFlagsProperty());
|
mountFlagsField.textProperty().bind(vault.defaultMountFlagsProperty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void chooseCustomMountPoint() {
|
public void chooseCustomMountPoint() {
|
||||||
chooseCustomMountPointOrReset(mountPointCustomDir);
|
try {
|
||||||
|
Path chosenPath = chooseCustomMountPointInternal();
|
||||||
|
vault.getVaultSettings().mountPoint().set(chosenPath);
|
||||||
|
} catch (NoDirSelectedException e) {
|
||||||
|
//no-op
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void chooseCustomMountPointOrReset(Toggle previousMountToggle) {
|
/**
|
||||||
|
* Prepares and opens a directory chooser dialog.
|
||||||
|
* This method blocks until the dialog is closed.
|
||||||
|
*
|
||||||
|
* @return the absolute path to the chosen directory
|
||||||
|
* @throws NoDirSelectedException if dialog is closed without choosing a directory
|
||||||
|
*/
|
||||||
|
private Path chooseCustomMountPointInternal() throws NoDirSelectedException {
|
||||||
DirectoryChooser directoryChooser = new DirectoryChooser();
|
DirectoryChooser directoryChooser = new DirectoryChooser();
|
||||||
directoryChooser.setTitle(resourceBundle.getString("vaultOptions.mount.mountPoint.directoryPickerTitle"));
|
directoryChooser.setTitle(resourceBundle.getString("vaultOptions.mount.mountPoint.directoryPickerTitle"));
|
||||||
try {
|
try {
|
||||||
var initialDir = Path.of(vault.getVaultSettings().getCustomMountPath().orElse(System.getProperty("user.home")));
|
var mp = vault.getVaultSettings().mountPoint().get();
|
||||||
|
var initialDir = mp != null && !isDriveLetter(mp) ? mp : Path.of(System.getProperty("user.home"));
|
||||||
|
|
||||||
if(Files.exists(initialDir)) {
|
if (Files.isDirectory(initialDir)) {
|
||||||
directoryChooser.setInitialDirectory(initialDir.toFile());
|
directoryChooser.setInitialDirectory(initialDir.toFile());
|
||||||
}
|
}
|
||||||
} catch (InvalidPathException e) {
|
} catch (InvalidPathException e) {
|
||||||
@@ -133,73 +142,122 @@ public class MountOptionsController implements FxController {
|
|||||||
}
|
}
|
||||||
File file = directoryChooser.showDialog(window);
|
File file = directoryChooser.showDialog(window);
|
||||||
if (file != null) {
|
if (file != null) {
|
||||||
vault.getVaultSettings().customMountPath().set(file.getAbsolutePath());
|
return file.toPath();
|
||||||
} else {
|
} else {
|
||||||
mountPoint.selectToggle(previousMountToggle);
|
throw new NoDirSelectedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void toggleMountPoint(@SuppressWarnings("unused") ObservableValue<? extends Toggle> observable, Toggle oldValue, Toggle newValue) {
|
private void selectedToggleChanged(ObservableValue<? extends Toggle> observable, Toggle oldToggle, Toggle newToggle) {
|
||||||
if (mountPointCustomDir.equals(newValue) && Strings.isNullOrEmpty(vault.getVaultSettings().customMountPath().get())) {
|
Path mountPointToBe = null;
|
||||||
chooseCustomMountPointOrReset(oldValue);
|
try {
|
||||||
|
//Remark: the mountpoint corresponding to the newToggle must be null, otherwise it would not be new!
|
||||||
|
if (mountPointDriveLetterBtn.equals(newToggle)) {
|
||||||
|
mountPointToBe = driveLetterSelection.getItems().get(0);
|
||||||
|
} else if (mountPointDirBtn.equals(newToggle)) {
|
||||||
|
mountPointToBe = chooseCustomMountPointInternal();
|
||||||
|
}
|
||||||
|
vault.getVaultSettings().mountPoint().set(mountPointToBe);
|
||||||
|
} catch (NoDirSelectedException e) {
|
||||||
|
if (!mountPointDirBtn.equals(oldToggle)) {
|
||||||
|
mountPointToggleGroup.selectToggle(oldToggle);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private boolean isDriveLetter(Path mountPoint) {
|
||||||
* Converts 'C' to "C:" to translate between model and GUI.
|
if (mountPoint != null) {
|
||||||
*/
|
var s = mountPoint.toString();
|
||||||
private static class WinDriveLetterLabelConverter extends StringConverter<String> {
|
return s.length() == 3 && mountPoint.toString().endsWith(":\\");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private final Set<String> occupiedDriveLetters;
|
private static class WinDriveLetterLabelConverter extends StringConverter<Path> {
|
||||||
|
|
||||||
|
private final Set<Path> occupiedDriveLetters;
|
||||||
private final ResourceBundle resourceBundle;
|
private final ResourceBundle resourceBundle;
|
||||||
|
|
||||||
WinDriveLetterLabelConverter(WindowsDriveLetters windowsDriveLetters, ResourceBundle resourceBundle) {
|
WinDriveLetterLabelConverter(WindowsDriveLetters windowsDriveLetters, ResourceBundle resourceBundle) {
|
||||||
this.occupiedDriveLetters = windowsDriveLetters.getOccupiedDriveLetters();
|
this.occupiedDriveLetters = windowsDriveLetters.getOccupied();
|
||||||
this.resourceBundle = resourceBundle;
|
this.resourceBundle = resourceBundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString(String driveLetter) {
|
public String toString(Path driveLetter) {
|
||||||
if (Strings.isNullOrEmpty(driveLetter)) {
|
if (driveLetter == null) {
|
||||||
return "";
|
return "";
|
||||||
} else if (occupiedDriveLetters.contains(driveLetter)) {
|
} else if (occupiedDriveLetters.contains(driveLetter)) {
|
||||||
return driveLetter + ": (" + resourceBundle.getString("vaultOptions.mount.winDriveLetterOccupied") + ")";
|
return driveLetter.toString().substring(0, 2) + " (" + resourceBundle.getString("vaultOptions.mount.winDriveLetterOccupied") + ")";
|
||||||
} else {
|
} else {
|
||||||
return driveLetter + ":";
|
return driveLetter.toString().substring(0, 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String fromString(String string) {
|
public Path fromString(String string) {
|
||||||
throw new UnsupportedOperationException();
|
if (string.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return Path.of(string + "\\");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//@formatter:off
|
||||||
|
private static class NoDirSelectedException extends Exception {}
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
// Getter & Setter
|
// Getter & Setter
|
||||||
|
|
||||||
public boolean isOsWindows() {
|
public ObservableValue<Boolean> mountFlagsSupportedProperty() {
|
||||||
return SystemUtils.IS_OS_WINDOWS;
|
return mountFlagsSupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCustomMountPointSupported() {
|
public boolean isMountFlagsSupported() {
|
||||||
return !(usedVolumeImpl == VolumeImpl.WEBDAV && isOsWindows());
|
return mountFlagsSupported.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableValue<Boolean> mountpointDirSupportedProperty() {
|
||||||
|
return mountpointDirSupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMountpointDirSupported() {
|
||||||
|
return mountpointDirSupported.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableValue<Boolean> mountpointDriveLetterSupportedProperty() {
|
||||||
|
return mountpointDriveLetterSupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMountpointDriveLetterSupported() {
|
||||||
|
return mountpointDriveLetterSupported.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableValue<Boolean> readOnlySupportedProperty() {
|
||||||
|
return mountpointDriveLetterSupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isReadOnlySupported() {
|
public boolean isReadOnlySupported() {
|
||||||
return !(usedVolumeImpl == VolumeImpl.FUSE && isOsWindows());
|
return readOnlySupported.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringProperty customMountPathProperty() {
|
public ObservableValue<Path> driveLetterProperty() {
|
||||||
return vault.getVaultSettings().customMountPath();
|
return driveLetter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCustomMountOptionsSupported() {
|
public Path getDriveLetter() {
|
||||||
return usedVolumeImpl != VolumeImpl.WEBDAV;
|
return driveLetter.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCustomMountPath() {
|
public ObservableValue<String> directoryPathProperty() {
|
||||||
return vault.getVaultSettings().customMountPath().get();
|
return directoryPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDirectoryPath() {
|
||||||
|
return directoryPath.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,15 +5,15 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
src: url('opensans-regular.ttf');
|
src: url('opensans_regular.ttf');
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
src: url('opensans-semibold.ttf');
|
src: url('opensans_semibold.ttf');
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
src: url('opensans-bold.ttf');
|
src: url('opensans_bold.ttf');
|
||||||
}
|
}
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
|
|||||||
@@ -5,15 +5,15 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
src: url('opensans-regular.ttf');
|
src: url('opensans_regular.ttf');
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
src: url('opensans-semibold.ttf');
|
src: url('opensans_semibold.ttf');
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
src: url('opensans-bold.ttf');
|
src: url('opensans_bold.ttf');
|
||||||
}
|
}
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
<ButtonBar buttonMinWidth="120" buttonOrder="B+X">
|
<ButtonBar buttonMinWidth="120" buttonOrder="B+X">
|
||||||
<buttons>
|
<buttons>
|
||||||
<Button text="%generic.button.back" ButtonBar.buttonData="BACK_PREVIOUS" onAction="#back"/>
|
<Button text="%generic.button.back" ButtonBar.buttonData="BACK_PREVIOUS" onAction="#back"/>
|
||||||
<Button text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" onAction="#next" defaultButton="true" disable="${controller.invalidVaultName}"/>
|
<Button text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" onAction="#next" defaultButton="true" disable="${!controller.validVaultName}"/>
|
||||||
</buttons>
|
</buttons>
|
||||||
</ButtonBar>
|
</ButtonBar>
|
||||||
</children>
|
</children>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
<?import javafx.scene.control.CheckBox?>
|
<?import javafx.scene.control.CheckBox?>
|
||||||
<?import javafx.scene.control.ChoiceBox?>
|
<?import javafx.scene.control.ChoiceBox?>
|
||||||
<?import javafx.scene.control.Hyperlink?>
|
<?import javafx.scene.control.Hyperlink?>
|
||||||
<?import javafx.scene.control.Label?>
|
|
||||||
<?import javafx.scene.control.ToggleGroup?>
|
<?import javafx.scene.control.ToggleGroup?>
|
||||||
<?import javafx.scene.layout.HBox?>
|
<?import javafx.scene.layout.HBox?>
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
@@ -27,7 +26,7 @@
|
|||||||
<CheckBox fx:id="autoCloseVaultsCheckbox" text="%preferences.general.autoCloseVaults" />
|
<CheckBox fx:id="autoCloseVaultsCheckbox" text="%preferences.general.autoCloseVaults" />
|
||||||
|
|
||||||
<HBox spacing="12" alignment="CENTER_LEFT">
|
<HBox spacing="12" alignment="CENTER_LEFT">
|
||||||
<Label text="%preferences.general.keychainBackend"/>
|
<CheckBox fx:id="useKeychainCheckbox" text="%preferences.general.keychainBackend"/>
|
||||||
<ChoiceBox fx:id="keychainBackendChoiceBox"/>
|
<ChoiceBox fx:id="keychainBackendChoiceBox"/>
|
||||||
</HBox>
|
</HBox>
|
||||||
|
|
||||||
|
|||||||
@@ -20,15 +20,10 @@
|
|||||||
<ChoiceBox fx:id="volumeTypeChoiceBox"/>
|
<ChoiceBox fx:id="volumeTypeChoiceBox"/>
|
||||||
</HBox>
|
</HBox>
|
||||||
|
|
||||||
<HBox spacing="12" alignment="CENTER_LEFT" visible="${controller.showWebDavSettings}">
|
<HBox spacing="12" alignment="CENTER_LEFT" visible="${controller.loopbackPortSupported}">
|
||||||
<Label text="%preferences.volume.webdav.port"/>
|
<Label text="%preferences.volume.tcp.port"/>
|
||||||
<NumericTextField fx:id="webDavPortField"/>
|
<NumericTextField fx:id="loopbackPortField"/>
|
||||||
<Button text="%generic.button.apply" fx:id="changeWebDavPortButton" onAction="#doChangeWebDavPort"/>
|
<Button text="%generic.button.apply" fx:id="loopbackPortApplyButton" onAction="#doChangeLoopbackPort"/>
|
||||||
</HBox>
|
|
||||||
|
|
||||||
<HBox spacing="12" alignment="CENTER_LEFT" visible="${controller.showWebDavScheme}">
|
|
||||||
<Label text="%preferences.volume.webdav.scheme"/>
|
|
||||||
<ChoiceBox fx:id="webDavUrlSchemeChoiceBox" maxWidth="Infinity"/>
|
|
||||||
</HBox>
|
</HBox>
|
||||||
</children>
|
</children>
|
||||||
</VBox>
|
</VBox>
|
||||||
|
|||||||
@@ -39,9 +39,6 @@
|
|||||||
<Insets bottom="6" top="6"/>
|
<Insets bottom="6" top="6"/>
|
||||||
</padding>
|
</padding>
|
||||||
</Label>
|
</Label>
|
||||||
<FormattedLabel visible="${controller.notExisting}" managed="${controller.notExisting}" format="%unlock.error.invalidMountPoint.notExisting" arg1="${controller.mountPoint}" wrapText="true"/>
|
|
||||||
<FormattedLabel visible="${controller.existing}" managed="${controller.existing}" format="%unlock.error.invalidMountPoint.existing" arg1="${controller.mountPoint}" wrapText="true"/>
|
|
||||||
<FormattedLabel visible="${controller.driveLetterOccupied}" managed="${controller.driveLetterOccupied}" format="%unlock.error.invalidMountPoint.driveLetterOccupied" arg1="${controller.mountPoint}" wrapText="true"/>
|
|
||||||
|
|
||||||
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
|
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
|
||||||
<ButtonBar buttonMinWidth="120" buttonOrder="+C">
|
<ButtonBar buttonMinWidth="120" buttonOrder="+C">
|
||||||
|
|||||||
@@ -12,20 +12,26 @@
|
|||||||
alignment="TOP_CENTER"
|
alignment="TOP_CENTER"
|
||||||
spacing="9">
|
spacing="9">
|
||||||
<Label text="%main.vaultDetail.accessLocation"/>
|
<Label text="%main.vaultDetail.accessLocation"/>
|
||||||
<Button styleClass="button-large" contentDisplay="GRAPHIC_ONLY" minWidth="120" onAction="#revealAccessLocation" defaultButton="${controller.vault.unlocked}">
|
<Button styleClass="button-large" contentDisplay="GRAPHIC_ONLY" minWidth="120" onAction="#revealAccessLocation" defaultButton="${controller.accessibleViaPath}" visible="${controller.accessibleViaPath}" managed="${controller.accessibleViaPath}">
|
||||||
<graphic>
|
<graphic>
|
||||||
<HBox spacing="12" alignment="CENTER">
|
<HBox spacing="12" alignment="CENTER">
|
||||||
<FontAwesome5IconView glyph="HDD" glyphSize="24"/>
|
<FontAwesome5IconView glyph="HDD" glyphSize="24"/>
|
||||||
<VBox spacing="4" alignment="CENTER_LEFT">
|
<VBox spacing="4" alignment="CENTER_LEFT">
|
||||||
<Label text="%main.vaultDetail.revealBtn"/>
|
<Label text="%main.vaultDetail.revealBtn"/>
|
||||||
<Label styleClass="label-extra-small" text="${controller.vault.accessPoint}" textOverrun="CENTER_ELLIPSIS"
|
|
||||||
visible="${controller.vault.accessPointPresent}" managed="${controller.vault.accessPointPresent}"/>
|
|
||||||
</VBox>
|
</VBox>
|
||||||
</HBox>
|
</HBox>
|
||||||
</graphic>
|
</graphic>
|
||||||
<tooltip>
|
</Button>
|
||||||
<Tooltip text="${controller.vault.accessPoint}"/>
|
<Button styleClass="button-large" contentDisplay="GRAPHIC_ONLY" minWidth="120" onAction="#copyMountUri" defaultButton="${controller.accessibleViaUri}" visible="${controller.accessibleViaUri}" managed="${controller.accessibleViaUri}">
|
||||||
</tooltip>
|
<graphic>
|
||||||
|
<HBox spacing="12" alignment="CENTER">
|
||||||
|
<FontAwesome5IconView glyph="LINK" glyphSize="24"/>
|
||||||
|
<VBox spacing="4" alignment="CENTER_LEFT">
|
||||||
|
<Label text="%generic.button.copy"/> <!-- TODO -->
|
||||||
|
<Label styleClass="label-extra-small" text="${controller.mountUri}" textOverrun="CENTER_ELLIPSIS"/>
|
||||||
|
</VBox>
|
||||||
|
</HBox>
|
||||||
|
</graphic>
|
||||||
</Button>
|
</Button>
|
||||||
<Button text="%main.vaultDetail.lockBtn" minWidth="120" onAction="#lock">
|
<Button text="%main.vaultDetail.lockBtn" minWidth="120" onAction="#lock">
|
||||||
<graphic>
|
<graphic>
|
||||||
|
|||||||
@@ -16,41 +16,46 @@
|
|||||||
fx:controller="org.cryptomator.ui.vaultoptions.MountOptionsController"
|
fx:controller="org.cryptomator.ui.vaultoptions.MountOptionsController"
|
||||||
spacing="6">
|
spacing="6">
|
||||||
<fx:define>
|
<fx:define>
|
||||||
<ToggleGroup fx:id="mountPoint"/>
|
<ToggleGroup fx:id="mountPointToggleGroup"/>
|
||||||
</fx:define>
|
</fx:define>
|
||||||
<padding>
|
<padding>
|
||||||
<Insets topRightBottomLeft="12"/>
|
<Insets topRightBottomLeft="12"/>
|
||||||
</padding>
|
</padding>
|
||||||
<children>
|
<children>
|
||||||
<CheckBox fx:id="readOnlyCheckbox" text="%vaultOptions.mount.readonly"/>
|
<Label text="Options depend on the selected volume provider in the general preferences"/>
|
||||||
|
<CheckBox fx:id="readOnlyCheckbox" text="%vaultOptions.mount.readonly" visible="${controller.readOnlySupported}" managed="${controller.readOnlySupported}"/>
|
||||||
|
|
||||||
<CheckBox fx:id="customMountFlagsCheckbox" text="%vaultOptions.mount.customMountFlags" onAction="#toggleUseCustomMountFlags" visible="${controller.customMountOptionsSupported}" managed="${controller.customMountOptionsSupported}"/>
|
<VBox visible="${controller.mountFlagsSupported}" managed="${controller.mountFlagsSupported}">
|
||||||
|
<CheckBox fx:id="customMountFlagsCheckbox" text="%vaultOptions.mount.customMountFlags" onAction="#toggleUseCustomMountFlags"/>
|
||||||
<TextField fx:id="mountFlags" HBox.hgrow="ALWAYS" maxWidth="Infinity">
|
<TextField fx:id="mountFlagsField" HBox.hgrow="ALWAYS" maxWidth="Infinity">
|
||||||
<VBox.margin>
|
<VBox.margin>
|
||||||
<Insets left="24"/>
|
<Insets left="24"/>
|
||||||
</VBox.margin>
|
</VBox.margin>
|
||||||
</TextField>
|
</TextField>
|
||||||
|
</VBox>
|
||||||
|
|
||||||
<Label text="%vaultOptions.mount.mountPoint">
|
<Label text="%vaultOptions.mount.mountPoint">
|
||||||
<VBox.margin>
|
<VBox.margin>
|
||||||
<Insets top="9"/>
|
<Insets top="9"/>
|
||||||
</VBox.margin>
|
</VBox.margin>
|
||||||
</Label>
|
</Label>
|
||||||
<RadioButton toggleGroup="${mountPoint}" fx:id="mountPointAuto" text="%vaultOptions.mount.mountPoint.auto"/>
|
|
||||||
<HBox spacing="6" visible="${controller.osWindows}" managed="${controller.osWindows}">
|
<RadioButton toggleGroup="${mountPointToggleGroup}" fx:id="mountPointAutoBtn" text="%vaultOptions.mount.mountPoint.auto"/>
|
||||||
<RadioButton toggleGroup="${mountPoint}" fx:id="mountPointWinDriveLetter" text="%vaultOptions.mount.mountPoint.driveLetter"/>
|
|
||||||
<ChoiceBox fx:id="driveLetterSelection" disable="${!mountPointWinDriveLetter.selected}"/>
|
<HBox spacing="6" visible="${controller.mountpointDriveLetterSupported}" managed="${controller.mountpointDriveLetterSupported}">
|
||||||
|
<RadioButton toggleGroup="${mountPointToggleGroup}" fx:id="mountPointDriveLetterBtn" text="%vaultOptions.mount.mountPoint.driveLetter"/>
|
||||||
|
<ChoiceBox fx:id="driveLetterSelection" disable="${!mountPointDriveLetterBtn.selected}" value="${controller.driveLetter}"/>
|
||||||
</HBox>
|
</HBox>
|
||||||
<HBox fx:id="customMountPointRadioBtn" spacing="6" alignment="CENTER_LEFT" visible="${controller.customMountOptionsSupported}" managed="${controller.customMountOptionsSupported}">
|
|
||||||
<RadioButton toggleGroup="${mountPoint}" fx:id="mountPointCustomDir" text="%vaultOptions.mount.mountPoint.custom" />
|
<HBox spacing="6" alignment="CENTER_LEFT" visible="${controller.mountpointDirSupported}" managed="${controller.mountpointDirSupported}">
|
||||||
<Button text="%vaultOptions.mount.mountPoint.directoryPickerButton" onAction="#chooseCustomMountPoint" contentDisplay="LEFT" disable="${!mountPointCustomDir.selected}">
|
<RadioButton toggleGroup="${mountPointToggleGroup}" fx:id="mountPointDirBtn" text="%vaultOptions.mount.mountPoint.custom"/>
|
||||||
|
<Button text="%vaultOptions.mount.mountPoint.directoryPickerButton" onAction="#chooseCustomMountPoint" contentDisplay="LEFT" disable="${!mountPointDirBtn.selected}">
|
||||||
<graphic>
|
<graphic>
|
||||||
<FontAwesome5IconView glyph="FOLDER_OPEN" glyphSize="15"/>
|
<FontAwesome5IconView glyph="FOLDER_OPEN" glyphSize="15"/>
|
||||||
</graphic>
|
</graphic>
|
||||||
</Button>
|
</Button>
|
||||||
</HBox>
|
</HBox>
|
||||||
<TextField text="${controller.customMountPath}" visible="${mountPointCustomDir.selected}" maxWidth="Infinity" disable="true" managed="${customMountPointRadioBtn.managed}">
|
<TextField fx:id="directoryPathField" text="${controller.directoryPath}" visible="${mountPointDirBtn.selected}" managed="${mountPointDirBtn.managed}" maxWidth="Infinity" editable="false" >
|
||||||
<VBox.margin>
|
<VBox.margin>
|
||||||
<Insets left="24"/>
|
<Insets left="24"/>
|
||||||
</VBox.margin>
|
</VBox.margin>
|
||||||
|
|||||||
@@ -252,8 +252,7 @@ preferences.interface.showTrayIcon=Show tray icon (requires restart)
|
|||||||
## Volume
|
## Volume
|
||||||
preferences.volume=Virtual Drive
|
preferences.volume=Virtual Drive
|
||||||
preferences.volume.type=Volume Type
|
preferences.volume.type=Volume Type
|
||||||
preferences.volume.webdav.port=WebDAV Port
|
preferences.volume.tcp.port=TCP Port
|
||||||
preferences.volume.webdav.scheme=WebDAV Scheme
|
|
||||||
## Updates
|
## Updates
|
||||||
preferences.updates=Updates
|
preferences.updates=Updates
|
||||||
preferences.updates.currentVersion=Current Version: %s
|
preferences.updates.currentVersion=Current Version: %s
|
||||||
@@ -383,9 +382,9 @@ vaultOptions.mount.winDriveLetterOccupied=occupied
|
|||||||
vaultOptions.mount.mountPoint=Mount Point
|
vaultOptions.mount.mountPoint=Mount Point
|
||||||
vaultOptions.mount.mountPoint.auto=Automatically pick a suitable location
|
vaultOptions.mount.mountPoint.auto=Automatically pick a suitable location
|
||||||
vaultOptions.mount.mountPoint.driveLetter=Use assigned drive letter
|
vaultOptions.mount.mountPoint.driveLetter=Use assigned drive letter
|
||||||
vaultOptions.mount.mountPoint.custom=Custom path
|
vaultOptions.mount.mountPoint.custom=Use chosen directory
|
||||||
vaultOptions.mount.mountPoint.directoryPickerButton=Choose…
|
vaultOptions.mount.mountPoint.directoryPickerButton=Choose…
|
||||||
vaultOptions.mount.mountPoint.directoryPickerTitle=Pick an empty directory
|
vaultOptions.mount.mountPoint.directoryPickerTitle=Pick a directory
|
||||||
## Master Key
|
## Master Key
|
||||||
vaultOptions.masterkey=Password
|
vaultOptions.masterkey=Password
|
||||||
vaultOptions.masterkey.changePasswordBtn=Change Password
|
vaultOptions.masterkey.changePasswordBtn=Change Password
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user