mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-15 17:21:27 +00:00
Compare commits
2 Commits
1.16.1
...
feature/hy
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d521c3f78 | ||
|
|
737d7e6317 |
4
.github/CONTRIBUTING.md
vendored
4
.github/CONTRIBUTING.md
vendored
@@ -16,10 +16,6 @@
|
||||
|
||||
- Suggest your change by [submitting a new issue](https://github.com/cryptomator/cryptomator/issues/new/choose) and start writing code.
|
||||
|
||||
## Do you intend to add a new translation or change an existing one?
|
||||
|
||||
Translations are not managed directly in this repository. Instead, we use [Crowdin](https://translate.cryptomator.org/), which automatically synchronizes translations with this repository. If you want to help us with translations, please visit our translation project on Crowdin.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
Help us keep Cryptomator open and inclusive. Please read and follow our [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md).
|
||||
|
||||
18
.github/ISSUE_TEMPLATE/bug.yml
vendored
18
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -1,14 +1,7 @@
|
||||
name: Bug Report
|
||||
description: Create a report to help us improve
|
||||
type: "Bug"
|
||||
labels: ["type:bug"]
|
||||
body:
|
||||
- type: input
|
||||
id: summary
|
||||
attributes:
|
||||
label: Summary
|
||||
placeholder: Please summarize your problem.
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
attributes:
|
||||
@@ -18,6 +11,13 @@ body:
|
||||
required: true
|
||||
- label: I agree to follow this project's [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md)
|
||||
required: true
|
||||
- type: input
|
||||
id: summary
|
||||
attributes:
|
||||
label: Summary
|
||||
placeholder: Please summarize your problem.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: software-versions
|
||||
attributes:
|
||||
@@ -97,4 +97,4 @@ body:
|
||||
id: further-info
|
||||
attributes:
|
||||
label: Anything else?
|
||||
description: Links? References? Screenshots? Configurations? Any data that might be necessary to reproduce the issue?
|
||||
description: Links? References? Screenshots? Configurations? Any data that might be necessary to reproduce the issue?
|
||||
|
||||
16
.github/ISSUE_TEMPLATE/feature.yml
vendored
16
.github/ISSUE_TEMPLATE/feature.yml
vendored
@@ -1,14 +1,7 @@
|
||||
name: Feature Request
|
||||
description: Suggest an idea for this project
|
||||
type: "Feature"
|
||||
labels: ["type:feature-request"]
|
||||
body:
|
||||
- type: input
|
||||
id: summary
|
||||
attributes:
|
||||
label: Summary
|
||||
placeholder: Please summarize your feature request.
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
attributes:
|
||||
@@ -18,6 +11,13 @@ body:
|
||||
required: true
|
||||
- label: I agree to follow this project's [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md)
|
||||
required: true
|
||||
- type: input
|
||||
id: summary
|
||||
attributes:
|
||||
label: Summary
|
||||
placeholder: Please summarize your feature request.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: motivation
|
||||
attributes:
|
||||
|
||||
10
.github/workflows/appimage.yml
vendored
10
.github/workflows/appimage.yml
vendored
@@ -11,7 +11,7 @@ on:
|
||||
|
||||
env:
|
||||
JAVA_DIST: 'temurin'
|
||||
JAVA_VERSION: '23.0.2'
|
||||
JAVA_VERSION: '23.0.1+11'
|
||||
|
||||
jobs:
|
||||
get-version:
|
||||
@@ -29,12 +29,12 @@ jobs:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
appimage-suffix: x86_64
|
||||
openjfx-url: 'https://download2.gluonhq.com/openjfx/23.0.2/openjfx-23.0.2_linux-x64_bin-jmods.zip'
|
||||
openjfx-sha: '063baebc6922e4a89c94b9dfb7a4f53e59e8d6fec400d4e670b31bc2ab324dec'
|
||||
openjfx-url: 'https://download2.gluonhq.com/openjfx/23.0.1/openjfx-23.0.1_linux-x64_bin-jmods.zip'
|
||||
openjfx-sha: '2164bca470bf70a5e2764645e2078ba7f787b274e5be3d7df30d87c5bb62bba6'
|
||||
- os: ubuntu-24.04-arm
|
||||
appimage-suffix: aarch64
|
||||
openjfx-url: 'https://download2.gluonhq.com/openjfx/23.0.2/openjfx-23.0.2_linux-aarch64_bin-jmods.zip'
|
||||
openjfx-sha: '9bbedaeae1590b69e2b22237bda310936df33e344dbc243bea2e86acaab3a0d8'
|
||||
openjfx-url: 'https://download2.gluonhq.com/openjfx/23.0.1/openjfx-23.0.1_linux-aarch64_bin-jmods.zip'
|
||||
openjfx-sha: '09c92fa9fa0b82adefd88640a14ebb2a49e5f3f733a57d1542f5590d060ffe1b'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Java
|
||||
|
||||
66
.github/workflows/av-whitelist.yml
vendored
66
.github/workflows/av-whitelist.yml
vendored
@@ -13,48 +13,15 @@ on:
|
||||
description: "Url to the file to upload"
|
||||
required: true
|
||||
type: string
|
||||
avast:
|
||||
description: "Upload to Avast"
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
kaspersky:
|
||||
description: "Upload to Kaspersky"
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
download-file:
|
||||
name: Downloads the file into the VM
|
||||
allowlist:
|
||||
name: Anti Virus Allowlisting
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
fileName: ${{ steps.extractName.outputs.fileName}}
|
||||
steps:
|
||||
- name: Extract file name
|
||||
id: extractName
|
||||
run: |
|
||||
url="${{ inputs.url }}"
|
||||
echo "fileName=${url##*/}" >> $GITHUB_OUTPUT
|
||||
- name: Download file
|
||||
run: curl --remote-name ${{ inputs.url }} -L -o ${{steps.extractName.outputs.fileName}}
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ steps.extractName.outputs.fileName }}
|
||||
path: ${{ steps.extractName.outputs.fileName }}
|
||||
if-no-files-found: error
|
||||
allowlist-kaspersky:
|
||||
name: Anti Virus Allowlisting Kaspersky
|
||||
runs-on: ubuntu-latest
|
||||
needs: download-file
|
||||
if: github.event_name == 'workflow_call' || inputs.kaspersky
|
||||
steps:
|
||||
- name: Download artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ needs.download-file.outputs.fileName }}
|
||||
path: upload
|
||||
run: |
|
||||
curl --remote-name ${{ inputs.url }} -L
|
||||
- name: Upload to Kaspersky
|
||||
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
|
||||
with:
|
||||
@@ -63,26 +30,11 @@ jobs:
|
||||
port: 990
|
||||
username: ${{ secrets.ALLOWLIST_KASPERSKY_USERNAME }}
|
||||
password: ${{ secrets.ALLOWLIST_KASPERSKY_PASSWORD }}
|
||||
local-dir: ./upload/
|
||||
allowlist-avast:
|
||||
name: Anti Virus Allowlisting Avast
|
||||
runs-on: ubuntu-latest
|
||||
needs: download-file
|
||||
if: github.event_name == 'workflow_call' || inputs.avast
|
||||
steps:
|
||||
- name: Download artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ needs.download-file.outputs.fileName }}
|
||||
path: upload
|
||||
- name: Upload to Avast
|
||||
uses: wlixcc/SFTP-Deploy-Action@v1.2.5
|
||||
- name: Upload to Avast
|
||||
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
|
||||
with:
|
||||
protocol: ftp
|
||||
server: whitelisting.avast.com
|
||||
port: 22
|
||||
port: 21
|
||||
username: ${{ secrets.ALLOWLIST_AVAST_USERNAME }}
|
||||
password: ${{ secrets.ALLOWLIST_AVAST_PASSWORD }}
|
||||
ssh_private_key: ''
|
||||
sftp_only: true
|
||||
local_path: './upload/*'
|
||||
remote_path: '/data'
|
||||
password: ${{ secrets.ALLOWLIST_AVAST_PASSWORD }}
|
||||
14
.github/workflows/debian.yml
vendored
14
.github/workflows/debian.yml
vendored
@@ -17,18 +17,18 @@ on:
|
||||
|
||||
env:
|
||||
JAVA_DIST: 'temurin'
|
||||
JAVA_VERSION: '23.0.2+7'
|
||||
JAVA_VERSION: '23.0.1+11'
|
||||
COFFEELIBS_JDK: 23
|
||||
COFFEELIBS_JDK_VERSION: '23.0.2+7-0ppa1'
|
||||
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/23.0.2/openjfx-23.0.2_linux-x64_bin-jmods.zip'
|
||||
OPENJFX_JMODS_AMD64_HASH: '063baebc6922e4a89c94b9dfb7a4f53e59e8d6fec400d4e670b31bc2ab324dec'
|
||||
OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/23.0.2/openjfx-23.0.2_linux-aarch64_bin-jmods.zip'
|
||||
OPENJFX_JMODS_AARCH64_HASH: '9bbedaeae1590b69e2b22237bda310936df33e344dbc243bea2e86acaab3a0d8'
|
||||
COFFEELIBS_JDK_VERSION: '23.0.1+11-0ppa1'
|
||||
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/23.0.1/openjfx-23.0.1_linux-x64_bin-jmods.zip'
|
||||
OPENJFX_JMODS_AMD64_HASH: '2164bca470bf70a5e2764645e2078ba7f787b274e5be3d7df30d87c5bb62bba6'
|
||||
OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/23.0.1/openjfx-23.0.1_linux-aarch64_bin-jmods.zip'
|
||||
OPENJFX_JMODS_AARCH64_HASH: '09c92fa9fa0b82adefd88640a14ebb2a49e5f3f733a57d1542f5590d060ffe1b'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Debian Package
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- id: versions
|
||||
|
||||
6
.github/workflows/mac-dmg-x64.yml
vendored
6
.github/workflows/mac-dmg-x64.yml
vendored
@@ -15,7 +15,7 @@ on:
|
||||
|
||||
env:
|
||||
JAVA_DIST: 'temurin'
|
||||
JAVA_VERSION: '23.0.2+7'
|
||||
JAVA_VERSION: '23.0.1+11'
|
||||
|
||||
jobs:
|
||||
get-version:
|
||||
@@ -35,8 +35,8 @@ jobs:
|
||||
architecture: x64
|
||||
output-suffix: x64
|
||||
fuse-lib: macFUSE
|
||||
openjfx-url: 'https://download2.gluonhq.com/openjfx/23.0.2/openjfx-23.0.2_osx-x64_bin-jmods.zip'
|
||||
openjfx-sha: '5e6c65c065eea22430c0eab36f37a5985eb8ad99e19e8772262021740d338f68'
|
||||
openjfx-url: 'https://download2.gluonhq.com/openjfx/23.0.1/openjfx-23.0.1_osx-x64_bin-jmods.zip'
|
||||
openjfx-sha: '8857965975c464a0e5d57709292ce357d0ebb39f6168c41d5ca38301e42c3c8e'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Java
|
||||
|
||||
6
.github/workflows/mac-dmg.yml
vendored
6
.github/workflows/mac-dmg.yml
vendored
@@ -16,7 +16,7 @@ on:
|
||||
|
||||
env:
|
||||
JAVA_DIST: 'temurin'
|
||||
JAVA_VERSION: '23.0.2+7'
|
||||
JAVA_VERSION: '23.0.1+11'
|
||||
|
||||
jobs:
|
||||
get-version:
|
||||
@@ -36,8 +36,8 @@ jobs:
|
||||
architecture: aarch64
|
||||
output-suffix: arm64
|
||||
fuse-lib: FUSE-T
|
||||
openjfx-url: 'https://download2.gluonhq.com/openjfx/23.0.2/openjfx-23.0.2_osx-aarch64_bin-jmods.zip'
|
||||
openjfx-sha: 'c690cc642a3924cf56622951f478ba57aec9ce09063761f800c3319331bed3fc'
|
||||
openjfx-url: 'https://download2.gluonhq.com/openjfx/23.0.1/openjfx-23.0.1_osx-aarch64_bin-jmods.zip'
|
||||
openjfx-sha: 'a800724a1f3e6757ecfa0bd5bf7ed64d2e6a7a3f5b3522650a70b8cfc7782fb6'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Java
|
||||
|
||||
5
.github/workflows/release-check.yml
vendored
5
.github/workflows/release-check.yml
vendored
@@ -57,6 +57,11 @@ jobs:
|
||||
dependency-check
|
||||
env:
|
||||
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 5
|
||||
- name: Validate urls used in app
|
||||
uses: urlstechie/urlchecker-action@0.0.34
|
||||
with:
|
||||
file_types: .md,.json
|
||||
include_files: README.md,src/main/resources/hyperlinks.json
|
||||
- name: Run org.owasp:dependency-check plugin
|
||||
id: dependency-check
|
||||
continue-on-error: true
|
||||
|
||||
7
.github/workflows/win-exe.yml
vendored
7
.github/workflows/win-exe.yml
vendored
@@ -16,7 +16,7 @@ on:
|
||||
|
||||
env:
|
||||
JAVA_DIST: 'zulu'
|
||||
JAVA_VERSION: '23.0.2+7'
|
||||
JAVA_VERSION: '23.0.1+11'
|
||||
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/23.0.1/openjfx-23.0.1_windows-x64_bin-jmods.zip'
|
||||
OPENJFX_JMODS_AMD64_HASH: 'ee176dcee3bd78bde7910735bd67f67c792882f5b89626796ae06f7a1c0119d3'
|
||||
WINFSP_MSI: 'https://github.com/winfsp/winfsp/releases/download/v2.0/winfsp-2.0.23075.msi'
|
||||
@@ -40,6 +40,9 @@ jobs:
|
||||
LOOPBACK_ALIAS: 'cryptomator-vault'
|
||||
WIN_CONSOLE_FLAG: ''
|
||||
steps:
|
||||
- name: Upgrade WIX to latest version
|
||||
run: choco install wixtoolset --version 3.14.1
|
||||
shell: pwsh
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
@@ -391,7 +394,7 @@ jobs:
|
||||
|
||||
allowlist-exe:
|
||||
uses: ./.github/workflows/av-whitelist.yml
|
||||
needs: [publish, allowlist-msi]
|
||||
needs: [publish]
|
||||
with:
|
||||
url: ${{ needs.publish.outputs.download-url-exe }}
|
||||
secrets: inherit
|
||||
|
||||
25
.idea/compiler.xml
generated
25
.idea/compiler.xml
generated
@@ -39,6 +39,31 @@
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.10/kotlin-reflect-1.6.10.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/net/ltgt/gradle/incap/incap/0.2/incap-0.2.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-compiler/2.55/dagger-compiler-2.55.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.55/dagger-2.55.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/jakarta/inject/jakarta.inject-api/2.0.1/jakarta.inject-api-2.0.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jspecify/jspecify/1.0.0/jspecify-1.0.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.55/dagger-spi-2.55.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/devtools/ksp/symbol-processing-api/2.0.21-1.0.28/symbol-processing-api-2.0.21-1.0.28.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.21/kotlin-stdlib-2.0.21.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/guava/failureaccess/1.0.2/failureaccess-1.0.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/guava/guava/33.0.0-jre/guava-33.0.0-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$/org/checkerframework/checker-qual/3.41.0/checker-qual-3.41.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.23.0/error_prone_annotations-2.23.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/j2objc/j2objc-annotations/2.8/j2objc-annotations-2.8.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/squareup/javapoet/1.13.0/javapoet-1.13.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/errorprone/javac-shaded/9-dev-r4023-3/javac-shaded-9-dev-r4023-3.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/squareup/kotlinpoet/1.11.0/kotlinpoet-1.11.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.10/kotlin-stdlib-jdk8-1.6.10.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.6.10/kotlin-stdlib-jdk7-1.6.10.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.10/kotlin-reflect-1.6.10.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/net/ltgt/gradle/incap/incap/0.2/incap-0.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-compat-qual/2.5.5/checker-compat-qual-2.5.5.jar" />
|
||||
</processorPath>
|
||||
<module name="cryptomator" />
|
||||
</profile>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[](https://cryptomator.org/)
|
||||
|
||||
[](https://github.com/cryptomator/cryptomator/actions/workflows/build.yml?query=branch%3Adevelop)
|
||||
[](https://github.com/cryptomator/cryptomator/actions?query=workflow%3ABuild)
|
||||
[](https://snyk.io/test/github/cryptomator/cryptomator)
|
||||
[](https://sonarcloud.io/dashboard?id=cryptomator_cryptomator)
|
||||
[](https://mastodon.online/@cryptomator)
|
||||
|
||||
6
dist/linux/appimage/build.sh
vendored
6
dist/linux/appimage/build.sh
vendored
@@ -23,12 +23,12 @@ mvn -B -f ../../../pom.xml clean package -Plinux -DskipTests -Djavafx.platform=l
|
||||
cp ../../../LICENSE.txt ../../../target
|
||||
cp ../../../target/cryptomator-*.jar ../../../target/mods
|
||||
|
||||
JAVAFX_VERSION=23.0.2
|
||||
JAVAFX_VERSION=22.0.2
|
||||
JAVAFX_ARCH="x64"
|
||||
JAVAFX_JMODS_SHA256='063baebc6922e4a89c94b9dfb7a4f53e59e8d6fec400d4e670b31bc2ab324dec'
|
||||
JAVAFX_JMODS_SHA256='2164bca470bf70a5e2764645e2078ba7f787b274e5be3d7df30d87c5bb62bba6'
|
||||
if [ "${CPU_ARCH}" = "aarch64" ]; then
|
||||
JAVAFX_ARCH="aarch64"
|
||||
JAVAFX_JMODS_SHA256='9bbedaeae1590b69e2b22237bda310936df33e344dbc243bea2e86acaab3a0d8'
|
||||
JAVAFX_JMODS_SHA256='09c92fa9fa0b82adefd88640a14ebb2a49e5f3f733a57d1542f5590d060ffe1b'
|
||||
fi
|
||||
|
||||
# download javaFX jmods
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright 2018 Armin Schrenk <armin.schrenk@zoho.eu> -->
|
||||
<component type="desktop-application">
|
||||
<id>org.cryptomator.Cryptomator</id>
|
||||
<metadata_license>FSFAP</metadata_license>
|
||||
<project_license>GPL-3.0-or-later</project_license>
|
||||
<name>Cryptomator</name>
|
||||
<summary>Encryption for your cloud made easy</summary>
|
||||
|
||||
<keywords>
|
||||
<keyword>encryption</keyword>
|
||||
<keyword>security</keyword>
|
||||
<keyword>privacy</keyword>
|
||||
</keywords>
|
||||
<summary>Encryption made easy and optimized for the cloud</summary>
|
||||
|
||||
<description>
|
||||
<p>
|
||||
@@ -49,16 +44,12 @@
|
||||
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<caption>Encrypts your data, protects your privacy</caption>
|
||||
<image>https://static.cryptomator.org/desktop/flathubScreenshots/MainWindowUnlockDialog_light.png</image>
|
||||
<caption>Light theme</caption>
|
||||
<image>https://user-images.githubusercontent.com/11858409/156986109-6e58f59c-8b8c-4501-b33b-bb1e33007cea.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<caption>Dark theme available</caption>
|
||||
<image>https://static.cryptomator.org/desktop/flathubScreenshots/MainWindowUnlocked_dark.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<caption>Easy to use - work on encrypted files as if they were not</caption>
|
||||
<image>https://static.cryptomator.org/desktop/flathubScreenshots/MainWindowUnlocked_light.png</image>
|
||||
<caption>Dark theme</caption>
|
||||
<image>https://user-images.githubusercontent.com/11858409/156986113-6c5d7801-86e0-4643-bc2f-aff9d95d3ce0.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
|
||||
@@ -83,21 +74,6 @@
|
||||
</content_rating>
|
||||
|
||||
<releases>
|
||||
<release date="2025-04-30" version="1.16.1">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.16.1</url>
|
||||
</release>
|
||||
<release date="2025-04-29" version="1.16.0">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.16.0</url>
|
||||
</release>
|
||||
<release date="2025-04-09" version="1.15.3">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.15.3</url>
|
||||
</release>
|
||||
<release date="2025-04-04" version="1.15.2">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.15.2</url>
|
||||
</release>
|
||||
<release date="2025-02-05" version="1.15.1">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.15.1</url>
|
||||
</release>
|
||||
<release date="2025-02-03" version="1.15.0">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.15.0</url>
|
||||
</release>
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
<svg height="16" viewBox="0 0 42 42" width="16" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<style id="current-color-scheme" type="text/css">
|
||||
.ColorScheme-Text {
|
||||
color:#222222;
|
||||
}
|
||||
.ColorScheme-Highlight {
|
||||
color:#49B04A;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g fill-rule="evenodd">
|
||||
<path d="m15.591 35.824c-.019.009-.936.775-1.458 1.208a.418.418 0 0 1 -.627-.111 9.322 9.322 0 0 1 -.3-5.974 15.843 15.843 0 0 0 2.894 2.043c.051 1.03-.161 2.644-.509 2.834zm6.409-6.824h-2l.5-5a2 2 0 1 1 1 0zm-14.544-3.241.744-1.366a1.579 1.579 0 0 0 -.019-1.557l.653-1.2c.2.014-.03-.113.165-.14.051-.217-.051-.336 0-.5a3.269 3.269 0 0 0 0-1.5 7.151 7.151 0 0 1 0-3 2.366 2.366 0 0 0 -2.378 1.448 2.409 2.409 0 0 0 .229 2.661l-.7 1.278a1.779 1.779 0 0 0 -1.317.891l-.741 1.372a1.577 1.577 0 0 0 -.019 1.487 3.028 3.028 0 0 0 -2.746 1.525 2.648 2.648 0 0 0 .044 2.631.748.748 0 0 0 .981.266.656.656 0 0 0 .284-.92 1.37 1.37 0 0 1 -.023-1.361 1.6 1.6 0 0 1 2.079-.63 1.408 1.408 0 0 1 .672 1.95 1.546 1.546 0 0 1 -1.2.78.688.688 0 0 0 -.636.749.707.707 0 0 0 .717.6.789.789 0 0 0 .082 0 2.989 2.989 0 0 0 2.322-1.513 2.669 2.669 0 0 0 -.377-3.084 1.767 1.767 0 0 0 1.184-.867zm13.544-10.759a13.013 13.013 0 0 1 5-1 21.6 21.6 0 0 1 4.5.5 9.312 9.312 0 0 0 -9.5-8.5c-5.794 0-9.176 4-9.5 8.5a21.858 21.858 0 0 1 4.5-.5 12.819 12.819 0 0 1 5 1zm3.5-5c1.209 0 2.5.866 2.5 2h-5c0-1.134 1.291-2 2.5-2zm-7 0c1.209 0 2.5.866 2.5 2h-5c0-1.134 1.291-2 2.5-2zm14.473 6a8.067 8.067 0 0 0 -8.08 8v2.141a3.891 3.891 0 0 0 -2.893 3.734v5.125a23.166 23.166 0 0 1 -4.174-1.623 7.857 7.857 0 0 1 -.027.878 3.263 3.263 0 0 1 -.729 2.074l-1.794 1.483a.379.379 0 0 1 -.276.188h-4c-1.324 0-2.346-1.336-2.653-3.343a7.058 7.058 0 0 1 .234-3.18 3.477 3.477 0 0 1 1.636-2.157 1.868 1.868 0 0 1 .783-.32h1.5a8.035 8.035 0 0 1 -1.5-5 11.1 11.1 0 0 1 .5-3 2.519 2.519 0 0 0 0-1.5 13.272 13.272 0 0 1 -.5-3.5c6.687-1.936 11 0 11 0s4.319-1.955 11 0" class="ColorScheme-Text" fill="currentColor"/>
|
||||
<path d="m39 28h-10v-4a3.13 3.13 0 0 1 3-3 3.087 3.087 0 0 1 3 3v1a1.034 1.034 0 0 0 1 1h1a1.034 1.034 0 0 0 1-1v-1a6 6 0 0 0 -12 0v4h-1a2.073 2.073 0 0 0 -2 2v6a2.073 2.073 0 0 0 2 2h14a2.073 2.073 0 0 0 2-2v-6a2.073 2.073 0 0 0 -2-2zm-5.391 5.94a1.609 1.609 0 0 1 -3.217 0v-1.876a1.609 1.609 0 0 1 3.217 0z" class="ColorScheme-Highlight" fill="currentColor"/>
|
||||
<style
|
||||
id="current-color-scheme" type="text/css">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
<g fill-rule="evenodd" style="fill:#f2f2f2;fill-opacity:1" class="ColorScheme-Text" fill="currentColor">
|
||||
<path d="m15.591 35.824c-.019.009-.936.775-1.458 1.208a.418.418 0 0 1 -.627-.111 9.322 9.322 0 0 1 -.3-5.974 15.843 15.843 0 0 0 2.894 2.043c.051 1.03-.161 2.644-.509 2.834zm6.409-6.824h-2l.5-5a2 2 0 1 1 1 0zm-14.544-3.241.744-1.366a1.579 1.579 0 0 0 -.019-1.557l.653-1.2c.2.014-.03-.113.165-.14.051-.217-.051-.336 0-.5a3.269 3.269 0 0 0 0-1.5 7.151 7.151 0 0 1 0-3 2.366 2.366 0 0 0 -2.378 1.448 2.409 2.409 0 0 0 .229 2.661l-.7 1.278a1.779 1.779 0 0 0 -1.317.891l-.741 1.372a1.577 1.577 0 0 0 -.019 1.487 3.028 3.028 0 0 0 -2.746 1.525 2.648 2.648 0 0 0 .044 2.631.748.748 0 0 0 .981.266.656.656 0 0 0 .284-.92 1.37 1.37 0 0 1 -.023-1.361 1.6 1.6 0 0 1 2.079-.63 1.408 1.408 0 0 1 .672 1.95 1.546 1.546 0 0 1 -1.2.78.688.688 0 0 0 -.636.749.707.707 0 0 0 .717.6.789.789 0 0 0 .082 0 2.989 2.989 0 0 0 2.322-1.513 2.669 2.669 0 0 0 -.377-3.084 1.767 1.767 0 0 0 1.184-.867zm13.544-10.759a13.013 13.013 0 0 1 5-1 21.6 21.6 0 0 1 4.5.5 9.312 9.312 0 0 0 -9.5-8.5c-5.794 0-9.176 4-9.5 8.5a21.858 21.858 0 0 1 4.5-.5 12.819 12.819 0 0 1 5 1zm3.5-5c1.209 0 2.5.866 2.5 2h-5c0-1.134 1.291-2 2.5-2zm-7 0c1.209 0 2.5.866 2.5 2h-5c0-1.134 1.291-2 2.5-2zm14.473 6a8.067 8.067 0 0 0 -8.08 8v2.141a3.891 3.891 0 0 0 -2.893 3.734v5.125a23.166 23.166 0 0 1 -4.174-1.623 7.857 7.857 0 0 1 -.027.878 3.263 3.263 0 0 1 -.729 2.074l-1.794 1.483a.379.379 0 0 1 -.276.188h-4c-1.324 0-2.346-1.336-2.653-3.343a7.058 7.058 0 0 1 .234-3.18 3.477 3.477 0 0 1 1.636-2.157 1.868 1.868 0 0 1 .783-.32h1.5a8.035 8.035 0 0 1 -1.5-5 11.1 11.1 0 0 1 .5-3 2.519 2.519 0 0 0 0-1.5 13.272 13.272 0 0 1 -.5-3.5c6.687-1.936 11 0 11 0s4.319-1.955 11 0"/>
|
||||
<path d="m39 28h-10v-4a3.13 3.13 0 0 1 3-3 3.087 3.087 0 0 1 3 3v1a1.034 1.034 0 0 0 1 1h1a1.034 1.034 0 0 0 1-1v-1a6 6 0 0 0 -12 0v4h-1a2.073 2.073 0 0 0 -2 2v6a2.073 2.073 0 0 0 2 2h14a2.073 2.073 0 0 0 2-2v-6a2.073 2.073 0 0 0 -2-2zm-5.391 5.94a1.609 1.609 0 0 1 -3.217 0v-1.876a1.609 1.609 0 0 1 3.217 0z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.2 KiB |
@@ -1,10 +1,8 @@
|
||||
<svg height="16" viewBox="0 0 42 42" width="16" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<style id="current-color-scheme" type="text/css">
|
||||
.ColorScheme-Text {
|
||||
color:#222222;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path d="m32.66 29.319a1.432 1.432 0 0 0 -.66-.319h-1.5a8.125 8.125 0 0 0 1.5-5 11.027 11.027 0 0 0 -.5-3 2.519 2.519 0 0 1 0-1.5 12.987 12.987 0 0 0 .5-3.5c-6.681-1.955-11 0-11 0s-4.313-1.936-11 0a13.272 13.272 0 0 0 .5 3.5 2.519 2.519 0 0 1 0 1.5 11.1 11.1 0 0 0 -.5 3 8.035 8.035 0 0 0 1.5 5h-1.5a1.868 1.868 0 0 0 -.783.319 3.477 3.477 0 0 0 -1.636 2.157 7.058 7.058 0 0 0 -.234 3.18c.307 2.008 1.329 3.344 2.653 3.344h4a.379.379 0 0 0 .277-.187l1.793-1.483a3.263 3.263 0 0 0 .729-2.074 7.857 7.857 0 0 0 .027-.878 23.166 23.166 0 0 0 4.174 1.622 24.4 24.4 0 0 0 4.051-1.614 7.848 7.848 0 0 0 .027.869 3.263 3.263 0 0 0 .729 2.074l1.793 1.484a.61.61 0 0 0 .4.187h4c1.324 0 2.223-1.336 2.529-3.343a7.057 7.057 0 0 0 -.234-3.18 3.477 3.477 0 0 0 -1.635-2.158zm-17.069 6.5c-.019.009-.936.775-1.458 1.208a.418.418 0 0 1 -.627-.111 9.322 9.322 0 0 1 -.3-5.974 15.843 15.843 0 0 0 2.894 2.048c.051 1.03-.161 2.644-.509 2.834zm6.409-6.819h-2l.5-5a2 2 0 1 1 1 0zm6.38 7.921a.418.418 0 0 1 -.627.111c-.522-.433-1.439-1.2-1.458-1.208-.348-.189-.56-1.8-.505-2.828a15.84 15.84 0 0 0 2.9-2.037 9.322 9.322 0 0 1 -.31 5.962zm-20.924-11.162.744-1.366a1.579 1.579 0 0 0 -.019-1.557l.653-1.2c.2.014-.03-.113.165-.14.051-.217-.051-.336 0-.5a3.269 3.269 0 0 0 0-1.5 7.151 7.151 0 0 1 0-3 2.366 2.366 0 0 0 -2.378 1.448 2.409 2.409 0 0 0 .229 2.661l-.7 1.278a1.779 1.779 0 0 0 -1.317.891l-.741 1.372a1.577 1.577 0 0 0 -.019 1.487 3.028 3.028 0 0 0 -2.746 1.525 2.648 2.648 0 0 0 .044 2.631.748.748 0 0 0 .981.266.656.656 0 0 0 .284-.92 1.37 1.37 0 0 1 -.023-1.361 1.6 1.6 0 0 1 2.079-.63 1.408 1.408 0 0 1 .672 1.95 1.546 1.546 0 0 1 -1.2.78.688.688 0 0 0 -.636.749.707.707 0 0 0 .717.6.789.789 0 0 0 .082 0 2.989 2.989 0 0 0 2.322-1.513 2.669 2.669 0 0 0 -.377-3.084 1.767 1.767 0 0 0 1.184-.867zm33.217 1.2a3.021 3.021 0 0 0 -2.658-1.525 1.574 1.574 0 0 0 -.107-1.283l-.745-1.367a1.779 1.779 0 0 0 -1.317-.891l-.7-1.278a2.409 2.409 0 0 0 .229-2.661 2.283 2.283 0 0 0 -2.375-1.454 7.039 7.039 0 0 1 0 3 3.272 3.272 0 0 0 0 1.5c.047.152-.047.3 0 .5.227.04-.069.156.165.14l.653 1.2a1.579 1.579 0 0 0 -.019 1.557l.745 1.367a1.753 1.753 0 0 0 1.045.832 2.66 2.66 0 0 0 -.238 2.916 2.989 2.989 0 0 0 2.326 1.509.79.79 0 0 0 .082 0 .707.707 0 0 0 .717-.6.688.688 0 0 0 -.636-.749 1.546 1.546 0 0 1 -1.2-.78 1.408 1.408 0 0 1 .672-1.95 1.628 1.628 0 0 1 1.179-.089 1.512 1.512 0 0 1 .9.719 1.37 1.37 0 0 1 -.023 1.361.656.656 0 0 0 .284.92.748.748 0 0 0 .981-.266 2.648 2.648 0 0 0 .04-2.633zm-19.673-11.959a13.013 13.013 0 0 1 5-1 21.6 21.6 0 0 1 4.5.5 9.312 9.312 0 0 0 -9.5-8.5c-5.794 0-9.176 4-9.5 8.5a21.858 21.858 0 0 1 4.5-.5 12.819 12.819 0 0 1 5 1zm3.5-5c1.209 0 2.5.866 2.5 2h-5c0-1.134 1.291-2 2.5-2zm-7 0c1.209 0 2.5.866 2.5 2h-5c0-1.134 1.291-2 2.5-2z" fill-rule="evenodd" class="ColorScheme-Text" fill="currentColor"/>
|
||||
<style id="current-color-scheme" type="text/css">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
<path d="m32.66 29.319a1.432 1.432 0 0 0 -.66-.319h-1.5a8.125 8.125 0 0 0 1.5-5 11.027 11.027 0 0 0 -.5-3 2.519 2.519 0 0 1 0-1.5 12.987 12.987 0 0 0 .5-3.5c-6.681-1.955-11 0-11 0s-4.313-1.936-11 0a13.272 13.272 0 0 0 .5 3.5 2.519 2.519 0 0 1 0 1.5 11.1 11.1 0 0 0 -.5 3 8.035 8.035 0 0 0 1.5 5h-1.5a1.868 1.868 0 0 0 -.783.319 3.477 3.477 0 0 0 -1.636 2.157 7.058 7.058 0 0 0 -.234 3.18c.307 2.008 1.329 3.344 2.653 3.344h4a.379.379 0 0 0 .277-.187l1.793-1.483a3.263 3.263 0 0 0 .729-2.074 7.857 7.857 0 0 0 .027-.878 23.166 23.166 0 0 0 4.174 1.622 24.4 24.4 0 0 0 4.051-1.614 7.848 7.848 0 0 0 .027.869 3.263 3.263 0 0 0 .729 2.074l1.793 1.484a.61.61 0 0 0 .4.187h4c1.324 0 2.223-1.336 2.529-3.343a7.057 7.057 0 0 0 -.234-3.18 3.477 3.477 0 0 0 -1.635-2.158zm-17.069 6.5c-.019.009-.936.775-1.458 1.208a.418.418 0 0 1 -.627-.111 9.322 9.322 0 0 1 -.3-5.974 15.843 15.843 0 0 0 2.894 2.048c.051 1.03-.161 2.644-.509 2.834zm6.409-6.819h-2l.5-5a2 2 0 1 1 1 0zm6.38 7.921a.418.418 0 0 1 -.627.111c-.522-.433-1.439-1.2-1.458-1.208-.348-.189-.56-1.8-.505-2.828a15.84 15.84 0 0 0 2.9-2.037 9.322 9.322 0 0 1 -.31 5.962zm-20.924-11.162.744-1.366a1.579 1.579 0 0 0 -.019-1.557l.653-1.2c.2.014-.03-.113.165-.14.051-.217-.051-.336 0-.5a3.269 3.269 0 0 0 0-1.5 7.151 7.151 0 0 1 0-3 2.366 2.366 0 0 0 -2.378 1.448 2.409 2.409 0 0 0 .229 2.661l-.7 1.278a1.779 1.779 0 0 0 -1.317.891l-.741 1.372a1.577 1.577 0 0 0 -.019 1.487 3.028 3.028 0 0 0 -2.746 1.525 2.648 2.648 0 0 0 .044 2.631.748.748 0 0 0 .981.266.656.656 0 0 0 .284-.92 1.37 1.37 0 0 1 -.023-1.361 1.6 1.6 0 0 1 2.079-.63 1.408 1.408 0 0 1 .672 1.95 1.546 1.546 0 0 1 -1.2.78.688.688 0 0 0 -.636.749.707.707 0 0 0 .717.6.789.789 0 0 0 .082 0 2.989 2.989 0 0 0 2.322-1.513 2.669 2.669 0 0 0 -.377-3.084 1.767 1.767 0 0 0 1.184-.867zm33.217 1.2a3.021 3.021 0 0 0 -2.658-1.525 1.574 1.574 0 0 0 -.107-1.283l-.745-1.367a1.779 1.779 0 0 0 -1.317-.891l-.7-1.278a2.409 2.409 0 0 0 .229-2.661 2.283 2.283 0 0 0 -2.375-1.454 7.039 7.039 0 0 1 0 3 3.272 3.272 0 0 0 0 1.5c.047.152-.047.3 0 .5.227.04-.069.156.165.14l.653 1.2a1.579 1.579 0 0 0 -.019 1.557l.745 1.367a1.753 1.753 0 0 0 1.045.832 2.66 2.66 0 0 0 -.238 2.916 2.989 2.989 0 0 0 2.326 1.509.79.79 0 0 0 .082 0 .707.707 0 0 0 .717-.6.688.688 0 0 0 -.636-.749 1.546 1.546 0 0 1 -1.2-.78 1.408 1.408 0 0 1 .672-1.95 1.628 1.628 0 0 1 1.179-.089 1.512 1.512 0 0 1 .9.719 1.37 1.37 0 0 1 -.023 1.361.656.656 0 0 0 .284.92.748.748 0 0 0 .981-.266 2.648 2.648 0 0 0 .04-2.633zm-19.673-11.959a13.013 13.013 0 0 1 5-1 21.6 21.6 0 0 1 4.5.5 9.312 9.312 0 0 0 -9.5-8.5c-5.794 0-9.176 4-9.5 8.5a21.858 21.858 0 0 1 4.5-.5 12.819 12.819 0 0 1 5 1zm3.5-5c1.209 0 2.5.866 2.5 2h-5c0-1.134 1.291-2 2.5-2zm-7 0c1.209 0 2.5.866 2.5 2h-5c0-1.134 1.291-2 2.5-2z" fill-rule="evenodd" style="fill:#f2f2f2;fill-opacity:1" class="ColorScheme-Text" fill="currentColor"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
2
dist/linux/debian/control
vendored
2
dist/linux/debian/control
vendored
@@ -2,7 +2,7 @@ Source: cryptomator
|
||||
Maintainer: Cryptobot <releases@cryptomator.org>
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>=10), coffeelibs-jdk-23 (>= 23.0.2+7-0ppa1), libgtk-3-0, libxxf86vm1, libgl1
|
||||
Build-Depends: debhelper (>=10), coffeelibs-jdk-23 (>= 23.0.1+11-0ppa1), libgtk-3-0, libxxf86vm1, libgl1
|
||||
Standards-Version: 4.5.0
|
||||
Homepage: https://cryptomator.org
|
||||
Vcs-Git: https://github.com/cryptomator/cryptomator.git
|
||||
|
||||
6
dist/mac/dmg/build.sh
vendored
6
dist/mac/dmg/build.sh
vendored
@@ -32,15 +32,15 @@ REVISION_NO=`git rev-list --count HEAD`
|
||||
VERSION_NO=`mvn -f../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout | sed -rn 's/.*([0-9]+\.[0-9]+\.[0-9]+).*/\1/p'`
|
||||
FUSE_LIB="FUSE-T"
|
||||
|
||||
JAVAFX_VERSION=23.0.2
|
||||
JAVAFX_VERSION=23.0.1
|
||||
JAVAFX_ARCH="undefined"
|
||||
JAVAFX_JMODS_SHA256="undefined"
|
||||
if [ "$(machine)" = "arm64e" ]; then
|
||||
JAVAFX_ARCH="aarch64"
|
||||
JAVAFX_JMODS_SHA256="c690cc642a3924cf56622951f478ba57aec9ce09063761f800c3319331bed3fc"
|
||||
JAVAFX_JMODS_SHA256="a800724a1f3e6757ecfa0bd5bf7ed64d2e6a7a3f5b3522650a70b8cfc7782fb6"
|
||||
else
|
||||
JAVAFX_ARCH="x64"
|
||||
JAVAFX_JMODS_SHA256="5e6c65c065eea22430c0eab36f37a5985eb8ad99e19e8772262021740d338f68"
|
||||
JAVAFX_JMODS_SHA256="8857965975c464a0e5d57709292ce357d0ebb39f6168c41d5ca38301e42c3c8e"
|
||||
fi
|
||||
JAVAFX_JMODS_URL="https://download2.gluonhq.com/openjfx/${JAVAFX_VERSION}/openjfx-${JAVAFX_VERSION}_osx-${JAVAFX_ARCH}_bin-jmods.zip"
|
||||
|
||||
|
||||
2
dist/win/build.ps1
vendored
2
dist/win/build.ps1
vendored
@@ -51,7 +51,7 @@ if ($clean -and (Test-Path -Path $runtimeImagePath)) {
|
||||
}
|
||||
|
||||
## download jfx jmods
|
||||
$javaFxVersion='23.0.2'
|
||||
$javaFxVersion='23.0.1'
|
||||
$javaFxJmodsUrl = "https://download2.gluonhq.com/openjfx/${javaFxVersion}/openjfx-${javaFxVersion}_windows-x64_bin-jmods.zip"
|
||||
$javaFxJmodsSHA256 = 'ee176dcee3bd78bde7910735bd67f67c792882f5b89626796ae06f7a1c0119d3'
|
||||
$javaFxJmods = '.\resources\jfxJmods.zip'
|
||||
|
||||
50
pom.xml
50
pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>cryptomator</artifactId>
|
||||
<version>1.16.1</version>
|
||||
<version>1.16.0-SNAPSHOT</version>
|
||||
<name>Cryptomator Desktop App</name>
|
||||
|
||||
<organization>
|
||||
@@ -33,42 +33,42 @@
|
||||
<nonModularGroupIds>org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents</nonModularGroupIds>
|
||||
|
||||
<!-- cryptomator dependencies -->
|
||||
<cryptomator.cryptofs.version>2.9.0</cryptomator.cryptofs.version>
|
||||
<cryptomator.integrations.version>1.5.1</cryptomator.integrations.version>
|
||||
<cryptomator.cryptofs.version>2.8.0</cryptomator.cryptofs.version>
|
||||
<cryptomator.integrations.version>1.5.0</cryptomator.integrations.version>
|
||||
<cryptomator.integrations.win.version>1.3.0</cryptomator.integrations.win.version>
|
||||
<cryptomator.integrations.mac.version>1.3.2</cryptomator.integrations.mac.version>
|
||||
<cryptomator.integrations.linux.version>1.5.3</cryptomator.integrations.linux.version>
|
||||
<cryptomator.fuse.version>5.0.5</cryptomator.fuse.version>
|
||||
<cryptomator.webdav.version>2.0.10</cryptomator.webdav.version>
|
||||
<cryptomator.integrations.mac.version>1.2.4</cryptomator.integrations.mac.version>
|
||||
<cryptomator.integrations.linux.version>1.5.2</cryptomator.integrations.linux.version>
|
||||
<cryptomator.fuse.version>5.0.2</cryptomator.fuse.version>
|
||||
<cryptomator.webdav.version>2.0.7</cryptomator.webdav.version>
|
||||
|
||||
<!-- 3rd party dependencies -->
|
||||
<commons-lang3.version>3.17.0</commons-lang3.version>
|
||||
<dagger.version>2.56.1</dagger.version>
|
||||
<dagger.version>2.55</dagger.version>
|
||||
<easybind.version>2.2</easybind.version>
|
||||
<jackson.version>2.18.3</jackson.version>
|
||||
<javafx.version>23.0.2</javafx.version>
|
||||
<jwt.version>4.5.0</jwt.version>
|
||||
<jackson.version>2.18.2</jackson.version>
|
||||
<javafx.version>23.0.1</javafx.version>
|
||||
<jwt.version>4.4.0</jwt.version>
|
||||
<nimbus-jose.version>9.37.3</nimbus-jose.version>
|
||||
<logback.version>1.5.18</logback.version>
|
||||
<slf4j.version>2.0.17</slf4j.version>
|
||||
<tinyoauth2.version>0.8.1</tinyoauth2.version>
|
||||
<logback.version>1.5.16</logback.version>
|
||||
<slf4j.version>2.0.16</slf4j.version>
|
||||
<tinyoauth2.version>0.8.0</tinyoauth2.version>
|
||||
<zxcvbn.version>1.9.0</zxcvbn.version>
|
||||
|
||||
<!-- test dependencies -->
|
||||
<junit.jupiter.version>5.12.2</junit.jupiter.version>
|
||||
<mockito.version>5.17.0</mockito.version>
|
||||
<junit.jupiter.version>5.11.4</junit.jupiter.version>
|
||||
<mockito.version>5.15.2</mockito.version>
|
||||
<hamcrest.version>3.0</hamcrest.version>
|
||||
|
||||
<!-- build-time dependencies -->
|
||||
<jetbrains.annotations.version>26.0.2</jetbrains.annotations.version>
|
||||
<dependency-check.version>12.1.1</dependency-check.version>
|
||||
<jacoco.version>0.8.13</jacoco.version>
|
||||
<jetbrains.annotations.version>26.0.1</jetbrains.annotations.version>
|
||||
<dependency-check.version>12.0.1</dependency-check.version>
|
||||
<jacoco.version>0.8.12</jacoco.version>
|
||||
<license-generator.version>2.5.0</license-generator.version>
|
||||
<junit-tree-reporter.version>1.4.0</junit-tree-reporter.version>
|
||||
<mvn-compiler.version>3.14.0</mvn-compiler.version>
|
||||
<mvn-compiler.version>3.13.0</mvn-compiler.version>
|
||||
<mvn-resources.version>3.3.1</mvn-resources.version>
|
||||
<mvn-dependency.version>3.8.1</mvn-dependency.version>
|
||||
<mvn-surefire.version>3.5.3</mvn-surefire.version>
|
||||
<mvn-surefire.version>3.5.2</mvn-surefire.version>
|
||||
<mvn-jar.version>3.4.2</mvn-jar.version>
|
||||
|
||||
<!-- Property used by surefire to determine jacoco engine -->
|
||||
@@ -80,7 +80,7 @@
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>cryptolib</artifactId>
|
||||
<version>2.2.1</version>
|
||||
<version>2.2.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
@@ -159,12 +159,6 @@
|
||||
<groupId>com.auth0</groupId>
|
||||
<artifactId>java-jwt</artifactId>
|
||||
<version>${jwt.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.nimbusds</groupId>
|
||||
|
||||
@@ -59,7 +59,6 @@ open module org.cryptomator.desktop {
|
||||
|
||||
uses org.cryptomator.common.locationpresets.LocationPresetsProvider;
|
||||
uses SSLContextProvider;
|
||||
uses org.cryptomator.event.NotificationHandler;
|
||||
|
||||
provides TrayMenuController with AwtTrayMenuController;
|
||||
provides Configurator with LogbackConfiguratorFactory;
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
package org.cryptomator;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class JavaFXUtil {
|
||||
|
||||
private JavaFXUtil() {}
|
||||
|
||||
public static boolean startPlatform() throws InterruptedException {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
try {
|
||||
Platform.startup(latch::countDown);
|
||||
} catch (IllegalStateException e) {
|
||||
//already initialized
|
||||
latch.countDown();
|
||||
}
|
||||
return latch.await(5, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
package org.cryptomator.common;
|
||||
|
||||
import org.cryptomator.cryptofs.event.BrokenDirFileEvent;
|
||||
import org.cryptomator.cryptofs.event.BrokenFileNodeEvent;
|
||||
import org.cryptomator.cryptofs.event.ConflictResolutionFailedEvent;
|
||||
import org.cryptomator.cryptofs.event.ConflictResolvedEvent;
|
||||
import org.cryptomator.cryptofs.event.DecryptionFailedEvent;
|
||||
import org.cryptomator.cryptofs.event.FilesystemEvent;
|
||||
import org.cryptomator.event.VaultEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.MapChangeListener;
|
||||
import javafx.collections.ObservableMap;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Map containing {@link VaultEvent}s.
|
||||
* The map is keyed by the ciphertext path of the affected resource _and_ the {@link FilesystemEvent}s class in order to group same events
|
||||
* <p>
|
||||
* Use {@link EventMap#put(VaultEvent)} to add an element and {@link EventMap#remove(VaultEvent)} to remove it.
|
||||
* <p>
|
||||
* The map is size restricted to {@value MAX_SIZE} elements. If a _new_ element (i.e. not already present) is added, the least recently added is removed.
|
||||
*/
|
||||
@Singleton
|
||||
public class EventMap implements ObservableMap<EventMap.EventKey, VaultEvent> {
|
||||
|
||||
private static final int MAX_SIZE = 300;
|
||||
|
||||
public record EventKey(Path ciphertextPath, Class<? extends FilesystemEvent> c) {}
|
||||
|
||||
private final ObservableMap<EventMap.EventKey, VaultEvent> delegate;
|
||||
|
||||
@Inject
|
||||
public EventMap() {
|
||||
delegate = FXCollections.observableHashMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(MapChangeListener<? super EventKey, ? super VaultEvent> mapChangeListener) {
|
||||
delegate.addListener(mapChangeListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(MapChangeListener<? super EventKey, ? super VaultEvent> mapChangeListener) {
|
||||
delegate.removeListener(mapChangeListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return delegate.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return delegate.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return delegate.containsKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
return delegate.containsValue(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VaultEvent get(Object key) {
|
||||
return delegate.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable VaultEvent put(EventKey key, VaultEvent value) {
|
||||
return delegate.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VaultEvent remove(Object key) {
|
||||
return delegate.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(@NotNull Map<? extends EventKey, ? extends VaultEvent> m) {
|
||||
delegate.putAll(m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
delegate.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Set<EventKey> keySet() {
|
||||
return delegate.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Collection<VaultEvent> values() {
|
||||
return delegate.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Set<Entry<EventKey, VaultEvent>> entrySet() {
|
||||
return delegate.entrySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(InvalidationListener invalidationListener) {
|
||||
delegate.addListener(invalidationListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(InvalidationListener invalidationListener) {
|
||||
delegate.removeListener(invalidationListener);
|
||||
}
|
||||
|
||||
public synchronized void put(VaultEvent e) {
|
||||
//compute key
|
||||
var key = computeKey(e.actualEvent());
|
||||
//if-else
|
||||
var nullOrEntry = delegate.get(key);
|
||||
if (nullOrEntry == null) {
|
||||
if (size() == MAX_SIZE) {
|
||||
delegate.entrySet().stream() //
|
||||
.min(Comparator.comparing(entry -> entry.getValue().actualEvent().getTimestamp())) //
|
||||
.ifPresent(oldestEntry -> delegate.remove(oldestEntry.getKey()));
|
||||
}
|
||||
delegate.put(key, e);
|
||||
} else {
|
||||
delegate.put(key, nullOrEntry.incrementCount(e.actualEvent()));
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized VaultEvent remove(VaultEvent similar) {
|
||||
//compute key
|
||||
var key = computeKey(similar.actualEvent());
|
||||
return this.remove(key);
|
||||
}
|
||||
|
||||
private EventKey computeKey(FilesystemEvent e) {
|
||||
var p = switch (e) {
|
||||
case DecryptionFailedEvent(_, Path ciphertextPath, _) -> ciphertextPath;
|
||||
case ConflictResolvedEvent(_, _, _, _, Path resolvedCiphertext) -> resolvedCiphertext;
|
||||
case ConflictResolutionFailedEvent(_, _, Path conflictingCiphertext, _) -> conflictingCiphertext;
|
||||
case BrokenDirFileEvent(_, Path ciphertext) -> ciphertext;
|
||||
case BrokenFileNodeEvent(_, _, Path ciphertext) -> ciphertext;
|
||||
};
|
||||
return new EventKey(p, e.getClass());
|
||||
}
|
||||
}
|
||||
31
src/main/java/org/cryptomator/common/Hyperlinks.java
Normal file
31
src/main/java/org/cryptomator/common/Hyperlinks.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package org.cryptomator.common;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public record Hyperlinks(String docsVolumeType, String docsGettingStarted, String homepageHub) {
|
||||
|
||||
private static final ObjectMapper JSON_DESERIALIZER = new ObjectMapper();
|
||||
/*
|
||||
String docsAccessingVaults;
|
||||
String docsExpertSettings;
|
||||
String docsManualMigration;
|
||||
String homepageDownload;
|
||||
String homepageHub;
|
||||
String homepageDonate;
|
||||
String homepageSponsors;
|
||||
String storeDesktop;
|
||||
*/
|
||||
|
||||
|
||||
public static Hyperlinks load() {
|
||||
try {
|
||||
return JSON_DESERIALIZER.readValue(Hyperlinks.class.getResource("/hyperlinks.json"), Hyperlinks.class);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package org.cryptomator.common.keychain;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
import org.cryptomator.common.Passphrase;
|
||||
import org.cryptomator.integrations.keychain.KeychainAccessException;
|
||||
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
|
||||
|
||||
@@ -14,24 +13,20 @@ import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
@Singleton
|
||||
public class KeychainManager implements KeychainAccessProvider {
|
||||
|
||||
private final ObjectExpression<KeychainAccessProvider> keychain;
|
||||
private final LoadingCache<String, BooleanProperty> passphraseStoredProperties;
|
||||
private final ReentrantReadWriteLock lock;
|
||||
|
||||
@Inject
|
||||
KeychainManager(ObjectExpression<KeychainAccessProvider> selectedKeychain) {
|
||||
this.keychain = selectedKeychain;
|
||||
this.passphraseStoredProperties = Caffeine.newBuilder() //
|
||||
.softValues() //
|
||||
.weakValues() //
|
||||
.build(this::createStoredPassphraseProperty);
|
||||
keychain.addListener(ignored -> passphraseStoredProperties.invalidateAll());
|
||||
this.lock = new ReentrantReadWriteLock(false);
|
||||
}
|
||||
|
||||
private KeychainAccessProvider getKeychainOrFail() throws KeychainAccessException {
|
||||
@@ -47,59 +42,29 @@ public class KeychainManager implements KeychainAccessProvider {
|
||||
return getClass().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storePassphrase(String key, String displayName, CharSequence passphrase) throws KeychainAccessException {
|
||||
storePassphrase(key, displayName, passphrase, true);
|
||||
}
|
||||
|
||||
//TODO: remove ignored parameter once the API is fixed
|
||||
@Override
|
||||
public void storePassphrase(String key, String displayName, CharSequence passphrase, boolean ignored) throws KeychainAccessException {
|
||||
try {
|
||||
lock.writeLock().lock();
|
||||
var kc = getKeychainOrFail();
|
||||
//this is the only keychain actually using the parameter
|
||||
var usesOSAuth = (kc.getClass().getName().equals("org.cryptomator.macos.keychain.TouchIdKeychainAccess"));
|
||||
kc.storePassphrase(key, displayName, passphrase, usesOSAuth);
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
getKeychainOrFail().storePassphrase(key, displayName, passphrase);
|
||||
setPassphraseStored(key, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] loadPassphrase(String key) throws KeychainAccessException {
|
||||
char[] passphrase = null;
|
||||
try {
|
||||
lock.readLock().lock();
|
||||
passphrase = getKeychainOrFail().loadPassphrase(key);
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
char[] passphrase = getKeychainOrFail().loadPassphrase(key);
|
||||
setPassphraseStored(key, passphrase != null);
|
||||
return passphrase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePassphrase(String key) throws KeychainAccessException {
|
||||
try {
|
||||
lock.writeLock().lock();
|
||||
getKeychainOrFail().deletePassphrase(key);
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
getKeychainOrFail().deletePassphrase(key);
|
||||
setPassphraseStored(key, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changePassphrase(String key, String displayName, CharSequence passphrase) throws KeychainAccessException {
|
||||
if (isPassphraseStored(key)) {
|
||||
try {
|
||||
lock.writeLock().lock();
|
||||
getKeychainOrFail().changePassphrase(key, displayName, passphrase);
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
getKeychainOrFail().changePassphrase(key, displayName, passphrase);
|
||||
setPassphraseStored(key, true);
|
||||
}
|
||||
}
|
||||
@@ -136,11 +101,13 @@ public class KeychainManager implements KeychainAccessProvider {
|
||||
}
|
||||
|
||||
private void setPassphraseStored(String key, boolean value) {
|
||||
BooleanProperty property = passphraseStoredProperties.get(key, _ -> new SimpleBooleanProperty(value));
|
||||
if (Platform.isFxApplicationThread()) {
|
||||
property.set(value);
|
||||
} else {
|
||||
Platform.runLater(() -> property.set(value));
|
||||
BooleanProperty property = passphraseStoredProperties.getIfPresent(key);
|
||||
if (property != null) {
|
||||
if (Platform.isFxApplicationThread()) {
|
||||
property.set(value);
|
||||
} else {
|
||||
Platform.runLater(() -> property.set(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,22 +134,4 @@ public class KeychainManager implements KeychainAccessProvider {
|
||||
}
|
||||
}
|
||||
|
||||
public ObjectExpression<KeychainAccessProvider> getKeychainImplementation() {
|
||||
return this.keychain;
|
||||
}
|
||||
|
||||
public static void migrate(KeychainAccessProvider oldProvider, KeychainAccessProvider newProvider, Map<String, String> idsAndNames) throws KeychainAccessException {
|
||||
if (oldProvider instanceof KeychainManager || newProvider instanceof KeychainManager) {
|
||||
throw new IllegalArgumentException("KeychainManger must not be the source or target of migration");
|
||||
}
|
||||
for (var entry : idsAndNames.entrySet()) {
|
||||
var passphrase = oldProvider.loadPassphrase(entry.getKey());
|
||||
if (passphrase != null) {
|
||||
var wrapper = new Passphrase(passphrase);
|
||||
oldProvider.deletePassphrase(entry.getKey()); //we cannot apply "first-write-then-delete" pattern here, since we can potentially write to the same passphrase store (e.g., touchID and regular keychain)
|
||||
newProvider.storePassphrase(entry.getKey(), entry.getValue(), wrapper);
|
||||
wrapper.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,6 @@ public class VaultSettings {
|
||||
public final StringExpression mountName;
|
||||
public final StringProperty mountService;
|
||||
public final IntegerProperty port;
|
||||
public final StringProperty lastKnownKeyLoader;
|
||||
|
||||
VaultSettings(VaultSettingsJson json) {
|
||||
this.id = json.id;
|
||||
@@ -75,7 +74,6 @@ public class VaultSettings {
|
||||
this.mountPoint = new SimpleObjectProperty<>(this, "mountPoint", json.mountPoint == null ? null : Path.of(json.mountPoint));
|
||||
this.mountService = new SimpleStringProperty(this, "mountService", json.mountService);
|
||||
this.port = new SimpleIntegerProperty(this, "port", json.port);
|
||||
this.lastKnownKeyLoader = new SimpleStringProperty(this, "lastKnownKeyLoader", json.lastKnownKeyLoader);
|
||||
// mount name is no longer an explicit setting, see https://github.com/cryptomator/cryptomator/pull/1318
|
||||
this.mountName = StringExpression.stringExpression(Bindings.createStringBinding(() -> {
|
||||
final String name;
|
||||
@@ -101,7 +99,7 @@ public class VaultSettings {
|
||||
}
|
||||
|
||||
Observable[] observables() {
|
||||
return new Observable[]{actionAfterUnlock, autoLockIdleSeconds, autoLockWhenIdle, displayName, maxCleartextFilenameLength, mountFlags, mountPoint, path, revealAfterMount, unlockAfterStartup, usesReadOnlyMode, port, mountService, lastKnownKeyLoader};
|
||||
return new Observable[]{actionAfterUnlock, autoLockIdleSeconds, autoLockWhenIdle, displayName, maxCleartextFilenameLength, mountFlags, mountPoint, path, revealAfterMount, unlockAfterStartup, usesReadOnlyMode, port, mountService};
|
||||
}
|
||||
|
||||
public static VaultSettings withRandomId() {
|
||||
@@ -132,7 +130,6 @@ public class VaultSettings {
|
||||
json.mountPoint = mountPoint.map(Path::toString).getValue();
|
||||
json.mountService = mountService.get();
|
||||
json.port = port.get();
|
||||
json.lastKnownKeyLoader = lastKnownKeyLoader.get();
|
||||
return json;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,9 +48,6 @@ class VaultSettingsJson {
|
||||
@JsonProperty("mountService")
|
||||
String mountService;
|
||||
|
||||
@JsonProperty("lastKnownKeyLoader")
|
||||
String lastKnownKeyLoader;
|
||||
|
||||
@JsonProperty("port")
|
||||
int port = VaultSettings.DEFAULT_PORT;
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ package org.cryptomator.common.vaults;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.Constants;
|
||||
import org.cryptomator.event.FileSystemEventAggregator;
|
||||
import org.cryptomator.common.mount.Mounter;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
@@ -19,11 +18,9 @@ import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||
import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
|
||||
import org.cryptomator.cryptofs.event.FilesystemEvent;
|
||||
import org.cryptomator.cryptolib.api.CryptoException;
|
||||
import org.cryptomator.cryptolib.api.MasterkeyLoader;
|
||||
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
|
||||
import org.cryptomator.event.VaultEvent;
|
||||
import org.cryptomator.integrations.mount.MountFailedException;
|
||||
import org.cryptomator.integrations.mount.Mountpoint;
|
||||
import org.cryptomator.integrations.mount.UnmountFailedException;
|
||||
@@ -35,7 +32,6 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
@@ -78,7 +74,6 @@ public class Vault {
|
||||
private final ObjectBinding<Mountpoint> mountPoint;
|
||||
private final Mounter mounter;
|
||||
private final Settings settings;
|
||||
private final FileSystemEventAggregator fileSystemEventAggregator;
|
||||
private final BooleanProperty showingStats;
|
||||
|
||||
private final AtomicReference<Mounter.MountHandle> mountHandle = new AtomicReference<>(null);
|
||||
@@ -90,8 +85,7 @@ public class Vault {
|
||||
VaultState state, //
|
||||
@Named("lastKnownException") ObjectProperty<Exception> lastKnownException, //
|
||||
VaultStats stats, //
|
||||
Mounter mounter, Settings settings, //
|
||||
FileSystemEventAggregator fileSystemEventAggregator) {
|
||||
Mounter mounter, Settings settings) {
|
||||
this.vaultSettings = vaultSettings;
|
||||
this.configCache = configCache;
|
||||
this.cryptoFileSystem = cryptoFileSystem;
|
||||
@@ -108,7 +102,6 @@ public class Vault {
|
||||
this.mountPoint = Bindings.createObjectBinding(this::getMountPoint, state);
|
||||
this.mounter = mounter;
|
||||
this.settings = settings;
|
||||
this.fileSystemEventAggregator = fileSystemEventAggregator;
|
||||
this.showingStats = new SimpleBooleanProperty(false);
|
||||
this.quickAccessEntry = new AtomicReference<>(null);
|
||||
}
|
||||
@@ -150,7 +143,6 @@ public class Vault {
|
||||
.withFlags(flags) //
|
||||
.withMaxCleartextNameLength(vaultSettings.maxCleartextFilenameLength.get()) //
|
||||
.withVaultConfigFilename(Constants.VAULTCONFIG_FILENAME) //
|
||||
.withFilesystemEventConsumer(this::consumeVaultEvent) //
|
||||
.build();
|
||||
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
|
||||
}
|
||||
@@ -259,11 +251,6 @@ public class Vault {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void consumeVaultEvent(FilesystemEvent e) {
|
||||
fileSystemEventAggregator.put(this, e);
|
||||
}
|
||||
|
||||
// ******************************************************************************
|
||||
// Observable Properties
|
||||
// *******************************************************************************
|
||||
@@ -425,17 +412,6 @@ public class Vault {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cleartext name from a given path to an encrypted vault file
|
||||
*/
|
||||
public String getCleartextName(Path ciphertextPath) throws IOException {
|
||||
if (!state.getValue().equals(VaultState.Value.UNLOCKED)) {
|
||||
throw new IllegalStateException("Vault is not unlocked");
|
||||
}
|
||||
var fs = cryptoFileSystem.get();
|
||||
return fs.getCleartextName(ciphertextPath);
|
||||
}
|
||||
|
||||
public VaultConfigCache getVaultConfigCache() {
|
||||
return configCache;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@@ -50,9 +49,9 @@ public class VaultListManager {
|
||||
@Inject
|
||||
public VaultListManager(ObservableList<Vault> vaultList, //
|
||||
AutoLocker autoLocker, //
|
||||
List<MountService> mountServices, //
|
||||
VaultComponent.Factory vaultComponentFactory, //
|
||||
ResourceBundle resourceBundle, //
|
||||
List<MountService> mountServices,
|
||||
VaultComponent.Factory vaultComponentFactory,
|
||||
ResourceBundle resourceBundle,
|
||||
Settings settings) {
|
||||
this.vaultList = vaultList;
|
||||
this.autoLocker = autoLocker;
|
||||
@@ -115,10 +114,6 @@ public class VaultListManager {
|
||||
private Vault create(VaultSettings vaultSettings) {
|
||||
var wrapper = new VaultConfigCache(vaultSettings);
|
||||
try {
|
||||
if (Objects.isNull(vaultSettings.lastKnownKeyLoader.get())) {
|
||||
var keyIdScheme = wrapper.get().getKeyId().getScheme();
|
||||
vaultSettings.lastKnownKeyLoader.set(keyIdScheme);
|
||||
}
|
||||
var vaultState = determineVaultState(vaultSettings.path.get());
|
||||
if (vaultState == LOCKED) { //for legacy reasons: pre v8 vault do not have a config, but they are in the NEEDS_MIGRATION state
|
||||
wrapper.reloadConfig();
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
package org.cryptomator.event;
|
||||
|
||||
public sealed interface Answer permits Answer.DoNothing, Answer.DoSomething {
|
||||
|
||||
|
||||
record DoNothing() implements Answer {}
|
||||
|
||||
record DoSomething(Runnable action) implements Answer {
|
||||
|
||||
void run() {
|
||||
action.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package org.cryptomator.event;
|
||||
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.event.FilesystemEvent;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
public record FSEventBucket(Vault vault, Path idPath, Class<? extends FilesystemEvent> c) {}
|
||||
@@ -1,5 +0,0 @@
|
||||
package org.cryptomator.event;
|
||||
|
||||
import org.cryptomator.cryptofs.event.FilesystemEvent;
|
||||
|
||||
public record FSEventBucketContent(FilesystemEvent mostRecentEvent, int count) {}
|
||||
@@ -1,107 +0,0 @@
|
||||
package org.cryptomator.event;
|
||||
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.event.BrokenDirFileEvent;
|
||||
import org.cryptomator.cryptofs.event.BrokenFileNodeEvent;
|
||||
import org.cryptomator.cryptofs.event.ConflictResolutionFailedEvent;
|
||||
import org.cryptomator.cryptofs.event.ConflictResolvedEvent;
|
||||
import org.cryptomator.cryptofs.event.DecryptionFailedEvent;
|
||||
import org.cryptomator.cryptofs.event.FilesystemEvent;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Aggregator for {@link FilesystemEvent}s.
|
||||
* <p>
|
||||
* The aggregator groups filesystem events by the vault where the event occurred, an identifying path (clear- or ciphertext) and the event class (aka type).
|
||||
* A group is called an {@link FSEventBucket}, its {@link FSEventBucketContent} is the most recent event object and a count of how often the event already occurred.
|
||||
*/
|
||||
@Singleton
|
||||
public class FileSystemEventAggregator {
|
||||
|
||||
private final ConcurrentHashMap<FSEventBucket, FSEventBucketContent> map;
|
||||
private final AtomicBoolean hasUpdates;
|
||||
|
||||
@Inject
|
||||
public FileSystemEventAggregator() {
|
||||
this.map = new ConcurrentHashMap<>();
|
||||
this.hasUpdates = new AtomicBoolean(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given event to the map. If a bucket for this event already exists, only the count is updated and the event set as the most recent one.
|
||||
*
|
||||
* @param v Vault where the event occurred
|
||||
* @param e Actual {@link FilesystemEvent}
|
||||
*/
|
||||
public void put(Vault v, FilesystemEvent e) {
|
||||
var key = computeKey(v, e);
|
||||
map.compute(key, (k, val) -> {
|
||||
if (val == null) {
|
||||
return new FSEventBucketContent(e, 1);
|
||||
} else {
|
||||
return new FSEventBucketContent(e, val.count() + 1);
|
||||
}
|
||||
});
|
||||
hasUpdates.set(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an event bucket from the map.
|
||||
*/
|
||||
public FSEventBucketContent remove(FSEventBucket key) {
|
||||
var content = map.remove(key);
|
||||
hasUpdates.set(true);
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the event map.
|
||||
*/
|
||||
public void clear() {
|
||||
map.clear();
|
||||
hasUpdates.set(true);
|
||||
}
|
||||
|
||||
|
||||
public boolean hasMaybeUpdates() {
|
||||
return hasUpdates.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones the map entries into a collection.
|
||||
* <p>
|
||||
* The collection is first cleared, then all map entries are added in one bulk operation. Cleans the hasUpdates status.
|
||||
*
|
||||
* @param target collection which is first cleared and then the EntrySet copied to.
|
||||
*/
|
||||
public void cloneTo(Collection<Map.Entry<FSEventBucket, FSEventBucketContent>> target) {
|
||||
hasUpdates.set(false);
|
||||
target.clear();
|
||||
target.addAll(map.entrySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to compute the identifying key for a given filesystem event
|
||||
*
|
||||
* @param v Vault where the event occurred
|
||||
* @param event Actual {@link FilesystemEvent}
|
||||
* @return a {@link FSEventBucket} used in the map and lru cache
|
||||
*/
|
||||
private static FSEventBucket computeKey(Vault v, FilesystemEvent event) {
|
||||
var p = switch (event) {
|
||||
case DecryptionFailedEvent(_, Path ciphertextPath, _) -> ciphertextPath;
|
||||
case ConflictResolvedEvent(_, _, _, _, Path resolvedCiphertext) -> resolvedCiphertext;
|
||||
case ConflictResolutionFailedEvent(_, _, Path conflictingCiphertext, _) -> conflictingCiphertext;
|
||||
case BrokenDirFileEvent(_, Path ciphertext) -> ciphertext;
|
||||
case BrokenFileNodeEvent(_, _, Path ciphertext) -> ciphertext;
|
||||
};
|
||||
return new FSEventBucket(v, p, event.getClass());
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package org.cryptomator.event;
|
||||
|
||||
import org.cryptomator.integrations.common.IntegrationsLoader;
|
||||
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public interface NotificationHandler {
|
||||
|
||||
Answer handle(VaultEvent e);
|
||||
|
||||
static Stream<NotificationHandler> loadAll() {
|
||||
return IntegrationsLoader.loadAll(ServiceLoader.load(NotificationHandler.class), NotificationHandler.class);
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package org.cryptomator.event;
|
||||
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.event.FilesystemEvent;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public record VaultEvent(Vault v, FilesystemEvent actualEvent, int count) implements Comparable<VaultEvent> {
|
||||
|
||||
public VaultEvent(Vault v, FilesystemEvent actualEvent) {
|
||||
this(v, actualEvent, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(VaultEvent other) {
|
||||
var timeResult = actualEvent.getTimestamp().compareTo(other.actualEvent().getTimestamp());
|
||||
if(timeResult != 0) {
|
||||
return timeResult;
|
||||
} else {
|
||||
return this.equals(other) ? 0 : this.actualEvent.getClass().getName().compareTo(other.actualEvent.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
public VaultEvent incrementCount(FilesystemEvent update) {
|
||||
return new VaultEvent(v, update, count+1);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package org.cryptomator.launcher;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import org.cryptomator.common.Hyperlinks;
|
||||
import org.cryptomator.integrations.autostart.AutoStartProvider;
|
||||
import org.cryptomator.integrations.tray.TrayIntegrationProvider;
|
||||
import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
|
||||
@@ -23,6 +24,12 @@ class CryptomatorModule {
|
||||
return ResourceBundle.getBundle("i18n.strings");
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static Hyperlinks provideHyperlinks() {
|
||||
return Hyperlinks.load();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("launchEventQueue")
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
package org.cryptomator.networking;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.Key;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.KeyStoreSpi;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Enumeration;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class CombinedKeyStoreSpi extends KeyStoreSpi {
|
||||
|
||||
private final KeyStore primary;
|
||||
private final KeyStore fallback;
|
||||
|
||||
public static CombinedKeyStoreSpi create(KeyStore primary, KeyStore fallback) {
|
||||
checkIfLoaded(primary);
|
||||
checkIfLoaded(fallback);
|
||||
return new CombinedKeyStoreSpi(primary, fallback);
|
||||
}
|
||||
|
||||
private static void checkIfLoaded(KeyStore s) {
|
||||
try {
|
||||
s.aliases();
|
||||
} catch (KeyStoreException e) {
|
||||
throw new IllegalArgumentException("Keystore %s is not loaded.".formatted(s.getType()));
|
||||
}
|
||||
}
|
||||
|
||||
private CombinedKeyStoreSpi(KeyStore primary, KeyStore fallback) {
|
||||
this.primary = primary;
|
||||
this.fallback = fallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, UnrecoverableKeyException {
|
||||
try {
|
||||
Key key = primary.getKey(alias, password);
|
||||
if (key == null) {
|
||||
key = fallback.getKey(alias, password);
|
||||
}
|
||||
return key;
|
||||
} catch (KeyStoreException e) {
|
||||
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate[] engineGetCertificateChain(String alias) {
|
||||
try {
|
||||
Certificate[] chain = primary.getCertificateChain(alias);
|
||||
if (chain == null) {
|
||||
chain = fallback.getCertificateChain(alias);
|
||||
}
|
||||
return chain;
|
||||
} catch (KeyStoreException e) {
|
||||
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate engineGetCertificate(String alias) {
|
||||
try {
|
||||
Certificate cert = primary.getCertificate(alias);
|
||||
if (cert == null) {
|
||||
cert = fallback.getCertificate(alias);
|
||||
}
|
||||
return cert;
|
||||
} catch (KeyStoreException e) {
|
||||
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date engineGetCreationDate(String alias) {
|
||||
try {
|
||||
Date date = primary.getCreationDate(alias);
|
||||
if (date == null) {
|
||||
date = fallback.getCreationDate(alias);
|
||||
}
|
||||
return date;
|
||||
} catch (KeyStoreException e) {
|
||||
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain) throws KeyStoreException {
|
||||
throw new UnsupportedOperationException("Read-only KeyStore");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) throws KeyStoreException {
|
||||
throw new UnsupportedOperationException("Read-only KeyStore");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException {
|
||||
throw new UnsupportedOperationException("Read-only KeyStore");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void engineDeleteEntry(String alias) throws KeyStoreException {
|
||||
throw new UnsupportedOperationException("Read-only KeyStore");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> engineAliases() {
|
||||
var aliases = new LinkedHashSet<String>();
|
||||
try {
|
||||
primary.aliases().asIterator().forEachRemaining(aliases::add);
|
||||
fallback.aliases().asIterator().forEachRemaining(aliases::add);
|
||||
return Collections.enumeration(aliases);
|
||||
} catch (KeyStoreException e) {
|
||||
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean engineContainsAlias(String alias) {
|
||||
try {
|
||||
return primary.containsAlias(alias) || fallback.containsAlias(alias);
|
||||
} catch (KeyStoreException e) {
|
||||
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int engineSize() {
|
||||
var aliases = engineAliases();
|
||||
var i = new AtomicInteger(0);
|
||||
aliases.asIterator().forEachRemaining(_ -> i.incrementAndGet());
|
||||
return i.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean engineIsKeyEntry(String alias) {
|
||||
try {
|
||||
return primary.isKeyEntry(alias) || fallback.isKeyEntry(alias);
|
||||
} catch (KeyStoreException e) {
|
||||
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean engineIsCertificateEntry(String alias) {
|
||||
try {
|
||||
return primary.isCertificateEntry(alias) || fallback.isCertificateEntry(alias);
|
||||
} catch (KeyStoreException e) {
|
||||
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String engineGetCertificateAlias(Certificate cert) {
|
||||
try {
|
||||
String alias = primary.getCertificateAlias(cert);
|
||||
if (alias == null) {
|
||||
alias = fallback.getCertificateAlias(cert);
|
||||
}
|
||||
return alias;
|
||||
} catch (KeyStoreException e) {
|
||||
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void engineStore(OutputStream stream, char[] password) throws IOException, NoSuchAlgorithmException, CertificateException {
|
||||
throw new UnsupportedOperationException("Read-only KeyStore");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void engineLoad(InputStream stream, char[] password) throws IOException, NoSuchAlgorithmException, CertificateException {
|
||||
// Nothing to do; the real keystores are already loaded.
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import java.io.IOException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Provider;
|
||||
import java.security.cert.CertificateException;
|
||||
|
||||
/**
|
||||
@@ -17,16 +16,6 @@ public class SSLContextWithMacKeychain extends SSLContextDifferentTrustStoreBase
|
||||
|
||||
@Override
|
||||
KeyStore getTruststore() throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException {
|
||||
var userKeyStore = KeyStore.getInstance("KeychainStore");
|
||||
var systemRootKeyStore = KeyStore.getInstance("KeychainStore-ROOT");
|
||||
userKeyStore.load(null);
|
||||
systemRootKeyStore.load(null);
|
||||
try {
|
||||
CombinedKeyStoreSpi spi = CombinedKeyStoreSpi.create(userKeyStore, systemRootKeyStore);
|
||||
Provider dummyProvider = new Provider("CombinedKeyStoreProvider", "1.0", "Provides a combined, read-only KeyStore") {};
|
||||
return new KeyStore(spi, dummyProvider, "CombinedKeyStoreProvider") {};
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new KeyStoreException(e);
|
||||
}
|
||||
return KeyStore.getInstance("KeychainStore-ROOT");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public class CreateNewVaultExpertSettingsController implements FxController {
|
||||
|
||||
public static final int MAX_SHORTENING_THRESHOLD = 220;
|
||||
public static final int MIN_SHORTENING_THRESHOLD = 36;
|
||||
private static final String DOCS_NAME_SHORTENING_URL = "https://docs.cryptomator.org/security/architecture/#name-shortening";
|
||||
private static final String DOCS_NAME_SHORTENING_URL = "https://docs.cryptomator.org/en/1.7/security/architecture/#name-shortening";
|
||||
|
||||
private final Stage window;
|
||||
private final Lazy<Application> application;
|
||||
|
||||
@@ -76,10 +76,8 @@ public class ReadmeGenerator {
|
||||
input.chars().forEachOrdered(c -> {
|
||||
if (c < 128) {
|
||||
sb.append((char) c);
|
||||
} else if (c <= 0xFF) {
|
||||
sb.append("\\'").append(String.format("%02X", c));
|
||||
} else if (c < 0xFFFF) {
|
||||
sb.append("\\uc1\\u").append(c);
|
||||
sb.append("\\u").append(c);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -12,9 +12,7 @@ public enum FxmlFile {
|
||||
CONVERTVAULT_HUBTOPASSWORD_START("/fxml/convertvault_hubtopassword_start.fxml"), //
|
||||
CONVERTVAULT_HUBTOPASSWORD_CONVERT("/fxml/convertvault_hubtopassword_convert.fxml"), //
|
||||
CONVERTVAULT_HUBTOPASSWORD_SUCCESS("/fxml/convertvault_hubtopassword_success.fxml"), //
|
||||
DECRYPTNAMES("/fxml/decryptnames.fxml"), //
|
||||
ERROR("/fxml/error.fxml"), //
|
||||
EVENT_VIEW("/fxml/eventview.fxml"), //
|
||||
FORGET_PASSWORD("/fxml/forget_password.fxml"), //
|
||||
HEALTH_START("/fxml/health_start.fxml"), //
|
||||
HEALTH_CHECK_LIST("/fxml/health_check_list.fxml"), //
|
||||
|
||||
@@ -7,7 +7,6 @@ public enum FontAwesome5Icon {
|
||||
ANCHOR("\uF13D"), //
|
||||
ARROW_UP("\uF062"), //
|
||||
BAN("\uF05E"), //
|
||||
BELL("\uF0F3"), //
|
||||
BUG("\uF188"), //
|
||||
CARET_DOWN("\uF0D7"), //
|
||||
CARET_RIGHT("\uF0Da"), //
|
||||
@@ -16,12 +15,10 @@ public enum FontAwesome5Icon {
|
||||
CLIPBOARD("\uF328"), //
|
||||
COG("\uF013"), //
|
||||
COGS("\uF085"), //
|
||||
COMPRESS_ALT("\uF422"), //
|
||||
COPY("\uF0C5"), //
|
||||
CROWN("\uF521"), //
|
||||
DONATE("\uF4B9"), //
|
||||
EDIT("\uF044"), //
|
||||
ELLIPSIS_V("\uF142"), //
|
||||
EXCHANGE_ALT("\uF362"), //
|
||||
EXCLAMATION("\uF12A"), //
|
||||
EXCLAMATION_CIRCLE("\uF06A"), //
|
||||
|
||||
@@ -15,7 +15,6 @@ import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyFactory;
|
||||
import org.jetbrains.annotations.VisibleForTesting;
|
||||
import org.slf4j.Logger;
|
||||
@@ -109,7 +108,6 @@ public class HubToPasswordConvertController implements FxController {
|
||||
.thenRunAsync(this::convertInternal, backgroundExecutorService) //
|
||||
.whenCompleteAsync((result, exception) -> {
|
||||
if (exception == null) {
|
||||
vault.getVaultSettings().lastKnownKeyLoader.set(MasterkeyFileLoadingStrategy.SCHEME);
|
||||
LOG.info("Conversion of vault {} succeeded.", vault.getPath());
|
||||
window.setScene(successScene.get());
|
||||
} else {
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
package org.cryptomator.ui.decryptname;
|
||||
|
||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public record CipherAndCleartext(Path ciphertext, String cleartextName) {
|
||||
|
||||
public String getCiphertextFilename() {
|
||||
return ciphertext.getFileName().toString();
|
||||
}
|
||||
|
||||
public ObservableValue<String> ciphertextFilenameProperty() {
|
||||
return new ReadOnlyStringWrapper(getCiphertextFilename());
|
||||
}
|
||||
|
||||
public String getCleartextName() {
|
||||
return cleartextName;
|
||||
}
|
||||
|
||||
public ObservableValue<String> cleartextNameProperty() {
|
||||
return new ReadOnlyStringWrapper(getCleartextName());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
package org.cryptomator.ui.decryptname;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.common.Constants;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.controls.FontAwesome5Icon;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ListProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleListProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
import javafx.scene.input.Clipboard;
|
||||
import javafx.scene.input.DataFormat;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.TransferMode;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@DecryptNameScoped
|
||||
public class DecryptFileNamesViewController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DecryptFileNamesViewController.class);
|
||||
private static final KeyCodeCombination COPY_TO_CLIPBOARD_SHORTCUT = new KeyCodeCombination(KeyCode.C, KeyCodeCombination.SHORTCUT_DOWN);
|
||||
private static final String COPY_TO_CLIPBOARD_SHORTCUT_STRING_WIN = "CTRL+C";
|
||||
private static final String COPY_TO_CLIPBOARD_SHORTCUT_STRING_MAC = "⌘C";
|
||||
private static final String COPY_TO_CLIPBOARD_SHORTCUT_STRING_LINUX = "CTRL+C";
|
||||
|
||||
private final ListProperty<CipherAndCleartext> mapping;
|
||||
private final StringProperty dropZoneText = new SimpleStringProperty();
|
||||
private final ObjectProperty<FontAwesome5Icon> dropZoneIcon = new SimpleObjectProperty<>();
|
||||
private final BooleanProperty wrongFilesSelected = new SimpleBooleanProperty(false);
|
||||
private final Stage window;
|
||||
private final Vault vault;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final List<Path> initialList;
|
||||
|
||||
@FXML
|
||||
public TableColumn<CipherAndCleartext, String> ciphertextColumn;
|
||||
@FXML
|
||||
public TableColumn<CipherAndCleartext, String> cleartextColumn;
|
||||
@FXML
|
||||
public TableView<CipherAndCleartext> cipherToCleartextTable;
|
||||
|
||||
@Inject
|
||||
public DecryptFileNamesViewController(@DecryptNameWindow Stage window, @DecryptNameWindow Vault vault, @DecryptNameWindow List<Path> pathsToDecrypt, ResourceBundle resourceBundle) {
|
||||
this.window = window;
|
||||
this.vault = vault;
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.mapping = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
this.initialList = pathsToDecrypt;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
cipherToCleartextTable.setItems(mapping);
|
||||
cipherToCleartextTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_ALL_COLUMNS);
|
||||
//DragNDrop
|
||||
cipherToCleartextTable.setOnDragEntered(event -> {
|
||||
if (event.getGestureSource() == null && event.getDragboard().hasFiles()) {
|
||||
cipherToCleartextTable.setItems(FXCollections.emptyObservableList());
|
||||
}
|
||||
});
|
||||
cipherToCleartextTable.setOnDragOver(event -> {
|
||||
if (event.getGestureSource() == null && event.getDragboard().hasFiles()) {
|
||||
if (SystemUtils.IS_OS_WINDOWS || SystemUtils.IS_OS_MAC) {
|
||||
event.acceptTransferModes(TransferMode.LINK);
|
||||
} else {
|
||||
event.acceptTransferModes(TransferMode.ANY);
|
||||
}
|
||||
}
|
||||
});
|
||||
cipherToCleartextTable.setOnDragDropped(event -> {
|
||||
if (event.getGestureSource() == null && event.getDragboard().hasFiles()) {
|
||||
checkAndDecrypt(event.getDragboard().getFiles().stream().map(File::toPath).toList());
|
||||
cipherToCleartextTable.setItems(mapping);
|
||||
}
|
||||
});
|
||||
cipherToCleartextTable.setOnDragExited(_ -> cipherToCleartextTable.setItems(mapping));
|
||||
//selectionModel and copy-to-clipboard action
|
||||
cipherToCleartextTable.getSelectionModel().setCellSelectionEnabled(true);
|
||||
cipherToCleartextTable.setOnKeyPressed(keyEvent -> {
|
||||
if (COPY_TO_CLIPBOARD_SHORTCUT.match(keyEvent)) {
|
||||
copySingleCelltoClipboard();
|
||||
}
|
||||
});
|
||||
ciphertextColumn.setCellValueFactory(new PropertyValueFactory<>("ciphertextFilename"));
|
||||
cleartextColumn.setCellValueFactory(new PropertyValueFactory<>("cleartextName"));
|
||||
|
||||
dropZoneText.setValue(resourceBundle.getString("decryptNames.dropZone.message"));
|
||||
dropZoneIcon.setValue(FontAwesome5Icon.FILE_IMPORT);
|
||||
|
||||
wrongFilesSelected.addListener((_, _, areWrongFiles) -> {
|
||||
if (areWrongFiles) {
|
||||
CompletableFuture.delayedExecutor(5, TimeUnit.SECONDS, Platform::runLater).execute(() -> {
|
||||
dropZoneText.setValue(resourceBundle.getString("decryptNames.dropZone.message"));
|
||||
dropZoneIcon.setValue(FontAwesome5Icon.FILE_IMPORT);
|
||||
wrongFilesSelected.setValue(false);
|
||||
});
|
||||
}
|
||||
});
|
||||
if (!initialList.isEmpty()) {
|
||||
checkAndDecrypt(initialList);
|
||||
}
|
||||
}
|
||||
|
||||
private void copySingleCelltoClipboard() {
|
||||
cipherToCleartextTable.getSelectionModel().getSelectedCells().stream().findFirst().ifPresent(tablePosition -> {
|
||||
var selectedItem = cipherToCleartextTable.getSelectionModel().getSelectedItem();
|
||||
//TODO: give user feedback, if content is copied -> must be done via a custom cell factory to access the actual table cell!
|
||||
if (tablePosition.getTableColumn().equals(ciphertextColumn)) {
|
||||
Clipboard.getSystemClipboard().setContent(Map.of(DataFormat.PLAIN_TEXT, selectedItem.ciphertext().toString()));
|
||||
} else {
|
||||
Clipboard.getSystemClipboard().setContent(Map.of(DataFormat.PLAIN_TEXT, selectedItem.cleartextName()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void selectFiles() {
|
||||
var fileChooser = new FileChooser();
|
||||
fileChooser.setTitle(resourceBundle.getString("decryptNames.filePicker.title"));
|
||||
fileChooser.setSelectedExtensionFilter(new FileChooser.ExtensionFilter(resourceBundle.getString("decryptNames.filePicker.extensionDescription"), List.of("*.c9r")));
|
||||
fileChooser.setInitialDirectory(vault.getPath().toFile());
|
||||
var ciphertextNodes = fileChooser.showOpenMultipleDialog(window);
|
||||
if (ciphertextNodes != null) {
|
||||
checkAndDecrypt(ciphertextNodes.stream().map(File::toPath).toList());
|
||||
}
|
||||
}
|
||||
|
||||
private void checkAndDecrypt(List<Path> pathsToDecrypt) {
|
||||
mapping.clear();
|
||||
//Assumption: All files are in the same directory
|
||||
var testPath = pathsToDecrypt.getFirst();
|
||||
if (!testPath.startsWith(vault.getPath())) {
|
||||
setDropZoneError(resourceBundle.getString("decryptNames.dropZone.error.foreignFiles").formatted(vault.getDisplayName()));
|
||||
return;
|
||||
}
|
||||
if (pathsToDecrypt.size() == 1 && testPath.endsWith(Constants.DIR_ID_BACKUP_FILE_NAME)) {
|
||||
setDropZoneError(resourceBundle.getString("decryptNames.dropZone.error.vaultInternalFiles"));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var newMapping = pathsToDecrypt.stream().filter(p -> !p.endsWith(Constants.DIR_ID_BACKUP_FILE_NAME)).map(this::getCleartextName).toList();
|
||||
mapping.addAll(newMapping);
|
||||
} catch (UncheckedIOException e) {
|
||||
setDropZoneError(resourceBundle.getString("decryptNames.dropZone.error.generic"));
|
||||
LOG.info("Failed to decrypt filenames for directory {}", testPath.getParent(), e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
setDropZoneError(resourceBundle.getString("decryptNames.dropZone.error.vaultInternalFiles"));
|
||||
} catch (UnsupportedOperationException e) {
|
||||
setDropZoneError(resourceBundle.getString("decryptNames.dropZone.error.noDirIdBackup"));
|
||||
}
|
||||
}
|
||||
|
||||
private void setDropZoneError(String text) {
|
||||
dropZoneIcon.setValue(FontAwesome5Icon.TIMES);
|
||||
dropZoneText.setValue(text);
|
||||
wrongFilesSelected.setValue(true);
|
||||
}
|
||||
|
||||
private CipherAndCleartext getCleartextName(Path ciphertextNode) {
|
||||
try {
|
||||
var cleartextName = vault.getCleartextName(ciphertextNode);
|
||||
return new CipherAndCleartext(ciphertextNode, cleartextName);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
//obvservable getter
|
||||
|
||||
public ObservableValue<String> dropZoneTextProperty() {
|
||||
return dropZoneText;
|
||||
}
|
||||
|
||||
public String getDropZoneText() {
|
||||
return dropZoneText.get();
|
||||
}
|
||||
|
||||
public ObservableValue<FontAwesome5Icon> dropZoneIconProperty() {
|
||||
return dropZoneIcon;
|
||||
}
|
||||
|
||||
public FontAwesome5Icon getDropZoneIcon() {
|
||||
return dropZoneIcon.get();
|
||||
}
|
||||
|
||||
public void clearTable() {
|
||||
mapping.clear();
|
||||
}
|
||||
|
||||
public void copyTableToClipboard() {
|
||||
var csv = mapping.stream().map(cipherAndClear -> "\"" + cipherAndClear.ciphertext() + "\", \"" + cipherAndClear.cleartextName() + "\"").collect(Collectors.joining("\n"));
|
||||
Clipboard.getSystemClipboard().setContent(Map.of(DataFormat.PLAIN_TEXT, csv));
|
||||
}
|
||||
|
||||
public String getCopyToClipboardShortcutString() {
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
return COPY_TO_CLIPBOARD_SHORTCUT_STRING_WIN;
|
||||
} else if (SystemUtils.IS_OS_MAC) {
|
||||
return COPY_TO_CLIPBOARD_SHORTCUT_STRING_MAC;
|
||||
} else {
|
||||
return COPY_TO_CLIPBOARD_SHORTCUT_STRING_LINUX;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package org.cryptomator.ui.decryptname;
|
||||
|
||||
import dagger.BindsInstance;
|
||||
import dagger.Lazy;
|
||||
import dagger.Subcomponent;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultState;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
@DecryptNameScoped
|
||||
@Subcomponent(modules = DecryptNameModule.class)
|
||||
public interface DecryptNameComponent {
|
||||
|
||||
Logger LOG = LoggerFactory.getLogger(DecryptNameComponent.class);
|
||||
|
||||
@DecryptNameWindow
|
||||
Stage window();
|
||||
|
||||
@FxmlScene(FxmlFile.DECRYPTNAMES)
|
||||
Lazy<Scene> decryptNamesView();
|
||||
|
||||
@DecryptNameWindow
|
||||
Vault vault();
|
||||
|
||||
default void showDecryptFileNameWindow() {
|
||||
Stage s = window();
|
||||
s.setScene(decryptNamesView().get());
|
||||
s.sizeToScene();
|
||||
if (vault().isUnlocked()) {
|
||||
s.show();
|
||||
} else {
|
||||
LOG.error("Aborted showing DecryptFileName window: vault state is not {}, but {}.", VaultState.Value.UNLOCKED, vault().getState());
|
||||
}
|
||||
}
|
||||
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
|
||||
DecryptNameComponent create(@BindsInstance @DecryptNameWindow Vault vault, @BindsInstance @Named("windowOwner") Stage owner, @BindsInstance @DecryptNameWindow List<Path> pathsToDecrypt);
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package org.cryptomator.ui.decryptname;
|
||||
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxControllerKey;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.StageFactory;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module
|
||||
public abstract class DecryptNameModule {
|
||||
|
||||
@Provides
|
||||
@DecryptNameScoped
|
||||
@DecryptNameWindow
|
||||
static Stage provideStage(StageFactory factory, @Named("windowOwner") Stage owner, @DecryptNameWindow Vault vault, ResourceBundle resourceBundle) {
|
||||
Stage stage = factory.create();
|
||||
stage.setResizable(true);
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
stage.initOwner(owner);
|
||||
stage.setTitle(resourceBundle.getString("decryptNames.title"));
|
||||
vault.stateProperty().addListener(((_, _, _) -> stage.close())); //as soon as the state changes from unlocked, close the window
|
||||
return stage;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@DecryptNameScoped
|
||||
@DecryptNameWindow
|
||||
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.DECRYPTNAMES)
|
||||
@DecryptNameScoped
|
||||
static Scene provideDecryptNamesViewScene(@DecryptNameWindow FxmlLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.DECRYPTNAMES);
|
||||
}
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(DecryptFileNamesViewController.class)
|
||||
abstract FxController bindDecryptNamesViewController(DecryptFileNamesViewController controller);
|
||||
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package org.cryptomator.ui.decryptname;
|
||||
|
||||
import javax.inject.Scope;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@Scope
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface DecryptNameScoped {}
|
||||
@@ -1,12 +0,0 @@
|
||||
package org.cryptomator.ui.decryptname;
|
||||
|
||||
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 DecryptNameWindow {}
|
||||
@@ -38,7 +38,7 @@ public class Dialogs {
|
||||
.setMessageKey("removeVault.message") //
|
||||
.setDescriptionKey("removeVault.description") //
|
||||
.setIcon(FontAwesome5Icon.QUESTION) //
|
||||
.setOkButtonKey("generic.button.remove") //
|
||||
.setOkButtonKey("removeVault.confirmBtn") //
|
||||
.setCancelButtonKey("generic.button.cancel") //
|
||||
.setOkAction(stage -> {
|
||||
LOG.debug("Removing vault {}.", vault.getDisplayName());
|
||||
@@ -54,7 +54,7 @@ public class Dialogs {
|
||||
.setMessageKey("removeCert.message") //
|
||||
.setDescriptionKey("removeCert.description") //
|
||||
.setIcon(FontAwesome5Icon.QUESTION) //
|
||||
.setOkButtonKey("generic.button.remove") //
|
||||
.setOkButtonKey("removeCert.confirmBtn") //
|
||||
.setCancelButtonKey("generic.button.cancel") //
|
||||
.setOkAction(stage -> {
|
||||
settings.licenseKey.set(null);
|
||||
|
||||
@@ -31,9 +31,8 @@ public class SimpleDialog {
|
||||
FxmlLoaderFactory loaderFactory = FxmlLoaderFactory.forController( //
|
||||
new SimpleDialogController(resolveText(builder.messageKey, null), //
|
||||
resolveText(builder.descriptionKey, null), //
|
||||
builder.icon, //
|
||||
resolveText(builder.okButtonKey, null), //
|
||||
builder.cancelButtonKey != null ? resolveText(builder.cancelButtonKey, null) : null, //
|
||||
builder.icon, resolveText(builder.okButtonKey, null), //
|
||||
resolveText(builder.cancelButtonKey, null), //
|
||||
() -> builder.okAction.accept(dialogStage), //
|
||||
() -> builder.cancelAction.accept(dialogStage)), //
|
||||
Scene::new, builder.resourceBundle);
|
||||
@@ -68,6 +67,7 @@ public class SimpleDialog {
|
||||
private String descriptionKey;
|
||||
private String okButtonKey;
|
||||
private String cancelButtonKey;
|
||||
|
||||
private FontAwesome5Icon icon;
|
||||
private Consumer<Stage> okAction = Stage::close;
|
||||
private Consumer<Stage> cancelAction = Stage::close;
|
||||
@@ -128,6 +128,7 @@ public class SimpleDialog {
|
||||
Objects.requireNonNull(messageKey, "SimpleDialog messageKey must be set.");
|
||||
Objects.requireNonNull(descriptionKey, "SimpleDialog descriptionKey must be set.");
|
||||
Objects.requireNonNull(okButtonKey, "SimpleDialog okButtonKey must be set.");
|
||||
Objects.requireNonNull(cancelButtonKey, "SimpleDialog cancelButtonKey must be set.");
|
||||
|
||||
try {
|
||||
return new SimpleDialog(this);
|
||||
|
||||
@@ -14,7 +14,6 @@ public class SimpleDialogController implements FxController {
|
||||
private final String cancelButtonText;
|
||||
private final Runnable okAction;
|
||||
private final Runnable cancelAction;
|
||||
private final boolean cancelButtonVisible;
|
||||
|
||||
public SimpleDialogController(String message, String description, FontAwesome5Icon icon, String okButtonText, String cancelButtonText, Runnable okAction, Runnable cancelAction) {
|
||||
this.message = message;
|
||||
@@ -24,11 +23,6 @@ public class SimpleDialogController implements FxController {
|
||||
this.cancelButtonText = cancelButtonText;
|
||||
this.okAction = okAction;
|
||||
this.cancelAction = cancelAction;
|
||||
this.cancelButtonVisible = cancelButtonText != null && !cancelButtonText.isEmpty();
|
||||
}
|
||||
|
||||
public boolean isCancelButtonVisible() {
|
||||
return cancelButtonVisible;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
|
||||
@@ -1,329 +0,0 @@
|
||||
package org.cryptomator.ui.eventview;
|
||||
|
||||
import org.cryptomator.event.FSEventBucket;
|
||||
import org.cryptomator.event.FSEventBucketContent;
|
||||
import org.cryptomator.event.FileSystemEventAggregator;
|
||||
import org.cryptomator.common.Nullable;
|
||||
import org.cryptomator.common.ObservableUtil;
|
||||
import org.cryptomator.cryptofs.CryptoPath;
|
||||
import org.cryptomator.cryptofs.event.BrokenDirFileEvent;
|
||||
import org.cryptomator.cryptofs.event.BrokenFileNodeEvent;
|
||||
import org.cryptomator.cryptofs.event.ConflictResolutionFailedEvent;
|
||||
import org.cryptomator.cryptofs.event.ConflictResolvedEvent;
|
||||
import org.cryptomator.cryptofs.event.DecryptionFailedEvent;
|
||||
import org.cryptomator.integrations.revealpath.RevealFailedException;
|
||||
import org.cryptomator.integrations.revealpath.RevealPathService;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.controls.FontAwesome5Icon;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Side;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.input.Clipboard;
|
||||
import javafx.scene.input.ClipboardContent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.util.Duration;
|
||||
import java.nio.file.Path;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.FormatStyle;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class EventListCellController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(EventListCellController.class);
|
||||
private static final DateTimeFormatter LOCAL_DATE_FORMATTER = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withZone(ZoneId.systemDefault());
|
||||
private static final DateTimeFormatter LOCAL_TIME_FORMATTER = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withZone(ZoneId.systemDefault());
|
||||
|
||||
private final FileSystemEventAggregator fileSystemEventAggregator;
|
||||
@Nullable
|
||||
private final RevealPathService revealService;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final ObjectProperty<Map.Entry<FSEventBucket, FSEventBucketContent>> eventEntry;
|
||||
private final StringProperty eventMessage;
|
||||
private final StringProperty eventDescription;
|
||||
private final ObjectProperty<FontAwesome5Icon> eventIcon;
|
||||
private final ObservableValue<String> eventCount;
|
||||
private final ObservableValue<Boolean> vaultUnlocked;
|
||||
private final ObservableValue<String> readableTime;
|
||||
private final ObservableValue<String> readableDate;
|
||||
private final ObservableValue<String> message;
|
||||
private final ObservableValue<String> description;
|
||||
private final ObservableValue<FontAwesome5Icon> icon;
|
||||
private final BooleanProperty actionsButtonVisible;
|
||||
private final Tooltip eventTooltip;
|
||||
|
||||
@FXML
|
||||
HBox root;
|
||||
@FXML
|
||||
ContextMenu eventActionsMenu;
|
||||
@FXML
|
||||
Button eventActionsButton;
|
||||
|
||||
@Inject
|
||||
public EventListCellController(FileSystemEventAggregator fileSystemEventAggregator, Optional<RevealPathService> revealService, ResourceBundle resourceBundle) {
|
||||
this.fileSystemEventAggregator = fileSystemEventAggregator;
|
||||
this.revealService = revealService.orElseGet(() -> null);
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.eventEntry = new SimpleObjectProperty<>(null);
|
||||
this.eventMessage = new SimpleStringProperty();
|
||||
this.eventDescription = new SimpleStringProperty();
|
||||
this.eventIcon = new SimpleObjectProperty<>();
|
||||
this.eventCount = ObservableUtil.mapWithDefault(eventEntry, e -> e.getValue().count() == 1? "" : "("+ e.getValue().count() +")", "");
|
||||
this.vaultUnlocked = ObservableUtil.mapWithDefault(eventEntry.flatMap(e -> e.getKey().vault().unlockedProperty()), Function.identity(), false);
|
||||
this.readableTime = ObservableUtil.mapWithDefault(eventEntry, e -> LOCAL_TIME_FORMATTER.format(e.getValue().mostRecentEvent().getTimestamp()), "");
|
||||
this.readableDate = ObservableUtil.mapWithDefault(eventEntry, e -> LOCAL_DATE_FORMATTER.format(e.getValue().mostRecentEvent().getTimestamp()), "");
|
||||
this.message = Bindings.createStringBinding(this::selectMessage, vaultUnlocked, eventMessage);
|
||||
this.description = Bindings.createStringBinding(this::selectDescription, vaultUnlocked, eventDescription);
|
||||
this.icon = Bindings.createObjectBinding(this::selectIcon, vaultUnlocked, eventIcon);
|
||||
this.actionsButtonVisible = new SimpleBooleanProperty();
|
||||
this.eventTooltip = new Tooltip();
|
||||
eventTooltip.setShowDelay(Duration.millis(500.0));
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
actionsButtonVisible.bind(Bindings.createBooleanBinding(this::determineActionsButtonVisibility, root.hoverProperty(), eventActionsMenu.showingProperty(), vaultUnlocked));
|
||||
vaultUnlocked.addListener((_, _, newValue) -> eventActionsMenu.hide());
|
||||
Tooltip.install(root, eventTooltip);
|
||||
}
|
||||
|
||||
private boolean determineActionsButtonVisibility() {
|
||||
return vaultUnlocked.getValue() && (eventActionsMenu.isShowing() || root.isHover());
|
||||
}
|
||||
|
||||
public void setEventEntry(@NotNull Map.Entry<FSEventBucket, FSEventBucketContent> item) {
|
||||
eventEntry.set(item);
|
||||
eventActionsMenu.hide();
|
||||
eventActionsMenu.getItems().clear();
|
||||
eventTooltip.setText(item.getKey().vault().getDisplayName());
|
||||
addAction("generic.action.dismiss", () -> {
|
||||
fileSystemEventAggregator.remove(item.getKey());
|
||||
});
|
||||
switch (item.getValue().mostRecentEvent()) {
|
||||
case ConflictResolvedEvent fse -> this.adjustToConflictResolvedEvent(fse);
|
||||
case ConflictResolutionFailedEvent fse -> this.adjustToConflictEvent(fse);
|
||||
case DecryptionFailedEvent fse -> this.adjustToDecryptionFailedEvent(fse);
|
||||
case BrokenDirFileEvent fse -> this.adjustToBrokenDirFileEvent(fse);
|
||||
case BrokenFileNodeEvent fse -> this.adjustToBrokenFileNodeEvent(fse);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void adjustToBrokenFileNodeEvent(BrokenFileNodeEvent bfe) {
|
||||
eventIcon.setValue(FontAwesome5Icon.TIMES);
|
||||
eventMessage.setValue(resourceBundle.getString("eventView.entry.brokenFileNode.message"));
|
||||
eventDescription.setValue(bfe.ciphertextPath().getFileName().toString());
|
||||
if (revealService != null) {
|
||||
addAction("eventView.entry.brokenFileNode.showEncrypted", () -> reveal(revealService, convertVaultPathToSystemPath(bfe.ciphertextPath())));
|
||||
} else {
|
||||
addAction("eventView.entry.brokenFileNode.copyEncrypted", () -> copyToClipboard(convertVaultPathToSystemPath(bfe.ciphertextPath()).toString()));
|
||||
}
|
||||
addAction("eventView.entry.brokenFileNode.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(bfe.cleartextPath()).toString()));
|
||||
}
|
||||
|
||||
private void adjustToConflictResolvedEvent(ConflictResolvedEvent cre) {
|
||||
eventIcon.setValue(FontAwesome5Icon.CHECK);
|
||||
eventMessage.setValue(resourceBundle.getString("eventView.entry.conflictResolved.message"));
|
||||
eventDescription.setValue(cre.resolvedCiphertextPath().getFileName().toString());
|
||||
if (revealService != null) {
|
||||
addAction("eventView.entry.conflictResolved.showDecrypted", () -> reveal(revealService, convertVaultPathToSystemPath(cre.resolvedCleartextPath())));
|
||||
} else {
|
||||
addAction("eventView.entry.conflictResolved.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(cre.resolvedCleartextPath()).toString()));
|
||||
}
|
||||
}
|
||||
|
||||
private void adjustToConflictEvent(ConflictResolutionFailedEvent cfe) {
|
||||
eventIcon.setValue(FontAwesome5Icon.COMPRESS_ALT);
|
||||
eventMessage.setValue(resourceBundle.getString("eventView.entry.conflict.message"));
|
||||
eventDescription.setValue(cfe.conflictingCiphertextPath().getFileName().toString());
|
||||
if (revealService != null) {
|
||||
addAction("eventView.entry.conflict.showDecrypted", () -> reveal(revealService, convertVaultPathToSystemPath(cfe.canonicalCleartextPath())));
|
||||
addAction("eventView.entry.conflict.showEncrypted", () -> reveal(revealService, cfe.conflictingCiphertextPath()));
|
||||
} else {
|
||||
addAction("eventView.entry.conflict.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(cfe.canonicalCleartextPath()).toString()));
|
||||
addAction("eventView.entry.conflict.copyEncrypted", () -> copyToClipboard(cfe.conflictingCiphertextPath().toString()));
|
||||
}
|
||||
}
|
||||
|
||||
private void adjustToDecryptionFailedEvent(DecryptionFailedEvent dfe) {
|
||||
eventIcon.setValue(FontAwesome5Icon.BAN);
|
||||
eventMessage.setValue(resourceBundle.getString("eventView.entry.decryptionFailed.message"));
|
||||
eventDescription.setValue(dfe.ciphertextPath().getFileName().toString());
|
||||
if (revealService != null) {
|
||||
addAction("eventView.entry.decryptionFailed.showEncrypted", () -> reveal(revealService, dfe.ciphertextPath()));
|
||||
} else {
|
||||
addAction("eventView.entry.decryptionFailed.copyEncrypted", () -> copyToClipboard(dfe.ciphertextPath().toString()));
|
||||
}
|
||||
}
|
||||
|
||||
private void adjustToBrokenDirFileEvent(BrokenDirFileEvent bde) {
|
||||
eventIcon.setValue(FontAwesome5Icon.TIMES);
|
||||
eventMessage.setValue(resourceBundle.getString("eventView.entry.brokenDirFile.message"));
|
||||
eventDescription.setValue(bde.ciphertextPath().getParent().getFileName().toString());
|
||||
if (revealService != null) {
|
||||
addAction("eventView.entry.brokenDirFile.showEncrypted", () -> reveal(revealService, bde.ciphertextPath()));
|
||||
} else {
|
||||
addAction("eventView.entry.brokenDirFile.copyEncrypted", () -> copyToClipboard(bde.ciphertextPath().toString()));
|
||||
}
|
||||
}
|
||||
|
||||
private void addAction(String localizationKey, Runnable action) {
|
||||
var entry = new MenuItem(resourceBundle.getString(localizationKey));
|
||||
entry.getStyleClass().addLast("dropdown-button-context-menu-item");
|
||||
entry.setOnAction(_ -> action.run());
|
||||
eventActionsMenu.getItems().addLast(entry);
|
||||
}
|
||||
|
||||
|
||||
private FontAwesome5Icon selectIcon() {
|
||||
if (vaultUnlocked.getValue()) {
|
||||
return eventIcon.getValue();
|
||||
} else {
|
||||
return FontAwesome5Icon.LOCK;
|
||||
}
|
||||
}
|
||||
|
||||
private String selectMessage() {
|
||||
if (vaultUnlocked.getValue()) {
|
||||
return eventMessage.getValue();
|
||||
} else {
|
||||
return "***********";
|
||||
}
|
||||
}
|
||||
|
||||
private String selectDescription() {
|
||||
if (vaultUnlocked.getValue()) {
|
||||
return eventDescription.getValue();
|
||||
} else if (eventEntry.getValue() != null) {
|
||||
var e = eventEntry.getValue().getKey();
|
||||
return resourceBundle.getString("eventView.entry.vaultLocked.description").formatted(e != null ? e.vault().getDisplayName() : "");
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@FXML
|
||||
public void toggleEventActionsMenu() {
|
||||
var e = eventEntry.get();
|
||||
if (e != null) {
|
||||
if (eventActionsMenu.isShowing()) {
|
||||
eventActionsMenu.hide();
|
||||
} else {
|
||||
eventActionsMenu.show(eventActionsButton, Side.BOTTOM, 0.0, 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Path convertVaultPathToSystemPath(Path p) {
|
||||
if (!(p instanceof CryptoPath)) {
|
||||
throw new IllegalArgumentException("Path " + p + " is not a vault path");
|
||||
}
|
||||
var v = eventEntry.getValue().getKey().vault();
|
||||
if (!v.isUnlocked()) {
|
||||
return Path.of(System.getProperty("user.home"));
|
||||
}
|
||||
|
||||
var mountUri = v.getMountPoint().uri();
|
||||
var internalPath = p.toString().substring(1);
|
||||
return Path.of(mountUri.getPath().concat(internalPath).substring(1));
|
||||
}
|
||||
|
||||
private void reveal(RevealPathService s, Path p) {
|
||||
try {
|
||||
s.reveal(p);
|
||||
} catch (RevealFailedException e) {
|
||||
LOG.warn("Failed to show path {}", p, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void copyToClipboard(String s) {
|
||||
var content = new ClipboardContent();
|
||||
content.putString(s);
|
||||
Clipboard.getSystemClipboard().setContent(content);
|
||||
}
|
||||
|
||||
//-- property accessors --
|
||||
public ObservableValue<String> messageProperty() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message.getValue();
|
||||
}
|
||||
|
||||
public ObservableValue<String> countProperty() {
|
||||
return eventCount;
|
||||
}
|
||||
|
||||
public String getCount() {
|
||||
return eventCount.getValue();
|
||||
}
|
||||
|
||||
public ObservableValue<String> descriptionProperty() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description.getValue();
|
||||
}
|
||||
|
||||
public ObservableValue<FontAwesome5Icon> iconProperty() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public FontAwesome5Icon getIcon() {
|
||||
return icon.getValue();
|
||||
}
|
||||
|
||||
public ObservableValue<Boolean> actionsButtonVisibleProperty() {
|
||||
return actionsButtonVisible;
|
||||
}
|
||||
|
||||
public boolean isActionsButtonVisible() {
|
||||
return actionsButtonVisible.getValue();
|
||||
}
|
||||
|
||||
public ObservableValue<String> eventLocalTimeProperty() {
|
||||
return readableTime;
|
||||
}
|
||||
|
||||
public String getEventLocalTime() {
|
||||
return readableTime.getValue();
|
||||
}
|
||||
|
||||
public ObservableValue<String> eventLocalDateProperty() {
|
||||
return readableDate;
|
||||
}
|
||||
|
||||
public String getEventLocalDate() {
|
||||
return readableDate.getValue();
|
||||
}
|
||||
|
||||
public ObservableValue<Boolean> vaultUnlockedProperty() {
|
||||
return vaultUnlocked;
|
||||
}
|
||||
|
||||
public boolean isVaultUnlocked() {
|
||||
return vaultUnlocked.getValue();
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package org.cryptomator.ui.eventview;
|
||||
|
||||
import org.cryptomator.event.FSEventBucket;
|
||||
import org.cryptomator.event.FSEventBucketContent;
|
||||
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.util.Callback;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Map;
|
||||
|
||||
@EventViewScoped
|
||||
public class EventListCellFactory implements Callback<ListView<Map.Entry<FSEventBucket, FSEventBucketContent>>, ListCell<Map.Entry<FSEventBucket, FSEventBucketContent>>> {
|
||||
|
||||
private static final String FXML_PATH = "/fxml/eventview_cell.fxml";
|
||||
|
||||
private final FxmlLoaderFactory fxmlLoaders;
|
||||
|
||||
@Inject
|
||||
EventListCellFactory(@EventViewWindow FxmlLoaderFactory fxmlLoaders) {
|
||||
this.fxmlLoaders = fxmlLoaders;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ListCell<Map.Entry<FSEventBucket, FSEventBucketContent>> call(ListView<Map.Entry<FSEventBucket, FSEventBucketContent>> eventListView) {
|
||||
try {
|
||||
FXMLLoader fxmlLoader = fxmlLoaders.load(FXML_PATH);
|
||||
return new Cell(fxmlLoader.getRoot(), fxmlLoader.getController());
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Failed to load %s.".formatted(FXML_PATH), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Cell extends ListCell<Map.Entry<FSEventBucket, FSEventBucketContent>> {
|
||||
|
||||
private final Parent root;
|
||||
private final EventListCellController controller;
|
||||
|
||||
public Cell(Parent root, EventListCellController controller) {
|
||||
this.root = root;
|
||||
this.controller = controller;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(Map.Entry<FSEventBucket, FSEventBucketContent> item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (empty || item == null) {
|
||||
setGraphic(null);
|
||||
this.getStyleClass().remove("list-cell");
|
||||
} else {
|
||||
this.getStyleClass().addLast("list-cell");
|
||||
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
|
||||
setGraphic(root);
|
||||
controller.setEventEntry(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package org.cryptomator.ui.eventview;
|
||||
|
||||
import dagger.Lazy;
|
||||
import dagger.Subcomponent;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
@EventViewScoped
|
||||
@Subcomponent(modules = {EventViewModule.class})
|
||||
public interface EventViewComponent {
|
||||
|
||||
@EventViewWindow
|
||||
Stage window();
|
||||
|
||||
@FxmlScene(FxmlFile.EVENT_VIEW)
|
||||
Lazy<Scene> scene();
|
||||
|
||||
default Stage showEventViewerWindow() {
|
||||
Stage stage = window();
|
||||
stage.setScene(scene().get());
|
||||
stage.sizeToScene();
|
||||
stage.show();
|
||||
stage.requestFocus();
|
||||
return stage;
|
||||
}
|
||||
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
|
||||
EventViewComponent create();
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
package org.cryptomator.ui.eventview;
|
||||
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.event.FSEventBucket;
|
||||
import org.cryptomator.event.FSEventBucketContent;
|
||||
import org.cryptomator.event.FileSystemEventAggregator;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.fxapp.FxFSEventList;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.transformation.FilteredList;
|
||||
import javafx.collections.transformation.SortedList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ChoiceBox;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.util.StringConverter;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@EventViewScoped
|
||||
public class EventViewController implements FxController {
|
||||
|
||||
private final FilteredList<Map.Entry<FSEventBucket, FSEventBucketContent>> filteredEventList;
|
||||
private final ObservableList<Vault> vaults;
|
||||
private final FileSystemEventAggregator aggregator;
|
||||
private final SortedList<Map.Entry<FSEventBucket, FSEventBucketContent>> sortedEventList;
|
||||
private final ObservableList<Vault> choiceBoxEntries;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final EventListCellFactory cellFactory;
|
||||
|
||||
@FXML
|
||||
ChoiceBox<Vault> vaultFilterChoiceBox;
|
||||
@FXML
|
||||
ListView<Map.Entry<FSEventBucket, FSEventBucketContent>> eventListView;
|
||||
|
||||
@Inject
|
||||
public EventViewController(FxFSEventList fxFSEventList, ObservableList<Vault> vaults, ResourceBundle resourceBundle, EventListCellFactory cellFactory, FileSystemEventAggregator aggregator) {
|
||||
this.filteredEventList = fxFSEventList.getObservableList().filtered(_ -> true);
|
||||
this.vaults = vaults;
|
||||
this.aggregator = aggregator;
|
||||
this.sortedEventList = new SortedList<>(filteredEventList, this::compareBuckets);
|
||||
this.choiceBoxEntries = FXCollections.observableArrayList();
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.cellFactory = cellFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparison method for the lru cache. During comparsion the map is accessed.
|
||||
* First the entries are compared by the event timestamp, then vaultId, then identifying path and lastly by class name.
|
||||
*
|
||||
* @param left an entry of a {@link FSEventBucket} and its content
|
||||
* @param right another entry of a {@link FSEventBucket} plus content, compared to {@code left}
|
||||
* @return a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second.
|
||||
*/
|
||||
private int compareBuckets(Map.Entry<FSEventBucket, FSEventBucketContent> left, Map.Entry<FSEventBucket, FSEventBucketContent> right) {
|
||||
var t1 = left.getValue().mostRecentEvent().getTimestamp();
|
||||
var t2 = right.getValue().mostRecentEvent().getTimestamp();
|
||||
var timeComparison = t1.compareTo(t2);
|
||||
if (timeComparison != 0) {
|
||||
return -timeComparison; //we need the reverse timesorting
|
||||
}
|
||||
var vaultIdComparison = left.getKey().vault().getId().compareTo(right.getKey().vault().getId());
|
||||
if (vaultIdComparison != 0) {
|
||||
return vaultIdComparison;
|
||||
}
|
||||
var pathComparison = left.getKey().idPath().compareTo(right.getKey().idPath());
|
||||
if (pathComparison != 0) {
|
||||
return pathComparison;
|
||||
}
|
||||
return left.getKey().c().getName().compareTo(right.getKey().c().getName());
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
choiceBoxEntries.add(null);
|
||||
choiceBoxEntries.addAll(vaults);
|
||||
vaults.addListener((ListChangeListener<? super Vault>) c -> {
|
||||
while (c.next()) {
|
||||
choiceBoxEntries.removeAll(c.getRemoved());
|
||||
choiceBoxEntries.addAll(c.getAddedSubList());
|
||||
}
|
||||
});
|
||||
|
||||
eventListView.setCellFactory(cellFactory);
|
||||
eventListView.setItems(sortedEventList);
|
||||
|
||||
vaultFilterChoiceBox.setItems(choiceBoxEntries);
|
||||
vaultFilterChoiceBox.valueProperty().addListener(this::applyVaultFilter);
|
||||
vaultFilterChoiceBox.setConverter(new VaultConverter(resourceBundle));
|
||||
}
|
||||
|
||||
private void applyVaultFilter(ObservableValue<? extends Vault> v, Vault oldV, Vault newV) {
|
||||
if (newV == null) {
|
||||
filteredEventList.setPredicate(_ -> true);
|
||||
} else {
|
||||
filteredEventList.setPredicate(e -> e.getKey().vault().equals(newV));
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
void clearEvents() {
|
||||
aggregator.clear();
|
||||
}
|
||||
|
||||
private static class VaultConverter extends StringConverter<Vault> {
|
||||
|
||||
private final ResourceBundle resourceBundle;
|
||||
|
||||
VaultConverter(ResourceBundle resourceBundle) {
|
||||
this.resourceBundle = resourceBundle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Vault v) {
|
||||
if (v == null) {
|
||||
return resourceBundle.getString("eventView.filter.allVaults");
|
||||
} else {
|
||||
return v.getDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vault fromString(String displayLanguage) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package org.cryptomator.ui.eventview;
|
||||
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxControllerKey;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.StageFactory;
|
||||
import org.cryptomator.ui.fxapp.FxFSEventList;
|
||||
|
||||
import javax.inject.Provider;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module
|
||||
abstract class EventViewModule {
|
||||
|
||||
@Provides
|
||||
@EventViewScoped
|
||||
@EventViewWindow
|
||||
static Stage provideStage(StageFactory factory, ResourceBundle resourceBundle, FxFSEventList fxFSEventList) {
|
||||
Stage stage = factory.create();
|
||||
stage.setHeight(498);
|
||||
stage.setTitle(resourceBundle.getString("eventView.title"));
|
||||
stage.setResizable(true);
|
||||
stage.initModality(Modality.NONE);
|
||||
stage.focusedProperty().addListener((_,_,isFocused) -> {
|
||||
if(isFocused) {
|
||||
fxFSEventList.unreadEventsProperty().setValue(false);
|
||||
}
|
||||
});
|
||||
return stage;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@EventViewScoped
|
||||
@EventViewWindow
|
||||
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.EVENT_VIEW)
|
||||
@EventViewScoped
|
||||
static Scene provideEventViewerScene(@EventViewWindow FxmlLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.EVENT_VIEW);
|
||||
}
|
||||
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(EventViewController.class)
|
||||
abstract FxController bindEventViewController(EventViewController controller);
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(EventListCellController.class)
|
||||
abstract FxController bindEventListCellController(EventListCellController controller);
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package org.cryptomator.ui.eventview;
|
||||
|
||||
import javax.inject.Scope;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@Scope
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface EventViewScoped {
|
||||
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package org.cryptomator.ui.eventview;
|
||||
|
||||
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 EventViewWindow {
|
||||
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package org.cryptomator.ui.eventview;
|
||||
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@EventViewScoped
|
||||
public class UpdateEventViewController implements FxController {
|
||||
|
||||
@Inject
|
||||
public UpdateEventViewController() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -43,8 +43,8 @@ public class AutoUnlocker {
|
||||
private CompletionStage<Void> unlockSequentially(Stream<Vault> vaultStream) {
|
||||
// this is an attempt to run all the unlock workflows sequentially, i.e. start the next workflow only after completing/failing the previous workflow.
|
||||
return vaultStream.filter(Vault::isLocked).reduce(CompletableFuture.completedFuture(null),
|
||||
(prevUnlock, nextVault) -> prevUnlock.thenCompose(_ -> appWindows.startUnlockWorkflow(nextVault, null)),
|
||||
(_, nextUnlock) -> nextUnlock.exceptionally(_ -> null) // we don't care here about the exception, logged elsewhere
|
||||
(prevUnlock, nextVault) -> prevUnlock.thenCompose(unused -> appWindows.startUnlockWorkflow(nextVault, null)),
|
||||
(prevUnlock, nextUnlock) -> nextUnlock.exceptionally(e -> null) // we don't care here about the exception, logged elsewhere
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,10 +29,9 @@ public class FxApplication {
|
||||
private final FxApplicationStyle applicationStyle;
|
||||
private final FxApplicationTerminator applicationTerminator;
|
||||
private final AutoUnlocker autoUnlocker;
|
||||
private final FxFSEventList fxFSEventList;
|
||||
|
||||
@Inject
|
||||
FxApplication(@Named("startupTime") long startupTime, Environment environment, Settings settings, AppLaunchEventHandler launchEventHandler, Lazy<TrayMenuComponent> trayMenu, FxApplicationWindows appWindows, FxApplicationStyle applicationStyle, FxApplicationTerminator applicationTerminator, AutoUnlocker autoUnlocker, FxFSEventList fxFSEventList) {
|
||||
FxApplication(@Named("startupTime") long startupTime, Environment environment, Settings settings, AppLaunchEventHandler launchEventHandler, Lazy<TrayMenuComponent> trayMenu, FxApplicationWindows appWindows, FxApplicationStyle applicationStyle, FxApplicationTerminator applicationTerminator, AutoUnlocker autoUnlocker) {
|
||||
this.startupTime = startupTime;
|
||||
this.environment = environment;
|
||||
this.settings = settings;
|
||||
@@ -42,7 +41,6 @@ public class FxApplication {
|
||||
this.applicationStyle = applicationStyle;
|
||||
this.applicationTerminator = applicationTerminator;
|
||||
this.autoUnlocker = autoUnlocker;
|
||||
this.fxFSEventList = fxFSEventList;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
@@ -87,7 +85,6 @@ public class FxApplication {
|
||||
migrateAndInformDokanyRemoval();
|
||||
|
||||
launchEventHandler.startHandlingLaunchEvents();
|
||||
fxFSEventList.schedulePollForUpdates();
|
||||
autoUnlocker.tryUnlockForTimespan(2, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,7 @@ package org.cryptomator.ui.fxapp;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import org.cryptomator.ui.decryptname.DecryptNameComponent;
|
||||
import org.cryptomator.ui.error.ErrorComponent;
|
||||
import org.cryptomator.ui.eventview.EventViewComponent;
|
||||
import org.cryptomator.ui.health.HealthCheckComponent;
|
||||
import org.cryptomator.ui.lock.LockComponent;
|
||||
import org.cryptomator.ui.mainwindow.MainWindowComponent;
|
||||
@@ -21,15 +19,11 @@ import org.cryptomator.ui.unlock.UnlockComponent;
|
||||
import org.cryptomator.ui.updatereminder.UpdateReminderComponent;
|
||||
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.scene.image.Image;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
@Module(includes = {UpdateCheckerModule.class}, subcomponents = {TrayMenuComponent.class, //
|
||||
DecryptNameComponent.class, //
|
||||
MainWindowComponent.class, //
|
||||
PreferencesComponent.class, //
|
||||
VaultOptionsComponent.class, //
|
||||
@@ -39,8 +33,7 @@ import java.io.InputStream;
|
||||
ErrorComponent.class, //
|
||||
HealthCheckComponent.class, //
|
||||
UpdateReminderComponent.class, //
|
||||
ShareVaultComponent.class, //
|
||||
EventViewComponent.class})
|
||||
ShareVaultComponent.class})
|
||||
abstract class FxApplicationModule {
|
||||
|
||||
private static Image createImageFromResource(String resourceName) throws IOException {
|
||||
@@ -73,10 +66,4 @@ abstract class FxApplicationModule {
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxApplicationScoped
|
||||
static EventViewComponent provideEventViewComponent(EventViewComponent.Factory factory) {
|
||||
return factory.create();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,9 +6,7 @@ import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultState;
|
||||
import org.cryptomator.integrations.tray.TrayIntegrationProvider;
|
||||
import org.cryptomator.ui.dialogs.Dialogs;
|
||||
import org.cryptomator.ui.dialogs.SimpleDialog;
|
||||
import org.cryptomator.ui.error.ErrorComponent;
|
||||
import org.cryptomator.ui.eventview.EventViewComponent;
|
||||
import org.cryptomator.ui.lock.LockComponent;
|
||||
import org.cryptomator.ui.mainwindow.MainWindowComponent;
|
||||
import org.cryptomator.ui.preferences.PreferencesComponent;
|
||||
@@ -53,7 +51,6 @@ public class FxApplicationWindows {
|
||||
private final UpdateReminderComponent.Factory updateReminderWindowFactory;
|
||||
private final LockComponent.Factory lockWorkflowFactory;
|
||||
private final ErrorComponent.Factory errorWindowFactory;
|
||||
private final Lazy<EventViewComponent> eventViewWindow;
|
||||
private final ExecutorService executor;
|
||||
private final VaultOptionsComponent.Factory vaultOptionsWindow;
|
||||
private final ShareVaultComponent.Factory shareVaultWindow;
|
||||
@@ -72,7 +69,6 @@ public class FxApplicationWindows {
|
||||
ErrorComponent.Factory errorWindowFactory, //
|
||||
VaultOptionsComponent.Factory vaultOptionsWindow, //
|
||||
ShareVaultComponent.Factory shareVaultWindow, //
|
||||
Lazy<EventViewComponent> eventViewWindow, //
|
||||
ExecutorService executor, //
|
||||
Dialogs dialogs) {
|
||||
this.primaryStage = primaryStage;
|
||||
@@ -84,7 +80,6 @@ public class FxApplicationWindows {
|
||||
this.updateReminderWindowFactory = updateReminderWindowFactory;
|
||||
this.lockWorkflowFactory = lockWorkflowFactory;
|
||||
this.errorWindowFactory = errorWindowFactory;
|
||||
this.eventViewWindow = eventViewWindow;
|
||||
this.executor = executor;
|
||||
this.vaultOptionsWindow = vaultOptionsWindow;
|
||||
this.shareVaultWindow = shareVaultWindow;
|
||||
@@ -98,17 +93,17 @@ public class FxApplicationWindows {
|
||||
|
||||
// register preferences shortcut
|
||||
if (desktop.isSupported(Desktop.Action.APP_PREFERENCES)) {
|
||||
desktop.setPreferencesHandler(_ -> showPreferencesWindow(SelectedPreferencesTab.ANY));
|
||||
desktop.setPreferencesHandler(evt -> showPreferencesWindow(SelectedPreferencesTab.ANY));
|
||||
}
|
||||
|
||||
// register preferences shortcut
|
||||
if (desktop.isSupported(Desktop.Action.APP_ABOUT)) {
|
||||
desktop.setAboutHandler(_ -> showPreferencesWindow(SelectedPreferencesTab.ABOUT));
|
||||
desktop.setAboutHandler(evt -> showPreferencesWindow(SelectedPreferencesTab.ABOUT));
|
||||
}
|
||||
|
||||
// register app reopen listener
|
||||
if (desktop.isSupported(Desktop.Action.APP_EVENT_REOPENED)) {
|
||||
desktop.addAppEventListener((AppReopenedListener) _ -> showMainWindow());
|
||||
desktop.addAppEventListener((AppReopenedListener) e -> showMainWindow());
|
||||
}
|
||||
|
||||
// observe visible windows
|
||||
@@ -140,12 +135,11 @@ public class FxApplicationWindows {
|
||||
}
|
||||
|
||||
public CompletionStage<Stage> showVaultOptionsWindow(Vault vault, SelectedVaultOptionsTab tab) {
|
||||
return showMainWindow().thenApplyAsync(_ -> vaultOptionsWindow.create(vault).showVaultOptionsWindow(tab), Platform::runLater) //
|
||||
.whenComplete(this::reportErrors);
|
||||
return showMainWindow().thenApplyAsync((window) -> vaultOptionsWindow.create(vault).showVaultOptionsWindow(tab), Platform::runLater).whenComplete(this::reportErrors);
|
||||
}
|
||||
|
||||
public void showQuitWindow(QuitResponse response, boolean forced) {
|
||||
CompletableFuture.runAsync(() -> quitWindowBuilder.build().showQuitWindow(response, forced), Platform::runLater);
|
||||
CompletableFuture.runAsync(() -> quitWindowBuilder.build().showQuitWindow(response,forced), Platform::runLater);
|
||||
}
|
||||
|
||||
public void showUpdateReminderWindow() {
|
||||
@@ -153,14 +147,13 @@ public class FxApplicationWindows {
|
||||
}
|
||||
|
||||
public void showDokanySupportEndWindow() {
|
||||
CompletableFuture.runAsync(() -> createDokanySupportEndDialog().showAndWait(), Platform::runLater);
|
||||
}
|
||||
|
||||
private SimpleDialog createDokanySupportEndDialog() {
|
||||
return dialogs.prepareDokanySupportEndDialog(mainWindow.get().window(), stage -> {
|
||||
showPreferencesWindow(SelectedPreferencesTab.VOLUME);
|
||||
stage.close();
|
||||
}).build();
|
||||
CompletableFuture.runAsync(() -> dialogs.prepareDokanySupportEndDialog(
|
||||
mainWindow.get().window(),
|
||||
stage -> {
|
||||
showPreferencesWindow(SelectedPreferencesTab.VOLUME);
|
||||
stage.close();
|
||||
}
|
||||
).build().showAndWait(), Platform::runLater);
|
||||
}
|
||||
|
||||
public CompletionStage<Void> startUnlockWorkflow(Vault vault, @Nullable Stage owner) {
|
||||
@@ -169,7 +162,8 @@ public class FxApplicationWindows {
|
||||
LOG.debug("Start unlock workflow for {}", vault.getDisplayName());
|
||||
return unlockWorkflowFactory.create(vault, owner).unlockWorkflow();
|
||||
}, Platform::runLater) //
|
||||
.thenAcceptAsync(UnlockWorkflow::run, executor).exceptionally(e -> {
|
||||
.thenAcceptAsync(UnlockWorkflow::run, executor)
|
||||
.exceptionally(e -> {
|
||||
showErrorWindow(e, owner == null ? primaryStage : owner, null);
|
||||
return null;
|
||||
});
|
||||
@@ -188,11 +182,6 @@ public class FxApplicationWindows {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public CompletionStage<Stage> showEventViewer() {
|
||||
return CompletableFuture.supplyAsync(() -> eventViewWindow.get().showEventViewerWindow(), Platform::runLater).whenComplete(this::reportErrors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the generic error scene in the given window.
|
||||
*
|
||||
@@ -210,4 +199,5 @@ public class FxApplicationWindows {
|
||||
LOG.error("Failed to display stage", error);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
package org.cryptomator.ui.fxapp;
|
||||
|
||||
import org.cryptomator.event.FSEventBucket;
|
||||
import org.cryptomator.event.FSEventBucketContent;
|
||||
import org.cryptomator.event.FileSystemEventAggregator;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* List of all occurred filesystem events.
|
||||
* <p>
|
||||
* The list exposes an observable list and a property to listen for updates. Internally it polls the {@link FileSystemEventAggregator} in a regular interval for updates.
|
||||
* If an update is available, the list from the {@link FileSystemEventAggregator } is cloned to this list on the FX application thread.
|
||||
*/
|
||||
@FxApplicationScoped
|
||||
public class FxFSEventList {
|
||||
|
||||
private final ObservableList<Map.Entry<FSEventBucket, FSEventBucketContent>> events;
|
||||
private final FileSystemEventAggregator eventAggregator;
|
||||
private final ScheduledExecutorService scheduler;
|
||||
private final BooleanProperty unreadEvents;
|
||||
|
||||
@Inject
|
||||
public FxFSEventList(FileSystemEventAggregator fsEventAggregator, ScheduledExecutorService scheduler) {
|
||||
this.events = FXCollections.observableArrayList();
|
||||
this.eventAggregator = fsEventAggregator;
|
||||
this.scheduler = scheduler;
|
||||
this.unreadEvents = new SimpleBooleanProperty(false);
|
||||
}
|
||||
|
||||
public void schedulePollForUpdates() {
|
||||
scheduler.schedule(this::checkForEventUpdates, 1000, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for event updates and reschedules.
|
||||
* If updates are available, the aggregated events are copied from back- to the frontend.
|
||||
* Reschedules itself on successful execution
|
||||
*/
|
||||
private void checkForEventUpdates() {
|
||||
if (eventAggregator.hasMaybeUpdates()) {
|
||||
Platform.runLater(() -> {
|
||||
eventAggregator.cloneTo(events);
|
||||
unreadEvents.setValue(true);
|
||||
schedulePollForUpdates();
|
||||
});
|
||||
} else {
|
||||
schedulePollForUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableList<Map.Entry<FSEventBucket, FSEventBucketContent>> getObservableList() {
|
||||
return events;
|
||||
}
|
||||
|
||||
public BooleanProperty unreadEventsProperty() {
|
||||
return unreadEvents;
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,6 @@ package org.cryptomator.ui.keyloading;
|
||||
import org.cryptomator.cryptolib.api.Masterkey;
|
||||
import org.cryptomator.cryptolib.api.MasterkeyLoader;
|
||||
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
|
||||
import org.cryptomator.ui.keyloading.hub.HubKeyLoadingStrategy;
|
||||
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -30,33 +28,6 @@ public interface KeyLoadingStrategy extends MasterkeyLoader {
|
||||
@Override
|
||||
Masterkey loadKey(URI keyId) throws MasterkeyLoadingFailedException;
|
||||
|
||||
/**
|
||||
* Determines whether the provided key loader scheme corresponds to a Hub Vault.
|
||||
* <p>
|
||||
* This method compares the {@code keyLoader} parameter with the known Hub Vault schemes
|
||||
* {@link HubKeyLoadingStrategy#SCHEME_HUB_HTTP} and {@link HubKeyLoadingStrategy#SCHEME_HUB_HTTPS}.
|
||||
*
|
||||
* @param keyLoader A string representing the key loader scheme to be checked.
|
||||
* @return {@code true} if the given key loader scheme represents a Hub Vault; {@code false} otherwise.
|
||||
*/
|
||||
static boolean isHubVault(String keyLoader) {
|
||||
return HubKeyLoadingStrategy.SCHEME_HUB_HTTP.equals(keyLoader) || HubKeyLoadingStrategy.SCHEME_HUB_HTTPS.equals(keyLoader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the provided key loader scheme corresponds to a Masterkey File Vault.
|
||||
* <p>
|
||||
* This method checks if the {@code keyLoader} parameter matches the known Masterkey File Vault scheme
|
||||
* {@link MasterkeyFileLoadingStrategy#SCHEME}.
|
||||
* </p>
|
||||
*
|
||||
* @param keyLoader A string representing the key loader scheme to be checked.
|
||||
* @return {@code true} if the given key loader scheme represents a Masterkey File Vault; {@code false} otherwise.
|
||||
*/
|
||||
static boolean isMasterkeyFileVault(String keyLoader) {
|
||||
return MasterkeyFileLoadingStrategy.SCHEME.equals(keyLoader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the loader to try and recover from an exception thrown during the last attempt.
|
||||
*
|
||||
|
||||
@@ -165,7 +165,6 @@ public class ReceiveKeyController implements FxController {
|
||||
var vaultKeyUri = hubConfig.URIs.API.resolve("vaults/" + vaultId + "/access-token");
|
||||
var request = HttpRequest.newBuilder(vaultKeyUri) //
|
||||
.header("Authorization", "Bearer " + bearerToken) //
|
||||
.header("Hub-Device-ID", deviceId) //
|
||||
.GET() //
|
||||
.timeout(REQ_TIMEOUT) //
|
||||
.build();
|
||||
|
||||
@@ -112,12 +112,12 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy {
|
||||
}
|
||||
|
||||
private void savePasswordToSystemkeychain(Passphrase passphrase) {
|
||||
try {
|
||||
if (keychain.isSupported() && !keychain.getPassphraseStoredProperty(vault.getId()).get()) {
|
||||
if (keychain.isSupported()) {
|
||||
try {
|
||||
keychain.storePassphrase(vault.getId(), vault.getDisplayName(), passphrase);
|
||||
} catch (KeychainAccessException e) {
|
||||
LOG.error("Failed to store passphrase in system keychain.", e);
|
||||
}
|
||||
} catch (KeychainAccessException e) {
|
||||
LOG.error("Failed to store passphrase in system keychain.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ import javafx.stage.Stage;
|
||||
import javafx.stage.WindowEvent;
|
||||
import javafx.util.Duration;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
@PassphraseEntryScoped
|
||||
public class PassphraseEntryController implements FxController {
|
||||
@@ -50,7 +49,6 @@ public class PassphraseEntryController implements FxController {
|
||||
private final ForgetPasswordComponent.Builder forgetPassword;
|
||||
private final KeychainManager keychain;
|
||||
private final StringBinding vaultName;
|
||||
private final ExecutorService backgroundExecutorService;
|
||||
private final BooleanProperty unlockInProgress = new SimpleBooleanProperty();
|
||||
private final ObjectBinding<ContentDisplay> unlockButtonContentDisplay = Bindings.when(unlockInProgress).then(ContentDisplay.LEFT).otherwise(ContentDisplay.TEXT_ONLY);
|
||||
private final BooleanProperty unlockButtonDisabled = new SimpleBooleanProperty();
|
||||
@@ -66,7 +64,7 @@ public class PassphraseEntryController implements FxController {
|
||||
public Animation unlockAnimation;
|
||||
|
||||
@Inject
|
||||
public PassphraseEntryController(@KeyLoading Stage window, @KeyLoading Vault vault, CompletableFuture<PassphraseEntryResult> result, @Nullable @Named("savedPassword") Passphrase savedPassword, ForgetPasswordComponent.Builder forgetPassword, KeychainManager keychain, ExecutorService backgroundExecutorService) {
|
||||
public PassphraseEntryController(@KeyLoading Stage window, @KeyLoading Vault vault, CompletableFuture<PassphraseEntryResult> result, @Nullable @Named("savedPassword") Passphrase savedPassword, ForgetPasswordComponent.Builder forgetPassword, KeychainManager keychain) {
|
||||
this.window = window;
|
||||
this.vault = vault;
|
||||
this.result = result;
|
||||
@@ -74,8 +72,8 @@ public class PassphraseEntryController implements FxController {
|
||||
this.forgetPassword = forgetPassword;
|
||||
this.keychain = keychain;
|
||||
this.vaultName = WeakBindings.bindString(vault.displayNameProperty());
|
||||
this.backgroundExecutorService = backgroundExecutorService;
|
||||
window.setOnHiding(this::windowClosed);
|
||||
result.whenCompleteAsync((r, t) -> unlockInProgress.set(false), Platform::runLater);
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -121,6 +119,8 @@ public class PassphraseEntryController implements FxController {
|
||||
new KeyFrame(Duration.millis(800), legsExtendedY, legsExtendedX, faceHidden), //
|
||||
new KeyFrame(Duration.millis(1000), faceVisible) //
|
||||
);
|
||||
|
||||
result.whenCompleteAsync((r, t) -> stopUnlockAnimation());
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -133,9 +133,6 @@ public class PassphraseEntryController implements FxController {
|
||||
result.cancel(true);
|
||||
LOG.debug("Unlock canceled by user.");
|
||||
}
|
||||
if( passwordField != null) {
|
||||
passwordField.getCharacters().destroy();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -145,7 +142,7 @@ public class PassphraseEntryController implements FxController {
|
||||
unlockInProgress.set(true);
|
||||
CharSequence pwFieldContents = passwordField.getCharacters();
|
||||
Passphrase pw = Passphrase.copyOf(pwFieldContents);
|
||||
result.completeAsync(() -> new PassphraseEntryResult(pw, savePasswordCheckbox.isSelected()), backgroundExecutorService);
|
||||
result.complete(new PassphraseEntryResult(pw, savePasswordCheckbox.isSelected()));
|
||||
startUnlockAnimation();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
package org.cryptomator.ui.mainwindow;
|
||||
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.Stage;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.LicenseHolder;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
@@ -13,17 +21,6 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Rectangle2D;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.Screen;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.WindowEvent;
|
||||
|
||||
@MainWindowScoped
|
||||
public class MainWindowController implements FxController {
|
||||
@@ -66,66 +63,27 @@ public class MainWindowController implements FxController {
|
||||
}
|
||||
window.focusedProperty().addListener(this::mainWindowFocusChanged);
|
||||
|
||||
int x = settings.windowXPosition.get();
|
||||
int y = settings.windowYPosition.get();
|
||||
int width = settings.windowWidth.get();
|
||||
int height = settings.windowHeight.get();
|
||||
if (windowPositionSaved(x, y, width, height)) {
|
||||
window.setX(x);
|
||||
window.setY(y);
|
||||
window.setWidth(Math.clamp(width, window.getMinWidth(), window.getMaxWidth()));
|
||||
window.setHeight(Math.clamp(height, window.getMinHeight(), window.getMaxHeight()));
|
||||
if (!neverTouched()) {
|
||||
window.setHeight(settings.windowHeight.get() > window.getMinHeight() ? settings.windowHeight.get() : window.getMinHeight());
|
||||
window.setWidth(settings.windowWidth.get() > window.getMinWidth() ? settings.windowWidth.get() : window.getMinWidth());
|
||||
window.setX(settings.windowXPosition.get());
|
||||
window.setY(settings.windowYPosition.get());
|
||||
}
|
||||
|
||||
window.setOnShowing(this::checkDisplayBounds);
|
||||
|
||||
settings.windowXPosition.bind(window.xProperty());
|
||||
settings.windowYPosition.bind(window.yProperty());
|
||||
settings.windowWidth.bind(window.widthProperty());
|
||||
settings.windowHeight.bind(window.heightProperty());
|
||||
window.widthProperty().addListener((_, _, _) -> savePositionalSettings());
|
||||
window.heightProperty().addListener((_, _, _) -> savePositionalSettings());
|
||||
window.xProperty().addListener((_, _, _) -> savePositionalSettings());
|
||||
window.yProperty().addListener((_, _, _) -> savePositionalSettings());
|
||||
}
|
||||
|
||||
private boolean windowPositionSaved(int x, int y, int width, int height) {
|
||||
return x != 0 || y != 0 || width != 0 || height != 0;
|
||||
private boolean neverTouched() {
|
||||
return (settings.windowHeight.get() == 0) && (settings.windowWidth.get() == 0) && (settings.windowXPosition.get() == 0) && (settings.windowYPosition.get() == 0);
|
||||
}
|
||||
|
||||
private void checkDisplayBounds(WindowEvent windowEvent) {
|
||||
int x = settings.windowXPosition.get();
|
||||
int y = settings.windowYPosition.get();
|
||||
int width = settings.windowWidth.get();
|
||||
int height = settings.windowHeight.get();
|
||||
|
||||
Rectangle2D primaryScreenBounds = Screen.getPrimary().getBounds();
|
||||
if (!isWithinDisplayBounds(x, y, width, height)) { //use stored window position
|
||||
LOG.debug("Resetting window position due to insufficient screen overlap");
|
||||
var centeredX = (primaryScreenBounds.getWidth() - window.getMinWidth()) / 2;
|
||||
var centeredY = (primaryScreenBounds.getHeight() - window.getMinHeight()) / 2;
|
||||
//check if we can keep width and height
|
||||
if (isWithinDisplayBounds((int) centeredX, (int) centeredY, width, height)) {
|
||||
//if so, keep window size
|
||||
window.setWidth(Math.clamp(width, window.getMinWidth(), window.getMaxWidth()));
|
||||
window.setHeight(Math.clamp(height, window.getMinHeight(), window.getMaxHeight()));
|
||||
}
|
||||
//reset position of upper left corner
|
||||
window.setX(centeredX);
|
||||
window.setY(centeredY);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isWithinDisplayBounds(int x, int y, int width, int height) {
|
||||
// define a rect which is inset on all sides from the window's rect:
|
||||
final int shrinkedX = x + 20; // 20px left
|
||||
final int shrinkedY = y + 5; // 5px top
|
||||
final int shrinkedWidth = width - 40; // 20px left + 20px right
|
||||
final int shrinkedHeigth = height - 25; // 5px top + 20px bottom
|
||||
return isRectangleWithinBounds(shrinkedX, shrinkedY, 0, shrinkedHeigth) // Left pixel column
|
||||
&& isRectangleWithinBounds(shrinkedX + shrinkedWidth, shrinkedY, 0, shrinkedHeigth) // Right pixel column
|
||||
&& isRectangleWithinBounds(shrinkedX, shrinkedY, shrinkedWidth, 0) // Top pixel row
|
||||
&& isRectangleWithinBounds(shrinkedX, shrinkedY + shrinkedHeigth, shrinkedWidth, 0); // Bottom pixel row
|
||||
}
|
||||
|
||||
private boolean isRectangleWithinBounds(int x, int y, int width, int height) {
|
||||
return !Screen.getScreensForRectangle(x, y, width, height).isEmpty();
|
||||
public void savePositionalSettings() {
|
||||
settings.windowWidth.setValue(window.getWidth());
|
||||
settings.windowHeight.setValue(window.getHeight());
|
||||
settings.windowXPosition.setValue(window.getX());
|
||||
settings.windowYPosition.setValue(window.getY());
|
||||
}
|
||||
|
||||
private void mainWindowFocusChanged(Observable observable) {
|
||||
@@ -166,7 +124,7 @@ public class MainWindowController implements FxController {
|
||||
return updateAvailable.get();
|
||||
}
|
||||
|
||||
public BooleanBinding licenseValidProperty() {
|
||||
public BooleanBinding licenseValidProperty(){
|
||||
return licenseHolder.validLicenseProperty();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.cryptomator.ui.mainwindow;
|
||||
|
||||
import dagger.Binds;
|
||||
import dagger.Lazy;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
@@ -15,11 +14,9 @@ import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.StageFactory;
|
||||
import org.cryptomator.ui.common.StageInitializer;
|
||||
import org.cryptomator.ui.error.ErrorComponent;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationTerminator;
|
||||
import org.cryptomator.ui.fxapp.PrimaryStage;
|
||||
import org.cryptomator.ui.migration.MigrationComponent;
|
||||
import org.cryptomator.ui.stats.VaultStatisticsComponent;
|
||||
import org.cryptomator.ui.traymenu.TrayMenuComponent;
|
||||
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
|
||||
|
||||
import javax.inject.Named;
|
||||
@@ -38,19 +35,11 @@ abstract class MainWindowModule {
|
||||
@Provides
|
||||
@MainWindow
|
||||
@MainWindowScoped
|
||||
static Stage provideMainWindow(@PrimaryStage Stage stage, StageInitializer initializer, FxApplicationTerminator terminator, Lazy<TrayMenuComponent> trayMenu) {
|
||||
static Stage provideMainWindow(@PrimaryStage Stage stage, StageInitializer initializer) {
|
||||
initializer.accept(stage);
|
||||
stage.setTitle("Cryptomator");
|
||||
stage.setMinWidth(650);
|
||||
stage.setMinHeight(498);
|
||||
stage.setOnCloseRequest(e -> {
|
||||
if (!trayMenu.get().isInitialized()) {
|
||||
terminator.terminate();
|
||||
e.consume();
|
||||
} else {
|
||||
stage.close();
|
||||
}
|
||||
});
|
||||
return stage;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab;
|
||||
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.stage.Stage;
|
||||
@@ -21,6 +21,7 @@ public class VaultDetailLockedController implements FxController {
|
||||
private final ReadOnlyObjectProperty<Vault> vault;
|
||||
private final FxApplicationWindows appWindows;
|
||||
private final VaultOptionsComponent.Factory vaultOptionsWindow;
|
||||
private final KeychainManager keychain;
|
||||
private final Stage mainWindow;
|
||||
private final ObservableValue<Boolean> passwordSaved;
|
||||
|
||||
@@ -29,11 +30,13 @@ public class VaultDetailLockedController implements FxController {
|
||||
this.vault = vault;
|
||||
this.appWindows = appWindows;
|
||||
this.vaultOptionsWindow = vaultOptionsWindow;
|
||||
this.keychain = keychain;
|
||||
this.mainWindow = mainWindow;
|
||||
this.passwordSaved = Bindings.createBooleanBinding(() -> {
|
||||
var v = vault.get();
|
||||
return v != null && keychain.getPassphraseStoredProperty(v.getId()).getValue();
|
||||
}, vault, keychain.getKeychainImplementation());
|
||||
if (keychain.isSupported() && !keychain.isLocked()) {
|
||||
this.passwordSaved = vault.flatMap(v -> keychain.getPassphraseStoredProperty(v.getId())).orElse(false);
|
||||
} else {
|
||||
this.passwordSaved = new SimpleBooleanProperty(false);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
|
||||
@@ -6,14 +6,12 @@ import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.tobiasdiez.easybind.EasyBind;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.Nullable;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.integrations.mount.Mountpoint;
|
||||
import org.cryptomator.integrations.revealpath.RevealFailedException;
|
||||
import org.cryptomator.integrations.revealpath.RevealPathService;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.VaultService;
|
||||
import org.cryptomator.ui.decryptname.DecryptNameComponent;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||
import org.cryptomator.ui.stats.VaultStatisticsComponent;
|
||||
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
|
||||
@@ -41,13 +39,10 @@ import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
@MainWindowScoped
|
||||
public class VaultDetailUnlockedController implements FxController {
|
||||
@@ -61,39 +56,26 @@ public class VaultDetailUnlockedController implements FxController {
|
||||
private final WrongFileAlertComponent.Builder wrongFileAlert;
|
||||
private final Stage mainWindow;
|
||||
private final Optional<RevealPathService> revealPathService;
|
||||
private final DecryptNameComponent.Factory decryptNameWindowFactory;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final LoadingCache<Vault, VaultStatisticsComponent> vaultStats;
|
||||
private final VaultStatisticsComponent.Builder vaultStatsBuilder;
|
||||
private final ObservableValue<Boolean> accessibleViaPath;
|
||||
private final ObservableValue<Boolean> accessibleViaUri;
|
||||
private final ObservableValue<String> mountPoint;
|
||||
private final BooleanProperty draggingOverLocateEncrypted = new SimpleBooleanProperty();
|
||||
private final BooleanProperty draggingOverDecryptName = new SimpleBooleanProperty();
|
||||
private final BooleanProperty draggingOver = new SimpleBooleanProperty();
|
||||
private final BooleanProperty ciphertextPathsCopied = new SimpleBooleanProperty();
|
||||
|
||||
@FXML
|
||||
public Button revealEncryptedDropZone;
|
||||
@FXML
|
||||
public Button decryptNameDropZone;
|
||||
//FXML
|
||||
public Button dropZone;
|
||||
|
||||
@Inject
|
||||
public VaultDetailUnlockedController(ObjectProperty<Vault> vault, //
|
||||
FxApplicationWindows appWindows, //
|
||||
VaultService vaultService, //
|
||||
VaultStatisticsComponent.Builder vaultStatsBuilder, //
|
||||
WrongFileAlertComponent.Builder wrongFileAlert, //
|
||||
@MainWindow Stage mainWindow, //
|
||||
Optional<RevealPathService> revealPathService, //
|
||||
DecryptNameComponent.Factory decryptNameWindowFactory, //
|
||||
ResourceBundle resourceBundle) {
|
||||
public VaultDetailUnlockedController(ObjectProperty<Vault> vault, FxApplicationWindows appWindows, VaultService vaultService, VaultStatisticsComponent.Builder vaultStatsBuilder, WrongFileAlertComponent.Builder wrongFileAlert, @MainWindow Stage mainWindow, Optional<RevealPathService> revealPathService, ResourceBundle resourceBundle) {
|
||||
this.vault = vault;
|
||||
this.appWindows = appWindows;
|
||||
this.vaultService = vaultService;
|
||||
this.wrongFileAlert = wrongFileAlert;
|
||||
this.mainWindow = mainWindow;
|
||||
this.revealPathService = revealPathService;
|
||||
this.decryptNameWindowFactory = decryptNameWindowFactory;
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.vaultStats = CacheBuilder.newBuilder().weakValues().build(CacheLoader.from(this::buildVaultStats));
|
||||
this.vaultStatsBuilder = vaultStatsBuilder;
|
||||
@@ -110,81 +92,89 @@ public class VaultDetailUnlockedController implements FxController {
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
revealEncryptedDropZone.setOnDragOver(e -> handleDragOver(e, draggingOverLocateEncrypted));
|
||||
revealEncryptedDropZone.setOnDragDropped(e -> handleDragDropped(e, this::getCiphertextPath, this::revealOrCopyPaths));
|
||||
revealEncryptedDropZone.setOnDragExited(_ -> draggingOverLocateEncrypted.setValue(false));
|
||||
dropZone.setOnDragEntered(this::handleDragEvent);
|
||||
dropZone.setOnDragOver(this::handleDragEvent);
|
||||
dropZone.setOnDragDropped(this::handleDragEvent);
|
||||
dropZone.setOnDragExited(this::handleDragEvent);
|
||||
|
||||
decryptNameDropZone.setOnDragOver(e -> handleDragOver(e, draggingOverDecryptName));
|
||||
decryptNameDropZone.setOnDragDropped(e -> showDecryptNameWindow(e.getDragboard().getFiles().stream().map(File::toPath).toList()));
|
||||
decryptNameDropZone.setOnDragExited(_ -> draggingOverDecryptName.setValue(false));
|
||||
|
||||
EasyBind.includeWhen(revealEncryptedDropZone.getStyleClass(), ACTIVE_CLASS, draggingOverLocateEncrypted);
|
||||
EasyBind.includeWhen(decryptNameDropZone.getStyleClass(), ACTIVE_CLASS, draggingOverDecryptName);
|
||||
EasyBind.includeWhen(dropZone.getStyleClass(), ACTIVE_CLASS, draggingOver);
|
||||
}
|
||||
|
||||
private void handleDragOver(DragEvent event, BooleanProperty prop) {
|
||||
if (event.getGestureSource() == null && event.getDragboard().hasFiles()) {
|
||||
if (SystemUtils.IS_OS_WINDOWS || SystemUtils.IS_OS_MAC) {
|
||||
private void handleDragEvent(DragEvent event) {
|
||||
if (DragEvent.DRAG_OVER.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
|
||||
if(SystemUtils.IS_OS_WINDOWS || SystemUtils.IS_OS_MAC) {
|
||||
event.acceptTransferModes(TransferMode.LINK);
|
||||
} else {
|
||||
event.acceptTransferModes(TransferMode.ANY);
|
||||
}
|
||||
prop.set(true);
|
||||
draggingOver.set(true);
|
||||
} else if (DragEvent.DRAG_DROPPED.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
|
||||
List<Path> ciphertextPaths = event.getDragboard().getFiles().stream().map(File::toPath).map(this::getCiphertextPath).flatMap(Optional::stream).toList();
|
||||
if (ciphertextPaths.isEmpty()) {
|
||||
wrongFileAlert.build().showWrongFileAlertWindow();
|
||||
} else {
|
||||
revealOrCopyPaths(ciphertextPaths);
|
||||
}
|
||||
event.setDropCompleted(!ciphertextPaths.isEmpty());
|
||||
event.consume();
|
||||
} else if (DragEvent.DRAG_EXITED.equals(event.getEventType())) {
|
||||
draggingOver.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> void handleDragDropped(DragEvent event, Function<Path, T> computation, Consumer<List<T>> positiveAction) {
|
||||
if (event.getGestureSource() == null && event.getDragboard().hasFiles()) {
|
||||
List<T> objects = event.getDragboard().getFiles().stream().map(File::toPath).map(computation).filter(Objects::nonNull).toList();
|
||||
if (objects.isEmpty()) {
|
||||
wrongFileAlert.build().showWrongFileAlertWindow();
|
||||
} else {
|
||||
positiveAction.accept(objects);
|
||||
}
|
||||
event.setDropCompleted(!objects.isEmpty());
|
||||
event.consume();
|
||||
}
|
||||
private VaultStatisticsComponent buildVaultStats(Vault vault) {
|
||||
return vaultStatsBuilder.vault(vault).build();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void chooseDecryptedFileAndReveal() {
|
||||
public void revealAccessLocation() {
|
||||
vaultService.reveal(vault.get());
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void copyMountUri() {
|
||||
ClipboardContent clipboardContent = new ClipboardContent();
|
||||
clipboardContent.putString(mountPoint.getValue());
|
||||
Clipboard.getSystemClipboard().setContent(clipboardContent);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void lock() {
|
||||
appWindows.startLockWorkflow(vault.get(), mainWindow);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void showVaultStatistics() {
|
||||
vaultStats.getUnchecked(vault.get()).showVaultStatisticsWindow();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void chooseFileAndReveal() {
|
||||
Preconditions.checkState(accessibleViaPath.getValue());
|
||||
var fileChooser = new FileChooser();
|
||||
fileChooser.setTitle(resourceBundle.getString("main.vaultDetail.locateEncrypted.filePickerTitle"));
|
||||
fileChooser.setTitle(resourceBundle.getString("main.vaultDetail.filePickerTitle"));
|
||||
fileChooser.setInitialDirectory(Path.of(mountPoint.getValue()).toFile());
|
||||
var cleartextFile = fileChooser.showOpenDialog(mainWindow);
|
||||
if (cleartextFile != null) {
|
||||
var ciphertextPath = getCiphertextPath(cleartextFile.toPath());
|
||||
if (ciphertextPath != null) {
|
||||
revealOrCopyPaths(List.of(ciphertextPath));
|
||||
}
|
||||
var ciphertextPaths = getCiphertextPath(cleartextFile.toPath()).stream().toList();
|
||||
revealOrCopyPaths(ciphertextPaths);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void showDecryptNameWindow() {
|
||||
showDecryptNameWindow(List.of());
|
||||
}
|
||||
|
||||
private void showDecryptNameWindow(List<Path> pathsToDecrypt) {
|
||||
decryptNameWindowFactory.create(vault.get(), mainWindow, pathsToDecrypt).showDecryptFileNameWindow();
|
||||
}
|
||||
|
||||
private boolean startsWithVaultAccessPoint(Path path) {
|
||||
return path.startsWith(Path.of(mountPoint.getValue()));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Path getCiphertextPath(Path path) {
|
||||
private Optional<Path> getCiphertextPath(Path path) {
|
||||
if (!startsWithVaultAccessPoint(path)) {
|
||||
LOG.debug("Path does not start with mount point of selected vault: {}", path);
|
||||
return null;
|
||||
LOG.debug("Path does not start with access point of selected vault: {}", path);
|
||||
return Optional.empty();
|
||||
}
|
||||
try {
|
||||
return vault.get().getCiphertextPath(path);
|
||||
return Optional.of(vault.get().getCiphertextPath(path));
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Unable to get ciphertext path from path: {}", path, e);
|
||||
return null;
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,32 +206,6 @@ public class VaultDetailUnlockedController implements FxController {
|
||||
});
|
||||
}
|
||||
|
||||
private VaultStatisticsComponent buildVaultStats(Vault vault) {
|
||||
return vaultStatsBuilder.vault(vault).build();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void revealAccessLocation() {
|
||||
vaultService.reveal(vault.get());
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void copyMountUri() {
|
||||
ClipboardContent clipboardContent = new ClipboardContent();
|
||||
clipboardContent.putString(mountPoint.getValue());
|
||||
Clipboard.getSystemClipboard().setContent(clipboardContent);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void lock() {
|
||||
appWindows.startLockWorkflow(vault.get(), mainWindow);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void showVaultStatistics() {
|
||||
vaultStats.getUnchecked(vault.get()).showVaultStatisticsWindow();
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public ReadOnlyObjectProperty<Vault> vaultProperty() {
|
||||
@@ -283,6 +247,4 @@ public class VaultDetailUnlockedController implements FxController {
|
||||
public boolean isCiphertextPathsCopied() {
|
||||
return ciphertextPathsCopied.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.VaultService;
|
||||
import org.cryptomator.ui.dialogs.Dialogs;
|
||||
import org.cryptomator.ui.fxapp.FxFSEventList;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
|
||||
import org.slf4j.Logger;
|
||||
@@ -27,7 +26,6 @@ import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Side;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.input.ContextMenuEvent;
|
||||
@@ -36,6 +34,7 @@ import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.input.TransferMode;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.Stage;
|
||||
import java.io.File;
|
||||
@@ -67,7 +66,6 @@ public class VaultListController implements FxController {
|
||||
private final VaultListCellFactory cellFactory;
|
||||
private final AddVaultWizardComponent.Builder addVaultWizard;
|
||||
private final BooleanBinding emptyVaultList;
|
||||
private final BooleanProperty unreadEvents;
|
||||
private final VaultListManager vaultListManager;
|
||||
private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty();
|
||||
private final ResourceBundle resourceBundle;
|
||||
@@ -78,7 +76,7 @@ public class VaultListController implements FxController {
|
||||
public ListView<Vault> vaultList;
|
||||
public StackPane root;
|
||||
@FXML
|
||||
private Button addVaultButton;
|
||||
private HBox addVaultButton;
|
||||
@FXML
|
||||
private ContextMenu addVaultContextMenu;
|
||||
|
||||
@@ -93,8 +91,7 @@ public class VaultListController implements FxController {
|
||||
ResourceBundle resourceBundle, //
|
||||
FxApplicationWindows appWindows, //
|
||||
Settings settings, //
|
||||
Dialogs dialogs, //
|
||||
FxFSEventList fxFSEventList) {
|
||||
Dialogs dialogs) {
|
||||
this.mainWindow = mainWindow;
|
||||
this.vaults = vaults;
|
||||
this.selectedVault = selectedVault;
|
||||
@@ -107,7 +104,6 @@ public class VaultListController implements FxController {
|
||||
this.dialogs = dialogs;
|
||||
|
||||
this.emptyVaultList = Bindings.isEmpty(vaults);
|
||||
this.unreadEvents = fxFSEventList.unreadEventsProperty();
|
||||
|
||||
selectedVault.addListener(this::selectedVaultDidChange);
|
||||
cellSize = settings.compactMode.map(compact -> compact ? 30.0 : 60.0);
|
||||
@@ -267,10 +263,6 @@ public class VaultListController implements FxController {
|
||||
appWindows.showPreferencesWindow(SelectedPreferencesTab.ANY);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void showEventViewer() {
|
||||
appWindows.showEventViewer();
|
||||
}
|
||||
// Getter and Setter
|
||||
|
||||
public BooleanBinding emptyVaultListProperty() {
|
||||
@@ -297,11 +289,4 @@ public class VaultListController implements FxController {
|
||||
return cellSize.getValue();
|
||||
}
|
||||
|
||||
public ObservableValue<Boolean> unreadEventsPresentProperty() {
|
||||
return unreadEvents;
|
||||
}
|
||||
|
||||
public boolean getUnreadEventsPresent() {
|
||||
return unreadEvents.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import javafx.fxml.FXML;
|
||||
public class WelcomeController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WelcomeController.class);
|
||||
private static final String GETTING_STARTED_URI = "https://docs.cryptomator.org/desktop/getting-started/";
|
||||
private static final String GETTING_STARTED_URI = "https://docs.cryptomator.org/en/1.7/desktop/getting-started/";
|
||||
|
||||
private final Application application;
|
||||
private final BooleanBinding noVaultPresent;
|
||||
|
||||
@@ -10,7 +10,7 @@ import javafx.stage.Stage;
|
||||
|
||||
public class MigrationImpossibleController implements FxController {
|
||||
|
||||
private static final String HELP_URI = "https://docs.cryptomator.org/help/manual-migration/";
|
||||
private static final String HELP_URI = "https://docs.cryptomator.org/en/1.7/help/manual-migration/";
|
||||
|
||||
private final Application application;
|
||||
private final Stage window;
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
package org.cryptomator.ui.preferences;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.cryptomator.common.keychain.KeychainManager;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.integrations.autostart.AutoStartProvider;
|
||||
import org.cryptomator.integrations.autostart.ToggleAutoStartFailedException;
|
||||
import org.cryptomator.integrations.common.NamedServiceProvider;
|
||||
import org.cryptomator.integrations.keychain.KeychainAccessException;
|
||||
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
|
||||
import org.cryptomator.integrations.quickaccess.QuickAccessService;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
@@ -17,7 +14,6 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.application.Application;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.CheckBox;
|
||||
@@ -27,10 +23,6 @@ import javafx.stage.Stage;
|
||||
import javafx.util.StringConverter;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@PreferencesScoped
|
||||
public class GeneralPreferencesController implements FxController {
|
||||
@@ -44,8 +36,6 @@ public class GeneralPreferencesController implements FxController {
|
||||
private final Application application;
|
||||
private final Environment environment;
|
||||
private final List<KeychainAccessProvider> keychainAccessProviders;
|
||||
private final KeychainManager keychain;
|
||||
private final ExecutorService backgroundExecutor;
|
||||
private final FxApplicationWindows appWindows;
|
||||
public CheckBox useKeychainCheckbox;
|
||||
public ChoiceBox<KeychainAccessProvider> keychainBackendChoiceBox;
|
||||
@@ -57,18 +47,12 @@ public class GeneralPreferencesController implements FxController {
|
||||
public CheckBox autoStartCheckbox;
|
||||
public ToggleGroup nodeOrientation;
|
||||
|
||||
private CompletionStage<Void> keychainMigrations = CompletableFuture.completedFuture(null);
|
||||
|
||||
@Inject
|
||||
GeneralPreferencesController(@PreferencesWindow Stage window, Settings settings, Optional<AutoStartProvider> autoStartProvider, //
|
||||
List<KeychainAccessProvider> keychainAccessProviders, KeychainManager keychain, Application application, //
|
||||
Environment environment, FxApplicationWindows appWindows, ExecutorService backgroundExecutor) {
|
||||
GeneralPreferencesController(@PreferencesWindow Stage window, Settings settings, Optional<AutoStartProvider> autoStartProvider, List<KeychainAccessProvider> keychainAccessProviders, Application application, Environment environment, FxApplicationWindows appWindows) {
|
||||
this.window = window;
|
||||
this.settings = settings;
|
||||
this.autoStartProvider = autoStartProvider;
|
||||
this.keychainAccessProviders = keychainAccessProviders;
|
||||
this.keychain = keychain;
|
||||
this.backgroundExecutor = backgroundExecutor;
|
||||
this.quickAccessServices = QuickAccessService.get().toList();
|
||||
this.application = application;
|
||||
this.environment = environment;
|
||||
@@ -89,7 +73,6 @@ public class GeneralPreferencesController implements FxController {
|
||||
Bindings.bindBidirectional(settings.keychainProvider, keychainBackendChoiceBox.valueProperty(), keychainSettingsConverter);
|
||||
useKeychainCheckbox.selectedProperty().bindBidirectional(settings.useKeychain);
|
||||
keychainBackendChoiceBox.disableProperty().bind(useKeychainCheckbox.selectedProperty().not());
|
||||
keychainBackendChoiceBox.valueProperty().addListener(this::migrateKeychainEntries);
|
||||
|
||||
useQuickAccessCheckbox.selectedProperty().bindBidirectional(settings.useQuickAccess);
|
||||
var quickAccessSettingsConverter = new ServiceToSettingsConverter<>(quickAccessServices);
|
||||
@@ -100,25 +83,6 @@ public class GeneralPreferencesController implements FxController {
|
||||
quickAccessServiceChoiceBox.disableProperty().bind(useQuickAccessCheckbox.selectedProperty().not());
|
||||
}
|
||||
|
||||
private void migrateKeychainEntries(Observable observable, KeychainAccessProvider oldProvider, KeychainAccessProvider newProvider) {
|
||||
//currently, we only migrate on macOS (touchID vs regular keychain)
|
||||
if (SystemUtils.IS_OS_MAC) {
|
||||
var idsAndNames = settings.directories.stream().collect(Collectors.toMap(vs -> vs.id, vs -> vs.displayName.getValue()));
|
||||
if (!idsAndNames.isEmpty()) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Migrating keychain entries {} from {} to {}", idsAndNames.keySet(), oldProvider.displayName(), newProvider.displayName());
|
||||
}
|
||||
keychainMigrations = keychainMigrations.thenRunAsync(() -> {
|
||||
try {
|
||||
KeychainManager.migrate(oldProvider, newProvider, idsAndNames);
|
||||
} catch (KeychainAccessException e) {
|
||||
LOG.warn("Failed to migrate all entries from {} to {}", oldProvider.displayName(), newProvider.displayName(), e);
|
||||
}
|
||||
}, backgroundExecutor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAutoStartSupported() {
|
||||
return autoStartProvider.isPresent();
|
||||
}
|
||||
|
||||
@@ -19,8 +19,6 @@ import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -34,10 +32,7 @@ import java.util.ResourceBundle;
|
||||
@PreferencesScoped
|
||||
public class UpdatesPreferencesController implements FxController {
|
||||
|
||||
private static final String DOWNLOADS_URI_TEMPLATE = "https://cryptomator.org/downloads/" //
|
||||
+ "?utm_source=cryptomator-desktop" //
|
||||
+ "&utm_medium=update-notification&" //
|
||||
+ "utm_campaign=app-update-%s";
|
||||
private static final String DOWNLOADS_URI = "https://cryptomator.org/downloads";
|
||||
|
||||
private final Application application;
|
||||
private final Environment environment;
|
||||
@@ -55,7 +50,6 @@ public class UpdatesPreferencesController implements FxController {
|
||||
private final BooleanProperty upToDateLabelVisible = new SimpleBooleanProperty(false);
|
||||
private final DateTimeFormatter formatter;
|
||||
private final BooleanBinding upToDate;
|
||||
private final String downloadsUri;
|
||||
|
||||
/* FXML */
|
||||
public CheckBox checkForUpdatesCheckbox;
|
||||
@@ -71,13 +65,12 @@ public class UpdatesPreferencesController implements FxController {
|
||||
this.latestVersion = updateChecker.latestVersionProperty();
|
||||
this.lastSuccessfulUpdateCheck = updateChecker.lastSuccessfulUpdateCheckProperty();
|
||||
this.timeDifferenceMessage = Bindings.createStringBinding(this::getTimeDifferenceMessage, lastSuccessfulUpdateCheck);
|
||||
this.currentVersion = environment.getAppVersion();
|
||||
this.currentVersion = updateChecker.getCurrentVersion();
|
||||
this.updateAvailable = updateChecker.updateAvailableProperty();
|
||||
this.formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(Locale.getDefault());
|
||||
this.upToDate = updateChecker.updateCheckStateProperty().isEqualTo(UpdateChecker.UpdateCheckState.CHECK_SUCCESSFUL).and(latestVersion.isEqualTo(currentVersion));
|
||||
this.checkFailed = updateChecker.checkFailedProperty();
|
||||
this.lastUpdateCheckMessage = Bindings.createStringBinding(this::getLastUpdateCheckMessage, lastSuccessfulUpdateCheck);
|
||||
this.downloadsUri = DOWNLOADS_URI_TEMPLATE.formatted(URLEncoder.encode(currentVersion, StandardCharsets.US_ASCII));
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
@@ -100,7 +93,7 @@ public class UpdatesPreferencesController implements FxController {
|
||||
|
||||
@FXML
|
||||
public void visitDownloadsPage() {
|
||||
application.getHostServices().showDocument(downloadsUri);
|
||||
application.getHostServices().showDocument(DOWNLOADS_URI);
|
||||
}
|
||||
|
||||
@FXML
|
||||
|
||||
@@ -22,7 +22,7 @@ import java.util.ResourceBundle;
|
||||
@PreferencesScoped
|
||||
public class VolumePreferencesController implements FxController {
|
||||
|
||||
public static final String DOCS_MOUNTING_URL = "https://docs.cryptomator.org/desktop/volume-type/";
|
||||
public static final String DOCS_MOUNTING_URL = "https://docs.cryptomator.org/en/1.7/desktop/volume-type/";
|
||||
public static final int MIN_PORT = 1024;
|
||||
public static final int MAX_PORT = 65535;
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package org.cryptomator.ui.sharevault;
|
||||
|
||||
import dagger.Lazy;
|
||||
import org.cryptomator.common.Hyperlinks;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
|
||||
import org.cryptomator.ui.keyloading.hub.HubKeyLoadingStrategy;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.application.Application;
|
||||
@@ -18,9 +19,9 @@ import java.net.URISyntaxException;
|
||||
public class ShareVaultController implements FxController {
|
||||
|
||||
private static final String SCHEME_PREFIX = "hub+";
|
||||
private static final String VISIT_HUB_URL = "https://cryptomator.org/hub/";
|
||||
private static final String BEST_PRACTICES_URL = "https://docs.cryptomator.org/en/latest/security/best-practices/#sharing-of-vaults";
|
||||
|
||||
private final String VISIT_HUB_URL;
|
||||
private final Stage window;
|
||||
private final Lazy<Application> application;
|
||||
private final Vault vault;
|
||||
@@ -29,11 +30,14 @@ public class ShareVaultController implements FxController {
|
||||
@Inject
|
||||
ShareVaultController(@ShareVaultWindow Stage window, //
|
||||
Lazy<Application> application, //
|
||||
Hyperlinks links, //
|
||||
@ShareVaultWindow Vault vault) {
|
||||
this.window = window;
|
||||
this.application = application;
|
||||
this.vault = vault;
|
||||
this.hubVault = KeyLoadingStrategy.isHubVault(vault.getVaultSettings().lastKnownKeyLoader.get());
|
||||
this.VISIT_HUB_URL =links.homepageHub();
|
||||
var vaultScheme = vault.getVaultConfigCache().getUnchecked().getKeyId().getScheme();
|
||||
this.hubVault = (vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTP) || vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTPS));
|
||||
}
|
||||
|
||||
@FXML
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.cryptomator.ui.vaultoptions;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultState;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
|
||||
import org.cryptomator.ui.keyloading.hub.HubKeyLoadingStrategy;
|
||||
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy;
|
||||
import org.slf4j.Logger;
|
||||
@@ -43,11 +42,11 @@ public class VaultOptionsController implements FxController {
|
||||
window.setOnShowing(this::windowWillAppear);
|
||||
selectedTabProperty.addListener(observable -> this.selectChosenTab());
|
||||
tabPane.getSelectionModel().selectedItemProperty().addListener(observable -> this.selectedTabChanged());
|
||||
var vaultKeyLoader = vault.getVaultSettings().lastKnownKeyLoader.get();
|
||||
if(!KeyLoadingStrategy.isMasterkeyFileVault(vaultKeyLoader)){
|
||||
var vaultScheme = vault.getVaultConfigCache().getUnchecked().getKeyId().getScheme();
|
||||
if(!vaultScheme.equals(MasterkeyFileLoadingStrategy.SCHEME)){
|
||||
tabPane.getTabs().remove(keyTab);
|
||||
}
|
||||
if(!KeyLoadingStrategy.isHubVault(vaultKeyLoader)){
|
||||
if(!(vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTP) || vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTPS))){
|
||||
tabPane.getTabs().remove(hubTab);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import java.io.UncheckedIOException;
|
||||
@WrongFileAlertScoped
|
||||
public class WrongFileAlertController implements FxController {
|
||||
|
||||
private static final String DOCUMENTATION_URI = "https://docs.cryptomator.org/desktop/accessing-vaults/";
|
||||
private static final String DOCUMENTATION_URI = "https://docs.cryptomator.org/en/1.7/desktop/accessing-vaults/";
|
||||
|
||||
private final Application app;
|
||||
private final Stage window;
|
||||
|
||||
@@ -16,10 +16,6 @@
|
||||
src: url('opensans_bold.ttf');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
src: url('firacode_regular.ttf');
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Root Styling & Colors *
|
||||
@@ -129,12 +125,6 @@
|
||||
-fx-fill: TEXT_FILL;
|
||||
}
|
||||
|
||||
.cryptic-text {
|
||||
-fx-fill: TEXT_FILL;
|
||||
-fx-font-family: 'Fira Code';
|
||||
-fx-font-size: 1.1em;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Glyph Icons *
|
||||
@@ -193,37 +183,19 @@
|
||||
}
|
||||
|
||||
.main-window .button-bar {
|
||||
-fx-min-height:42px;
|
||||
-fx-max-height:42px;
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-border-color: CONTROL_BORDER_NORMAL transparent transparent transparent;
|
||||
-fx-border-width: 1px 0 0 0;
|
||||
}
|
||||
|
||||
.main-window .button-bar .button-left {
|
||||
.main-window .button-left {
|
||||
-fx-border-color: CONTROL_BORDER_NORMAL;
|
||||
-fx-border-width: 0 1px 0 0;
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-background-radius: 0px;
|
||||
-fx-min-height: 42px;
|
||||
-fx-max-height: 42px;
|
||||
}
|
||||
|
||||
.main-window .button-bar .button-right {
|
||||
.main-window .button-right {
|
||||
-fx-border-color: CONTROL_BORDER_NORMAL;
|
||||
-fx-border-width: 0 0 0 1px;
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-background-radius: 0px;
|
||||
-fx-min-height: 42px;
|
||||
-fx-max-height: 42px;
|
||||
}
|
||||
|
||||
.main-window .button-bar .button-left:armed {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED;
|
||||
}
|
||||
|
||||
.main-window .button-bar .button-right:armed {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -312,10 +284,6 @@
|
||||
-fx-font-size: 1.0em;
|
||||
}
|
||||
|
||||
.list-cell .header-misc {
|
||||
-fx-font-size: 1.0em;
|
||||
}
|
||||
|
||||
.list-cell .detail-label {
|
||||
-fx-text-fill: TEXT_FILL_MUTED;
|
||||
-fx-font-size: 0.8em;
|
||||
@@ -344,42 +312,6 @@
|
||||
-fx-fill: transparent;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Event List *
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
.event-window .button-bar {
|
||||
-fx-min-height:42px;
|
||||
-fx-max-height:42px;
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-border-color: transparent transparent CONTROL_BORDER_NORMAL transparent;
|
||||
-fx-border-width: 0 0 1px 0;
|
||||
}
|
||||
|
||||
.event-window .button-bar .button-right {
|
||||
-fx-border-color: transparent transparent transparent CONTROL_BORDER_NORMAL;
|
||||
-fx-border-width: 0 0 0 1px;
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-background-radius: 0px;
|
||||
-fx-min-height: 42px;
|
||||
-fx-max-height: 42px;
|
||||
}
|
||||
|
||||
.event-window .button-bar .button-right:armed {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED;
|
||||
}
|
||||
|
||||
.event-window .list-view .list-cell:hover {
|
||||
-fx-background-color: CONTROL_BG_SELECTED;
|
||||
}
|
||||
|
||||
.event-window .list-view .list-cell:selected {
|
||||
-fx-background-color: PRIMARY, CONTROL_BG_SELECTED;
|
||||
-fx-background-insets: 0, 0 0 0 3px;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* NotificationBar *
|
||||
@@ -409,12 +341,6 @@
|
||||
-fx-background-color: PRIMARY;
|
||||
}
|
||||
|
||||
.notification-debug:hover .notification-label,
|
||||
.notification-update:hover .notification-label,
|
||||
.notification-support:hover .notification-label {
|
||||
-fx-underline:true;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* ScrollBar *
|
||||
@@ -649,16 +575,6 @@
|
||||
-fx-graphic-text-gap: 9px;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Update indicator
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
.icon-update-indicator {
|
||||
-fx-fill: RED_5;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Hyperlinks *
|
||||
@@ -1022,167 +938,4 @@
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0, 1px;
|
||||
-fx-background-radius: 4px;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Decrypt Name Window
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
.decrypt-name-window .button-bar {
|
||||
-fx-min-height:42px;
|
||||
-fx-max-height:42px;
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-border-color: transparent transparent CONTROL_BORDER_NORMAL transparent;
|
||||
-fx-border-width: 0 0 1px 0;
|
||||
}
|
||||
|
||||
.decrypt-name-window .button-bar .button-right {
|
||||
-fx-border-color: transparent transparent transparent CONTROL_BORDER_NORMAL;
|
||||
-fx-border-width: 0 0 0 1px;
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-background-radius: 0px;
|
||||
-fx-min-height: 42px;
|
||||
-fx-max-height: 42px;
|
||||
}
|
||||
|
||||
.decrypt-name-window .button-bar .button-right:armed {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED;
|
||||
}
|
||||
|
||||
.decrypt-name-window .table-view {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0,1;
|
||||
/* There is some oddness if padding is in em values rather than pixels,
|
||||
in particular, the left border of the control doesn't show. */
|
||||
-fx-padding: 1; /* 0.083333em; */
|
||||
}
|
||||
|
||||
.table-view > .placeholder {
|
||||
-fx-background-color: transparent;
|
||||
-fx-background-radius: 0px;
|
||||
}
|
||||
|
||||
.table-view > .placeholder > .button {
|
||||
-fx-border-width: 0;
|
||||
-fx-border-color: transparent;
|
||||
-fx-background-radius: 0px;
|
||||
}
|
||||
|
||||
.table-view:focused {
|
||||
-fx-background-color: CONTROL_BORDER_FOCUSED, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0, 1;
|
||||
-fx-background-radius: 0, 0;
|
||||
/* There is some oddness if padding is in em values rather than pixels,
|
||||
in particular, the left border of the control doesn't show. */
|
||||
-fx-padding: 1; /* 0.083333em; */
|
||||
}
|
||||
|
||||
.table-view > .virtual-flow > .scroll-bar:vertical {
|
||||
-fx-background-insets: 0, 0 0 0 1;
|
||||
-fx-padding: -1 -1 -1 0;
|
||||
}
|
||||
|
||||
.table-view > .virtual-flow > .corner {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL ;
|
||||
-fx-background-insets: 0, 1 0 0 1;
|
||||
}
|
||||
|
||||
/* Each row in the table is a table-row-cell. Inside a table-row-cell is any
|
||||
number of table-cell. */
|
||||
.table-row-cell {
|
||||
-fx-background-color: GRAY_3, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0, 0 0 1 0;
|
||||
-fx-padding: 0.0em; /* 0 */
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
}
|
||||
|
||||
.table-row-cell:odd {
|
||||
-fx-background-color: GRAY_3, GRAY_1;
|
||||
-fx-background-insets: 0, 0 0 1 0;
|
||||
}
|
||||
|
||||
.table-cell {
|
||||
-fx-padding: 3px 6px 3px 6px;
|
||||
-fx-background-color: transparent;
|
||||
-fx-border-color: transparent CONTROL_BORDER_NORMAL transparent transparent;
|
||||
-fx-border-width: 1px;
|
||||
-fx-cell-size: 30px;
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
-fx-text-overrun: center-ellipsis;
|
||||
}
|
||||
|
||||
.table-view:focused > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled:selected > .table-cell {
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
}
|
||||
|
||||
/* selected, hover - not specified */
|
||||
|
||||
/* selected, focused, hover */
|
||||
/* selected, focused */
|
||||
/* selected */
|
||||
.table-view:focused:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:selected,
|
||||
.table-view:focused:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:focused:selected,
|
||||
.table-view:focused:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:focused:selected:hover {
|
||||
-fx-background-color: CONTROL_PRIMARY_BG_NORMAL, PRIMARY_D1;
|
||||
-fx-background-insets: 0 0 0 0, 1 1 1 3;
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
}
|
||||
/* focused */
|
||||
.table-view:focused:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:focused {
|
||||
-fx-background-color: CONTROL_PRIMARY_BORDER_FOCUSED, CONTROL_PRIMARY_BG_NORMAL , CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0 1 0 0, 1 2 1 1, 2 3 2 2;
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
}
|
||||
/* focused, hover */
|
||||
.table-view:focused:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:focused:hover {
|
||||
-fx-background-color: CONTROL_PRIMARY_BORDER_FOCUSED, CONTROL_PRIMARY_BG_NORMAL , PRIMARY_D2;
|
||||
-fx-background-insets: 0 1 0 0, 1 2 1 1, 2 3 2 2;
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
}
|
||||
/* hover */
|
||||
.table-view:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:hover {
|
||||
-fx-background-color: PRIMARY_D2;
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
-fx-background-insets: 0 0 1 0;
|
||||
}
|
||||
|
||||
/* The column-resize-line is shown when the user is attempting to resize a column. */
|
||||
.table-view .column-resize-line {
|
||||
-fx-background-color: CONTROL_BG_ARMED;
|
||||
-fx-padding: 0.0em 0.0416667em 0.0em 0.0416667em; /* 0 0.571429 0 0.571429 */
|
||||
}
|
||||
|
||||
/* This is the area behind the column headers. An ideal place to specify background
|
||||
and border colors for the whole area (not individual column-header's). */
|
||||
.table-view .column-header-background {
|
||||
-fx-background-color: GRAY_2;
|
||||
-fx-padding: 0;
|
||||
}
|
||||
|
||||
/* The column header row is made up of a number of column-header, one for each
|
||||
TableColumn, and a 'filler' area that extends from the right-most column
|
||||
to the edge of the tableview, or up to the 'column control' button. */
|
||||
.table-view .column-header {
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
-fx-font-size: 1.083333em; /* 13pt ; 1 more than the default font */
|
||||
-fx-size: 24;
|
||||
-fx-border-style: solid;
|
||||
-fx-border-color:
|
||||
transparent
|
||||
GRAY_3
|
||||
GRAY_3
|
||||
transparent;
|
||||
-fx-border-insets: 0 0 0 0;
|
||||
-fx-border-width: 0.083333em;
|
||||
}
|
||||
|
||||
.table-view .column-header .label {
|
||||
-fx-alignment: center;
|
||||
}
|
||||
|
||||
.table-view .empty-table {
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-font-size: 1.166667em; /* 14pt - 2 more than the default font */
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -16,10 +16,6 @@
|
||||
src: url('opensans_bold.ttf');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
src: url('firacode_regular.ttf');
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Root Styling & Colors *
|
||||
@@ -83,7 +79,6 @@
|
||||
PROGRESS_INDICATOR_END: GRAY_4;
|
||||
PROGRESS_BAR_BG: GRAY_8;
|
||||
|
||||
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
-fx-font-family: 'Open Sans';
|
||||
@@ -129,12 +124,6 @@
|
||||
-fx-fill: TEXT_FILL;
|
||||
}
|
||||
|
||||
.cryptic-text {
|
||||
-fx-fill: TEXT_FILL;
|
||||
-fx-font-family: 'Fira Code';
|
||||
-fx-font-size: 1.1em;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Glyph Icons *
|
||||
@@ -193,8 +182,6 @@
|
||||
}
|
||||
|
||||
.main-window .button-bar {
|
||||
-fx-min-height:42px;
|
||||
-fx-max-height:42px;
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-border-color: CONTROL_BORDER_NORMAL transparent transparent transparent;
|
||||
-fx-border-width: 1px 0 0 0;
|
||||
@@ -203,27 +190,11 @@
|
||||
.main-window .button-bar .button-left {
|
||||
-fx-border-color: CONTROL_BORDER_NORMAL;
|
||||
-fx-border-width: 0 1px 0 0;
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-background-radius: 0px;
|
||||
-fx-min-height: 42px;
|
||||
-fx-max-height: 42px;
|
||||
}
|
||||
|
||||
.main-window .button-bar .button-right {
|
||||
-fx-border-color: CONTROL_BORDER_NORMAL;
|
||||
-fx-border-width: 0 0 0 1px;
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-background-radius: 0px;
|
||||
-fx-min-height: 42px;
|
||||
-fx-max-height: 42px;
|
||||
}
|
||||
|
||||
.main-window .button-bar .button-left:armed {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED;
|
||||
}
|
||||
|
||||
.main-window .button-bar .button-right:armed {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -312,10 +283,6 @@
|
||||
-fx-font-size: 1.0em;
|
||||
}
|
||||
|
||||
.list-cell .header-misc {
|
||||
-fx-font-size: 1.0em;
|
||||
}
|
||||
|
||||
.list-cell .detail-label {
|
||||
-fx-text-fill: TEXT_FILL_MUTED;
|
||||
-fx-font-size: 0.8em;
|
||||
@@ -344,42 +311,6 @@
|
||||
-fx-fill: transparent;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Event List *
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
.event-window .button-bar {
|
||||
-fx-min-height:42px;
|
||||
-fx-max-height:42px;
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-border-color: transparent transparent CONTROL_BORDER_NORMAL transparent;
|
||||
-fx-border-width: 0 0 1px 0;
|
||||
}
|
||||
|
||||
.event-window .button-bar .button-right {
|
||||
-fx-border-color: transparent transparent transparent CONTROL_BORDER_NORMAL;
|
||||
-fx-border-width: 0 0 0 1px;
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-background-radius: 0px;
|
||||
-fx-min-height: 42px;
|
||||
-fx-max-height: 42px;
|
||||
}
|
||||
|
||||
.event-window .button-bar .button-right:armed {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED;
|
||||
}
|
||||
|
||||
.event-window .list-view .list-cell:hover {
|
||||
-fx-background-color: CONTROL_BG_SELECTED;
|
||||
}
|
||||
|
||||
.event-window .list-view .list-cell:selected {
|
||||
-fx-background-color: PRIMARY, CONTROL_BG_SELECTED;
|
||||
-fx-background-insets: 0, 0 0 0 3px;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* NotificationBar *
|
||||
@@ -409,12 +340,6 @@
|
||||
-fx-background-color: PRIMARY;
|
||||
}
|
||||
|
||||
.notification-debug:hover .notification-label,
|
||||
.notification-update:hover .notification-label,
|
||||
.notification-support:hover .notification-label {
|
||||
-fx-underline:true;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* ScrollBar *
|
||||
@@ -649,16 +574,6 @@
|
||||
-fx-graphic-text-gap: 9px;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Update indicator
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
.icon-update-indicator {
|
||||
-fx-fill: RED_5;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Hyperlinks *
|
||||
@@ -871,11 +786,11 @@
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Dropdown button context menu
|
||||
* Add Vault - MenuItem *
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
.dropdown-button-context-menu-item {
|
||||
.add-vault-menu-item {
|
||||
-fx-padding: 4px 8px;
|
||||
}
|
||||
|
||||
@@ -1022,167 +937,4 @@
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0, 1px;
|
||||
-fx-background-radius: 4px;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Decrypt Name Window
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
.decrypt-name-window .button-bar {
|
||||
-fx-min-height:42px;
|
||||
-fx-max-height:42px;
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-border-color: transparent transparent CONTROL_BORDER_NORMAL transparent;
|
||||
-fx-border-width: 0 0 1px 0;
|
||||
}
|
||||
|
||||
.decrypt-name-window .button-bar .button-right {
|
||||
-fx-border-color: transparent transparent transparent CONTROL_BORDER_NORMAL;
|
||||
-fx-border-width: 0 0 0 1px;
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-background-radius: 0px;
|
||||
-fx-min-height: 42px;
|
||||
-fx-max-height: 42px;
|
||||
}
|
||||
|
||||
.decrypt-name-window .button-bar .button-right:armed {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED;
|
||||
}
|
||||
|
||||
.decrypt-name-window .table-view {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0,1;
|
||||
/* There is some oddness if padding is in em values rather than pixels,
|
||||
in particular, the left border of the control doesn't show. */
|
||||
-fx-padding: 1; /* 0.083333em; */
|
||||
}
|
||||
|
||||
.table-view > .placeholder {
|
||||
-fx-background-color: transparent;
|
||||
-fx-background-radius: 0px;
|
||||
}
|
||||
|
||||
.table-view > .placeholder > .button {
|
||||
-fx-border-width: 0;
|
||||
-fx-border-color: transparent;
|
||||
-fx-background-radius: 0px;
|
||||
}
|
||||
|
||||
.table-view:focused {
|
||||
-fx-background-color: CONTROL_BORDER_FOCUSED, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0, 1;
|
||||
-fx-background-radius: 0, 0;
|
||||
/* There is some oddness if padding is in em values rather than pixels,
|
||||
in particular, the left border of the control doesn't show. */
|
||||
-fx-padding: 1; /* 0.083333em; */
|
||||
}
|
||||
|
||||
.table-view > .virtual-flow > .scroll-bar:vertical {
|
||||
-fx-background-insets: 0, 0 0 0 1;
|
||||
-fx-padding: -1 -1 -1 0;
|
||||
}
|
||||
|
||||
.table-view > .virtual-flow > .corner {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL ;
|
||||
-fx-background-insets: 0, 1 0 0 1;
|
||||
}
|
||||
|
||||
/* Each row in the table is a table-row-cell. Inside a table-row-cell is any
|
||||
number of table-cell. */
|
||||
.table-row-cell {
|
||||
-fx-background-color: GRAY_6, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0, 0 0 1 0;
|
||||
-fx-padding: 0.0em; /* 0 */
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
}
|
||||
|
||||
.table-row-cell:odd {
|
||||
-fx-background-color: GRAY_6, GRAY_9;
|
||||
-fx-background-insets: 0, 0 0 1 0;
|
||||
}
|
||||
|
||||
.table-cell {
|
||||
-fx-padding: 3px 6px 3px 6px;
|
||||
-fx-background-color: transparent;
|
||||
-fx-border-color: transparent CONTROL_BORDER_NORMAL transparent transparent;
|
||||
-fx-border-width: 1px;
|
||||
-fx-cell-size: 30px;
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
-fx-text-overrun: center-ellipsis;
|
||||
}
|
||||
|
||||
.table-view:focused > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled:selected > .table-cell {
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
}
|
||||
|
||||
/* selected, hover - not specified */
|
||||
|
||||
/* selected, focused, hover */
|
||||
/* selected, focused */
|
||||
/* selected */
|
||||
.table-view:focused:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:selected,
|
||||
.table-view:focused:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:focused:selected,
|
||||
.table-view:focused:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:focused:selected:hover {
|
||||
-fx-background-color: CONTROL_PRIMARY_BG_NORMAL, CONTROL_BG_SELECTED;
|
||||
-fx-background-insets: 0 0 0 0, 1 1 1 3;
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
}
|
||||
/* focused */
|
||||
.table-view:focused:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:focused {
|
||||
-fx-background-color: CONTROL_PRIMARY_BORDER_FOCUSED, CONTROL_PRIMARY_BG_NORMAL , CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0 1 0 0, 1 2 1 1, 2 3 2 2;
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
}
|
||||
/* focused, hover */
|
||||
.table-view:focused:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:focused:hover {
|
||||
-fx-background-color: CONTROL_PRIMARY_BORDER_FOCUSED, CONTROL_PRIMARY_BG_NORMAL , PRIMARY_L2;
|
||||
-fx-background-insets: 0 1 0 0, 1 2 1 1, 2 3 2 2;
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
}
|
||||
/* hover */
|
||||
.table-view:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:hover {
|
||||
-fx-background-color: PRIMARY_L2;
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
-fx-background-insets: 0 0 1 0;
|
||||
}
|
||||
|
||||
/* The column-resize-line is shown when the user is attempting to resize a column. */
|
||||
.table-view .column-resize-line {
|
||||
-fx-background-color: CONTROL_BG_ARMED;
|
||||
-fx-padding: 0.0em 0.0416667em 0.0em 0.0416667em; /* 0 0.571429 0 0.571429 */
|
||||
}
|
||||
|
||||
/* This is the area behind the column headers. An ideal place to specify background
|
||||
and border colors for the whole area (not individual column-header's). */
|
||||
.table-view .column-header-background {
|
||||
-fx-background-color: GRAY_7;
|
||||
-fx-padding: 0;
|
||||
}
|
||||
|
||||
/* The column header row is made up of a number of column-header, one for each
|
||||
TableColumn, and a 'filler' area that extends from the right-most column
|
||||
to the edge of the tableview, or up to the 'column control' button. */
|
||||
.table-view .column-header {
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
-fx-font-size: 1.083333em; /* 13pt ; 1 more than the default font */
|
||||
-fx-size: 24;
|
||||
-fx-border-style: solid;
|
||||
-fx-border-color:
|
||||
CONTROL_BORDER_NORMAL
|
||||
GRAY_5
|
||||
GRAY_5
|
||||
transparent;
|
||||
-fx-border-insets: 0 0 0 0;
|
||||
-fx-border-width: 0.083333em;
|
||||
}
|
||||
|
||||
.table-view .column-header .label {
|
||||
-fx-alignment: center;
|
||||
}
|
||||
|
||||
.table-view .empty-table {
|
||||
-fx-background-color: MAIN_BG;
|
||||
-fx-font-size: 1.166667em; /* 14pt - 2 more than the default font */
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import org.cryptomator.ui.controls.FormattedLabel?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.TableColumn?>
|
||||
<?import javafx.scene.control.TableView?>
|
||||
<?import javafx.scene.control.Tooltip?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="org.cryptomator.ui.decryptname.DecryptFileNamesViewController"
|
||||
styleClass="decrypt-name-window"
|
||||
minWidth="400"
|
||||
maxWidth="400"
|
||||
minHeight="145">
|
||||
<HBox styleClass="button-bar" alignment="CENTER">
|
||||
<padding>
|
||||
<Insets left="6"/>
|
||||
</padding>
|
||||
<Region HBox.hgrow="ALWAYS"/>
|
||||
<Button styleClass="button-right" contentDisplay="GRAPHIC_ONLY" onAction="#copyTableToClipboard">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="CLIPBOARD" glyphSize="16"/>
|
||||
</graphic>
|
||||
<tooltip>
|
||||
<Tooltip text="%decryptNames.copyTable.tooltip"/>
|
||||
</tooltip>
|
||||
</Button>
|
||||
<Button styleClass="button-right" contentDisplay="GRAPHIC_ONLY" onAction="#clearTable">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="TRASH" glyphSize="16"/>
|
||||
</graphic>
|
||||
<tooltip>
|
||||
<Tooltip text="%decryptNames.clearTable.tooltip"/>
|
||||
</tooltip>
|
||||
</Button>
|
||||
</HBox>
|
||||
<TableView fx:id="cipherToCleartextTable" VBox.vgrow="ALWAYS">
|
||||
<placeholder>
|
||||
<Button alignment="CENTER" onAction="#selectFiles" text="${controller.dropZoneText}" contentDisplay="TOP" maxWidth="Infinity" maxHeight="Infinity">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="${controller.dropZoneIcon}" glyphSize="16"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
</placeholder>
|
||||
<columns>
|
||||
<TableColumn fx:id="ciphertextColumn" prefWidth="${cipherToCleartextTable.width * 0.5}">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="LOCK"/>
|
||||
</graphic>
|
||||
</TableColumn>
|
||||
<TableColumn fx:id="cleartextColumn" prefWidth="${cipherToCleartextTable.width * 0.5}">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="LOCK_OPEN"/>
|
||||
</graphic>
|
||||
</TableColumn>
|
||||
</columns>
|
||||
</TableView>
|
||||
<HBox>
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="6"/>
|
||||
</padding>
|
||||
<Region HBox.hgrow="ALWAYS"/>
|
||||
<FormattedLabel styleClass="label-small" format="%decryptNames.copyHint" arg1="${controller.copyToClipboardShortcutString}"/>
|
||||
</HBox>
|
||||
</VBox>
|
||||
@@ -1,35 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ListView?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import javafx.scene.control.ChoiceBox?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Tooltip?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.eventview.EventViewController"
|
||||
minWidth="300"
|
||||
prefWidth="300"
|
||||
styleClass="event-window"
|
||||
>
|
||||
<HBox styleClass="button-bar" alignment="CENTER">
|
||||
<padding>
|
||||
<Insets left="6" />
|
||||
</padding>
|
||||
<ChoiceBox fx:id="vaultFilterChoiceBox" minWidth="42"/>
|
||||
<Region HBox.hgrow="ALWAYS"/>
|
||||
<Button styleClass="button-right" onAction="#clearEvents" contentDisplay="GRAPHIC_ONLY">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="TRASH" glyphSize="16"/>
|
||||
</graphic>
|
||||
<tooltip>
|
||||
<Tooltip text="%eventView.clearListButton.tooltip"/>
|
||||
</tooltip>
|
||||
</Button>
|
||||
</HBox>
|
||||
<ListView fx:id="eventListView" fixedCellSize="60" VBox.vgrow="ALWAYS"/>
|
||||
</VBox>
|
||||
@@ -1,45 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ContextMenu?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<HBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.eventview.EventListCellController"
|
||||
prefHeight="60"
|
||||
prefWidth="200"
|
||||
spacing="12"
|
||||
alignment="CENTER_LEFT"
|
||||
fx:id="root">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<!-- Remark Check the containing list view for a fixed cell size before editing height properties -->
|
||||
<VBox alignment="CENTER" minWidth="20">
|
||||
<FontAwesome5IconView glyph="${controller.icon}" HBox.hgrow="NEVER" glyphSize="16"/>
|
||||
</VBox>
|
||||
<VBox spacing="4" HBox.hgrow="ALWAYS">
|
||||
<HBox spacing="4">
|
||||
<Label styleClass="header-label" text="${controller.message}"/>
|
||||
<Label styleClass="header-misc" text="${controller.count}" visible="${controller.vaultUnlocked}"/>
|
||||
</HBox>
|
||||
<Label text="${controller.description}"/>
|
||||
</VBox>
|
||||
<Button fx:id="eventActionsButton" contentDisplay="GRAPHIC_ONLY" onAction="#toggleEventActionsMenu" managed="${controller.actionsButtonVisible}" visible="${controller.actionsButtonVisible}">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="ELLIPSIS_V" glyphSize="16"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
<VBox alignment="CENTER" maxWidth="64" minWidth="64" visible="${!controller.actionsButtonVisible}" managed="${!controller.actionsButtonVisible}">
|
||||
<Label text="${controller.eventLocalTime}" />
|
||||
<Label text="${controller.eventLocalDate}" />
|
||||
</VBox>
|
||||
|
||||
<fx:define>
|
||||
<ContextMenu fx:id="eventActionsMenu"/>
|
||||
</fx:define>
|
||||
</HBox>
|
||||
@@ -1,16 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ButtonBar?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.Group?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.Group?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.shape.Circle?>
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.control.ButtonBar?>
|
||||
<HBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.dialogs.SimpleDialogController"
|
||||
@@ -41,7 +41,7 @@
|
||||
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
|
||||
<ButtonBar buttonMinWidth="120" buttonOrder="+CI">
|
||||
<buttons>
|
||||
<Button text="${controller.cancelButtonText}" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#handleCancel" visible="${controller.cancelButtonVisible}" managed="${controller.cancelButtonVisible}"/>
|
||||
<Button text="${controller.cancelButtonText}" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#handleCancel"/>
|
||||
<Button text="${controller.okButtonText}" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#handleOk"/>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import org.cryptomator.ui.controls.ThroughputLabel?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.Tooltip?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.text.Text?>
|
||||
<?import javafx.scene.control.Tooltip?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.mainwindow.VaultDetailUnlockedController"
|
||||
@@ -46,37 +44,28 @@
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
|
||||
<HBox alignment="BOTTOM_CENTER">
|
||||
<StackPane visible="${controller.accessibleViaPath}" managed="${controller.accessibleViaPath}">
|
||||
<HBox visible="${controller.accessibleViaPath}" managed="${controller.accessibleViaPath}">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="0"/>
|
||||
</padding>
|
||||
<Button fx:id="revealEncryptedDropZone" styleClass="drag-n-drop" text="%main.vaultDetail.locateEncryptedFileBtn" minWidth="120" maxWidth="180" prefHeight="72" wrapText="true" textAlignment="CENTER" onAction="#chooseDecryptedFileAndReveal" contentDisplay="TOP" visible="${!controller.ciphertextPathsCopied}" managed="${!controller.ciphertextPathsCopied}">
|
||||
<Button fx:id="dropZone" styleClass="drag-n-drop" text="%main.vaultDetail.locateEncryptedFileBtn" minWidth="120" maxWidth="180" wrapText="true" textAlignment="CENTER" onAction="#chooseFileAndReveal" contentDisplay="TOP" visible="${!controller.ciphertextPathsCopied}" managed="${!controller.ciphertextPathsCopied}">
|
||||
<graphic>
|
||||
<Text styleClass="cryptic-text" text="abc → 101010"/>
|
||||
<FontAwesome5IconView glyph="FILE_DOWNLOAD" glyphSize="15"/>
|
||||
</graphic>
|
||||
<tooltip>
|
||||
<Tooltip text="%main.vaultDetail.locateEncryptedFileBtn.tooltip"/>
|
||||
</tooltip>
|
||||
</Button>
|
||||
<!-- TODO: instead of showing a button, show on error a small dialog and if copied, show a tooltip -->
|
||||
<Button styleClass="drag-n-drop" text="%main.vaultDetail.encryptedPathsCopied" minWidth="120" maxWidth="180" prefHeight="72" wrapText="true" textAlignment="CENTER" onAction="#chooseDecryptedFileAndReveal" contentDisplay="TOP" visible="${controller.ciphertextPathsCopied}" managed="${controller.ciphertextPathsCopied}">
|
||||
<Button styleClass="drag-n-drop" text="%main.vaultDetail.encryptedPathsCopied" minWidth="120" maxWidth="180" wrapText="true" textAlignment="CENTER" onAction="#chooseFileAndReveal" contentDisplay="TOP" visible="${controller.ciphertextPathsCopied}" managed="${controller.ciphertextPathsCopied}">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="CHECK" glyphSize="15"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
</StackPane>
|
||||
<!-- decrypt file name -->
|
||||
<Button fx:id="decryptNameDropZone" styleClass="drag-n-drop" text="%main.vaultDetail.decryptName.buttonLabel" minWidth="120" maxWidth="180" prefHeight="72" wrapText="true" textAlignment="CENTER" onAction="#showDecryptNameWindow" contentDisplay="TOP">
|
||||
<graphic>
|
||||
<Text styleClass="cryptic-text" text="101010 → abc"/>
|
||||
</graphic>
|
||||
<tooltip>
|
||||
<Tooltip text="%main.vaultDetail.decryptName.tooltip"/>
|
||||
</tooltip>
|
||||
</Button>
|
||||
</HBox>
|
||||
|
||||
<Region HBox.hgrow="ALWAYS"/>
|
||||
|
||||
<Button text="%main.vaultDetail.stats" minWidth="120" onAction="#showVaultStatistics" contentDisplay="BOTTOM" prefHeight="72">
|
||||
<Button text="%main.vaultDetail.stats" minWidth="120" onAction="#showVaultStatistics" contentDisplay="BOTTOM">
|
||||
<graphic>
|
||||
<VBox spacing="6">
|
||||
<HBox alignment="CENTER_RIGHT" spacing="6">
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ContextMenu?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.ListView?>
|
||||
<?import javafx.scene.control.MenuItem?>
|
||||
<?import javafx.scene.control.Tooltip?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.shape.Arc?>
|
||||
<?import javafx.scene.shape.Circle?>
|
||||
<?import javafx.scene.control.ContextMenu?>
|
||||
<?import javafx.scene.control.MenuItem?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.shape.Rectangle?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
<?import javafx.scene.shape.Arc?>
|
||||
<StackPane xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:id="root"
|
||||
@@ -40,44 +35,33 @@
|
||||
</VBox>
|
||||
</StackPane>
|
||||
<HBox styleClass="button-bar">
|
||||
<Button fx:id="addVaultButton" onMouseClicked="#toggleMenu" styleClass="button-left" alignment="CENTER" minWidth="20" contentDisplay="GRAPHIC_ONLY">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="PLUS" glyphSize="16"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
<HBox fx:id="addVaultButton" onMouseClicked="#toggleMenu" styleClass="button-left" alignment="CENTER" minWidth="20">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<FontAwesome5IconView glyph="PLUS" HBox.hgrow="NEVER" glyphSize="16"/>
|
||||
</HBox>
|
||||
<Region HBox.hgrow="ALWAYS"/>
|
||||
<StackPane>
|
||||
<Button onMouseClicked="#showEventViewer" styleClass="button-right" minWidth="20" contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="BELL" glyphSize="16"/>
|
||||
</graphic>
|
||||
<tooltip>
|
||||
<Tooltip text="%main.vaultlist.showEventsButton.tooltip"/>
|
||||
</tooltip>
|
||||
</Button>
|
||||
<AnchorPane mouseTransparent="true" minWidth="12" maxWidth="12" minHeight="12" maxHeight="12" StackPane.alignment="CENTER">
|
||||
<Circle radius="4" styleClass="icon-update-indicator" AnchorPane.topAnchor="-8" AnchorPane.rightAnchor="-6" visible="${controller.unreadEventsPresent}" />
|
||||
</AnchorPane>
|
||||
</StackPane>
|
||||
<Button onMouseClicked="#showPreferences" styleClass="button-right" alignment="CENTER" minWidth="20" contentDisplay="GRAPHIC_ONLY">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="COG" glyphSize="16"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
<HBox onMouseClicked="#showPreferences" styleClass="button-right" alignment="CENTER" minWidth="20">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<FontAwesome5IconView glyph="COG" HBox.hgrow="NEVER" glyphSize="16"/>
|
||||
</HBox>
|
||||
</HBox>
|
||||
</VBox>
|
||||
<Region styleClass="drag-n-drop-border" visible="${controller.draggingVaultOver}"/>
|
||||
<fx:define>
|
||||
<ContextMenu fx:id="addVaultContextMenu">
|
||||
<items>
|
||||
<MenuItem styleClass="dropdown-button-context-menu-item" text="%main.vaultlist.addVaultBtn.menuItemNew" onAction="#didClickAddNewVault">
|
||||
<MenuItem styleClass="add-vault-menu-item" text="%main.vaultlist.addVaultBtn.menuItemNew" onAction="#didClickAddNewVault" >
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="PLUS" textAlignment="CENTER" wrappingWidth="14"/>
|
||||
<FontAwesome5IconView glyph="PLUS" textAlignment="CENTER" wrappingWidth="14" />
|
||||
</graphic>
|
||||
</MenuItem>
|
||||
<MenuItem styleClass="dropdown-button-context-menu-item" text="%main.vaultlist.addVaultBtn.menuItemExisting" onAction="#didClickAddExistingVault">
|
||||
<MenuItem styleClass="add-vault-menu-item" text="%main.vaultlist.addVaultBtn.menuItemExisting" onAction="#didClickAddExistingVault" >
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="FOLDER_OPEN" textAlignment="CENTER" wrappingWidth="14"/>
|
||||
<FontAwesome5IconView glyph="FOLDER_OPEN" textAlignment="CENTER" wrappingWidth="14" />
|
||||
</graphic>
|
||||
</MenuItem>
|
||||
</items>
|
||||
|
||||
@@ -14,15 +14,17 @@
|
||||
spacing="12"
|
||||
alignment="CENTER_LEFT">
|
||||
<!-- Remark Check the containing list view for a fixed cell size before editing height properties -->
|
||||
<VBox alignment="CENTER" minWidth="20">
|
||||
<FontAwesome5IconView fx:id="vaultStateView" glyph="${controller.glyph}" HBox.hgrow="NEVER" glyphSize="16"/>
|
||||
</VBox>
|
||||
<VBox spacing="4" HBox.hgrow="ALWAYS">
|
||||
<Label styleClass="header-label" text="${controller.vault.displayName}"/>
|
||||
<Label styleClass="detail-label" text="${controller.vault.displayablePath}" textOverrun="CENTER_ELLIPSIS" visible="${!controller.compactMode}" managed="${!controller.compactMode}">
|
||||
<tooltip>
|
||||
<Tooltip text="${controller.vault.displayablePath}"/>
|
||||
</tooltip>
|
||||
</Label>
|
||||
</VBox>
|
||||
<children>
|
||||
<VBox alignment="CENTER" minWidth="20">
|
||||
<FontAwesome5IconView fx:id="vaultStateView" glyph="${controller.glyph}" HBox.hgrow="NEVER" glyphSize="16"/>
|
||||
</VBox>
|
||||
<VBox spacing="4" HBox.hgrow="ALWAYS">
|
||||
<Label styleClass="header-label" text="${controller.vault.displayName}"/>
|
||||
<Label styleClass="detail-label" text="${controller.vault.displayablePath}" textOverrun="CENTER_ELLIPSIS" visible="${!controller.compactMode}" managed="${!controller.compactMode}">
|
||||
<tooltip>
|
||||
<Tooltip text="${controller.vault.displayablePath}"/>
|
||||
</tooltip>
|
||||
</Label>
|
||||
</VBox>
|
||||
</children>
|
||||
</HBox>
|
||||
|
||||
4
src/main/resources/hyperlinks.json
Normal file
4
src/main/resources/hyperlinks.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"docsGettingStarted": "https://docs.cryptomator.org/desktop/getting-started/",
|
||||
"homepageHub": "https://cryptomator.org/hub"
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
additionalStyleSheets=
|
||||
|
||||
# Generics
|
||||
generic.action.dismiss=Dismiss
|
||||
## Button
|
||||
generic.button.apply=Apply
|
||||
generic.button.back=Back
|
||||
@@ -284,7 +283,7 @@ preferences.title=Preferences
|
||||
## General
|
||||
preferences.general=General
|
||||
preferences.general.startHidden=Hide window when starting Cryptomator
|
||||
preferences.general.autoCloseVaults=Lock vaults without asking when quitting application
|
||||
preferences.general.autoCloseVaults=Lock open vaults automatically when quitting application
|
||||
preferences.general.debugLogging=Enable debug logging
|
||||
preferences.general.debugDirectory=Reveal log files
|
||||
preferences.general.autoStart=Launch Cryptomator on system start
|
||||
@@ -396,7 +395,6 @@ main.vaultlist.contextMenu.vaultoptions=Show Vault Options
|
||||
main.vaultlist.contextMenu.reveal=Reveal Drive
|
||||
main.vaultlist.addVaultBtn.menuItemNew=Create New Vault...
|
||||
main.vaultlist.addVaultBtn.menuItemExisting=Open Existing Vault...
|
||||
main.vaultlist.showEventsButton.tooltip=Open event view
|
||||
##Notificaition
|
||||
main.notification.updateAvailable=Update is available.
|
||||
main.notification.support=Support Cryptomator.
|
||||
@@ -425,9 +423,7 @@ main.vaultDetail.stats=Vault Statistics
|
||||
main.vaultDetail.locateEncryptedFileBtn=Locate Encrypted File
|
||||
main.vaultDetail.locateEncryptedFileBtn.tooltip=Choose a file from your vault to locate its encrypted counterpart
|
||||
main.vaultDetail.encryptedPathsCopied=Paths Copied to Clipboard!
|
||||
main.vaultDetail.locateEncrypted.filePickerTitle=Select File Inside Vault
|
||||
main.vaultDetail.decryptName.buttonLabel=Decrypt File Name
|
||||
main.vaultDetail.decryptName.tooltip=Choose an encrypted vault file to decrypt its name
|
||||
main.vaultDetail.filePickerTitle=Select File Inside Vault
|
||||
### Missing
|
||||
main.vaultDetail.missing.info=Cryptomator could not find a vault at this path.
|
||||
main.vaultDetail.missing.recheck=Recheck
|
||||
@@ -581,43 +577,4 @@ shareVault.hub.message=How to share a Hub vault
|
||||
shareVault.hub.description=In order to share the vault content with another team member, you have to perform two steps:
|
||||
shareVault.hub.instruction.1=1. Share access of the encrypted vault folder via cloud storage.
|
||||
shareVault.hub.instruction.2=2. Grant access to team member in Cryptomator Hub.
|
||||
shareVault.hub.openHub=Open Cryptomator Hub
|
||||
|
||||
# Decrypt File Names
|
||||
decryptNames.title=Decrypt File Names
|
||||
decryptNames.filePicker.title=Select encrypted file
|
||||
decryptNames.filePicker.extensionDescription=Cryptomator encrypted file
|
||||
decryptNames.copyTable.tooltip=Copy table
|
||||
decryptNames.clearTable.tooltip=Clear table
|
||||
decryptNames.copyHint=Copy cell content with %s
|
||||
decryptNames.dropZone.message=Drop files or click to select
|
||||
decryptNames.dropZone.error.vaultInternalFiles=Vault internal files with no decrypt-able name selected
|
||||
decryptNames.dropZone.error.foreignFiles=Files do not belong to vault "%s"
|
||||
decryptNames.dropZone.error.noDirIdBackup=Directory of selected files does not contain dirId.c9r file
|
||||
decryptNames.dropZone.error.generic=Failed to decrypt file names
|
||||
|
||||
|
||||
# Event View
|
||||
eventView.title=Events
|
||||
eventView.filter.allVaults=All
|
||||
eventView.clearListButton.tooltip=Clear list
|
||||
## event list entries
|
||||
eventView.entry.vaultLocked.description=Unlock "%s" for details
|
||||
eventView.entry.conflictResolved.message=Resolved conflict
|
||||
eventView.entry.conflictResolved.showDecrypted=Show decrypted file
|
||||
eventView.entry.conflictResolved.copyDecrypted=Copy decrypted path
|
||||
eventView.entry.conflict.message=Conflict resolution failed
|
||||
eventView.entry.conflict.showDecrypted=Show decrypted, original file
|
||||
eventView.entry.conflict.copyDecrypted=Copy decrypted, original path
|
||||
eventView.entry.conflict.showEncrypted=Show conflicting, encrypted file
|
||||
eventView.entry.conflict.copyEncrypted=Copy conflicting, encrypted path
|
||||
eventView.entry.decryptionFailed.message=Decryption failed
|
||||
eventView.entry.decryptionFailed.showEncrypted=Show encrypted file
|
||||
eventView.entry.decryptionFailed.copyEncrypted=Copy encrypted path
|
||||
eventView.entry.brokenDirFile.message=Broken directory link
|
||||
eventView.entry.brokenDirFile.showEncrypted=Show broken, encrypted link
|
||||
eventView.entry.brokenDirFile.copyEncrypted=Copy path of broken link
|
||||
eventView.entry.brokenFileNode.message=Broken filesystem node
|
||||
eventView.entry.brokenFileNode.showEncrypted=Show broken, encrypted node
|
||||
eventView.entry.brokenFileNode.copyEncrypted=Copy path of broken, encrypted node
|
||||
eventView.entry.brokenFileNode.copyDecrypted=Copy decrypted path
|
||||
shareVault.hub.openHub=Open Cryptomator Hub
|
||||
@@ -121,10 +121,3 @@
|
||||
#Retry If Readonly
|
||||
|
||||
# Share Vault
|
||||
|
||||
|
||||
# Decrypt File Names
|
||||
|
||||
|
||||
# Event View
|
||||
## event list entries
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# Locale Specific CSS files such as CJK, RTL,...
|
||||
|
||||
# Generics
|
||||
generic.action.dismiss=تجاهل
|
||||
## Button
|
||||
generic.button.apply=تطبيق
|
||||
generic.button.back=رجوع
|
||||
@@ -177,7 +176,6 @@ hub.registerFailed.description.generic=حدث خطأ في عملية تسجيل
|
||||
hub.registerFailed.description.deviceAlreadyExists=هذا الجهاز مسجل لمستخدم مختلف بالفعل. حاول تغيير حساب المستخدم أو استخدام جهاز مختلف.
|
||||
### Unauthorized
|
||||
hub.unauthorized.message=تم رفض الوصول
|
||||
hub.unauthorized.description=غير مسموح لك بفتح هذا المستودع. اتصل بمالك المستودع لطلب الوصول.
|
||||
### Requires Account Initialization
|
||||
hub.requireAccountInit.message=مطلوب اتخاذ إجراء
|
||||
hub.requireAccountInit.description.0=للمتابعة، يرجى إكمال الخطوات المطلوبة في
|
||||
@@ -283,7 +281,7 @@ preferences.title=تفضيلات
|
||||
## General
|
||||
preferences.general=عام
|
||||
preferences.general.startHidden=إخفاء النافذة عند بدء تشغيل Cryptomator
|
||||
preferences.general.autoCloseVaults=اقفل المخازن دون السؤال عند الإقلاع عن التطبيق
|
||||
preferences.general.autoCloseVaults=اقفل الخزانات المفتوحة تلقائياً عند الإقلاع عن التطبيق
|
||||
preferences.general.debugLogging=تمكين سجلات التصحيح
|
||||
preferences.general.debugDirectory=عرض ملفات السجل
|
||||
preferences.general.autoStart=تشغيل Cryptomator عند بدء تشغيل النظام
|
||||
@@ -423,6 +421,7 @@ main.vaultDetail.stats=إحصائيات الخزنة
|
||||
main.vaultDetail.locateEncryptedFileBtn=تحديد موقع الملف المشفر
|
||||
main.vaultDetail.locateEncryptedFileBtn.tooltip=اختر ملف من خزانتك لتحديد مكان نظيره المشفر
|
||||
main.vaultDetail.encryptedPathsCopied=تم نسخ مسارات الملفات إلى الحافظة!
|
||||
main.vaultDetail.filePickerTitle=إختر الملف من الخزنة
|
||||
### Missing
|
||||
main.vaultDetail.missing.info=لم يتمكن Cryptomator من العثور على خزنة في هذا المسار.
|
||||
main.vaultDetail.missing.recheck=إعادة الفحص
|
||||
@@ -553,10 +552,6 @@ dokanySupportEnd.description=نوع وحدة التخزين Dokany لم يعد
|
||||
dokanySupportEnd.preferencesBtn=فتح التفضيلات
|
||||
|
||||
#Retry If Readonly
|
||||
retryIfReadonly.title=الوصول إلى المخزن المقيّد
|
||||
retryIfReadonly.message=لا يوجد وصول للكتابة إلى مجلد المخزن
|
||||
retryIfReadonly.description=Cryptomator لا يمكنه الكتابة إلى دليل المخزن. يمكنك تغيير المخزن ليكون للقراءة فقط وحاول مرة أخرى. يمكن تعطيل هذا الخيار في خيارات المخزن.
|
||||
retryIfReadonly.retry=غيّر وأعد المحاولة
|
||||
|
||||
# Share Vault
|
||||
shareVault.title=مشاركة الخزانة
|
||||
@@ -576,10 +571,4 @@ shareVault.hub.message=كيفية مشاركة خزانة Hub
|
||||
shareVault.hub.description=لمشاركة محتوى الخزانة مع عضو آخر في الفريق، عليك القيام بخطوتين:
|
||||
shareVault.hub.instruction.1=1. شارك الوصول إلى مجلد الخزانة المشفر عبر التخزين السحابي.
|
||||
shareVault.hub.instruction.2=2. امنح الوصول لعضو الفريق في Cryptomator Hub.
|
||||
shareVault.hub.openHub=زيارة Cryptomator Hub
|
||||
|
||||
# Decrypt File Names
|
||||
|
||||
|
||||
# Event View
|
||||
## event list entries
|
||||
shareVault.hub.openHub=زيارة Cryptomator Hub
|
||||
@@ -1,7 +1,6 @@
|
||||
# Locale Specific CSS files such as CJK, RTL,...
|
||||
|
||||
# Generics
|
||||
generic.action.dismiss=Кире ҡаҡ
|
||||
## Button
|
||||
generic.button.apply=Ҡуллан
|
||||
generic.button.back=Артҡа
|
||||
@@ -270,6 +269,7 @@ preferences.title=Көйләүҙәр
|
||||
## General
|
||||
preferences.general=Дөйөм
|
||||
preferences.general.startHidden=Cryptomator башланған саҡта тәҙрәне йәшерергә
|
||||
preferences.general.autoCloseVaults=Ҡушымтанан сыҡҡан ваҡытта һаҡлағыстарҙы автоматик рәүештә бикләргә
|
||||
preferences.general.debugLogging=Төҙөкләндереү журналын асырға
|
||||
preferences.general.debugDirectory=Журнал файлдарын күрһәт
|
||||
preferences.general.autoStart=Система стартында Cryptomator-ҙы эшләтеп ебәрергә
|
||||
@@ -389,6 +389,7 @@ main.vaultDetail.stats=Һаҡлағыс статистикаһы
|
||||
main.vaultDetail.locateEncryptedFileBtn=Шифрланған файлды тап
|
||||
main.vaultDetail.locateEncryptedFileBtn.tooltip=Шифрланған аналогын табыр өсөн һаҡлағыстан файл һайлағыҙ
|
||||
main.vaultDetail.encryptedPathsCopied=Юлдарҙын Clipboard-ҡа күсермәһе алынды!
|
||||
main.vaultDetail.filePickerTitle=Һаҡлағыс эсендә файл һайлау
|
||||
### Missing
|
||||
main.vaultDetail.missing.info=Cryptomator был юлдан һаҡлағыс таба алманы.
|
||||
main.vaultDetail.missing.recheck=Яңынан тикшер
|
||||
@@ -517,10 +518,3 @@ dokanySupportEnd.preferencesBtn=Көйләүҙәрҙе ас
|
||||
#Retry If Readonly
|
||||
|
||||
# Share Vault
|
||||
|
||||
|
||||
# Decrypt File Names
|
||||
|
||||
|
||||
# Event View
|
||||
## event list entries
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# Locale Specific CSS files such as CJK, RTL,...
|
||||
|
||||
# Generics
|
||||
generic.action.dismiss=Адхіліць
|
||||
## Button
|
||||
generic.button.apply=Ужыць
|
||||
generic.button.back=Назад
|
||||
@@ -259,6 +258,7 @@ preferences.title=Налады
|
||||
## General
|
||||
preferences.general=Агульныя
|
||||
preferences.general.startHidden=Хаваць акно пры запуску Cryptomator
|
||||
preferences.general.autoCloseVaults=Замыкаць адчыненыя скарбніцы аўтаматычна пры выхадзе з праграмы
|
||||
preferences.general.debugLogging=Уключыць пратакаляванне адладкі
|
||||
preferences.general.debugDirectory=Паказаць файлы пратаколу
|
||||
preferences.general.autoStart=Запускаць Cryptomator падчас запуску сістэмы
|
||||
@@ -376,6 +376,7 @@ main.vaultDetail.stats=Статыстыка скарбніцы
|
||||
main.vaultDetail.locateEncryptedFileBtn=Знайсці зашыфраваны файл
|
||||
main.vaultDetail.locateEncryptedFileBtn.tooltip=Абяры файл у тваёй скрабніцы, каб знайсці ягоны зашыфраваны адпаведнік
|
||||
main.vaultDetail.encryptedPathsCopied=Шлях скапіяваны ў буфер абмену!
|
||||
main.vaultDetail.filePickerTitle=Абраць файл унутры скарбніцы
|
||||
### Missing
|
||||
main.vaultDetail.missing.info=Cryptomator ня змог знайсці скарбніцу па гэтай сцежцы.
|
||||
main.vaultDetail.missing.recheck=Пераправерыць
|
||||
@@ -497,10 +498,3 @@ dokanySupportEnd.preferencesBtn=Адчыніць налады
|
||||
#Retry If Readonly
|
||||
|
||||
# Share Vault
|
||||
|
||||
|
||||
# Decrypt File Names
|
||||
|
||||
|
||||
# Event View
|
||||
## event list entries
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# Locale Specific CSS files such as CJK, RTL,...
|
||||
|
||||
# Generics
|
||||
generic.action.dismiss=Отхвърляне
|
||||
## Button
|
||||
generic.button.apply=Прилагане
|
||||
generic.button.back=Назад
|
||||
@@ -270,11 +269,11 @@ preferences.title=Настройки
|
||||
## General
|
||||
preferences.general=Общи
|
||||
preferences.general.startHidden=Скриване на прозореца при отваряне на Криптоматор
|
||||
preferences.general.autoCloseVaults=Заключване на хранилищата при затваряне на приложението
|
||||
preferences.general.debugLogging=Дневник за отстраняване на дефекти
|
||||
preferences.general.debugDirectory=Файлове на дневниците
|
||||
preferences.general.autoStart=Отваряне на Криптоматор при старт на системата
|
||||
preferences.general.keychainBackend=Съхраняванеа паролите в
|
||||
preferences.general.quickAccessService=Добавяне на отключените хранилища в зоната за бърз достъп
|
||||
## Interface
|
||||
preferences.interface=Външен вид
|
||||
preferences.interface.theme=Тема
|
||||
@@ -390,6 +389,7 @@ main.vaultDetail.stats=Статистики на хранилището
|
||||
main.vaultDetail.locateEncryptedFileBtn=Намиране на шифровани файлове
|
||||
main.vaultDetail.locateEncryptedFileBtn.tooltip=Изберете файл от хранилището, за да бъде намерено шифрованото му копие
|
||||
main.vaultDetail.encryptedPathsCopied=Пътищата са копирани!
|
||||
main.vaultDetail.filePickerTitle=Изберете файл от хранилището
|
||||
### Missing
|
||||
main.vaultDetail.missing.info=Криптоматор не намира хранилище на това място.
|
||||
main.vaultDetail.missing.recheck=Повторен опит
|
||||
@@ -518,10 +518,3 @@ dokanySupportEnd.preferencesBtn=Към настройките
|
||||
#Retry If Readonly
|
||||
|
||||
# Share Vault
|
||||
|
||||
|
||||
# Decrypt File Names
|
||||
|
||||
|
||||
# Event View
|
||||
## event list entries
|
||||
|
||||
@@ -185,10 +185,3 @@ vaultOptions.mount.mountPoint.directoryPickerButton=নির্বাচন ক
|
||||
#Retry If Readonly
|
||||
|
||||
# Share Vault
|
||||
|
||||
|
||||
# Decrypt File Names
|
||||
|
||||
|
||||
# Event View
|
||||
## event list entries
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user