diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml
index 26111c518..3f56c90ac 100644
--- a/.github/workflows/appimage.yml
+++ b/.github/workflows/appimage.yml
@@ -19,7 +19,7 @@ on:
env:
JAVA_DIST: 'temurin'
- JAVA_VERSION: '24.0.1+9'
+ JAVA_VERSION: '25.0.0'
jobs:
get-version:
@@ -37,16 +37,16 @@ jobs:
include:
- os: ubuntu-latest
appimage-suffix: x86_64
- openjfx-url: 'https://download2.gluonhq.com/openjfx/24.0.1/openjfx-24.0.1_linux-x64_bin-jmods.zip'
- openjfx-sha: '425fac742b9fbd095b2ce868cff82d1024620f747c94a7144d0a4879e756146c'
+ openjfx-url: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_linux-x64_bin-jmods.zip'
+ openjfx-sha: '96e520f48610d8ffb94ca30face1f11ffe8a977ddc1c4ff80b1a9e9f048bd94e'
- os: ubuntu-24.04-arm
appimage-suffix: aarch64
- openjfx-url: 'https://download2.gluonhq.com/openjfx/24.0.1/openjfx-24.0.1_linux-aarch64_bin-jmods.zip'
- openjfx-sha: '7e02edd0f4ee5527a27c94b0bbba66fcaaff41009119e45d0eca0f96ddfb6e7b'
+ openjfx-url: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_linux-aarch64_bin-jmods.zip'
+ openjfx-sha: '951c52481af0ec5885b06f1ebaa8a10da7e8ea23c5e1ef3e2f6f11fa1b3a7ce1'
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Setup Java
- uses: actions/setup-java@v4
+ uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
@@ -75,7 +75,7 @@ jobs:
- name: Set version
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
- name: Run maven
- run: mvn -B clean package -Plinux -DskipTests -Djavafx.platform=linux
+ run: mvn -B clean package -Plinux -DskipTests
- name: Patch target dir
run: |
cp LICENSE.txt target
@@ -95,7 +95,7 @@ jobs:
--verbose
--output runtime
--module-path "${{ steps.jep-493-check.outputs.jmod_paths }}"
- --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net,java.compiler
+ --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net,java.compiler
--strip-native-commands
--no-header-files
--no-man-pages
@@ -117,7 +117,6 @@ jobs:
--app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}"
--java-options "--enable-preview"
--java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator"
- --java-options "--sun-misc-unsafe-memory-access=allow"
--java-options "-Xss5m"
--java-options "-Xmx256m"
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
@@ -133,6 +132,7 @@ jobs:
--java-options "-Dcryptomator.integrationsLinux.trayIconsDir=\"@{appdir}/usr/share/icons/hicolor/symbolic/apps\""
--java-options "-Dcryptomator.buildNumber=\"appimage-${{ needs.get-version.outputs.revNum }}\""
--java-options "-Dcryptomator.networking.truststore.p12Path=\"/etc/cryptomator/certs.p12\""
+ --java-options "-XX:ErrorFile=/cryptomator/cryptomator_crash.log"
--resource-dir dist/linux/resources
- name: Patch Cryptomator.AppDir
run: |
@@ -175,7 +175,7 @@ jobs:
gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage
gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage.zsync
- name: Upload artifacts
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: appimage-${{ matrix.appimage-suffix }}
path: |
@@ -185,7 +185,7 @@ jobs:
if-no-files-found: error
- name: Publish AppImage on GitHub Releases
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published'
- uses: softprops/action-gh-release@v2
+ uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
with:
fail_on_unmatched_files: true
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
@@ -198,10 +198,10 @@ jobs:
name: Create PR for aur-bin repo
needs: [build, get-version]
runs-on: ubuntu-latest
- if: github.event_name == 'release'
+ if: github.event_name == 'release' && needs.get-version.outputs.versionType == 'stable'
steps:
- name: Download AppImages
- uses: actions/download-artifact@v4
+ uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
path: downloads/
merge-multiple: true
@@ -212,7 +212,7 @@ jobs:
echo "x64-sha256sum=${X64_SHA256}" >> "$GITHUB_OUTPUT"
AARCH64_SHA256=$(sha256sum downloads/cryptomator-*-aarch64.AppImage | cut -d ' ' -f1)
echo "aarch64-sha256sum=${AARCH64_SHA256}" >> "$GITHUB_OUTPUT"
- - uses: actions/checkout@v4
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
repository: 'cryptomator/aur-bin'
token: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
@@ -247,7 +247,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
- name: Slack Notification
- uses: rtCamp/action-slack-notify@v2
+ uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_USERNAME: 'Cryptobot'
diff --git a/.github/workflows/aur.yml b/.github/workflows/aur.yml
index e290be872..b028273a4 100644
--- a/.github/workflows/aur.yml
+++ b/.github/workflows/aur.yml
@@ -19,6 +19,8 @@ jobs:
runs-on: ubuntu-latest
needs: [get-version]
if: github.event_name == 'workflow_dispatch' || needs.get-version.outputs.versionType == 'stable'
+ env:
+ INPUT_TAG: ${{ inputs.tag }}
outputs:
url: ${{ steps.url.outputs.url}}
sha256: ${{ steps.sha256.outputs.sha256}}
@@ -27,8 +29,8 @@ jobs:
id: url
run: |
URL="";
- if [[ -n "${{ inputs.tag }}" ]]; then
- URL="https://github.com/cryptomator/cryptomator/archive/refs/tags/${{ inputs.tag }}.tar.gz"
+ if [[ -n "${INPUT_TAG}" ]]; then
+ URL="https://github.com/cryptomator/cryptomator/archive/refs/tags/${INPUT_TAG}.tar.gz"
else
URL="https://github.com/cryptomator/cryptomator/archive/refs/tags/${{ github.event.release.tag_name }}.tar.gz"
fi
@@ -46,7 +48,7 @@ jobs:
env:
AUR_PR_URL: tbd
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
repository: 'cryptomator/aur'
token: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
@@ -79,8 +81,8 @@ jobs:
env:
GH_TOKEN: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
- name: Slack Notification
- uses: rtCamp/action-slack-notify@v2
if: github.event_name == 'release'
+ uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_USERNAME: 'Cryptobot'
diff --git a/.github/workflows/av-whitelist.yml b/.github/workflows/av-whitelist.yml
index 8febeca49..ca74a3281 100644
--- a/.github/workflows/av-whitelist.yml
+++ b/.github/workflows/av-whitelist.yml
@@ -30,16 +30,18 @@ jobs:
runs-on: ubuntu-latest
outputs:
fileName: ${{ steps.extractName.outputs.fileName}}
+ env:
+ INPUT_URL: ${{ inputs.url }}
steps:
- name: Extract file name
id: extractName
run: |
- url="${{ inputs.url }}"
+ url="${INPUT_URL}"
echo "fileName=${url##*/}" >> $GITHUB_OUTPUT
- name: Download file
- run: curl --remote-name ${{ inputs.url }} -L -o ${{steps.extractName.outputs.fileName}}
+ run: curl --remote-name ${INPUT_URL} -L -o ${{steps.extractName.outputs.fileName}}
- name: Upload artifact
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: ${{ steps.extractName.outputs.fileName }}
path: ${{ steps.extractName.outputs.fileName }}
@@ -51,12 +53,12 @@ jobs:
if: github.event_name == 'workflow_call' || inputs.kaspersky
steps:
- name: Download artifact
- uses: actions/download-artifact@v4
+ uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
name: ${{ needs.download-file.outputs.fileName }}
path: upload
- name: Upload to Kaspersky
- uses: SamKirkland/FTP-Deploy-Action@v4.3.5
+ uses: SamKirkland/FTP-Deploy-Action@a51268f67f6605236975928ae28b0f7e9971d50a # v4.6.3
with:
protocol: ftps
server: allowlist.kaspersky-labs.com
@@ -71,12 +73,12 @@ jobs:
if: github.event_name == 'workflow_call' || inputs.avast
steps:
- name: Download artifact
- uses: actions/download-artifact@v4
+ uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
name: ${{ needs.download-file.outputs.fileName }}
path: upload
- - name: Upload to Avast
- uses: wlixcc/SFTP-Deploy-Action@v1.2.6
+ - name: Upload to Avast
+ uses: wlixcc/SFTP-Deploy-Action@a5ccb9c6211a94cc59404f0fdb2a9936a6dfee64 # v1.2.6
with:
server: whitelisting.avast.com
port: 22
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 7dae2755c..c9a0ea0c9 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -11,7 +11,7 @@ on:
env:
JAVA_DIST: 'temurin'
- JAVA_VERSION: 24
+ JAVA_VERSION: 25
defaults:
run:
@@ -22,14 +22,14 @@ jobs:
name: Compile and Test
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-java@v4
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+ - uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
cache: 'maven'
- name: Cache SonarCloud packages
- uses: actions/cache@v4
+ uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
@@ -37,7 +37,7 @@ jobs:
- name: Build and Test
run: >
xvfb-run
- mvn -B verify -Djavafx.platform=linux
+ mvn -B verify
jacoco:report
org.sonarsource.scanner.maven:sonar-maven-plugin:sonar
-Pcoverage
@@ -49,7 +49,7 @@ jobs:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
- name: Draft a release
if: startsWith(github.ref, 'refs/tags/')
- uses: softprops/action-gh-release@v2
+ uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
with:
draft: true
discussion_category_name: releases
diff --git a/.github/workflows/check-jdk-updates.yml b/.github/workflows/check-jdk-updates.yml
index bf8d19e15..4e9633aac 100644
--- a/.github/workflows/check-jdk-updates.yml
+++ b/.github/workflows/check-jdk-updates.yml
@@ -6,7 +6,7 @@ on:
workflow_dispatch:
env:
- JDK_VERSION: '24.0.1+9'
+ JAVA_VERSION: '25.0.0'
JDK_VENDOR: temurin
RUNTIME_VERSION_HELPER: >
public class Test {
@@ -26,7 +26,7 @@ jobs:
run: echo 'JDK_MAJOR_VERSION=${{ env.JDK_VERSION }}'.substring(0,20) >> "$env:GITHUB_ENV"
shell: pwsh
- name: Checkout latest JDK ${{ env.JDK_MAJOR_VERSION }}
- uses: actions/setup-java@v4
+ uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
with:
java-version: ${{ env.JDK_MAJOR_VERSION}}
distribution: ${{ env.JDK_VENDOR }}
@@ -70,7 +70,7 @@ jobs:
}
- name: Notify
if: steps.determine.outputs.UPDATE_AVAILABLE == 'true'
- uses: rtCamp/action-slack-notify@v2
+ uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_USERNAME: 'Cryptobot'
diff --git a/.github/workflows/debian.yml b/.github/workflows/debian.yml
index 9763717b3..7d07cc948 100644
--- a/.github/workflows/debian.yml
+++ b/.github/workflows/debian.yml
@@ -23,13 +23,12 @@ on:
env:
JAVA_DIST: 'temurin'
- JAVA_VERSION: '24.0.1+9'
- COFFEELIBS_JDK: 24
- COFFEELIBS_JDK_VERSION: '24.0.1+9-0ppa3'
- OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/24.0.1/openjfx-24.0.1_linux-x64_bin-jmods.zip'
- OPENJFX_JMODS_AMD64_HASH: '425fac742b9fbd095b2ce868cff82d1024620f747c94a7144d0a4879e756146c'
- OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/24.0.1/openjfx-24.0.1_linux-aarch64_bin-jmods.zip'
- OPENJFX_JMODS_AARCH64_HASH: '7e02edd0f4ee5527a27c94b0bbba66fcaaff41009119e45d0eca0f96ddfb6e7b'
+ JAVA_VERSION: '25.0.0'
+ DEB_BUILD_DEPENDS: 'debhelper (>=10), openjdk-25-jdk (>= 25+36), libgtk-3-0 (>= 3.20.0), libxxf86vm1, libgl1'
+ OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_linux-x64_bin-jmods.zip'
+ OPENJFX_JMODS_AMD64_HASH: '96e520f48610d8ffb94ca30face1f11ffe8a977ddc1c4ff80b1a9e9f048bd94e'
+ OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_linux-aarch64_bin-jmods.zip'
+ OPENJFX_JMODS_AARCH64_HASH: '951c52481af0ec5885b06f1ebaa8a10da7e8ea23c5e1ef3e2f6f11fa1b3a7ce1'
jobs:
get-version:
@@ -41,30 +40,34 @@ jobs:
name: Build Debian Package
runs-on: ubuntu-22.04
needs: [get-version]
+ env:
+ INPUT_PPAVER: ${{ inputs.ppaver }}
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- id: deb-version
name: Determine deb-version
run: |
- if [ -n "${{inputs.ppaver}}" ]; then
- echo "debVersion=${{inputs.ppaver }}" >> "$GITHUB_OUTPUT"
+ if [ -n "${INPUT_PPAVER}" ]; then
+ echo "debVersion=${INPUT_PPAVER}" >> "$GITHUB_OUTPUT"
else
echo "debVersion=${{needs.get-version.outputs.semVerStr}}" >> "$GITHUB_OUTPUT"
fi
- name: Install build tools
run: |
- sudo add-apt-repository ppa:coffeelibs/openjdk
sudo apt-get update
- sudo apt-get install debhelper devscripts dput coffeelibs-jdk-${{ env.COFFEELIBS_JDK }}=${{ env.COFFEELIBS_JDK_VERSION }}
+ sudo apt-get install devscripts dput
+ sudo apt-get satisfy "${DEB_BUILD_DEPENDS}"
+ env:
+ DEB_BUILD_DEPENDS: ${{ env.DEB_BUILD_DEPENDS }}
- name: Setup Java
- uses: actions/setup-java@v4
+ uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
check-latest: true
cache: 'maven'
- name: Run maven
- run: mvn -B clean package -Plinux -Djavafx.platform=linux -DskipTests
+ run: mvn -B clean package -Plinux -DskipTests
- name: Download OpenJFX jmods
id: download-jmods
run: |
@@ -140,7 +143,7 @@ jobs:
run: |
gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator_*_amd64.deb
- name: Upload artifacts
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: linux-deb-package
path: |
diff --git a/.github/workflows/dependency-check.yml b/.github/workflows/dependency-check.yml
index b44604490..22d2a5fc5 100644
--- a/.github/workflows/dependency-check.yml
+++ b/.github/workflows/dependency-check.yml
@@ -7,12 +7,13 @@ on:
jobs:
check-dependencies:
- uses: skymatic/workflows/.github/workflows/run-dependency-check.yml@v1
+ uses: skymatic/workflows/.github/workflows/run-dependency-check.yml@1074588008ae3326a2221ea451783280518f0366 # v3.0.1
with:
runner-os: 'ubuntu-latest'
java-distribution: 'temurin'
- java-version: 24
- check-command: 'mvn -B validate -Pdependency-check -Djavafx.platform=linux'
+ java-version: 25
secrets:
nvd-api-key: ${{ secrets.NVD_API_KEY }}
+ ossindex-username: ${{ secrets.OSSINDEX_USERNAME }}
+ ossindex-token: ${{ secrets.OSSINDEX_API_TOKEN }}
slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
diff --git a/.github/workflows/dl-stats.yml b/.github/workflows/dl-stats.yml
index b16899520..401fa010a 100644
--- a/.github/workflows/dl-stats.yml
+++ b/.github/workflows/dl-stats.yml
@@ -10,7 +10,7 @@ jobs:
steps:
- name: Get download count of latest releases
id: get-stats
- uses: actions/github-script@v7
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
const query = `query($owner:String!, $name:String!) {
@@ -53,7 +53,7 @@ jobs:
INTERVAL: 900
JSON_DATA: ${{ steps.get-stats.outputs.result }}
- name: Upload Results
- uses: fjogeleit/http-request-action@v1
+ uses: fjogeleit/http-request-action@1297c6fc63a79b147d1676540a3fd9d2e37817c5 # v1.16.5
with:
url: 'https://graphite-us-central1.grafana.net/metrics'
method: 'POST'
diff --git a/.github/workflows/error-db.yml b/.github/workflows/error-db.yml
index 301713681..5c2ffebe8 100644
--- a/.github/workflows/error-db.yml
+++ b/.github/workflows/error-db.yml
@@ -14,7 +14,7 @@ jobs:
- name: Query Discussion Data
if: github.event_name == 'discussion_comment' || github.event_name == 'discussion' && github.event.action != 'deleted'
id: query-data
- uses: actions/github-script@v7
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
const query = `query ($owner: String!, $name: String!, $discussionNumber: Int!) {
@@ -42,7 +42,7 @@ jobs:
return await github.graphql(query, variables)
- name: Get Gist
id: get-gist
- uses: andymckay/get-gist-action@master
+ uses: andymckay/get-gist-action@cf3bc8164af24126f7e5979eb6d3dc0c12309bd1 # not_tagged
with:
gistURL: https://gist.github.com/cryptobot/accba9fb9555e7192271b85606f97230
- name: Merge Error Code Data
@@ -58,7 +58,7 @@ jobs:
env:
DISCUSSION: ${{ steps.query-data.outputs.result }}
- name: Patch Gist
- uses: exuanbo/actions-deploy-gist@v1
+ uses: exuanbo/actions-deploy-gist@47697fceaeea2006a90594ee24eb9cd0a1121ef8 # v1.1.4
with:
token: ${{ secrets.CRYPTOBOT_GIST_TOKEN }}
gist_id: accba9fb9555e7192271b85606f97230
diff --git a/.github/workflows/flathub.yml b/.github/workflows/flathub.yml
index 85e2cb435..d233a747b 100644
--- a/.github/workflows/flathub.yml
+++ b/.github/workflows/flathub.yml
@@ -26,13 +26,10 @@ jobs:
- name: Determine tarball url
id: url
run: |
- URL="";
- if [[ -n "${{ inputs.tag }}" ]]; then
- URL="https://github.com/cryptomator/cryptomator/archive/refs/tags/${{ inputs.tag }}.tar.gz"
- else
- URL="https://github.com/cryptomator/cryptomator/archive/refs/tags/${{ github.event.release.tag_name }}.tar.gz"
- fi
+ URL="https://github.com/cryptomator/cryptomator/archive/refs/tags/${TAG}.tar.gz"
echo "url=${URL}" >> "$GITHUB_OUTPUT"
+ env:
+ TAG: ${{ inputs.tag || github.event.release.tag_name}}
- name: Download source tarball and compute checksum
id: sha512
run: |
@@ -46,7 +43,7 @@ jobs:
env:
FLATHUB_PR_URL: tbd
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
repository: 'flathub/org.cryptomator.Cryptomator'
token: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
@@ -74,7 +71,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
- name: Slack Notification
- uses: rtCamp/action-slack-notify@v2
+ uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3
if: github.event_name == 'release'
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
diff --git a/.github/workflows/get-version.yml b/.github/workflows/get-version.yml
index 4ee423386..b1c728fa8 100644
--- a/.github/workflows/get-version.yml
+++ b/.github/workflows/get-version.yml
@@ -23,7 +23,7 @@ on:
env:
JAVA_DIST: 'temurin'
- JAVA_VERSION: 24
+ JAVA_VERSION: 25
jobs:
determine-version:
@@ -35,11 +35,11 @@ jobs:
revNum: ${{ steps.versions.outputs.revNum }}
type: ${{ steps.versions.outputs.type}}
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0
- name: Setup Java
- uses: actions/setup-java@v4
+ uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
@@ -49,8 +49,8 @@ jobs:
run: |
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
SEM_VER_STR=${GITHUB_REF##*/}
- elif [[ "${{ inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
- SEM_VER_STR="${{ inputs.version }}"
+ elif [[ "${VERSION_STRING}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
+ SEM_VER_STR="${VERSION_STRING}"
else
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
fi
@@ -70,7 +70,9 @@ jobs:
echo "semVerNum=${SEM_VER_NUM}" >> $GITHUB_OUTPUT
echo "revNum=${REVCOUNT}" >> $GITHUB_OUTPUT
echo "type=${TYPE}" >> $GITHUB_OUTPUT
+ env:
+ VERSION_STRING: ${{ inputs.version }}
- name: Validate Version
- uses: skymatic/semver-validation-action@v3
+ uses: skymatic/semver-validation-action@7a6ae1c9e121540d11c9c7e4e667c83d583aa153 # v3.0.0
with:
version: ${{ steps.versions.outputs.semVerStr }}
\ No newline at end of file
diff --git a/.github/workflows/mac-dmg-x64.yml b/.github/workflows/mac-dmg-x64.yml
index 0a52e71a8..b0af671a6 100644
--- a/.github/workflows/mac-dmg-x64.yml
+++ b/.github/workflows/mac-dmg-x64.yml
@@ -24,7 +24,7 @@ on:
env:
JAVA_DIST: 'temurin'
- JAVA_VERSION: '24.0.1+9'
+ JAVA_VERSION: '25.0.0'
jobs:
get-version:
@@ -44,12 +44,12 @@ jobs:
architecture: x64
output-suffix: x64
fuse-lib: macFUSE
- openjfx-url: 'https://download2.gluonhq.com/openjfx/24.0.1/openjfx-24.0.1_osx-x64_bin-jmods.zip'
- openjfx-sha: '6e62a426d43c168a488521f904a523f3dd6ee2cf103e08136f2fd465c828a105'
+ openjfx-url: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_osx-x64_bin-jmods.zip'
+ openjfx-sha: '0eba73fb28a24c845175d16fa2f8c081c936ce6de1be9b79eb6119fa32e53d52'
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Setup Java
- uses: actions/setup-java@v4
+ uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
@@ -79,7 +79,7 @@ jobs:
- name: Set version
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
- name: Run maven
- run: mvn -B -Djavafx.platform=mac clean package -Pmac -DskipTests
+ run: mvn -B clean package -Pmac -DskipTests
- name: Patch target dir
run: |
cp LICENSE.txt target
@@ -99,7 +99,7 @@ jobs:
--verbose
--output runtime
--module-path "${{ steps.jep-493-check.outputs.jmod_paths }}"
- --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler
+ --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler
--strip-native-commands
--no-header-files
--no-man-pages
@@ -121,7 +121,6 @@ jobs:
--app-version "${{ needs.get-version.outputs.semVerNum }}"
--java-options "--enable-preview"
--java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.mac"
- --java-options "--sun-misc-unsafe-memory-access=allow"
--java-options "-Xss5m"
--java-options "-Xmx256m"
--java-options "-Dfile.encoding=\"utf-8\""
@@ -151,9 +150,23 @@ jobs:
VERSION_NO: ${{ needs.get-version.outputs.semVerNum }}
REVISION_NO: ${{ needs.get-version.outputs.revNum }}
PROVISIONING_PROFILE_BASE64: ${{ secrets.MACOS_PROVISIONING_PROFILE_BASE64 }}
+ - name: Build and install DockTilePlugin
+ env:
+ DERIVED_DATA_PATH: dist/mac/DockTilePlugin/build
+ run: |
+ xcodebuild -project dist/mac/DockTilePlugin/DockTilePlugin.xcodeproj \
+ -scheme DockTilePlugin \
+ -configuration Release \
+ -destination "platform=macOS,arch=x86_64" \
+ -derivedDataPath ${DERIVED_DATA_PATH} \
+ -quiet \
+ clean build
+ mkdir -p Cryptomator.app/Contents/PlugIns
+ cp -R ${DERIVED_DATA_PATH}/Build/Products/Release/Cryptomator.docktileplugin Cryptomator.app/Contents/PlugIns/
+ rm -rf ${DERIVED_DATA_PATH}
- name: Generate license for dmg
run: >
- mvn -B -Djavafx.platform=mac license:add-third-party
+ mvn -B license:add-third-party
-Dlicense.thirdPartyFilename=license.rtf
-Dlicense.outputDirectory=dist/mac/dmg/resources
-Dlicense.fileTemplate=dist/mac/dmg/resources/licenseTemplate.ftl
@@ -248,7 +261,7 @@ jobs:
CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }}
- name: Notarize .dmg
if: startsWith(github.ref, 'refs/tags/') || inputs.notarize
- uses: cocoalibs/xcode-notarization-action@v1
+ uses: cocoalibs/xcode-notarization-action@5cf433d494b6fa26504b574c591f4dd120388846 # v1.0.3
with:
app-path: 'Cryptomator-*.dmg'
apple-id: ${{ secrets.MACOS_NOTARIZATION_APPLE_ID }}
@@ -269,7 +282,7 @@ jobs:
run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db
continue-on-error: true
- name: Upload artifacts
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: dmg-${{ matrix.output-suffix }}
path: |
@@ -278,7 +291,7 @@ jobs:
if-no-files-found: error
- name: Publish dmg on GitHub Releases
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published'
- uses: softprops/action-gh-release@v2
+ uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
with:
fail_on_unmatched_files: true
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
diff --git a/.github/workflows/mac-dmg.yml b/.github/workflows/mac-dmg.yml
index 29b4a4b0c..cc1af8a78 100644
--- a/.github/workflows/mac-dmg.yml
+++ b/.github/workflows/mac-dmg.yml
@@ -22,7 +22,7 @@ on:
env:
JAVA_DIST: 'temurin'
- JAVA_VERSION: '24.0.1+9'
+ JAVA_VERSION: '25.0.0'
jobs:
get-version:
@@ -42,12 +42,12 @@ jobs:
architecture: aarch64
output-suffix: arm64
fuse-lib: FUSE-T
- openjfx-url: 'https://download2.gluonhq.com/openjfx/24.0.1/openjfx-24.0.1_osx-aarch64_bin-jmods.zip'
- openjfx-sha: 'b5a94a13077507003fa852512bfa33f4fb680bc8076d8002e4227a84c85171d4'
+ openjfx-url: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_osx-aarch64_bin-jmods.zip'
+ openjfx-sha: '13f8c0513c40c95881479fbcf0465a29a60217393fb0656f5e4eab78a9442fba'
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Setup Java
- uses: actions/setup-java@v4
+ uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
@@ -77,7 +77,7 @@ jobs:
- name: Set version
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
- name: Run maven
- run: mvn -B -Djavafx.platform=mac clean package -Pmac -DskipTests
+ run: mvn -B clean package -Pmac -DskipTests
- name: Patch target dir
run: |
cp LICENSE.txt target
@@ -97,7 +97,7 @@ jobs:
--verbose
--output runtime
--module-path "${{ steps.jep-493-check.outputs.jmod_paths }}"
- --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler
+ --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler
--strip-native-commands
--no-header-files
--no-man-pages
@@ -119,7 +119,6 @@ jobs:
--app-version "${{ needs.get-version.outputs.semVerNum }}"
--java-options "--enable-preview"
--java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.mac"
- --java-options "--sun-misc-unsafe-memory-access=allow"
--java-options "-Xss5m"
--java-options "-Xmx256m"
--java-options "-Dfile.encoding=\"utf-8\""
@@ -136,6 +135,7 @@ jobs:
--java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/Library/Application Support/Cryptomator/mnt\""
--java-options "-Dcryptomator.showTrayIcon=true"
--java-options "-Dcryptomator.buildNumber=\"dmg-${{ needs.get-version.outputs.revNum }}\""
+ --java-options "-XX:ErrorFile=/cryptomator/cryptomator_crash.log"
--mac-package-identifier org.cryptomator
--resource-dir dist/mac/resources
- name: Patch Cryptomator.app
@@ -149,9 +149,23 @@ jobs:
VERSION_NO: ${{ needs.get-version.outputs.semVerNum }}
REVISION_NO: ${{ needs.get-version.outputs.revNum }}
PROVISIONING_PROFILE_BASE64: ${{ secrets.MACOS_PROVISIONING_PROFILE_BASE64 }}
+ - name: Build and install DockTilePlugin
+ env:
+ DERIVED_DATA_PATH: dist/mac/DockTilePlugin/build
+ run: |
+ xcodebuild -project dist/mac/DockTilePlugin/DockTilePlugin.xcodeproj \
+ -scheme DockTilePlugin \
+ -configuration Release \
+ -destination "platform=macOS,arch=arm64" \
+ -derivedDataPath ${DERIVED_DATA_PATH} \
+ -quiet \
+ clean build
+ mkdir -p Cryptomator.app/Contents/PlugIns
+ cp -R ${DERIVED_DATA_PATH}/Build/Products/Release/Cryptomator.docktileplugin Cryptomator.app/Contents/PlugIns/
+ rm -rf ${DERIVED_DATA_PATH}
- name: Generate license for dmg
run: >
- mvn -B -Djavafx.platform=mac license:add-third-party
+ mvn -B license:add-third-party
-Dlicense.thirdPartyFilename=license.rtf
-Dlicense.outputDirectory=dist/mac/dmg/resources
-Dlicense.fileTemplate=dist/mac/dmg/resources/licenseTemplate.ftl
@@ -246,7 +260,7 @@ jobs:
CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }}
- name: Notarize .dmg
if: startsWith(github.ref, 'refs/tags/') || inputs.notarize
- uses: cocoalibs/xcode-notarization-action@v1
+ uses: cocoalibs/xcode-notarization-action@5cf433d494b6fa26504b574c591f4dd120388846 # v1.0.3
with:
app-path: 'Cryptomator-*.dmg'
apple-id: ${{ secrets.MACOS_NOTARIZATION_APPLE_ID }}
@@ -267,7 +281,7 @@ jobs:
run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db
continue-on-error: true
- name: Upload artifacts
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: dmg-${{ matrix.output-suffix }}
path: |
@@ -276,7 +290,7 @@ jobs:
if-no-files-found: error
- name: Publish dmg on GitHub Releases
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published'
- uses: softprops/action-gh-release@v2
+ uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
with:
fail_on_unmatched_files: true
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
diff --git a/.github/workflows/no-response.yml b/.github/workflows/no-response.yml
index 43c634e20..9da0bfbc6 100644
--- a/.github/workflows/no-response.yml
+++ b/.github/workflows/no-response.yml
@@ -12,7 +12,7 @@ jobs:
issues: write
pull-requests: write
steps:
- - uses: actions/stale@v9
+ - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
with:
days-before-stale: 14
days-before-close: 0
diff --git a/.github/workflows/post-publish.yml b/.github/workflows/post-publish.yml
index eaa6fb3f4..14b115f02 100644
--- a/.github/workflows/post-publish.yml
+++ b/.github/workflows/post-publish.yml
@@ -19,14 +19,14 @@ jobs:
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- name: Publish asc on GitHub Releases
- uses: softprops/action-gh-release@v2
+ uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
with:
fail_on_unmatched_files: true
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
files: |
cryptomator-*.tar.gz.asc
- name: Slack Notification
- uses: rtCamp/action-slack-notify@v2
+ uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_USERNAME: 'Cryptobot'
diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml
index 28ab593cd..cfb013c05 100644
--- a/.github/workflows/pullrequest.yml
+++ b/.github/workflows/pullrequest.yml
@@ -5,7 +5,7 @@ on:
env:
JAVA_DIST: 'temurin'
- JAVA_VERSION: 24
+ JAVA_VERSION: 25
defaults:
run:
@@ -16,11 +16,11 @@ jobs:
name: Compile and Test
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-java@v4
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+ - uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
cache: 'maven'
- name: Build and Test
- run: xvfb-run mvn -B clean install jacoco:report -Pcoverage -Djavafx.platform=linux
\ No newline at end of file
+ run: xvfb-run mvn -B clean install jacoco:report -Pcoverage
\ No newline at end of file
diff --git a/.github/workflows/release-check.yml b/.github/workflows/release-check.yml
index 448d9a5a4..e6c12a2d1 100644
--- a/.github/workflows/release-check.yml
+++ b/.github/workflows/release-check.yml
@@ -12,16 +12,16 @@ defaults:
env:
JAVA_DIST: 'temurin'
- JAVA_VERSION: 23
+ JAVA_VERSION: 25
jobs:
check-preconditions:
name: Validate commits pushed to release/hotfix branch to fulfill release requirements
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Setup Java
- uses: actions/setup-java@v4
+ uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
@@ -49,7 +49,7 @@ jobs:
exit 1
fi
- name: Cache NVD DB
- uses: actions/cache@v4
+ uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: ~/.m2/repository/org/owasp/dependency-check-data/
key: dependency-check-${{ github.run_id }}
@@ -60,6 +60,6 @@ jobs:
- name: Run org.owasp:dependency-check plugin
id: dependency-check
continue-on-error: true
- run: mvn -B verify -Pdependency-check -DskipTests -Djavafx.platform=linux
+ run: mvn -B verify -Pdependency-check -DskipTests
env:
NVD_API_KEY: ${{ secrets.NVD_API_KEY }}
\ No newline at end of file
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
index 9a14cbe23..1a2dd28af 100644
--- a/.github/workflows/stale.yml
+++ b/.github/workflows/stale.yml
@@ -12,7 +12,7 @@ jobs:
issues: write
pull-requests: write
steps:
- - uses: actions/stale@v9
+ - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
with:
days-before-stale: 365
days-before-close: 90
diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml
index 2045fc6da..6dd12982a 100644
--- a/.github/workflows/win-exe.yml
+++ b/.github/workflows/win-exe.yml
@@ -26,8 +26,8 @@ on:
env:
- OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/24.0.1/openjfx-24.0.1_windows-x64_bin-jmods.zip'
- OPENJFX_JMODS_AMD64_HASH: 'f13d17c7caf88654fc835f1b4e75a9b0f34a888eb8abef381796c0002e63b03f'
+ OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_windows-x64_bin-jmods.zip'
+ OPENJFX_JMODS_AMD64_HASH: 'c8eb9fd039b00e0020cf6c3db8ed7876bf3ee4d27860aa697a247b83b8296ae7'
WINFSP_MSI: 'https://github.com/winfsp/winfsp/releases/download/v2.1/winfsp-2.1.25156.msi'
WINFSP_MSI_HASH: '073a70e00f77423e34bed98b86e600def93393ba5822204fac57a29324db9f7a'
WINFSP_UNINSTALLER: 'https://github.com/cryptomator/winfsp-uninstaller/releases/latest/download/winfsp-uninstaller.exe'
@@ -51,21 +51,13 @@ jobs:
include:
- arch: x64
os: windows-latest
- java-dist: 'zulu'
- java-version: '24.0.1+9'
+ java-dist: 'zulu' #TODO: is finally temurin possible?
+ java-version: '25.0.0+36'
java-package: 'jdk'
- - arch: arm64
- os: windows-11-arm
- java-dist: 'liberica'
- java-version: '24.0.1+11'
- java-package: 'jdk+fx' #This is needed, as liberica contains JFX 24 Jmods for Windows ARM64
- env:
- LOOPBACK_ALIAS: 'cryptomator-vault'
- WIN_CONSOLE_FLAG: ''
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Setup Java
- uses: actions/setup-java@v4
+ uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
with:
distribution: ${{ matrix.java-dist }}
java-version: ${{ matrix.java-version }}
@@ -105,7 +97,7 @@ jobs:
- name: Set version
run: mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
- name: Run maven
- run: mvn -B clean package -Pwin -DskipTests -Djavafx.platform=win
+ run: mvn -B clean package -Pwin -DskipTests
- name: Patch target dir
run: |
cp LICENSE.txt target
@@ -125,7 +117,7 @@ jobs:
--verbose
--output runtime
--module-path "${{ steps.jep-493-check.outputs.jmod_paths }}"
- --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.mscapi,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler
+ --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.crypto.mscapi,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler
--strip-native-commands
--no-header-files
--no-man-pages
@@ -147,7 +139,6 @@ jobs:
--app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}"
--java-options "--enable-preview"
--java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.win,org.cryptomator.integrations.win"
- --java-options "--sun-misc-unsafe-memory-access=allow"
--java-options "-Xss5m"
--java-options "-Xmx256m"
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
@@ -159,29 +150,20 @@ jobs:
--java-options "-Dcryptomator.p12Path=\"@{appdata}/Cryptomator/key.p12;@{userhome}/AppData/Roaming/Cryptomator/key.p12\""
--java-options "-Dcryptomator.ipcSocketPath=\"@{localappdata}/Cryptomator/ipc.socket\""
--java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/Cryptomator\""
- --java-options "-Dcryptomator.loopbackAlias=\"${{ env.LOOPBACK_ALIAS }}\""
+ --java-options "-Dcryptomator.loopbackAlias=\"cryptomator-vault\""
--java-options "-Dcryptomator.showTrayIcon=true"
--java-options "-Dcryptomator.buildNumber=\"msi-${{ needs.get-version.outputs.revNum }}\""
--java-options "-Dcryptomator.integrationsWin.autoStartShellLinkName=\"Cryptomator\""
--java-options "-Dcryptomator.integrationsWin.keychainPaths=\"@{appdata}/Cryptomator/keychain.json;@{userhome}/AppData/Roaming/Cryptomator/keychain.json\""
--java-options "-Dcryptomator.integrationsWin.windowsHelloKeychainPaths=\"@{appdata}/Cryptomator/windowsHelloKeychain.json\""
- --java-options "-Djavafx.verbose=${{ inputs.isDebug }}"
+ --java-options "-Dcryptomator.disableUpdateCheck=false"
+ --java-options "-XX:ErrorFile=C:/cryptomator/cryptomator_crash.log"
--resource-dir dist/win/resources
--icon dist/win/resources/Cryptomator.ico
--add-launcher "Cryptomator (Debug)=dist/win/debug-launcher.properties"
- name: Patch Application Directory
run: |
cp dist/win/contrib/* appdir/Cryptomator
- - name: Set LOOPBACK_ALIAS in patchWebDAV.bat
- shell: pwsh
- run: |
- $patchScript = "appdir\Cryptomator\patchWebDAV.bat"
- try {
- (Get-Content $patchScript ) -replace '::REPLACE ME', "SET LOOPBACK_ALIAS=`"${{ env.LOOPBACK_ALIAS}}`"" | Set-Content $patchScript
- } catch {
- Write-Host "Failed to set LOOPBACK_ALIAS for patchWebDAV.bat"
- exit 1
- }
- name: Fix permissions
run: |
attrib -r appdir/Cryptomator/Cryptomator.exe
@@ -212,7 +194,7 @@ jobs:
Get-ChildItem -Recurse -Path "jpackage-jmod" -File wixhelper.dll | Select-Object -Last 1 | Copy-Item -Destination "appdir"
- name: Sign DLLs with Actalis CodeSigner
if: inputs.sign || github.event_name == 'release'
- uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f
+ uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f # no specific version
with:
base-dir: 'appdir'
file-extensions: 'dll,exe,ps1'
@@ -237,7 +219,7 @@ jobs:
}
- name: Generate license for MSI
run: >
- mvn -B license:add-third-party "-Djavafx.platform=win"
+ mvn -B license:add-third-party
"-Dlicense.thirdPartyFilename=license.rtf"
"-Dlicense.outputDirectory=dist/win/resources"
"-Dlicense.fileTemplate=dist/win/resources/licenseTemplate.ftl"
@@ -271,7 +253,7 @@ jobs:
JP_WIXHELPER_DIR: ${{ github.workspace }}\appdir
- name: Sign msi with Actalis CodeSigner
if: inputs.sign || github.event_name == 'release'
- uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f
+ uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f # no specific version
with:
base-dir: 'installer'
file-extensions: 'msi'
@@ -289,7 +271,7 @@ jobs:
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- name: Upload artifacts
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: msi-${{ matrix.arch }}
path: |
@@ -310,28 +292,22 @@ jobs:
java-dist: 'zulu'
java-version: '24.0.1+9'
java-package: 'jdk'
- - arch: arm64
- os: windows-11-arm
- executable-suffix: arm64
- java-dist: 'liberica'
- java-version: '24.0.1+11'
- java-package: 'jdk+fx' #This is needed, as liberica contains JFX 24 Jmods for Windows ARM64
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Install wix and extensions
run: |
dotnet tool install --global wix --version 6.0.0
wix.exe extension add WixToolset.BootstrapperApplications.wixext/6.0.0 --global
wix.exe extension add WixToolset.Util.wixext/6.0.0 --global
- name: Download .msi
- uses: actions/download-artifact@v4
+ uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
name: msi-${{ matrix.arch }}
path: dist/win/bundle/resources
- name: Strip version info from msi file name
run: mv dist/win/bundle/resources/Cryptomator*.msi dist/win/bundle/resources/Cryptomator.msi
- name: Setup Java
- uses: actions/setup-java@v4
+ uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
with:
distribution: ${{ matrix.java-dist }}
java-version: ${{ matrix.java-version }}
@@ -340,7 +316,7 @@ jobs:
cache: 'maven'
- name: Generate license for exe
run: >
- mvn -B license:add-third-party "-Djavafx.platform=win"
+ mvn -B license:add-third-party
"-Dlicense.thirdPartyFilename=license.rtf"
"-Dlicense.fileTemplate=dist/win/bundle/resources/licenseTemplate.ftl"
"-Dlicense.outputDirectory=dist/win/bundle/resources"
@@ -383,7 +359,7 @@ jobs:
wix burn detach installer/unsigned/Cryptomator-Installer.exe -engine tmp/engine.exe
- name: Sign burn engine with Actalis CodeSigner
if: inputs.sign || github.event_name == 'release'
- uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f
+ uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f # no specific version
with:
base-dir: 'tmp'
file-extensions: 'exe'
@@ -396,7 +372,7 @@ jobs:
wix burn reattach installer/unsigned/Cryptomator-Installer.exe -engine tmp/engine.exe -o installer/Cryptomator-Installer.exe
- name: Sign installer with Actalis CodeSigner
if: inputs.sign || github.event_name == 'release'
- uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f
+ uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f # no specific version
with:
base-dir: 'installer'
file-extensions: 'exe'
@@ -414,7 +390,7 @@ jobs:
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- name: Upload artifacts
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: exe-${{ matrix.executable-suffix }}
path: |
@@ -429,26 +405,22 @@ jobs:
needs: [ build-msi, build-exe ]
outputs:
download-url-msi-x64: ${{ fromJSON(steps.publish.outputs.assets)[0].browser_download_url }}
- download-url-msi-arm64: ${{ fromJSON(steps.publish.outputs.assets)[1].browser_download_url }}
download-url-exe-x64: ${{ fromJSON(steps.publish.outputs.assets)[2].browser_download_url }}
- download-url-exe-arm64: ${{ fromJSON(steps.publish.outputs.assets)[3].browser_download_url }}
steps:
- name: Download installers
- uses: actions/download-artifact@v4
+ uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
merge-multiple: true
- name: Publish installers on GitHub Releases
id: publish
- uses: softprops/action-gh-release@v2
+ uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
with:
fail_on_unmatched_files: true
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
# do not change ordering of filelist, required for correct job output
files: |
*x64.msi
- *arm64.msi
*x64.exe
- *arm64.exe
*.asc
allowlist-msi-x64:
@@ -458,13 +430,6 @@ jobs:
url: ${{ needs.publish.outputs.download-url-msi-x64 }}
secrets: inherit
- allowlist-msi-arm64:
- uses: ./.github/workflows/av-whitelist.yml
- needs: [ publish ]
- with:
- url: ${{ needs.publish.outputs.download-url-msi-arm64 }}
- secrets: inherit
-
allowlist-exe-x64:
uses: ./.github/workflows/av-whitelist.yml
needs: [ publish, allowlist-msi-x64 ]
@@ -472,13 +437,6 @@ jobs:
url: ${{ needs.publish.outputs.download-url-exe-x64 }}
secrets: inherit
- allowlist-exe-arm64:
- uses: ./.github/workflows/av-whitelist.yml
- needs: [ publish, allowlist-msi-arm64 ]
- with:
- url: ${{ needs.publish.outputs.download-url-exe-arm64 }}
- secrets: inherit
-
notify-winget:
name: Notify for winget-release
if: needs.get-version.outputs.versionType == 'stable'
@@ -486,7 +444,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Slack Notification
- uses: rtCamp/action-slack-notify@v2
+ uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_USERNAME: 'Cryptobot'
diff --git a/.github/workflows/winget.yml b/.github/workflows/winget.yml
index 0beb75544..5ab150229 100644
--- a/.github/workflows/winget.yml
+++ b/.github/workflows/winget.yml
@@ -18,7 +18,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
- name: Submit package
- uses: vedantmgoyal2009/winget-releaser@main
+ uses: vedantmgoyal2009/winget-releaser@19e706d4c9121098010096f9c495a70a7518b30f # no_specific_version
with:
identifier: Cryptomator.Cryptomator
version: ${{ inputs.tag }}
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 29885bf6a..28d93ef1d 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -14,15 +14,15 @@
-
-
+
+
-
+
-
-
+
+
@@ -39,15 +39,16 @@
-
-
+
+
+
-
+
-
-
+
+
@@ -64,6 +65,7 @@
+
diff --git a/.idea/misc.xml b/.idea/misc.xml
index cbe05c79b..52746672a 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -8,7 +8,7 @@
-
+
\ No newline at end of file
diff --git a/dist/linux/appimage/build.sh b/dist/linux/appimage/build.sh
index 8d45739cb..947fe56c4 100755
--- a/dist/linux/appimage/build.sh
+++ b/dist/linux/appimage/build.sh
@@ -19,16 +19,16 @@ if [[ ! "${CPU_ARCH}" =~ x86_64|aarch64 ]]; then echo "Platform ${CPU_ARCH} not
mvn -f ../../../pom.xml versions:set -DnewVersion=${SEMVER_STR}
# compile
-mvn -B -f ../../../pom.xml clean package -Plinux -DskipTests -Djavafx.platform=linux
+mvn -B -f ../../../pom.xml clean package -Plinux -DskipTests
cp ../../../LICENSE.txt ../../../target
cp ../../../target/cryptomator-*.jar ../../../target/mods
-JAVAFX_VERSION=24.0.1
+JAVAFX_VERSION=25
JAVAFX_ARCH="x64"
-JAVAFX_JMODS_SHA256='425fac742b9fbd095b2ce868cff82d1024620f747c94a7144d0a4879e756146c'
+JAVAFX_JMODS_SHA256='96e520f48610d8ffb94ca30face1f11ffe8a977ddc1c4ff80b1a9e9f048bd94e'
if [ "${CPU_ARCH}" = "aarch64" ]; then
JAVAFX_ARCH="aarch64"
- JAVAFX_JMODS_SHA256='7e02edd0f4ee5527a27c94b0bbba66fcaaff41009119e45d0eca0f96ddfb6e7b'
+ JAVAFX_JMODS_SHA256='951c52481af0ec5885b06f1ebaa8a10da7e8ea23c5e1ef3e2f6f11fa1b3a7ce1'
fi
# download javaFX jmods
@@ -62,7 +62,7 @@ ${JAVA_HOME}/bin/jlink \
--verbose \
--output runtime \
--module-path "${JMOD_PATHS}" \
- --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net,java.compiler \
+ --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net,java.compiler \
--strip-native-commands \
--no-header-files \
--no-man-pages \
@@ -98,6 +98,7 @@ ${JAVA_HOME}/bin/jpackage \
--java-options "-Dcryptomator.integrationsLinux.trayIconsDir=\"@{appdir}/usr/share/icons/hicolor/symbolic/apps\"" \
--java-options "-Dcryptomator.buildNumber=\"appimage-${REVISION_NO}\"" \
--java-options "-Dcryptomator.networking.truststore.p12Path=\"/etc/cryptomator/certs.p12\"" \
+ --java-options "-XX:ErrorFile=/cryptomator/cryptomator_crash.log" \
--resource-dir ../resources
# transform AppDir
diff --git a/dist/linux/debian/control b/dist/linux/debian/control
index b0c2c6725..713f03972 100644
--- a/dist/linux/debian/control
+++ b/dist/linux/debian/control
@@ -2,7 +2,7 @@ Source: cryptomator
Maintainer: Cryptobot
Section: utils
Priority: optional
-Build-Depends: debhelper (>=10), coffeelibs-jdk-24 (>= 24.0.1+9-0ppa3), libgtk-3-0, libxxf86vm1, libgl1
+Build-Depends: debhelper (>=10), openjdk-25-jdk (>= 25+36), libgtk-3-0 (>= 3.20.0), libxxf86vm1, libgl1
Standards-Version: 4.5.0
Homepage: https://cryptomator.org
Vcs-Git: https://github.com/cryptomator/cryptomator.git
@@ -12,7 +12,7 @@ Package: cryptomator
Architecture: any
Section: utils
Priority: optional
-Depends: ${shlibs:Depends}, ${misc:Depends}, fuse3
+Depends: ${shlibs:Depends}, ${misc:Depends}, fuse3, libgtk-3-0 (>= 3.20.0)
Recommends: gvfs-backends, gvfs-fuse, gnome-keyring
XB-AppName: Cryptomator
XB-Category: Utility;Security;FileTools;
diff --git a/dist/linux/debian/rules b/dist/linux/debian/rules
index f69f4a789..456a97e89 100755
--- a/dist/linux/debian/rules
+++ b/dist/linux/debian/rules
@@ -4,11 +4,12 @@
# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1
-JAVA_HOME = /usr/lib/jvm/java-24-coffeelibs
DEB_BUILD_ARCH ?= $(shell dpkg-architecture -qDEB_BUILD_ARCH)
ifeq ($(DEB_BUILD_ARCH),amd64)
+JAVA_HOME = /usr/lib/jvm/java-25-openjdk-amd64
JMODS_PATH = jmods/amd64:${JAVA_HOME}/jmods
else ifeq ($(DEB_BUILD_ARCH),arm64)
+JAVA_HOME = /usr/lib/jvm/java-25-openjdk-arm64
JMODS_PATH = jmods/aarch64:${JAVA_HOME}/jmods
endif
@@ -28,7 +29,7 @@ override_dh_auto_build:
$(JAVA_HOME)/bin/jlink \
--output runtime \
--module-path "${JMODS_PATH}" \
- --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net,java.compiler \
+ --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net,java.compiler \
--strip-native-commands \
--no-header-files \
--no-man-pages \
@@ -45,7 +46,6 @@ override_dh_auto_build:
--vendor "Skymatic GmbH" \
--java-options "--enable-preview" \
--java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator" \
- --java-options "--sun-misc-unsafe-memory-access=allow" \
--copyright "(C) 2016 - 2025 Skymatic GmbH" \
--java-options "-Xss5m" \
--java-options "-Xmx256m" \
diff --git a/dist/mac/.gitignore b/dist/mac/.gitignore
index bd6569978..81a0afdc2 100644
--- a/dist/mac/.gitignore
+++ b/dist/mac/.gitignore
@@ -1 +1,2 @@
embedded.provisionprofile
+xcuserdata/
diff --git a/dist/mac/DockTilePlugin/CryptomatorDockTilePlugin.swift b/dist/mac/DockTilePlugin/CryptomatorDockTilePlugin.swift
new file mode 100644
index 000000000..1b54ea17d
--- /dev/null
+++ b/dist/mac/DockTilePlugin/CryptomatorDockTilePlugin.swift
@@ -0,0 +1,19 @@
+//
+// CryptomatorDockTilePlugin.swift
+// Integrations
+//
+// Created by Tobias Hagemann on 22.09.25.
+// Copyright © 2025 Cryptomator. All rights reserved.
+//
+
+import AppKit
+
+class CryptomatorDockTilePlugin: NSObject, NSDockTilePlugIn {
+ func setDockTile(_ dockTile: NSDockTile?) {
+ guard let dockTile = dockTile, let image = Bundle(for: Self.self).image(forResource: "Cryptomator") else {
+ return
+ }
+ dockTile.contentView = NSImageView(image: image)
+ dockTile.display()
+ }
+}
diff --git a/dist/mac/DockTilePlugin/DockTilePlugin.xcodeproj/project.pbxproj b/dist/mac/DockTilePlugin/DockTilePlugin.xcodeproj/project.pbxproj
new file mode 100644
index 000000000..fa7ec5b77
--- /dev/null
+++ b/dist/mac/DockTilePlugin/DockTilePlugin.xcodeproj/project.pbxproj
@@ -0,0 +1,314 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 77;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 74E08DE12E8584DE007E665C /* CryptomatorDockTilePlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74E08DE02E85847E007E665C /* CryptomatorDockTilePlugin.swift */; };
+ 74E08DED2E858532007E665C /* Cryptomator.icns in Resources */ = {isa = PBXBuildFile; fileRef = 74E08DEC2E858532007E665C /* Cryptomator.icns */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ 74E08DD92E858467007E665C /* Cryptomator.docktileplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Cryptomator.docktileplugin; sourceTree = BUILT_PRODUCTS_DIR; };
+ 74E08DE02E85847E007E665C /* CryptomatorDockTilePlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptomatorDockTilePlugin.swift; sourceTree = ""; };
+ 74E08DEC2E858532007E665C /* Cryptomator.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = Cryptomator.icns; path = ../resources/Cryptomator.icns; sourceTree = SOURCE_ROOT; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 74E08DD62E858467007E665C /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 74E08DD02E858467007E665C = {
+ isa = PBXGroup;
+ children = (
+ 74E08DE02E85847E007E665C /* CryptomatorDockTilePlugin.swift */,
+ 74E08DEC2E858532007E665C /* Cryptomator.icns */,
+ 74E08DDA2E858467007E665C /* Products */,
+ );
+ sourceTree = "";
+ };
+ 74E08DDA2E858467007E665C /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 74E08DD92E858467007E665C /* Cryptomator.docktileplugin */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 74E08DD82E858467007E665C /* DockTilePlugin */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 74E08DDD2E858467007E665C /* Build configuration list for PBXNativeTarget "DockTilePlugin" */;
+ buildPhases = (
+ 74E08DD52E858467007E665C /* Sources */,
+ 74E08DD62E858467007E665C /* Frameworks */,
+ 74E08DD72E858467007E665C /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = DockTilePlugin;
+ packageProductDependencies = (
+ );
+ productName = DockTilePlugin;
+ productReference = 74E08DD92E858467007E665C /* Cryptomator.docktileplugin */;
+ productType = "com.apple.product-type.bundle";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 74E08DD12E858467007E665C /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ BuildIndependentTargetsInParallel = 1;
+ LastUpgradeCheck = 2600;
+ ORGANIZATIONNAME = Cryptomator;
+ TargetAttributes = {
+ 74E08DD82E858467007E665C = {
+ CreatedOnToolsVersion = 26.0.1;
+ };
+ };
+ };
+ buildConfigurationList = 74E08DD42E858467007E665C /* Build configuration list for PBXProject "DockTilePlugin" */;
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 74E08DD02E858467007E665C;
+ minimizedProjectReferenceProxies = 1;
+ preferredProjectObjectVersion = 77;
+ productRefGroup = 74E08DDA2E858467007E665C /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 74E08DD82E858467007E665C /* DockTilePlugin */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 74E08DD72E858467007E665C /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 74E08DED2E858532007E665C /* Cryptomator.icns in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 74E08DD52E858467007E665C /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 74E08DE12E8584DE007E665C /* CryptomatorDockTilePlugin.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+ 74E08DDB2E858467007E665C /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ DEVELOPMENT_TEAM = YZQJQUHA3L;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MACOSX_DEPLOYMENT_TARGET = 11.5;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = macosx;
+ SWIFT_VERSION = 5.0;
+ };
+ name = Debug;
+ };
+ 74E08DDC2E858467007E665C /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ DEVELOPMENT_TEAM = YZQJQUHA3L;
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MACOSX_DEPLOYMENT_TARGET = 11.5;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ SDKROOT = macosx;
+ SWIFT_VERSION = 5.0;
+ };
+ name = Release;
+ };
+ 74E08DDE2E858467007E665C /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_STYLE = Manual;
+ COMBINE_HIDPI_IMAGES = YES;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = "";
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Cryptomator. All rights reserved.";
+ INFOPLIST_KEY_NSPrincipalClass = CryptomatorDockTilePlugin;
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles";
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = org.cryptomator.DockTilePlugin;
+ PRODUCT_NAME = Cryptomator;
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SKIP_INSTALL = YES;
+ STRING_CATALOG_GENERATE_SYMBOLS = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ WRAPPER_EXTENSION = docktileplugin;
+ };
+ name = Debug;
+ };
+ 74E08DDF2E858467007E665C /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_STYLE = Manual;
+ COMBINE_HIDPI_IMAGES = YES;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = "";
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Cryptomator. All rights reserved.";
+ INFOPLIST_KEY_NSPrincipalClass = CryptomatorDockTilePlugin;
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles";
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = org.cryptomator.DockTilePlugin;
+ PRODUCT_NAME = Cryptomator;
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SKIP_INSTALL = YES;
+ STRING_CATALOG_GENERATE_SYMBOLS = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ WRAPPER_EXTENSION = docktileplugin;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 74E08DD42E858467007E665C /* Build configuration list for PBXProject "DockTilePlugin" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 74E08DDB2E858467007E665C /* Debug */,
+ 74E08DDC2E858467007E665C /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 74E08DDD2E858467007E665C /* Build configuration list for PBXNativeTarget "DockTilePlugin" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 74E08DDE2E858467007E665C /* Debug */,
+ 74E08DDF2E858467007E665C /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 74E08DD12E858467007E665C /* Project object */;
+}
diff --git a/dist/mac/DockTilePlugin/DockTilePlugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/dist/mac/DockTilePlugin/DockTilePlugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 000000000..919434a62
--- /dev/null
+++ b/dist/mac/DockTilePlugin/DockTilePlugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/dist/mac/DockTilePlugin/DockTilePlugin.xcodeproj/xcshareddata/xcschemes/DockTilePlugin.xcscheme b/dist/mac/DockTilePlugin/DockTilePlugin.xcodeproj/xcshareddata/xcschemes/DockTilePlugin.xcscheme
new file mode 100644
index 000000000..7d86bdcc5
--- /dev/null
+++ b/dist/mac/DockTilePlugin/DockTilePlugin.xcodeproj/xcshareddata/xcschemes/DockTilePlugin.xcscheme
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/mac/dmg/build.sh b/dist/mac/dmg/build.sh
index c018c2d33..26673589b 100755
--- a/dist/mac/dmg/build.sh
+++ b/dist/mac/dmg/build.sh
@@ -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=24.0.1
+JAVAFX_VERSION=25
JAVAFX_ARCH="undefined"
JAVAFX_JMODS_SHA256="undefined"
if [ "$(machine)" = "arm64e" ]; then
JAVAFX_ARCH="aarch64"
- JAVAFX_JMODS_SHA256="b5a94a13077507003fa852512bfa33f4fb680bc8076d8002e4227a84c85171d4"
+ JAVAFX_JMODS_SHA256="13f8c0513c40c95881479fbcf0465a29a60217393fb0656f5e4eab78a9442fba"
else
JAVAFX_ARCH="x64"
- JAVAFX_JMODS_SHA256="6e62a426d43c168a488521f904a523f3dd6ee2cf103e08136f2fd465c828a105"
+ JAVAFX_JMODS_SHA256="0eba73fb28a24c845175d16fa2f8c081c936ce6de1be9b79eb6119fa32e53d52"
fi
JAVAFX_JMODS_URL="https://download2.gluonhq.com/openjfx/${JAVAFX_VERSION}/openjfx-${JAVAFX_VERSION}_osx-${JAVAFX_ARCH}_bin-jmods.zip"
@@ -71,7 +71,7 @@ if [ "${POM_JFX_VERSION}" -ne "${JMOD_VERSION}" ]; then
fi
# compile
-mvn -B -Djavafx.platform=mac -f../../../pom.xml clean package -DskipTests -Pmac
+mvn -B -f../../../pom.xml clean package -DskipTests -Pmac
cp ../../../LICENSE.txt ../../../target
cp ../../../target/${MAIN_JAR_GLOB} ../../../target/mods
@@ -85,7 +85,7 @@ fi
${JAVA_HOME}/bin/jlink \
--output runtime \
--module-path "${JMOD_PATHS}" \
- --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,java.compiler \
+ --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,java.compiler \
--strip-native-commands \
--no-header-files \
--no-man-pages \
@@ -115,6 +115,7 @@ ${JAVA_HOME}/bin/jpackage \
--java-options "-Dsun.java2d.metal=true" \
--java-options "-Dcryptomator.appVersion=\"${VERSION_NO}\"" \
--java-options "-Dcryptomator.logDir=\"@{userhome}/Library/Logs/${APP_NAME}\"" \
+ --java-options "-XX:ErrorFile=/cryptomator/cryptomator_crash.log" \
--java-options "-Dcryptomator.pluginDir=\"@{userhome}/Library/Application Support/${APP_NAME}/Plugins\"" \
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/Library/Application Support/${APP_NAME}/settings.json\"" \
--java-options "-Dcryptomator.ipcSocketPath=\"@{userhome}/Library/Application Support/${APP_NAME}/ipc.socket\"" \
@@ -132,8 +133,21 @@ sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NO}|g" ${APP_NAME}.app/
sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NO}|g" ${APP_NAME}.app/Contents/Info.plist
cp ../embedded.provisionprofile ${APP_NAME}.app/Contents/
+# build and install dock tile plugin
+echo "Building and installing Cryptomator.docktileplugin..."
+DERIVED_DATA_PATH=../DockTilePlugin/build
+xcodebuild -project ../DockTilePlugin/DockTilePlugin.xcodeproj \
+ -scheme DockTilePlugin \
+ -configuration Release \
+ -derivedDataPath ${DERIVED_DATA_PATH} \
+ -quiet \
+ clean build
+mkdir -p ${APP_NAME}.app/Contents/PlugIns
+cp -R ${DERIVED_DATA_PATH}/Build/Products/Release/Cryptomator.docktileplugin ${APP_NAME}.app/Contents/PlugIns/
+rm -rf ${DERIVED_DATA_PATH}
+
# generate license
-mvn -B -Djavafx.platform=mac -f../../../pom.xml license:add-third-party \
+mvn -B -f../../../pom.xml license:add-third-party \
-Dlicense.thirdPartyFilename=license.rtf \
-Dlicense.outputDirectory=dist/mac/dmg/resources \
-Dlicense.fileTemplate=resources/licenseTemplate.ftl \
diff --git a/dist/mac/resources/Info.plist b/dist/mac/resources/Info.plist
index 21641c708..d59c3fe73 100644
--- a/dist/mac/resources/Info.plist
+++ b/dist/mac/resources/Info.plist
@@ -116,5 +116,8 @@
NSSupportsAutomaticGraphicsSwitching
+
+ NSDockTilePlugIn
+ Cryptomator.docktileplugin
diff --git a/dist/win/build.ps1 b/dist/win/build.ps1
index b1706a28e..6fdee1c9a 100644
--- a/dist/win/build.ps1
+++ b/dist/win/build.ps1
@@ -65,7 +65,7 @@ Write-Host "`$Env:JAVA_HOME=$Env:JAVA_HOME"
$copyright = "(C) $CopyrightStartYear - $((Get-Date).Year) $Vendor"
# compile
-&mvn -B -f $buildDir/../../pom.xml clean package -DskipTests -Pwin "-Djavafx.platform=win"
+&mvn -B -f $buildDir/../../pom.xml clean package -DskipTests -Pwin
Copy-Item "$buildDir\..\..\target\$MainJarGlob.jar" -Destination "$buildDir\..\..\target\mods"
# add runtime
@@ -86,16 +86,16 @@ switch ($archName) {
'ARM64' {
$javafxBaseJmod = Join-Path $Env:JAVA_HOME "jmods\javafx.base.jmod"
if (!(Test-Path $javafxBaseJmod)) {
- Write-Error "JavaFX module not found in JDK. Please ensure full JDK (including jmods) is installed."
+ Write-Error "JavaFX module not found in JDK. Please ensure a JDK with JavaFX (including jmods) is installed."
exit 1
}
$jmodPaths = "$Env:JAVA_HOME/jmods"
}
'x64' {
- $javaFxVersion='24.0.1'
+ $javaFxVersion='25'
$javaFxJmodsUrl = "https://download2.gluonhq.com/openjfx/${javaFxVersion}/openjfx-${javaFxVersion}_windows-x64_bin-jmods.zip"
- $javaFxJmodsSHA256 = 'f13d17c7caf88654fc835f1b4e75a9b0f34a888eb8abef381796c0002e63b03f'
+ $javaFxJmodsSHA256 = 'c8eb9fd039b00e0020cf6c3db8ed7876bf3ee4d27860aa697a247b83b8296ae7'
$javaFxJmods = '.\resources\jfxJmods.zip'
if( !(Test-Path -Path $javaFxJmods) ) {
@@ -133,7 +133,7 @@ if ((& "$Env:JAVA_HOME\bin\jlink" --help | Select-String -Pattern "Linking from
--verbose `
--output runtime `
--module-path $jmodPaths `
- --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.accessibility,jdk.management.jfr,jdk.crypto.mscapi,java.compiler,javafx.base,javafx.graphics,javafx.controls,javafx.fxml `
+ --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.accessibility,jdk.management.jfr,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.crypto.mscapi,java.compiler,javafx.base,javafx.graphics,javafx.controls,javafx.fxml `
--strip-native-commands `
--no-header-files `
--no-man-pages `
@@ -154,6 +154,7 @@ $javaOptions = @(
"--java-options", "-Dfile.encoding=`"utf-8`""
"--java-options", "-Djava.net.useSystemProxies=true"
"--java-options", "-Dcryptomator.logDir=`"@{localappdata}/$AppName`""
+"--java-options", "-XX:ErrorFile=`"C:/cryptomator/cryptomator_crash.log`""
"--java-options", "-Dcryptomator.pluginDir=`"@{appdata}/$AppName/Plugins`""
"--java-options", "-Dcryptomator.settingsPath=`"@{appdata}/$AppName/settings.json;@{userhome}/AppData/Roaming/$AppName/settings.json`""
"--java-options", "-Dcryptomator.ipcSocketPath=`"@{localappdata}/$AppName/ipc.socket`""
@@ -165,6 +166,7 @@ $javaOptions = @(
"--java-options", "-Dcryptomator.integrationsWin.windowsHelloKeychainPaths=`"@{appdata}/$AppName/windowsHelloKeychain.json`""
"--java-options", "-Dcryptomator.showTrayIcon=true"
"--java-options", "-Dcryptomator.buildNumber=`"msi-$revisionNo`""
+"--java-options", "-Dcryptomator.disableUpdateCheck=false"
)
@@ -192,7 +194,7 @@ if ($LASTEXITCODE -ne 0) {
}
#Create RTF license for msi
-&mvn -B -f $buildDir/../../pom.xml license:add-third-party "-Djavafx.platform=win" `
+&mvn -B -f $buildDir/../../pom.xml license:add-third-party `
"-Dlicense.thirdPartyFilename=license.rtf" `
"-Dlicense.fileTemplate=$buildDir\resources\licenseTemplate.ftl" `
"-Dlicense.outputDirectory=$buildDir\resources\" `
@@ -205,14 +207,6 @@ if ($LASTEXITCODE -ne 0) {
Copy-Item "contrib\*" -Destination "$AppName"
attrib -r "$AppName\$AppName.exe"
attrib -r "$AppName\${AppName} (Debug).exe"
-# patch batch script to set hostfile
-$webDAVPatcher = "$AppName\patchWebDAV.bat"
-try {
- (Get-Content $webDAVPatcher ) -replace '::REPLACE ME', "SET LOOPBACK_ALIAS=`"$LoopbackAlias`"" | Set-Content $webDAVPatcher
-} catch {
- Write-Host "Failed to set LOOPBACK_ALIAS for patchWebDAV.bat"
- exit 1
-}
# create .msi
$Env:JP_WIXWIZARD_RESOURCES = "$buildDir\resources"
@@ -243,7 +237,7 @@ if ($LASTEXITCODE -ne 0) {
}
#Create RTF license for bundle
-&mvn -B -f $buildDir/../../pom.xml license:add-third-party "-Djavafx.platform=win" `
+&mvn -B -f $buildDir/../../pom.xml license:add-third-party `
"-Dlicense.thirdPartyFilename=license.rtf" `
"-Dlicense.fileTemplate=$buildDir\bundle\resources\licenseTemplate.ftl" `
"-Dlicense.outputDirectory=$buildDir\bundle\resources\" `
@@ -298,9 +292,9 @@ return 0;
# ============================
if ($clean) {
Write-Host "Cleaning up previous build artifacts..."
- Remove-Item -Path ".\runtime" -Force -Recurse -ErrorAction Ignore
- Remove-Item -Path ".\$AppName" -Force -Recurse -ErrorAction Ignore
- Remove-Item -Path ".\installer" -Force -Recurse -ErrorAction Ignore
+ Remove-Item -Path ".\runtime" -Force -Recurse -ErrorAction Ignore -ProgressAction SilentlyContinue
+ Remove-Item -Path ".\$AppName" -Force -Recurse -ErrorAction Ignore -ProgressAction SilentlyContinue
+ Remove-Item -Path ".\installer" -Force -Recurse -ErrorAction Ignore -ProgressAction SilentlyContinue
}
return Main
diff --git a/dist/win/bundle/bundleWithWinfsp.wxs b/dist/win/bundle/bundleWithWinfsp.wxs
index 1c9e7da20..c3bec0ee8 100644
--- a/dist/win/bundle/bundleWithWinfsp.wxs
+++ b/dist/win/bundle/bundleWithWinfsp.wxs
@@ -27,6 +27,8 @@
+
+
@@ -41,7 +43,9 @@ Do you want to continue?"" RepairArgument="-q" UninstallArgument="-s" />
-
+
+
+
diff --git a/dist/win/contrib/patchUpdateCheck.bat b/dist/win/contrib/patchUpdateCheck.bat
new file mode 100644
index 000000000..f93a7ff0a
--- /dev/null
+++ b/dist/win/contrib/patchUpdateCheck.bat
@@ -0,0 +1,18 @@
+@echo off
+:: Batch wrapper for PowerShell script to modify Cryptomator update check settings
+:: This is executed as a Custom Action during MSI installation
+:: This file must be located in the INSTALLDIR
+
+set "DISABLEUPDATECHECK=%~1"
+
+:: Log for debugging
+echo DISABLEUPDATECHECK=%DISABLEUPDATECHECK%
+
+:: Change to INSTALLDIR
+cd %~dp0
+:: Execute the PowerShell script
+powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy RemoteSigned -File ".\patchUpdateCheck.ps1"^
+ -DisableUpdateCheck "%DISABLEUPDATECHECK%"
+
+:: Return the exit code from PowerShell
+exit /b %ERRORLEVEL%
\ No newline at end of file
diff --git a/dist/win/contrib/patchUpdateCheck.ps1 b/dist/win/contrib/patchUpdateCheck.ps1
new file mode 100644
index 000000000..44001d22b
--- /dev/null
+++ b/dist/win/contrib/patchUpdateCheck.ps1
@@ -0,0 +1,58 @@
+# PowerShell script to modify Cryptomator.cfg to set disableUpdateCheck property
+# This script is executed as a Custom Action during MSI installation
+# If the DisableUpdateCheck parameter is set to true, it disables the update check in Cryptomator by modifying the Cryptomator.cfg file.
+# NOTE: This file must be located in the same directory as set in the MSI property INSTALLDIR
+
+param(
+ [Parameter(Mandatory)][string]$DisableUpdateCheck
+)
+
+try {
+ # Log parameters for debugging (visible in MSI verbose logs)
+ Write-Host "DisableUpdateCheck: $DisableUpdateCheck"
+
+ # Parse DisableUpdateCheck value (handle various input formats)
+ $shouldDisable = $false
+ if ($DisableUpdateCheck) {
+ $DisableUpdateCheck = $DisableUpdateCheck.Trim().ToLower()
+ $shouldDisable = ($DisableUpdateCheck -eq 'true') -or ($DisableUpdateCheck -eq '1') -or ($DisableUpdateCheck -eq 'yes')
+ }
+
+ Write-Host "Setting cryptomator.disableUpdateCheck to: $shouldDisable"
+ if (-not $shouldDisable) {
+ Write-Host 'Disable-Update-Check property is by default "false". Skipping config modification.'
+ exit 0
+ }
+
+ # Determine the .cfg file path
+ $cfgDir = Join-Path $PSScriptRoot 'app'
+ $cfgFiles = Get-ChildItem -Path $cfgDir -Filter '*.cfg' -File
+
+ if ($cfgFiles.Count -eq 0) {
+ Write-Error "No .cfg file found in directory: $cfgDir"
+ exit 1
+ }
+
+ foreach ($file in $cfgFiles) {
+ $cfgFile = $file.FullName
+ Write-Host "Modifying configuration file: $cfgFile"
+ # Read the current configuration
+ $content = Get-Content $cfgFile -Raw -ErrorAction Stop
+
+ # Add the new option based on the property value
+ # Use regular expressions substitutions to replace the property
+ $searchExpression = '(?java-options=-Dcryptomator\.disableUpdateCheck)=false'
+ $replacementExpression = '${Prefix}=true'
+ $content = $content -replace $searchExpression,$replacementExpression
+
+ # Write the modified content back
+ Set-Content -Path $cfgFile -Value $content -NoNewline
+ Write-Host "Successfully updated $cfgFile"
+ }
+
+ exit 0
+}
+catch {
+ Write-Error "Error modifying configuration file: $_"
+ exit 1
+}
\ No newline at end of file
diff --git a/dist/win/contrib/patchWebDAV.bat b/dist/win/contrib/patchWebDAV.bat
index 735cfb823..3a90d3de3 100644
--- a/dist/win/contrib/patchWebDAV.bat
+++ b/dist/win/contrib/patchWebDAV.bat
@@ -1,7 +1,18 @@
@echo off
-:: Default values for Cryptomator builds
-::REPLACE ME
+:: Batch wrapper for PowerShell script to adjust Windows network settings for the Cryptomator WebDAVAdapter
+:: This is executed as a Custom Action during MSI installation
+:: This file must be located in the INSTALLDIR
+set "LOOPBACK_ALIAS=%1"
+
+:: Log for debugging
+echo LOOPBACK_ALIAS=%LOOPBACK_ALIAS%
+
+:: Change to INSTALLDIR
cd %~dp0
-powershell -NoLogo -NoProfile -NonInteractive -ExecutionPolicy RemoteSigned -Command .\patchWebDAV.ps1^
- -LoopbackAlias %LOOPBACK_ALIAS%
\ No newline at end of file
+:: Execute the PowerShell script
+powershell -NoLogo -NoProfile -NonInteractive -ExecutionPolicy RemoteSigned -File .\patchWebDAV.ps1^
+ -LoopbackAlias %LOOPBACK_ALIAS%
+
+:: Return the exit code from PowerShell
+exit /b %ERRORLEVEL%
\ No newline at end of file
diff --git a/dist/win/resources/main.wxs b/dist/win/resources/main.wxs
index 5616193a7..1da7be13a 100644
--- a/dist/win/resources/main.wxs
+++ b/dist/win/resources/main.wxs
@@ -26,6 +26,7 @@
+
@@ -126,10 +127,18 @@
+
+
+
+
-
+
+
+
+
+
+
+
diff --git a/dist/win/resources/overrides.wxi b/dist/win/resources/overrides.wxi
index a5580e5f0..1e84f788c 100644
--- a/dist/win/resources/overrides.wxi
+++ b/dist/win/resources/overrides.wxi
@@ -41,7 +41,7 @@ Media Type of the encrypted data files. Default is "application/vnd.cryptomator.
Close Application settings:
- CloseApplicationTarget
-Full name of executable to be checkd in the close application util. Default is "cryptomator.exe"
+Full name of executable to be checked in the close application util. Default is "cryptomator.exe"
Legacy Installation settings:
- SkipCryptomatorLegacyCheck
diff --git a/pom.xml b/pom.xml
index 25c542099..6b461a5f8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -26,7 +26,7 @@
UTF-8
- 24
+ 25
@@ -35,57 +35,46 @@
2.9.0
1.7.0-SNAPSHOT
- 1.5.0
- 1.4.0
+ 1.5.1
+ 1.4.1
1.7.0-SNAPSHOT
- 5.0.5
- 2.0.10
+ 5.1.0
+ 3.0.0
- 3.17.0
- 2.56.2
+ 3.19.0
+ 2.57.2
2.2
- 2.19.1
- 24.0.1
+ 2.20.0
+ 25
4.5.0
- 9.37.3
- 1.5.18
+ 10.5
+ 1.5.19
2.0.17
0.8.1
1.9.0
- 5.13.1
- 5.18.0
+ 5.13.4
+ 5.20.0
3.0
- 26.0.2
- 12.1.3
- 0.8.13
- 2.5.0
+ 26.0.2-1
+ 12.1.5
+ 0.8.14
+ 2.7.0
1.4.0
- 3.14.0
+ 3.14.1
3.3.1
3.8.1
- 3.5.3
+ 3.5.4
3.4.2
-
-
-
-
- org.cryptomator
- webdav-nio-adapter-servlet
- 1.2.9
-
-
-
-
@@ -225,7 +214,7 @@
com.github.ben-manes.caffeine
caffeine
- 3.2.1
+ 3.2.2
@@ -255,7 +244,7 @@
com.google.jimfs
jimfs
- 1.3.0
+ 1.3.1
test
diff --git a/src/main/java/org/cryptomator/common/recovery/BackupRestorer.java b/src/main/java/org/cryptomator/common/recovery/BackupRestorer.java
new file mode 100644
index 000000000..e5afc5112
--- /dev/null
+++ b/src/main/java/org/cryptomator/common/recovery/BackupRestorer.java
@@ -0,0 +1,53 @@
+package org.cryptomator.common.recovery;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.FileTime;
+import java.util.stream.Stream;
+
+import static org.cryptomator.common.Constants.MASTERKEY_BACKUP_SUFFIX;
+
+public final class BackupRestorer {
+
+ private static final Logger LOG = LoggerFactory.getLogger(BackupRestorer.class);
+
+ private BackupRestorer() {}
+
+ public static void restoreIfBackupPresent(Path vaultPath, String filePrefix) {
+ Path targetFile = vaultPath.resolve(filePrefix);
+
+ try (Stream files = Files.list(vaultPath)) {
+ files.filter(file -> isFileMatchingPattern(file.getFileName().toString(), filePrefix))
+ .max((f1, f2) -> {
+ try {
+ FileTime time1 = Files.getLastModifiedTime(f1);
+ FileTime time2 = Files.getLastModifiedTime(f2);
+ return time1.compareTo(time2);
+ } catch (IOException e) {
+ return 0;
+ }
+ })
+ .ifPresent(backupFile -> copyBackupFile(backupFile, targetFile));
+ } catch (IOException e) {
+ LOG.info("Unable to restore backup files in '{}'", vaultPath, e);
+ }
+ }
+
+ private static boolean isFileMatchingPattern(String fileName, String filePrefix) {
+ return fileName.startsWith(filePrefix) && fileName.endsWith(MASTERKEY_BACKUP_SUFFIX);
+ }
+
+ private static void copyBackupFile(Path backupFile, Path configPath) {
+ try {
+ Files.copy(backupFile, configPath, StandardCopyOption.REPLACE_EXISTING);
+ LOG.debug("Backup restored - file: '{}' path: '{}'", backupFile, configPath);
+ } catch (IOException e) {
+ LOG.warn("Unable to copy backup file from '{}' to '{}'", backupFile, configPath, e);
+ }
+ }
+}
diff --git a/src/main/java/org/cryptomator/common/recovery/CryptoFsInitializer.java b/src/main/java/org/cryptomator/common/recovery/CryptoFsInitializer.java
new file mode 100644
index 000000000..359405d15
--- /dev/null
+++ b/src/main/java/org/cryptomator/common/recovery/CryptoFsInitializer.java
@@ -0,0 +1,33 @@
+package org.cryptomator.common.recovery;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+import org.cryptomator.cryptofs.CryptoFileSystemProperties;
+import org.cryptomator.cryptofs.CryptoFileSystemProvider;
+import org.cryptomator.cryptolib.api.Masterkey;
+import org.cryptomator.cryptolib.api.CryptorProvider;
+import org.cryptomator.cryptolib.api.CryptoException;
+import org.cryptomator.cryptolib.api.MasterkeyLoader;
+
+import static org.cryptomator.common.Constants.DEFAULT_KEY_ID;
+
+public final class CryptoFsInitializer {
+
+ private CryptoFsInitializer() {}
+
+ public static void init(Path recoveryPath,
+ Masterkey masterkey,
+ int shorteningThreshold,
+ CryptorProvider.Scheme scheme) throws IOException, CryptoException {
+
+ MasterkeyLoader loader = ignored -> masterkey.copy();
+ CryptoFileSystemProperties fsProps = CryptoFileSystemProperties //
+ .cryptoFileSystemProperties() //
+ .withCipherCombo(scheme) //
+ .withKeyLoader(loader) //
+ .withShorteningThreshold(shorteningThreshold) //
+ .build();
+ CryptoFileSystemProvider.initialize(recoveryPath, fsProps, DEFAULT_KEY_ID);
+ }
+}
diff --git a/src/main/java/org/cryptomator/common/recovery/MasterkeyService.java b/src/main/java/org/cryptomator/common/recovery/MasterkeyService.java
new file mode 100644
index 000000000..7d487ec54
--- /dev/null
+++ b/src/main/java/org/cryptomator/common/recovery/MasterkeyService.java
@@ -0,0 +1,101 @@
+package org.cryptomator.common.recovery;
+
+import org.cryptomator.common.vaults.Vault;
+import org.cryptomator.cryptolib.api.CryptoException;
+import org.cryptomator.cryptolib.api.Cryptor;
+import org.cryptomator.cryptolib.api.CryptorProvider;
+import org.cryptomator.cryptolib.api.Masterkey;
+import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
+import org.cryptomator.ui.recoverykey.RecoveryKeyFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.NoSuchElementException;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.stream.Stream;
+
+import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
+import static org.cryptomator.cryptofs.common.Constants.DATA_DIR_NAME;
+
+public final class MasterkeyService {
+
+ private static final Logger LOG = LoggerFactory.getLogger(MasterkeyService.class);
+
+ private MasterkeyService() {}
+
+ public static void recoverFromRecoveryKey(String recoveryKey, RecoveryKeyFactory recoveryKeyFactory, Path recoveryPath, CharSequence newPassword) throws IOException {
+ recoveryKeyFactory.newMasterkeyFileWithPassphrase(recoveryPath, recoveryKey, newPassword);
+ }
+
+ public static Masterkey load(MasterkeyFileAccess masterkeyFileAccess, Path masterkeyFilePath, CharSequence password) throws IOException {
+ return masterkeyFileAccess.load(masterkeyFilePath, password);
+ }
+
+ public static CryptorProvider.Scheme validateRecoveryKeyAndDetectCombo(RecoveryKeyFactory recoveryKeyFactory, //
+ Vault vault, String recoveryKey, //
+ MasterkeyFileAccess masterkeyFileAccess) throws IOException, CryptoException, NoSuchElementException {
+ String tmpPass = UUID.randomUUID().toString();
+ try (RecoveryDirectory recoveryDirectory = RecoveryDirectory.create(vault.getPath())) {
+ Path tempRecoveryPath = recoveryDirectory.getRecoveryPath();
+ recoverFromRecoveryKey(recoveryKey, recoveryKeyFactory, tempRecoveryPath, tmpPass);
+ Path masterkeyFilePath = tempRecoveryPath.resolve(MASTERKEY_FILENAME);
+
+ try (Masterkey mk = load(masterkeyFileAccess, masterkeyFilePath, tmpPass)) {
+ return detect(mk, vault.getPath()).orElseThrow();
+ }
+ }
+ }
+
+ public static Optional detect(Masterkey masterkey, Path vaultPath) {
+ try (Stream paths = Files.walk(vaultPath.resolve(DATA_DIR_NAME))) {
+ Optional c9rFile = paths //
+ .filter(p -> p.toString().endsWith(".c9r")) //
+ .filter(p -> !p.endsWith("dir.c9r")) //
+ .findFirst();
+ if (c9rFile.isEmpty()) {
+ LOG.info("Unable to detect Crypto scheme: No *.c9r file found in {}", vaultPath);
+ return Optional.empty();
+ }
+ return determineScheme(c9rFile.get(), masterkey);
+ } catch (IOException e) {
+ LOG.info("Unable to detect Crypto scheme: Failed to inspect vault", e);
+ return Optional.empty();
+ }
+ }
+
+ private static Optional determineScheme(Path c9rFile, Masterkey masterkey) {
+ return Arrays.stream(CryptorProvider.Scheme.values()).filter(scheme -> {
+ try (Cryptor cryptor = CryptorProvider.forScheme(scheme).provide(masterkey.copy(), SecureRandom.getInstanceStrong())) {
+ int headerSize = cryptor.fileHeaderCryptor().headerSize();
+
+ ByteBuffer headerBuf = ByteBuffer.allocate(headerSize);
+
+ try (FileChannel channel = FileChannel.open(c9rFile, StandardOpenOption.READ)) {
+ channel.read(headerBuf, 0);
+ }
+
+ headerBuf.flip();
+ cryptor.fileHeaderCryptor().decryptHeader(headerBuf.duplicate());
+ LOG.debug("Detected Crypto scheme: {}", scheme);
+ return true;
+ } catch (IllegalArgumentException | CryptoException e) {
+ LOG.debug("Could not decrypt with scheme: {}", scheme);
+ return false;
+ } catch (IOException | NoSuchAlgorithmException e) {
+ LOG.warn("Unable to detect Crypto scheme: Failed to decrypt .c9r file", e);
+ return false;
+ }
+ }).findFirst();
+ }
+
+}
diff --git a/src/main/java/org/cryptomator/common/recovery/RecoveryActionType.java b/src/main/java/org/cryptomator/common/recovery/RecoveryActionType.java
new file mode 100644
index 000000000..a05073dad
--- /dev/null
+++ b/src/main/java/org/cryptomator/common/recovery/RecoveryActionType.java
@@ -0,0 +1,10 @@
+package org.cryptomator.common.recovery;
+
+public enum RecoveryActionType {
+ RESTORE_ALL,
+ RESTORE_MASTERKEY,
+ RESTORE_VAULT_CONFIG,
+ RESET_PASSWORD,
+ SHOW_KEY,
+ CONVERT_VAULT
+}
diff --git a/src/main/java/org/cryptomator/common/recovery/RecoveryDirectory.java b/src/main/java/org/cryptomator/common/recovery/RecoveryDirectory.java
new file mode 100644
index 000000000..be2af245b
--- /dev/null
+++ b/src/main/java/org/cryptomator/common/recovery/RecoveryDirectory.java
@@ -0,0 +1,56 @@
+package org.cryptomator.common.recovery;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.Comparator;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class RecoveryDirectory implements AutoCloseable {
+
+ private static final Logger LOG = LoggerFactory.getLogger(RecoveryDirectory.class);
+
+ private final Path recoveryPath;
+ private final Path vaultPath;
+
+ private RecoveryDirectory(Path vaultPath, Path recoveryPath) {
+ this.vaultPath = vaultPath;
+ this.recoveryPath = recoveryPath;
+ }
+
+ public static RecoveryDirectory create(Path vaultPath) throws IOException {
+ Path tempDir = Files.createTempDirectory("cryptomator");
+ return new RecoveryDirectory(vaultPath, tempDir);
+ }
+
+ public void moveRecoveredFile(String file) throws IOException {
+ Files.move(recoveryPath.resolve(file), vaultPath.resolve(file), StandardCopyOption.REPLACE_EXISTING);
+ }
+
+ private void deleteRecoveryDirectory() {
+ try (var paths = Files.walk(recoveryPath)) {
+ paths.sorted(Comparator.reverseOrder()).forEach(p -> {
+ try {
+ Files.delete(p);
+ } catch (IOException e) {
+ LOG.info("Unable to delete {}. Please delete it manually.", p);
+ }
+ });
+ } catch (IOException e) {
+ LOG.error("Failed to clean up recovery directory", e);
+ }
+ }
+
+ @Override
+ public void close() {
+ deleteRecoveryDirectory();
+ }
+
+ public Path getRecoveryPath() {
+ return recoveryPath;
+ }
+
+}
diff --git a/src/main/java/org/cryptomator/common/recovery/VaultPreparator.java b/src/main/java/org/cryptomator/common/recovery/VaultPreparator.java
new file mode 100644
index 000000000..d99ba50a0
--- /dev/null
+++ b/src/main/java/org/cryptomator/common/recovery/VaultPreparator.java
@@ -0,0 +1,54 @@
+package org.cryptomator.common.recovery;
+
+import org.apache.commons.lang3.SystemUtils;
+import org.cryptomator.common.settings.VaultSettings;
+import org.cryptomator.common.vaults.Vault;
+import org.cryptomator.common.vaults.VaultComponent;
+import org.cryptomator.common.vaults.VaultConfigCache;
+import org.cryptomator.common.vaults.VaultListManager;
+import org.cryptomator.integrations.mount.MountService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.ResourceBundle;
+
+import static org.cryptomator.common.vaults.VaultState.Value.LOCKED;
+
+public final class VaultPreparator {
+
+ private static final Logger LOG = LoggerFactory.getLogger(VaultPreparator.class);
+
+ private VaultPreparator() {}
+
+ public static Vault prepareVault(Path selectedDirectory, //
+ VaultComponent.Factory vaultComponentFactory, //
+ List mountServices, //
+ ResourceBundle resourceBundle) {
+ VaultSettings vaultSettings = VaultSettings.withRandomId();
+ vaultSettings.path.set(selectedDirectory);
+ if (selectedDirectory.getFileName() != null) {
+ vaultSettings.displayName.set(selectedDirectory.getFileName().toString());
+ } else {
+ vaultSettings.displayName.set(resourceBundle.getString("defaults.vault.vaultName"));
+ }
+
+ var wrapper = new VaultConfigCache(vaultSettings);
+ Vault vault = vaultComponentFactory.create(vaultSettings, wrapper, LOCKED, null).vault();
+ try {
+ VaultListManager.determineVaultState(vault.getPath());
+ } catch (IOException e) {
+ LOG.warn("Failed to determine vault state for {}", vaultSettings.path.get(), e);
+ }
+
+ //due to https://github.com/cryptomator/cryptomator/issues/2880#issuecomment-1680313498
+ var nameOfWinfspLocalMounter = "org.cryptomator.frontend.fuse.mount.WinFspMountProvider";
+ if (SystemUtils.IS_OS_WINDOWS && vaultSettings.path.get().toString().contains("Dropbox") && mountServices.stream().anyMatch(s -> s.getClass().getName().equals(nameOfWinfspLocalMounter))) {
+ vaultSettings.mountService.setValue(nameOfWinfspLocalMounter);
+ }
+
+ return vault;
+ }
+}
diff --git a/src/main/java/org/cryptomator/common/vaults/Vault.java b/src/main/java/org/cryptomator/common/vaults/Vault.java
index 2e1ae4bba..2fa613e3e 100644
--- a/src/main/java/org/cryptomator/common/vaults/Vault.java
+++ b/src/main/java/org/cryptomator/common/vaults/Vault.java
@@ -23,7 +23,6 @@ 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 +34,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;
@@ -75,6 +73,7 @@ public class Vault {
private final BooleanBinding missing;
private final BooleanBinding needsMigration;
private final BooleanBinding unknownError;
+ private final BooleanBinding missingVaultConfig;
private final ObjectBinding mountPoint;
private final Mounter mounter;
private final Settings settings;
@@ -103,6 +102,7 @@ public class Vault {
this.processing = Bindings.createBooleanBinding(this::isProcessing, state);
this.unlocked = Bindings.createBooleanBinding(this::isUnlocked, state);
this.missing = Bindings.createBooleanBinding(this::isMissing, state);
+ this.missingVaultConfig = Bindings.createBooleanBinding(this::isMissingVaultConfig, state);
this.needsMigration = Bindings.createBooleanBinding(this::isNeedsMigration, state);
this.unknownError = Bindings.createBooleanBinding(this::isUnknownError, state);
this.mountPoint = Bindings.createObjectBinding(this::getMountPoint, state);
@@ -336,6 +336,14 @@ public class Vault {
return state.get() == VaultState.Value.ERROR;
}
+ public BooleanBinding missingVaultConfigProperty() {
+ return missingVaultConfig;
+ }
+
+ public boolean isMissingVaultConfig() {
+ return state.get() == VaultState.Value.VAULT_CONFIG_MISSING || state.get() == VaultState.Value.ALL_MISSING;
+ }
+
public ReadOnlyStringProperty displayNameProperty() {
return vaultSettings.displayName;
}
diff --git a/src/main/java/org/cryptomator/common/vaults/VaultConfigCache.java b/src/main/java/org/cryptomator/common/vaults/VaultConfigCache.java
index b879b1f81..4a95fe50b 100644
--- a/src/main/java/org/cryptomator/common/vaults/VaultConfigCache.java
+++ b/src/main/java/org/cryptomator/common/vaults/VaultConfigCache.java
@@ -20,7 +20,7 @@ public class VaultConfigCache {
private final VaultSettings settings;
private final AtomicReference config;
- VaultConfigCache(VaultSettings settings) {
+ public VaultConfigCache(VaultSettings settings) {
this.settings = settings;
this.config = new AtomicReference<>(null);
}
diff --git a/src/main/java/org/cryptomator/common/vaults/VaultListManager.java b/src/main/java/org/cryptomator/common/vaults/VaultListManager.java
index ce1b2433c..e73075d0d 100644
--- a/src/main/java/org/cryptomator/common/vaults/VaultListManager.java
+++ b/src/main/java/org/cryptomator/common/vaults/VaultListManager.java
@@ -9,6 +9,7 @@
package org.cryptomator.common.vaults;
import org.apache.commons.lang3.SystemUtils;
+import org.cryptomator.common.recovery.BackupRestorer;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
@@ -34,9 +35,7 @@ import java.util.ResourceBundle;
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
-import static org.cryptomator.common.vaults.VaultState.Value.ERROR;
-import static org.cryptomator.common.vaults.VaultState.Value.LOCKED;
-import static org.cryptomator.common.vaults.VaultState.Value.NEEDS_MIGRATION;
+import static org.cryptomator.common.vaults.VaultState.Value.*;
@Singleton
public class VaultListManager {
@@ -67,6 +66,12 @@ public class VaultListManager {
autoLocker.init();
}
+ public boolean isAlreadyAdded(Path vaultPath) {
+ assert vaultPath.isAbsolute();
+ assert vaultPath.normalize().equals(vaultPath);
+ return vaultList.stream().anyMatch(v -> vaultPath.equals(v.getPath()));
+ }
+
public Vault add(Path pathToVault) throws IOException {
Path normalizedPathToVault = pathToVault.normalize().toAbsolutePath();
if (CryptoFileSystemProvider.checkDirStructureForVault(normalizedPathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) == DirStructure.UNRELATED) {
@@ -114,59 +119,122 @@ public class VaultListManager {
.findAny();
}
+ public void addVault(Vault vault) {
+ Path path = vault.getPath().normalize().toAbsolutePath();
+ if (!isAlreadyAdded(path)) {
+ vaultList.add(vault);
+ }
+ }
+
private Vault create(VaultSettings vaultSettings) {
var wrapper = new VaultConfigCache(vaultSettings);
try {
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();
- if (Objects.isNull(vaultSettings.lastKnownKeyLoader.get())) {
- var keyIdScheme = wrapper.get().getKeyId().getScheme();
- vaultSettings.lastKnownKeyLoader.set(keyIdScheme);
- }
- } else if (vaultState == NEEDS_MIGRATION) {
- vaultSettings.lastKnownKeyLoader.set(MasterkeyFileLoadingStrategy.SCHEME);
- }
+ initializeLastKnownKeyLoaderIfPossible(vaultSettings, vaultState, wrapper);
+
return vaultComponentFactory.create(vaultSettings, wrapper, vaultState, null).vault();
} catch (IOException e) {
- LOG.warn("Failed to determine vault state for " + vaultSettings.path.get(), e);
+ LOG.warn("Failed to determine vault state for {}", vaultSettings.path.get(), e);
return vaultComponentFactory.create(vaultSettings, wrapper, ERROR, e).vault();
}
}
+ private void initializeLastKnownKeyLoaderIfPossible(VaultSettings vaultSettings, VaultState.Value vaultState, VaultConfigCache wrapper) throws IOException {
+ if (vaultSettings.lastKnownKeyLoader.get() != null) {
+ return;
+ }
+
+ switch (vaultState) {
+ case LOCKED -> {
+ wrapper.reloadConfig();
+ vaultSettings.lastKnownKeyLoader.set(wrapper.get().getKeyId().getScheme());
+ }
+ case NEEDS_MIGRATION -> {
+ //for legacy reasons: pre v8 vault do not have a config, but they are in the NEEDS_MIGRATION state
+ vaultSettings.lastKnownKeyLoader.set(MasterkeyFileLoadingStrategy.SCHEME);
+ }
+ case VAULT_CONFIG_MISSING -> {
+ //Nothing to do here, since there is no config to read
+ }
+ case MISSING, ALL_MISSING, ERROR, PROCESSING -> {
+ // no config available or not safe to load
+ }
+ default -> {
+ if (Files.exists(vaultSettings.path.get().resolve(VAULTCONFIG_FILENAME))) {
+ try {
+ wrapper.reloadConfig();
+ vaultSettings.lastKnownKeyLoader.set(wrapper.get().getKeyId().getScheme());
+ } catch (IOException e) {
+ LOG.debug("Unable to load config for {}", vaultSettings.path.get(), e);
+ }
+ }
+ }
+ }
+ }
+
public static VaultState.Value redetermineVaultState(Vault vault) {
VaultState state = vault.stateProperty();
- VaultState.Value previousState = state.getValue();
- return switch (previousState) {
- case LOCKED, NEEDS_MIGRATION, MISSING -> {
- try {
- var determinedState = determineVaultState(vault.getPath());
- if (determinedState == LOCKED) {
- vault.getVaultConfigCache().reloadConfig();
- }
- state.set(determinedState);
- yield determinedState;
- } catch (IOException e) {
- LOG.warn("Failed to determine vault state for " + vault.getPath(), e);
- state.set(ERROR);
- vault.setLastKnownException(e);
- yield ERROR;
- }
+ VaultState.Value previous = state.getValue();
+
+ if (previous.equals(UNLOCKED) || previous.equals(PROCESSING)) {
+ return previous;
+ }
+
+ try {
+ VaultState.Value determined = determineVaultState(vault.getPath());
+
+ if (determined == LOCKED) {
+ vault.getVaultConfigCache().reloadConfig();
}
- case ERROR, UNLOCKED, PROCESSING -> previousState;
- };
+
+ state.set(determined);
+ return determined;
+ } catch (IOException e) {
+ LOG.warn("Failed to (re)determine vault state for {}", vault.getPath(), e);
+ vault.setLastKnownException(e);
+ state.set(ERROR);
+ return ERROR;
+ }
}
- private static VaultState.Value determineVaultState(Path pathToVault) throws IOException {
+ public static VaultState.Value determineVaultState(Path pathToVault) throws IOException {
if (!Files.exists(pathToVault)) {
- return VaultState.Value.MISSING;
+ return MISSING;
}
+
+ VaultState.Value structureResult = checkDirStructure(pathToVault);
+
+ if (structureResult == LOCKED || structureResult == NEEDS_MIGRATION) {
+ return structureResult;
+ }
+
+ Path pathToVaultConfig = pathToVault.resolve(VAULTCONFIG_FILENAME);
+ Path pathToMasterkey = pathToVault.resolve(MASTERKEY_FILENAME);
+
+ if (!Files.exists(pathToVaultConfig)) {
+ BackupRestorer.restoreIfBackupPresent(pathToVault, VAULTCONFIG_FILENAME);
+ }
+ if (!Files.exists(pathToMasterkey)) {
+ BackupRestorer.restoreIfBackupPresent(pathToVault, MASTERKEY_FILENAME);
+ }
+
+ boolean hasConfig = Files.exists(pathToVaultConfig);
+
+ if (!hasConfig && !Files.exists(pathToMasterkey)) {
+ return ALL_MISSING;
+ }
+ if (!hasConfig) {
+ return VAULT_CONFIG_MISSING;
+ }
+
+ return checkDirStructure(pathToVault);
+ }
+
+ private static VaultState.Value checkDirStructure(Path pathToVault) throws IOException {
return switch (CryptoFileSystemProvider.checkDirStructureForVault(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME)) {
- case VAULT -> VaultState.Value.LOCKED;
- case UNRELATED -> VaultState.Value.MISSING;
- case MAYBE_LEGACY -> Migrators.get().needsMigration(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) ? //
- VaultState.Value.NEEDS_MIGRATION //
- : VaultState.Value.MISSING;
+ case VAULT -> LOCKED;
+ case UNRELATED -> MISSING;
+ case MAYBE_LEGACY -> Migrators.get().needsMigration(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) ? NEEDS_MIGRATION : MISSING;
};
}
diff --git a/src/main/java/org/cryptomator/common/vaults/VaultState.java b/src/main/java/org/cryptomator/common/vaults/VaultState.java
index ff09c8b82..f8b9b412a 100644
--- a/src/main/java/org/cryptomator/common/vaults/VaultState.java
+++ b/src/main/java/org/cryptomator/common/vaults/VaultState.java
@@ -25,6 +25,16 @@ public class VaultState extends ObservableValueBase implements
*/
MISSING,
+ /**
+ * No vault config found at the provided path
+ */
+ VAULT_CONFIG_MISSING,
+
+ /**
+ * No vault config and masterkey found at the provided path
+ */
+ ALL_MISSING,
+
/**
* Vault requires migration to a newer vault format
*/
diff --git a/src/main/java/org/cryptomator/ui/common/FxmlFile.java b/src/main/java/org/cryptomator/ui/common/FxmlFile.java
index ce8c65a37..68607808d 100644
--- a/src/main/java/org/cryptomator/ui/common/FxmlFile.java
+++ b/src/main/java/org/cryptomator/ui/common/FxmlFile.java
@@ -42,9 +42,10 @@ public enum FxmlFile {
QUIT("/fxml/quit.fxml"), //
QUIT_FORCED("/fxml/quit_forced.fxml"), //
RECOVERYKEY_CREATE("/fxml/recoverykey_create.fxml"), //
+ RECOVERYKEY_EXPERT_SETTINGS("/fxml/recoverykey_expert_settings.fxml"), //
+ RECOVERYKEY_ONBOARDING("/fxml/recoverykey_onboarding.fxml"), //
RECOVERYKEY_RECOVER("/fxml/recoverykey_recover.fxml"), //
RECOVERYKEY_RESET_PASSWORD("/fxml/recoverykey_reset_password.fxml"), //
- RECOVERYKEY_RESET_PASSWORD_SUCCESS("/fxml/recoverykey_reset_password_success.fxml"), //
RECOVERYKEY_SUCCESS("/fxml/recoverykey_success.fxml"), //
SHARE_VAULT("/fxml/share_vault.fxml"), //
SIMPLE_DIALOG("/fxml/simple_dialog.fxml"), //
diff --git a/src/main/java/org/cryptomator/ui/convertvault/ConvertVaultModule.java b/src/main/java/org/cryptomator/ui/convertvault/ConvertVaultModule.java
index f70242d2b..d010ede43 100644
--- a/src/main/java/org/cryptomator/ui/convertvault/ConvertVaultModule.java
+++ b/src/main/java/org/cryptomator/ui/convertvault/ConvertVaultModule.java
@@ -4,8 +4,10 @@ import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
+import org.cryptomator.common.recovery.RecoveryActionType;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.VaultConfig;
+import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
import org.cryptomator.ui.changepassword.NewPasswordController;
import org.cryptomator.ui.changepassword.PasswordStrengthUtil;
import org.cryptomator.ui.common.DefaultSceneFactory;
@@ -20,6 +22,7 @@ import org.cryptomator.ui.recoverykey.RecoveryKeyValidateController;
import javax.inject.Named;
import javax.inject.Provider;
+import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
@@ -119,8 +122,8 @@ abstract class ConvertVaultModule {
@Provides
@IntoMap
@FxControllerKey(RecoveryKeyValidateController.class)
- static FxController bindRecoveryKeyValidateController(@ConvertVaultWindow Vault vault, @ConvertVaultWindow VaultConfig.UnverifiedVaultConfig vaultConfig, @ConvertVaultWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory) {
- return new RecoveryKeyValidateController(vault, vaultConfig, recoveryKey, recoveryKeyFactory);
+ static FxController provideRecoveryKeyValidateController(@ConvertVaultWindow Vault vault, @ConvertVaultWindow VaultConfig.UnverifiedVaultConfig vaultConfig, @ConvertVaultWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory, MasterkeyFileAccess masterkeyFileAccess) {
+ return new RecoveryKeyValidateController(vault, vaultConfig, recoveryKey, recoveryKeyFactory, masterkeyFileAccess, new SimpleObjectProperty<>(RecoveryActionType.CONVERT_VAULT), new SimpleObjectProperty<>(null));
}
}
diff --git a/src/main/java/org/cryptomator/ui/dialogs/Dialogs.java b/src/main/java/org/cryptomator/ui/dialogs/Dialogs.java
index 837bea012..6f3a86026 100644
--- a/src/main/java/org/cryptomator/ui/dialogs/Dialogs.java
+++ b/src/main/java/org/cryptomator/ui/dialogs/Dialogs.java
@@ -20,6 +20,8 @@ public class Dialogs {
private final ResourceBundle resourceBundle;
private final StageFactory stageFactory;
+ private static final String BUTTON_KEY_CLOSE = "generic.button.close";
+
@Inject
public Dialogs(ResourceBundle resourceBundle, StageFactory stageFactory) {
this.resourceBundle = resourceBundle;
@@ -47,6 +49,43 @@ public class Dialogs {
});
}
+ public SimpleDialog.Builder prepareContactHubVaultOwner(Stage window) {
+ return createDialogBuilder().setOwner(window) //
+ .setTitleKey("contactHubVaultOwner.title") //
+ .setMessageKey("contactHubVaultOwner.message") //
+ .setDescriptionKey("contactHubVaultOwner.description") //
+ .setIcon(FontAwesome5Icon.EXCLAMATION)//
+ .setOkButtonKey(BUTTON_KEY_CLOSE);
+ }
+
+ public SimpleDialog.Builder prepareRecoveryVaultAdded(Stage window, String displayName) {
+ return createDialogBuilder().setOwner(window) //
+ .setTitleKey("recover.existing.title") //
+ .setMessageKey("recover.existing.message") //
+ .setDescriptionKey("recover.existing.description", displayName) //
+ .setIcon(FontAwesome5Icon.CHECK)//
+ .setOkButtonKey(BUTTON_KEY_CLOSE);
+ }
+ public SimpleDialog.Builder prepareRecoveryVaultAlreadyExists(Stage window, String displayName) {
+ return createDialogBuilder().setOwner(window) //
+ .setTitleKey("recover.alreadyExists.title") //
+ .setMessageKey("recover.alreadyExists.message") //
+ .setDescriptionKey("recover.alreadyExists.description", displayName) //
+ .setIcon(FontAwesome5Icon.EXCLAMATION)//
+ .setOkButtonKey(BUTTON_KEY_CLOSE);
+ }
+
+ public SimpleDialog.Builder prepareRecoverPasswordSuccess(Stage window) {
+ return createDialogBuilder()
+ .setOwner(window) //
+ .setTitleKey("recoveryKey.recover.title") //
+ .setMessageKey("recoveryKey.recover.resetSuccess.message") //
+ .setDescriptionKey("recoveryKey.recover.resetSuccess.description") //
+ .setIcon(FontAwesome5Icon.CHECK)
+ .setOkAction(Stage::close)
+ .setOkButtonKey(BUTTON_KEY_CLOSE);
+ }
+
public SimpleDialog.Builder prepareRemoveCertDialog(Stage window, Settings settings) {
return createDialogBuilder() //
.setOwner(window) //
@@ -69,7 +108,7 @@ public class Dialogs {
.setMessageKey("dokanySupportEnd.message") //
.setDescriptionKey("dokanySupportEnd.description") //
.setIcon(FontAwesome5Icon.EXCLAMATION) //
- .setOkButtonKey("generic.button.close") //
+ .setOkButtonKey(BUTTON_KEY_CLOSE) //
.setCancelButtonKey("dokanySupportEnd.preferencesBtn") //
.setOkAction(Stage::close) //
.setCancelAction(cancelAction);
@@ -83,8 +122,20 @@ public class Dialogs {
.setDescriptionKey("retryIfReadonly.description") //
.setIcon(FontAwesome5Icon.EXCLAMATION) //
.setOkButtonKey("retryIfReadonly.retry") //
- .setCancelButtonKey("generic.button.close") //
+ .setCancelButtonKey(BUTTON_KEY_CLOSE) //
.setOkAction(okAction) //
.setCancelAction(Stage::close);
}
+
+ public SimpleDialog.Builder prepareNoDDirectorySelectedDialog(Stage window) {
+ return createDialogBuilder() //
+ .setOwner(window) //
+ .setTitleKey("recover.invalidSelection.title") //
+ .setMessageKey("recover.invalidSelection.message") //
+ .setDescriptionKey("recover.invalidSelection.description") //
+ .setIcon(FontAwesome5Icon.EXCLAMATION) //
+ .setOkButtonKey("generic.button.change") //
+ .setOkAction(Stage::close);
+ }
+
}
diff --git a/src/main/java/org/cryptomator/ui/dialogs/SimpleDialog.java b/src/main/java/org/cryptomator/ui/dialogs/SimpleDialog.java
index 08f77849e..5efe4abfe 100644
--- a/src/main/java/org/cryptomator/ui/dialogs/SimpleDialog.java
+++ b/src/main/java/org/cryptomator/ui/dialogs/SimpleDialog.java
@@ -30,7 +30,7 @@ public class SimpleDialog {
FxmlLoaderFactory loaderFactory = FxmlLoaderFactory.forController( //
new SimpleDialogController(resolveText(builder.messageKey, null), //
- resolveText(builder.descriptionKey, null), //
+ resolveText(builder.descriptionKey, builder.descriptionArgs), //
builder.icon, //
resolveText(builder.okButtonKey, null), //
builder.cancelButtonKey != null ? resolveText(builder.cancelButtonKey, null) : null, //
@@ -66,6 +66,7 @@ public class SimpleDialog {
private String[] titleArgs;
private String messageKey;
private String descriptionKey;
+ private String[] descriptionArgs;
private String okButtonKey;
private String cancelButtonKey;
private FontAwesome5Icon icon;
@@ -93,8 +94,9 @@ public class SimpleDialog {
return this;
}
- public Builder setDescriptionKey(String descriptionKey) {
+ public Builder setDescriptionKey(String descriptionKey, String... args) {
this.descriptionKey = descriptionKey;
+ this.descriptionArgs = args;
return this;
}
diff --git a/src/main/java/org/cryptomator/ui/eventview/EventViewController.java b/src/main/java/org/cryptomator/ui/eventview/EventViewController.java
index ca4fe9d55..fcc1b9375 100644
--- a/src/main/java/org/cryptomator/ui/eventview/EventViewController.java
+++ b/src/main/java/org/cryptomator/ui/eventview/EventViewController.java
@@ -49,7 +49,7 @@ public class EventViewController implements FxController {
}
/**
- * Comparison method for the lru cache. During comparsion the map is accessed.
+ * Comparison method for the lru cache. During comparison 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
diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java
index 8eb221883..74abac546 100644
--- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java
+++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java
@@ -15,15 +15,13 @@ import org.cryptomator.ui.lock.LockComponent;
import org.cryptomator.ui.mainwindow.MainWindowComponent;
import org.cryptomator.ui.preferences.PreferencesComponent;
import org.cryptomator.ui.quit.QuitComponent;
+import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
import org.cryptomator.ui.sharevault.ShareVaultComponent;
import org.cryptomator.ui.traymenu.TrayMenuComponent;
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;
@@ -40,7 +38,8 @@ import java.io.InputStream;
HealthCheckComponent.class, //
UpdateReminderComponent.class, //
ShareVaultComponent.class, //
- EventViewComponent.class})
+ EventViewComponent.class, //
+ RecoveryKeyComponent.class})
abstract class FxApplicationModule {
private static Image createImageFromResource(String resourceName) throws IOException {
diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationTerminator.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationTerminator.java
index 74938bc64..91ff918e3 100644
--- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationTerminator.java
+++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationTerminator.java
@@ -28,7 +28,7 @@ import static org.cryptomator.common.vaults.VaultState.Value.*;
@FxApplicationScoped
public class FxApplicationTerminator {
- private static final Set STATES_ALLOWING_TERMINATION = EnumSet.of(LOCKED, NEEDS_MIGRATION, MISSING, ERROR);
+ private static final Set STATES_ALLOWING_TERMINATION = EnumSet.of(LOCKED, NEEDS_MIGRATION, MISSING, ERROR, VAULT_CONFIG_MISSING, ALL_MISSING);
private static final Set STATES_PREVENT_TERMINATION = EnumSet.of(PROCESSING);
private static final Logger LOG = LoggerFactory.getLogger(FxApplicationTerminator.class);
@@ -110,7 +110,7 @@ public class FxApplicationTerminator {
} else if (settings.autoCloseVaults.get() && !preventQuitWithGracefulLock.get()) {
var lockAllTask = vaultService.createLockAllTask(vaults.filtered(Vault::isUnlocked), false);
lockAllTask.setOnSucceeded(event -> {
- LOG.info("Locked remaining vaults was succesful.");
+ LOG.info("Locked remaining vaults was successful.");
exitingResponse.performQuit();
});
lockAllTask.setOnFailed(event -> {
diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileController.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileController.java
index 9b2231921..4e1c663e9 100644
--- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileController.java
+++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileController.java
@@ -1,14 +1,18 @@
package org.cryptomator.ui.keyloading.masterkeyfile;
+import org.cryptomator.common.recovery.RecoveryActionType;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.keyloading.KeyLoading;
+import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
-import javafx.beans.binding.StringBinding;
+import javafx.beans.property.SimpleObjectProperty;
import javafx.fxml.FXML;
+import javafx.scene.control.Button;
+import javafx.scene.control.CheckBox;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
@@ -27,17 +31,41 @@ public class ChooseMasterkeyFileController implements FxController {
private final Stage window;
private final Vault vault;
private final CompletableFuture result;
+ private final RecoveryKeyComponent.Factory recoveryKeyWindow;
private final ResourceBundle resourceBundle;
+ @FXML
+ private CheckBox restoreInsteadCheckBox;
+ @FXML
+ private Button forwardButton;
+
@Inject
- public ChooseMasterkeyFileController(@KeyLoading Stage window, @KeyLoading Vault vault, CompletableFuture result, ResourceBundle resourceBundle) {
+ public ChooseMasterkeyFileController(@KeyLoading Stage window, //
+ @KeyLoading Vault vault, //
+ CompletableFuture result, //
+ RecoveryKeyComponent.Factory recoveryKeyWindow, //
+ ResourceBundle resourceBundle) {
this.window = window;
this.vault = vault;
this.result = result;
+ this.recoveryKeyWindow = recoveryKeyWindow;
this.resourceBundle = resourceBundle;
this.window.setOnHiding(this::windowClosed);
}
+ @FXML
+ private void initialize() {
+ restoreInsteadCheckBox.selectedProperty().addListener((_, _, newVal) -> {
+ if (newVal) {
+ forwardButton.setText(resourceBundle.getString("addvaultwizard.existing.restore"));
+ forwardButton.setOnAction(_ -> restoreMasterkey());
+ } else {
+ forwardButton.setText(resourceBundle.getString("generic.button.choose"));
+ forwardButton.setOnAction(_ -> proceed());
+ }
+ });
+ }
+
@FXML
public void cancel() {
window.close();
@@ -47,6 +75,13 @@ public class ChooseMasterkeyFileController implements FxController {
result.cancel(true);
}
+ @FXML
+ void restoreMasterkey() {
+ Stage ownerStage = (Stage) window.getOwner();
+ window.close();
+ recoveryKeyWindow.create(vault, ownerStage, new SimpleObjectProperty<>(RecoveryActionType.RESTORE_MASTERKEY)).showOnboardingDialogWindow();
+ }
+
@FXML
public void proceed() {
LOG.trace("proceed()");
@@ -62,7 +97,7 @@ public class ChooseMasterkeyFileController implements FxController {
//--- Setter & Getter ---
- public String getDisplayName(){
+ public String getDisplayName() {
return vault.getDisplayName();
}
diff --git a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java
index fa1b441d9..a186fbe71 100644
--- a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java
+++ b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java
@@ -18,6 +18,7 @@ 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.recoverykey.RecoveryKeyComponent;
import org.cryptomator.ui.stats.VaultStatisticsComponent;
import org.cryptomator.ui.traymenu.TrayMenuComponent;
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
@@ -32,7 +33,7 @@ import javafx.stage.Stage;
import java.util.Map;
import java.util.ResourceBundle;
-@Module(subcomponents = {AddVaultWizardComponent.class, MigrationComponent.class, VaultStatisticsComponent.class, WrongFileAlertComponent.class, ErrorComponent.class})
+@Module(subcomponents = {AddVaultWizardComponent.class, MigrationComponent.class, VaultStatisticsComponent.class, WrongFileAlertComponent.class, ErrorComponent.class, RecoveryKeyComponent.class})
abstract class MainWindowModule {
@Provides
diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java
index 7e309fdaf..be4f7f78c 100644
--- a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java
+++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java
@@ -52,7 +52,7 @@ public class VaultDetailController implements FxController {
case LOCKED -> FontAwesome5Icon.LOCK;
case PROCESSING -> FontAwesome5Icon.SPINNER;
case UNLOCKED -> FontAwesome5Icon.LOCK_OPEN;
- case NEEDS_MIGRATION, MISSING, ERROR -> FontAwesome5Icon.EXCLAMATION_TRIANGLE;
+ case NEEDS_MIGRATION, MISSING, VAULT_CONFIG_MISSING, ALL_MISSING, ERROR -> FontAwesome5Icon.EXCLAMATION_TRIANGLE;
};
} else {
return FontAwesome5Icon.EXCLAMATION_TRIANGLE;
diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java
index 6f57a0d17..85e71937b 100644
--- a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java
+++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java
@@ -1,20 +1,26 @@
package org.cryptomator.ui.mainwindow;
+import org.cryptomator.common.recovery.RecoveryActionType;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.dialogs.Dialogs;
+import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
+import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
import javax.inject.Inject;
import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import java.io.File;
+import java.nio.file.Files;
import java.util.ResourceBundle;
import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_GLOB;
+import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
@MainWindowScoped
public class VaultDetailMissingVaultController implements FxController {
@@ -23,6 +29,7 @@ public class VaultDetailMissingVaultController implements FxController {
private final ObservableList vaults;
private final ResourceBundle resourceBundle;
private final Stage window;
+ private final RecoveryKeyComponent.Factory recoveryKeyWindow;
private final Dialogs dialogs;
@Inject
@@ -30,11 +37,13 @@ public class VaultDetailMissingVaultController implements FxController {
ObservableList vaults, //
ResourceBundle resourceBundle, //
@MainWindow Stage window, //
- Dialogs dialogs) {
+ Dialogs dialogs, //
+ RecoveryKeyComponent.Factory recoveryKeyWindow) {
this.vault = vault;
this.vaults = vaults;
this.resourceBundle = resourceBundle;
this.window = window;
+ this.recoveryKeyWindow = recoveryKeyWindow;
this.dialogs = dialogs;
}
@@ -48,6 +57,19 @@ public class VaultDetailMissingVaultController implements FxController {
dialogs.prepareRemoveVaultDialog(window, vault.get(), vaults).build().showAndWait();
}
+ @FXML
+ void restoreVaultConfig() {
+ if(KeyLoadingStrategy.isHubVault(vault.get().getVaultSettings().lastKnownKeyLoader.get())){
+ dialogs.prepareContactHubVaultOwner(window).build().showAndWait();
+ }
+ else if(Files.exists(vault.get().getPath().resolve(MASTERKEY_FILENAME))){
+ recoveryKeyWindow.create(vault.get(), window, new SimpleObjectProperty<>(RecoveryActionType.RESTORE_VAULT_CONFIG)).showOnboardingDialogWindow();
+ }
+ else {
+ recoveryKeyWindow.create(vault.get(), window, new SimpleObjectProperty<>(RecoveryActionType.RESTORE_ALL)).showOnboardingDialogWindow();
+ }
+ }
+
@FXML
void changeLocation() {
// copied from ChooseExistingVaultController class
diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultListCellController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultListCellController.java
index 75ce21dfe..9324c8c7b 100644
--- a/src/main/java/org/cryptomator/ui/mainwindow/VaultListCellController.java
+++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultListCellController.java
@@ -55,7 +55,7 @@ public class VaultListCellController implements FxController {
case LOCKED -> FontAwesome5Icon.LOCK;
case PROCESSING -> FontAwesome5Icon.SPINNER;
case UNLOCKED -> FontAwesome5Icon.LOCK_OPEN;
- case NEEDS_MIGRATION, MISSING, ERROR -> FontAwesome5Icon.EXCLAMATION_TRIANGLE;
+ case NEEDS_MIGRATION, MISSING, VAULT_CONFIG_MISSING, ALL_MISSING, ERROR -> FontAwesome5Icon.EXCLAMATION_TRIANGLE;
};
} else {
return FontAwesome5Icon.EXCLAMATION_TRIANGLE;
diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java
index 97c03194f..db667f111 100644
--- a/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java
+++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java
@@ -20,11 +20,13 @@ import javafx.stage.Stage;
import java.util.EnumSet;
import java.util.Objects;
+import static org.cryptomator.common.vaults.VaultState.Value.ALL_MISSING;
import static org.cryptomator.common.vaults.VaultState.Value.ERROR;
import static org.cryptomator.common.vaults.VaultState.Value.LOCKED;
import static org.cryptomator.common.vaults.VaultState.Value.MISSING;
import static org.cryptomator.common.vaults.VaultState.Value.NEEDS_MIGRATION;
import static org.cryptomator.common.vaults.VaultState.Value.UNLOCKED;
+import static org.cryptomator.common.vaults.VaultState.Value.VAULT_CONFIG_MISSING;
@MainWindowScoped
public class VaultListContextMenuController implements FxController {
@@ -63,7 +65,7 @@ public class VaultListContextMenuController implements FxController {
this.selectedVaultState = selectedVault.flatMap(Vault::stateProperty).orElse(null);
this.selectedVaultPassphraseStored = selectedVault.map(this::isPasswordStored).orElse(false);
- this.selectedVaultRemovable = selectedVaultState.map(EnumSet.of(LOCKED, MISSING, ERROR, NEEDS_MIGRATION)::contains).orElse(false);
+ this.selectedVaultRemovable = selectedVaultState.map(EnumSet.of(LOCKED, MISSING, ERROR, NEEDS_MIGRATION, ALL_MISSING, VAULT_CONFIG_MISSING)::contains).orElse(false);
this.selectedVaultUnlockable = selectedVaultState.map(LOCKED::equals).orElse(false);
this.selectedVaultLockable = selectedVaultState.map(UNLOCKED::equals).orElse(false);
}
@@ -102,6 +104,12 @@ public class VaultListContextMenuController implements FxController {
vaultService.reveal(vault);
}
+ @FXML
+ public void didClickShareVault() {
+ var vault = Objects.requireNonNull(selectedVault.get());
+ appWindows.showShareVaultWindow(vault);
+ }
+
// Getter and Setter
public ObservableValue selectedVaultUnlockableProperty() {
diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java
index a457ade3f..f25528498 100644
--- a/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java
+++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java
@@ -1,11 +1,16 @@
package org.cryptomator.ui.mainwindow;
import org.apache.commons.lang3.SystemUtils;
+import org.cryptomator.common.recovery.RecoveryActionType;
+import org.cryptomator.common.recovery.VaultPreparator;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.vaults.Vault;
+import org.cryptomator.common.vaults.VaultComponent;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
import org.cryptomator.cryptofs.DirStructure;
+import org.cryptomator.cryptofs.common.Constants;
+import org.cryptomator.integrations.mount.MountService;
import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.VaultService;
@@ -13,6 +18,7 @@ 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.cryptomator.ui.recoverykey.RecoveryKeyComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -22,6 +28,7 @@ import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
+import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
@@ -37,11 +44,14 @@ import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.StackPane;
+import javafx.stage.DirectoryChooser;
import javafx.stage.Stage;
import java.io.File;
import java.io.IOException;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.util.EnumSet;
+import java.util.List;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.Set;
@@ -50,10 +60,12 @@ import java.util.stream.Collectors;
import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_EXT;
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
+import static org.cryptomator.common.vaults.VaultState.Value.ALL_MISSING;
import static org.cryptomator.common.vaults.VaultState.Value.ERROR;
import static org.cryptomator.common.vaults.VaultState.Value.LOCKED;
import static org.cryptomator.common.vaults.VaultState.Value.MISSING;
import static org.cryptomator.common.vaults.VaultState.Value.NEEDS_MIGRATION;
+import static org.cryptomator.common.vaults.VaultState.Value.VAULT_CONFIG_MISSING;
@MainWindowScoped
public class VaultListController implements FxController {
@@ -75,6 +87,10 @@ public class VaultListController implements FxController {
private final ObservableValue cellSize;
private final Dialogs dialogs;
+ private final VaultComponent.Factory vaultComponentFactory;
+ private final RecoveryKeyComponent.Factory recoveryKeyWindow;
+ private final List mountServices;
+
public ListView vaultList;
public StackPane root;
@FXML
@@ -94,6 +110,9 @@ public class VaultListController implements FxController {
FxApplicationWindows appWindows, //
Settings settings, //
Dialogs dialogs, //
+ RecoveryKeyComponent.Factory recoveryKeyWindow, //
+ VaultComponent.Factory vaultComponentFactory, //
+ List mountServices, //
FxFSEventList fxFSEventList) {
this.mainWindow = mainWindow;
this.vaults = vaults;
@@ -105,6 +124,9 @@ public class VaultListController implements FxController {
this.resourceBundle = resourceBundle;
this.appWindows = appWindows;
this.dialogs = dialogs;
+ this.recoveryKeyWindow = recoveryKeyWindow;
+ this.vaultComponentFactory = vaultComponentFactory;
+ this.mountServices = mountServices;
this.emptyVaultList = Bindings.isEmpty(vaults);
this.unreadEvents = fxFSEventList.unreadEventsProperty();
@@ -204,6 +226,26 @@ public class VaultListController implements FxController {
VaultListManager.redetermineVaultState(newValue);
}
+ private Optional chooseValidVaultDirectory() {
+ DirectoryChooser directoryChooser = new DirectoryChooser();
+ File selectedDirectory;
+
+ do {
+ selectedDirectory = directoryChooser.showDialog(mainWindow);
+ if (selectedDirectory == null) {
+ return Optional.empty();
+ }
+
+ Path selectedPath = selectedDirectory.toPath();
+ if (!Files.isDirectory(selectedPath.resolve(Constants.DATA_DIR_NAME))) {
+ dialogs.prepareNoDDirectorySelectedDialog(mainWindow).build().showAndWait();
+ selectedDirectory = null;
+ }
+ } while (selectedDirectory == null);
+
+ return Optional.of(selectedDirectory.toPath());
+ }
+
@FXML
public void didClickAddNewVault() {
addVaultWizard.build().showAddNewVaultWizard(resourceBundle);
@@ -214,9 +256,40 @@ public class VaultListController implements FxController {
addVaultWizard.build().showAddExistingVaultWizard(resourceBundle);
}
+ @FXML
+ public void didClickRecoverExistingVault() {
+ Optional selectedDirectory = chooseValidVaultDirectory();
+ if (selectedDirectory.isEmpty()) {
+ return;
+ }
+
+ Path path = selectedDirectory.get();
+ Optional matchingVaultListEntry = vaultListManager.get(path);
+ if (matchingVaultListEntry.isPresent()) {
+ dialogs.prepareRecoveryVaultAlreadyExists(mainWindow, matchingVaultListEntry.get().getDisplayName()) //
+ .setOkAction(Stage::close) //
+ .build().showAndWait();
+ return;
+ }
+
+ Vault preparedVault = VaultPreparator.prepareVault(path, vaultComponentFactory, mountServices, resourceBundle);
+ VaultListManager.redetermineVaultState(preparedVault);
+
+ switch (preparedVault.getState()) {
+ case VAULT_CONFIG_MISSING -> recoveryKeyWindow.create(preparedVault, mainWindow, new SimpleObjectProperty<>(RecoveryActionType.RESTORE_VAULT_CONFIG)).showOnboardingDialogWindow();
+ case ALL_MISSING -> recoveryKeyWindow.create(preparedVault, mainWindow, new SimpleObjectProperty<>(RecoveryActionType.RESTORE_ALL)).showOnboardingDialogWindow();
+ case LOCKED, NEEDS_MIGRATION -> {
+ vaultListManager.addVault(preparedVault);
+ dialogs.prepareRecoveryVaultAdded(mainWindow, preparedVault.getDisplayName()).setOkAction(Stage::close).build().showAndWait();
+ }
+ default -> LOG.warn("Unhandled vault state during recovery: {}", preparedVault.getState());
+ }
+
+ }
+
private void pressedShortcutToRemoveVault() {
final var vault = selectedVault.get();
- if (vault != null && EnumSet.of(LOCKED, MISSING, ERROR, NEEDS_MIGRATION).contains(vault.getState())) {
+ if (vault != null && EnumSet.of(LOCKED, MISSING, ERROR, NEEDS_MIGRATION, ALL_MISSING, VAULT_CONFIG_MISSING).contains(vault.getState())) {
dialogs.prepareRemoveVaultDialog(mainWindow, vault, vaults).build().showAndWait();
}
}
diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyComponent.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyComponent.java
index 3986fa01d..6bfe36a4f 100644
--- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyComponent.java
+++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyComponent.java
@@ -3,11 +3,13 @@ package org.cryptomator.ui.recoverykey;
import dagger.BindsInstance;
import dagger.Lazy;
import dagger.Subcomponent;
+import org.cryptomator.common.recovery.RecoveryActionType;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import javax.inject.Named;
+import javafx.beans.property.ObjectProperty;
import javafx.scene.Scene;
import javafx.stage.Stage;
@@ -24,6 +26,9 @@ public interface RecoveryKeyComponent {
@FxmlScene(FxmlFile.RECOVERYKEY_RECOVER)
Lazy recoverScene();
+ @FxmlScene(FxmlFile.RECOVERYKEY_ONBOARDING)
+ Lazy recoverOnboardingScene();
+
default void showRecoveryKeyCreationWindow() {
Stage stage = window();
stage.setScene(creationScene().get());
@@ -38,11 +43,19 @@ public interface RecoveryKeyComponent {
stage.show();
}
+ default void showOnboardingDialogWindow() {
+ Stage stage = window();
+ stage.setScene(recoverOnboardingScene().get());
+ stage.sizeToScene();
+ stage.show();
+ }
@Subcomponent.Factory
interface Factory {
- RecoveryKeyComponent create(@BindsInstance @RecoveryKeyWindow Vault vault, @BindsInstance @Named("keyRecoveryOwner") Stage owner);
+ RecoveryKeyComponent create(@BindsInstance @RecoveryKeyWindow Vault vault, //
+ @BindsInstance @Named("keyRecoveryOwner") Stage owner, //
+ @BindsInstance @Named("recoverType") ObjectProperty recoverType);
}
}
diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyCreationController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyCreationController.java
index 77f191015..456de11f4 100644
--- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyCreationController.java
+++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyCreationController.java
@@ -1,28 +1,45 @@
package org.cryptomator.ui.recoverykey;
import dagger.Lazy;
+import org.cryptomator.common.recovery.CryptoFsInitializer;
+import org.cryptomator.common.recovery.MasterkeyService;
+import org.cryptomator.common.recovery.RecoveryActionType;
+import org.cryptomator.common.recovery.RecoveryDirectory;
import org.cryptomator.common.vaults.Vault;
+import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
+import org.cryptomator.cryptolib.api.Masterkey;
+import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
import org.cryptomator.ui.common.Animations;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
+import org.cryptomator.ui.controls.FormattedLabel;
import org.cryptomator.ui.controls.NiceSecurePasswordField;
+import org.cryptomator.ui.dialogs.Dialogs;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
+import javax.inject.Named;
+import javafx.beans.property.IntegerProperty;
+import javafx.beans.property.ObjectProperty;
import javafx.beans.property.StringProperty;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.Scene;
+import javafx.scene.control.Button;
import javafx.stage.Stage;
import java.io.IOException;
+import java.nio.file.Path;
import java.util.ResourceBundle;
import java.util.concurrent.ExecutorService;
+import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
+import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
+
@RecoveryKeyScoped
public class RecoveryKeyCreationController implements FxController {
@@ -30,23 +47,71 @@ public class RecoveryKeyCreationController implements FxController {
private final Stage window;
private final Lazy successScene;
+ private final Lazy recoverykeyExpertSettingsScene;
+ private final MasterkeyFileAccess masterkeyFileAccess;
private final Vault vault;
private final ExecutorService executor;
private final RecoveryKeyFactory recoveryKeyFactory;
private final StringProperty recoveryKeyProperty;
private final FxApplicationWindows appWindows;
public NiceSecurePasswordField passwordField;
+ private final IntegerProperty shorteningThreshold;
+ private final ObjectProperty recoverType;
+ private final ResourceBundle resourceBundle;
+ public FormattedLabel descriptionLabel;
+ public Button cancelButton;
+ public Button nextButton;
+ private final VaultListManager vaultListManager;
+ private final Dialogs dialogs;
@Inject
- public RecoveryKeyCreationController(@RecoveryKeyWindow Stage window, @FxmlScene(FxmlFile.RECOVERYKEY_SUCCESS) Lazy successScene, @RecoveryKeyWindow Vault vault, RecoveryKeyFactory recoveryKeyFactory, ExecutorService executor, @RecoveryKeyWindow StringProperty recoveryKey, FxApplicationWindows appWindows, ResourceBundle resourceBundle) {
+ public RecoveryKeyCreationController(FxApplicationWindows appWindows, //
+ @RecoveryKeyWindow Stage window, //
+ @FxmlScene(FxmlFile.RECOVERYKEY_SUCCESS) Lazy successScene, //
+ @FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) Lazy recoverykeyExpertSettingsScene, //
+ @RecoveryKeyWindow Vault vault, //
+ RecoveryKeyFactory recoveryKeyFactory, //
+ MasterkeyFileAccess masterkeyFileAccess, //
+ ExecutorService executor, //
+ @RecoveryKeyWindow StringProperty recoveryKey, //
+ @Named("shorteningThreshold") IntegerProperty shorteningThreshold, //
+ @Named("recoverType") ObjectProperty recoverType, //
+ VaultListManager vaultListManager, //
+ ResourceBundle resourceBundle, //
+ Dialogs dialogs) {
this.window = window;
- window.setTitle(resourceBundle.getString("recoveryKey.display.title"));
this.successScene = successScene;
+ this.recoverykeyExpertSettingsScene = recoverykeyExpertSettingsScene;
this.vault = vault;
this.executor = executor;
this.recoveryKeyFactory = recoveryKeyFactory;
this.recoveryKeyProperty = recoveryKey;
this.appWindows = appWindows;
+ this.recoverType = recoverType;
+ this.resourceBundle = resourceBundle;
+ this.masterkeyFileAccess = masterkeyFileAccess;
+ this.shorteningThreshold = shorteningThreshold;
+ this.vaultListManager = vaultListManager;
+ this.dialogs = dialogs;
+ }
+
+ @FXML
+ public void initialize() {
+ if (recoverType.get() == RecoveryActionType.SHOW_KEY) {
+ window.setTitle(resourceBundle.getString("recoveryKey.display.title"));
+ } else if (recoverType.get() == RecoveryActionType.RESTORE_VAULT_CONFIG) {
+ window.setTitle(resourceBundle.getString("recover.recoverVaultConfig.title"));
+ descriptionLabel.formatProperty().set(resourceBundle.getString("recoveryKey.recover.description"));
+ cancelButton.setOnAction((_) -> back());
+ cancelButton.setText(resourceBundle.getString("generic.button.back"));
+ nextButton.setOnAction((_) -> restoreWithPassword());
+ }
+ }
+
+ @FXML
+ public void back() {
+ window.setScene(recoverykeyExpertSettingsScene.get());
+ window.centerOnScreen();
}
@FXML
@@ -71,6 +136,42 @@ public class RecoveryKeyCreationController implements FxController {
executor.submit(task);
}
+ @FXML
+ public void restoreWithPassword() {
+
+ try (RecoveryDirectory recoveryDirectory = RecoveryDirectory.create(vault.getPath())) {
+ Path recoveryPath = recoveryDirectory.getRecoveryPath();
+
+ Path masterkeyFilePath = vault.getPath().resolve(MASTERKEY_FILENAME);
+
+ try (Masterkey masterkey = MasterkeyService.load(masterkeyFileAccess, masterkeyFilePath, passwordField.getCharacters())) {
+ var combo = MasterkeyService.detect(masterkey, vault.getPath())
+ .orElseThrow(() -> new IllegalStateException("Could not detect combo for vault path: " + vault.getPath()));
+
+ CryptoFsInitializer.init(recoveryPath, masterkey, shorteningThreshold.get(), combo);
+ }
+
+ recoveryDirectory.moveRecoveredFile(VAULTCONFIG_FILENAME);
+
+ if (!vaultListManager.isAlreadyAdded(vault.getPath())) {
+ vaultListManager.add(vault.getPath());
+ }
+ window.close();
+ dialogs.prepareRecoverPasswordSuccess((Stage)window.getOwner()) //
+ .setTitleKey("recover.recoverVaultConfig.title") //
+ .setMessageKey("recoveryKey.recover.resetVaultConfigSuccess.message") //
+ .setDescriptionKey("recoveryKey.recover.resetMasterkeyFileSuccess.description")
+ .build().showAndWait();
+
+ } catch (InvalidPassphraseException e) {
+ LOG.info("Password invalid", e);
+ Animations.createShakeWindowAnimation(window).play();
+ } catch (IOException | CryptoException | IllegalStateException e) {
+ LOG.error("Recovery process failed", e);
+ appWindows.showErrorWindow(e, window, null);
+ }
+ }
+
@FXML
public void close() {
window.close();
diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyExpertSettingsController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyExpertSettingsController.java
new file mode 100644
index 000000000..5e72b8969
--- /dev/null
+++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyExpertSettingsController.java
@@ -0,0 +1,123 @@
+package org.cryptomator.ui.recoverykey;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javafx.application.Application;
+import javafx.beans.binding.Bindings;
+import javafx.beans.binding.BooleanBinding;
+import javafx.beans.property.IntegerProperty;
+import javafx.beans.property.ObjectProperty;
+import javafx.fxml.FXML;
+import javafx.scene.Scene;
+import javafx.scene.control.CheckBox;
+import javafx.stage.Stage;
+
+import dagger.Lazy;
+import org.cryptomator.common.recovery.RecoveryActionType;
+import org.cryptomator.common.vaults.Vault;
+import org.cryptomator.common.vaults.VaultState;
+import org.cryptomator.ui.addvaultwizard.CreateNewVaultExpertSettingsController;
+import org.cryptomator.ui.common.FxController;
+import org.cryptomator.ui.common.FxmlFile;
+import org.cryptomator.ui.common.FxmlScene;
+import org.cryptomator.ui.controls.NumericTextField;
+
+@RecoveryKeyScoped
+public class RecoveryKeyExpertSettingsController 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/vault/#name-shortening";
+
+ private final Stage window;
+ private final Lazy application;
+ private final Vault vault;
+ private final ObjectProperty recoverType;
+ private final IntegerProperty shorteningThreshold;
+ private final Lazy resetPasswordScene;
+ private final Lazy createScene;
+ private final Lazy onBoardingScene;
+ private final Lazy recoverScene;
+ private final BooleanBinding validShorteningThreshold;
+
+ @FXML
+ public CheckBox expertSettingsCheckBox;
+ @FXML
+ public NumericTextField shorteningThresholdTextField;
+
+ @Inject
+ public RecoveryKeyExpertSettingsController(@RecoveryKeyWindow Stage window, //
+ Lazy application, //
+ @RecoveryKeyWindow Vault vault, //
+ @Named("recoverType") ObjectProperty recoverType, //
+ @Named("shorteningThreshold") IntegerProperty shorteningThreshold, //
+ @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy resetPasswordScene, //
+ @FxmlScene(FxmlFile.RECOVERYKEY_CREATE) Lazy createScene, //
+ @FxmlScene(FxmlFile.RECOVERYKEY_ONBOARDING) Lazy onBoardingScene, //
+ @FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy recoverScene) {
+ this.window = window;
+ this.application = application;
+ this.vault = vault;
+ this.recoverType = recoverType;
+ this.shorteningThreshold = shorteningThreshold;
+ this.resetPasswordScene = resetPasswordScene;
+ this.createScene = createScene;
+ this.onBoardingScene = onBoardingScene;
+ this.recoverScene = recoverScene;
+ this.validShorteningThreshold = Bindings.createBooleanBinding(this::isValidShorteningThreshold, shorteningThreshold);
+ }
+
+ @FXML
+ public void initialize() {
+ shorteningThresholdTextField.setPromptText(MIN_SHORTENING_THRESHOLD + "-" + MAX_SHORTENING_THRESHOLD);
+ shorteningThresholdTextField.setText(Integer.toString(MAX_SHORTENING_THRESHOLD));
+ shorteningThresholdTextField.textProperty().addListener((_, _, newValue) -> {
+ try {
+ int intValue = Integer.parseInt(newValue);
+ shorteningThreshold.set(intValue);
+ } catch (NumberFormatException e) {
+ shorteningThreshold.set(0); //the value is set to 0 to ensure that an invalid value assignment is detected during a NumberFormatException
+ }
+ });
+ }
+
+ @FXML
+ public void toggleUseExpertSettings() {
+ if (!expertSettingsCheckBox.isSelected()) {
+ shorteningThresholdTextField.setText(Integer.toString(CreateNewVaultExpertSettingsController.MAX_SHORTENING_THRESHOLD));
+ }
+ }
+
+ public void openDocs() {
+ application.get().getHostServices().showDocument(DOCS_NAME_SHORTENING_URL);
+ }
+
+ public BooleanBinding validShorteningThresholdProperty() {
+ return validShorteningThreshold;
+ }
+
+ public boolean isValidShorteningThreshold() {
+ var value = shorteningThreshold.get();
+ return value >= MIN_SHORTENING_THRESHOLD && value <= MAX_SHORTENING_THRESHOLD;
+ }
+
+ @FXML
+ public void back() {
+ if (recoverType.get() == RecoveryActionType.RESTORE_ALL && vault.getState() == VaultState.Value.VAULT_CONFIG_MISSING) {
+ window.setScene(recoverScene.get());
+ } else if (recoverType.get() == RecoveryActionType.RESTORE_ALL && vault.getState() == VaultState.Value.ALL_MISSING) {
+ window.setScene(recoverScene.get());
+ } else if (recoverType.get() == RecoveryActionType.RESTORE_VAULT_CONFIG) {
+ window.setScene(onBoardingScene.get());
+ }
+ }
+
+ @FXML
+ public void next() {
+ if (recoverType.get() == RecoveryActionType.RESTORE_VAULT_CONFIG) {
+ window.setScene(createScene.get());
+ } else {
+ window.setScene(resetPasswordScene.get());
+ }
+ }
+}
diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java
index 06095eebc..809d16b61 100644
--- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java
+++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java
@@ -5,8 +5,12 @@ import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
import org.cryptomator.common.Nullable;
+import org.cryptomator.common.recovery.RecoveryActionType;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.VaultConfig;
+import org.cryptomator.cryptolib.api.CryptorProvider;
+import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
+import org.cryptomator.ui.addvaultwizard.CreateNewVaultExpertSettingsController;
import org.cryptomator.ui.common.DefaultSceneFactory;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxControllerKey;
@@ -19,6 +23,10 @@ import org.cryptomator.ui.common.StageFactory;
import javax.inject.Named;
import javax.inject.Provider;
+import javafx.beans.property.IntegerProperty;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleIntegerProperty;
+import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
@@ -99,12 +107,18 @@ abstract class RecoveryKeyModule {
}
@Provides
- @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD_SUCCESS)
+ @FxmlScene(FxmlFile.RECOVERYKEY_ONBOARDING)
@RecoveryKeyScoped
- static Scene provideRecoveryKeyResetPasswordSuccessScene(@RecoveryKeyWindow FxmlLoaderFactory fxmlLoaders) {
- return fxmlLoaders.createScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD_SUCCESS);
+ static Scene provideRecoveryKeyOnboardingScene(@RecoveryKeyWindow FxmlLoaderFactory fxmlLoaders) {
+ return fxmlLoaders.createScene(FxmlFile.RECOVERYKEY_ONBOARDING);
}
+ @Provides
+ @FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS)
+ @RecoveryKeyScoped
+ static Scene provideRecoveryKeyExpertSettingsScene(@RecoveryKeyWindow FxmlLoaderFactory fxmlLoaders) {
+ return fxmlLoaders.createScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS);
+ }
// ------------------
@@ -120,6 +134,25 @@ abstract class RecoveryKeyModule {
return new RecoveryKeyDisplayController(window, vault.getDisplayName(), recoveryKey.get(), localization);
}
+ @Provides
+ @Named("shorteningThreshold")
+ @RecoveryKeyScoped
+ static IntegerProperty provideShorteningThreshold() {
+ return new SimpleIntegerProperty(CreateNewVaultExpertSettingsController.MAX_SHORTENING_THRESHOLD);
+ }
+
+ @Provides
+ @Named("cipherCombo")
+ @RecoveryKeyScoped
+ static ObjectProperty provideCipherCombo() {
+ return new SimpleObjectProperty<>();
+ }
+
+ @Binds
+ @IntoMap
+ @FxControllerKey(RecoveryKeyExpertSettingsController.class)
+ abstract FxController provideRecoveryKeyExpertSettingsController(RecoveryKeyExpertSettingsController controller);
+
@Binds
@IntoMap
@FxControllerKey(RecoveryKeyRecoverController.class)
@@ -137,14 +170,14 @@ abstract class RecoveryKeyModule {
@Binds
@IntoMap
- @FxControllerKey(RecoveryKeyResetPasswordSuccessController.class)
- abstract FxController bindRecoveryKeyResetPasswordSuccessController(RecoveryKeyResetPasswordSuccessController controller);
+ @FxControllerKey(RecoveryKeyOnboardingController.class)
+ abstract FxController bindRecoveryKeyOnboardingController(RecoveryKeyOnboardingController controller);
@Provides
@IntoMap
@FxControllerKey(RecoveryKeyValidateController.class)
- static FxController bindRecoveryKeyValidateController(@RecoveryKeyWindow Vault vault, @RecoveryKeyWindow @Nullable VaultConfig.UnverifiedVaultConfig vaultConfig, @RecoveryKeyWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory) {
- return new RecoveryKeyValidateController(vault, vaultConfig, recoveryKey, recoveryKeyFactory);
+ static FxController bindRecoveryKeyValidateController(@RecoveryKeyWindow Vault vault, @RecoveryKeyWindow @Nullable VaultConfig.UnverifiedVaultConfig vaultConfig, @RecoveryKeyWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory, @Named("recoverType") ObjectProperty recoverType, @Named("cipherCombo") ObjectProperty cipherCombo, @Nullable MasterkeyFileAccess masterkeyFileAccess) {
+ return new RecoveryKeyValidateController(vault, vaultConfig, recoveryKey, recoveryKeyFactory, masterkeyFileAccess, recoverType, cipherCombo);
}
@Provides
diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyOnboardingController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyOnboardingController.java
new file mode 100644
index 000000000..dd15413d8
--- /dev/null
+++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyOnboardingController.java
@@ -0,0 +1,176 @@
+package org.cryptomator.ui.recoverykey;
+
+import dagger.Lazy;
+import org.cryptomator.common.recovery.RecoveryActionType;
+import org.cryptomator.common.vaults.Vault;
+import org.cryptomator.common.vaults.VaultState;
+import org.cryptomator.ui.common.FxController;
+import org.cryptomator.ui.common.FxmlFile;
+import org.cryptomator.ui.common.FxmlScene;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javafx.beans.binding.Bindings;
+import javafx.beans.binding.BooleanBinding;
+import javafx.beans.property.ObjectProperty;
+import javafx.fxml.FXML;
+import javafx.scene.Scene;
+import javafx.scene.control.Button;
+import javafx.scene.control.CheckBox;
+import javafx.scene.control.Label;
+import javafx.scene.control.RadioButton;
+import javafx.scene.control.Toggle;
+import javafx.scene.control.ToggleGroup;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Region;
+import javafx.scene.layout.VBox;
+import javafx.stage.Stage;
+import java.util.ResourceBundle;
+
+import static org.cryptomator.common.recovery.RecoveryActionType.RESTORE_ALL;
+import static org.cryptomator.common.recovery.RecoveryActionType.RESTORE_VAULT_CONFIG;
+
+@RecoveryKeyScoped
+public class RecoveryKeyOnboardingController implements FxController {
+
+ private final Stage window;
+ private final Vault vault;
+ private final Lazy recoverykeyRecoverScene;
+ private final Lazy recoverykeyExpertSettingsScene;
+ private final ObjectProperty recoverType;
+ private final ResourceBundle resourceBundle;
+
+ public Label titleLabel;
+ public Label messageLabel;
+ public Label pleaseConfirm;
+ public Label secondTextDesc;
+
+ @FXML
+ private CheckBox affirmationBox;
+ @FXML
+ private RadioButton recoveryKeyRadio;
+ @FXML
+ private RadioButton passwordRadio;
+ @FXML
+ private Button nextButton;
+ @FXML
+ private VBox chooseMethodeBox;
+ @FXML
+ private ToggleGroup methodToggleGroup = new ToggleGroup();
+ @FXML
+ private HBox hBox;
+
+ @Inject
+ public RecoveryKeyOnboardingController(@RecoveryKeyWindow Stage window, //
+ @RecoveryKeyWindow Vault vault, //
+ @FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy recoverykeyRecoverScene, //
+ @FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) Lazy recoverykeyExpertSettingsScene, //
+ @Named("recoverType") ObjectProperty recoverType, //
+ ResourceBundle resourceBundle) {
+ this.window = window;
+ this.vault = vault;
+ this.recoverykeyRecoverScene = recoverykeyRecoverScene;
+ this.recoverykeyExpertSettingsScene = recoverykeyExpertSettingsScene;
+ this.recoverType = recoverType;
+ this.resourceBundle = resourceBundle;
+ }
+
+ @FXML
+ public void initialize() {
+ recoveryKeyRadio.setToggleGroup(methodToggleGroup);
+ passwordRadio.setToggleGroup(methodToggleGroup);
+
+ BooleanBinding showMethodSelection = Bindings.createBooleanBinding(
+ () -> recoverType.get() == RecoveryActionType.RESTORE_VAULT_CONFIG, recoverType);
+ chooseMethodeBox.visibleProperty().bind(showMethodSelection);
+ chooseMethodeBox.managedProperty().bind(showMethodSelection);
+
+ nextButton.disableProperty().bind(
+ affirmationBox.selectedProperty().not()
+ .or(methodToggleGroup.selectedToggleProperty().isNull().and(showMethodSelection))
+ );
+
+ switch (recoverType.get()) {
+ case RESTORE_MASTERKEY -> {
+ window.setTitle(resourceBundle.getString("recover.recoverMasterkey.title"));
+ messageLabel.setVisible(false);
+ messageLabel.setManaged(false);
+ pleaseConfirm.setText(resourceBundle.getString("recover.onBoarding.pleaseConfirm"));
+ }
+ case RESTORE_ALL -> {
+ window.setTitle(resourceBundle.getString("recover.recoverVaultConfig.title"));
+ messageLabel.setVisible(true);
+ messageLabel.setManaged(true);
+ pleaseConfirm.setText(resourceBundle.getString("recover.onBoarding.otherwisePleaseConfirm"));
+ }
+ case RESTORE_VAULT_CONFIG -> {
+ window.setTitle(resourceBundle.getString("recover.recoverVaultConfig.title"));
+ messageLabel.setVisible(false);
+ messageLabel.setManaged(false);
+ pleaseConfirm.setText(resourceBundle.getString("recover.onBoarding.pleaseConfirm"));
+ }
+ default -> window.setTitle("");
+ }
+
+ if (vault.getState() == VaultState.Value.ALL_MISSING) {
+ messageLabel.setText(resourceBundle.getString("recover.onBoarding.allMissing.intro"));
+ } else {
+ messageLabel.setText(resourceBundle.getString("recover.onBoarding.intro"));
+ }
+
+ titleLabel.textProperty().bind(Bindings.createStringBinding(() ->
+ recoverType.get() == RecoveryActionType.RESTORE_MASTERKEY
+ ? resourceBundle.getString("recover.recoverMasterkey.title")
+ : resourceBundle.getString("recover.recoverVaultConfig.title"), recoverType));
+
+ BooleanBinding isRestoreMasterkey = Bindings.createBooleanBinding(
+ () -> recoverType.get() == RecoveryActionType.RESTORE_MASTERKEY, recoverType);
+ hBox.minHeightProperty().bind(Bindings.when(isRestoreMasterkey).then(206.0).otherwise(Region.USE_COMPUTED_SIZE));
+
+ secondTextDesc.textProperty().bind(Bindings.createStringBinding(() -> {
+ RecoveryActionType type = recoverType.get();
+ Toggle sel = methodToggleGroup.getSelectedToggle();
+ return switch (type) {
+ case RESTORE_VAULT_CONFIG -> resourceBundle.getString(sel == passwordRadio
+ ? "recover.onBoarding.intro.password"
+ : "recover.onBoarding.intro.recoveryKey");
+ case RESTORE_MASTERKEY -> resourceBundle.getString("recover.onBoarding.intro.masterkey.recoveryKey");
+ case RESTORE_ALL -> resourceBundle.getString("recover.onBoarding.intro.recoveryKey");
+ default -> "";
+ };
+ }, recoverType, methodToggleGroup.selectedToggleProperty()));
+
+ showMethodSelection.addListener((_, _, nowShown) -> {
+ if (nowShown && methodToggleGroup.getSelectedToggle() == null) {
+ methodToggleGroup.selectToggle(recoveryKeyRadio);
+ }
+ });
+ }
+
+ @FXML
+ public void close() {
+ window.close();
+ }
+
+ @FXML
+ public void next() {
+ switch (recoverType.get()) {
+ case RESTORE_VAULT_CONFIG, RESTORE_ALL -> {
+ Object selectedToggle = methodToggleGroup.getSelectedToggle();
+ if (selectedToggle == recoveryKeyRadio) {
+ recoverType.set(RESTORE_ALL);
+ window.setScene(recoverykeyRecoverScene.get());
+ } else if (selectedToggle == passwordRadio) {
+ recoverType.set(RESTORE_VAULT_CONFIG);
+ window.setScene(recoverykeyExpertSettingsScene.get());
+ } else {
+ window.setScene(recoverykeyRecoverScene.get());
+ }
+ }
+ case RESTORE_MASTERKEY -> window.setScene(recoverykeyRecoverScene.get());
+ default -> window.setScene(recoverykeyRecoverScene.get()); // Fallback
+ }
+ window.centerOnScreen();
+ }
+
+}
diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java
index 944c52043..8eb505d47 100644
--- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java
+++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java
@@ -1,54 +1,105 @@
package org.cryptomator.ui.recoverykey;
import dagger.Lazy;
-import org.cryptomator.common.Nullable;
+import org.cryptomator.common.recovery.RecoveryActionType;
import org.cryptomator.common.vaults.Vault;
-import org.cryptomator.cryptofs.VaultConfig;
+import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import javax.inject.Inject;
-import javafx.beans.Observable;
-import javafx.beans.property.StringProperty;
-import javafx.beans.value.ObservableValue;
+import javax.inject.Named;
+import javafx.beans.property.ObjectProperty;
import javafx.fxml.FXML;
import javafx.scene.Scene;
+import javafx.scene.control.Button;
import javafx.stage.Stage;
import java.util.ResourceBundle;
@RecoveryKeyScoped
public class RecoveryKeyRecoverController implements FxController {
- private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyCreationController.class);
-
private final Stage window;
- private final Lazy resetPasswordScene;
+ private final Vault vault;
+ private final Lazy nextScene;
+ private final Lazy onBoardingScene;
+ private final ResourceBundle resourceBundle;
+ public ObjectProperty recoverType;
+
+ @FXML
+ private Button cancelButton;
@FXML
RecoveryKeyValidateController recoveryKeyValidateController;
@Inject
- public RecoveryKeyRecoverController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, @RecoveryKeyWindow StringProperty recoveryKey, @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy resetPasswordScene, ResourceBundle resourceBundle) {
+ public RecoveryKeyRecoverController(@RecoveryKeyWindow Stage window, //
+ @RecoveryKeyWindow Vault vault, //
+ ResourceBundle resourceBundle, //
+ @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy resetPasswordScene, //
+ @FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) Lazy expertSettingsScene, //
+ @FxmlScene(FxmlFile.RECOVERYKEY_ONBOARDING) Lazy onBoardingScene, //
+ @Named("recoverType") ObjectProperty recoverType) {
this.window = window;
- window.setTitle(resourceBundle.getString("recoveryKey.recover.title"));
- this.resetPasswordScene = resetPasswordScene;
+ this.vault = vault;
+ this.resourceBundle = resourceBundle;
+ this.onBoardingScene = onBoardingScene;
+ this.recoverType = recoverType;
+ this.nextScene = switch (recoverType.get()) {
+ case RESTORE_ALL, RESTORE_VAULT_CONFIG -> {
+ window.setTitle(resourceBundle.getString("recover.recoverVaultConfig.title"));
+ yield expertSettingsScene;
+ }
+ case RESTORE_MASTERKEY -> {
+ window.setTitle(resourceBundle.getString("recover.recoverMasterkey.title"));
+ yield resetPasswordScene;
+ }
+ case RESET_PASSWORD -> {
+ window.setTitle(resourceBundle.getString("recoveryKey.recover.title"));
+ yield resetPasswordScene;
+ }
+ case SHOW_KEY -> {
+ window.setTitle(resourceBundle.getString("recoveryKey.display.title"));
+ yield resetPasswordScene;
+ }
+ default -> throw new IllegalArgumentException("Unexpected recovery action type: " + recoverType.get());
+ };
}
@FXML
public void initialize() {
+ if (recoverType.get() == RecoveryActionType.RESET_PASSWORD) {
+ cancelButton.setText(resourceBundle.getString("generic.button.cancel"));
+ } else {
+ cancelButton.setText(resourceBundle.getString("generic.button.back"));
+ }
}
@FXML
- public void close() {
- window.close();
+ public void closeOrReturn() {
+ switch (recoverType.get()) {
+ case RESET_PASSWORD -> window.close();
+ case RESTORE_MASTERKEY -> {
+ window.setScene(onBoardingScene.get());
+ window.centerOnScreen();
+ }
+ default -> {
+ if(vault.getState().equals(VaultState.Value.ALL_MISSING)){
+ recoverType.set(RecoveryActionType.RESTORE_ALL);
+ }
+ else {
+ recoverType.set(RecoveryActionType.RESTORE_VAULT_CONFIG);
+ }
+ window.setScene(onBoardingScene.get());
+ window.centerOnScreen();
+ }
+ }
}
@FXML
public void recover() {
- window.setScene(resetPasswordScene.get());
+ window.setScene(nextScene.get());
}
/* Getter/Setter */
diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java
index 18a952ea5..0c06ba9b2 100644
--- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java
+++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java
@@ -1,25 +1,44 @@
package org.cryptomator.ui.recoverykey;
import dagger.Lazy;
+import org.cryptomator.common.recovery.CryptoFsInitializer;
+import org.cryptomator.common.recovery.MasterkeyService;
+import org.cryptomator.common.recovery.RecoveryActionType;
+import org.cryptomator.common.recovery.RecoveryDirectory;
import org.cryptomator.common.vaults.Vault;
+import org.cryptomator.common.vaults.VaultListManager;
+import org.cryptomator.cryptolib.api.CryptoException;
+import org.cryptomator.cryptolib.api.CryptorProvider;
+import org.cryptomator.cryptolib.api.Masterkey;
+import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
+import org.cryptomator.ui.changepassword.NewPasswordController;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
-import org.cryptomator.ui.changepassword.NewPasswordController;
+import org.cryptomator.ui.dialogs.Dialogs;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
+import javax.inject.Named;
+import javafx.beans.property.IntegerProperty;
+import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.StringProperty;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.Scene;
+import javafx.scene.control.Button;
import javafx.stage.Stage;
import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ResourceBundle;
import java.util.concurrent.ExecutorService;
+import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
+import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
+
@RecoveryKeyScoped
public class RecoveryKeyResetPasswordController implements FxController {
@@ -30,48 +49,140 @@ public class RecoveryKeyResetPasswordController implements FxController {
private final RecoveryKeyFactory recoveryKeyFactory;
private final ExecutorService executor;
private final StringProperty recoveryKey;
- private final Lazy recoverResetPasswordSuccessScene;
+ private final Lazy recoverExpertSettingsScene;
+ private final Lazy recoverykeyRecoverScene;
private final FxApplicationWindows appWindows;
+ private final MasterkeyFileAccess masterkeyFileAccess;
+ private final VaultListManager vaultListManager;
+ private final IntegerProperty shorteningThreshold;
+ private final ObjectProperty recoverType;
+ private final ObjectProperty cipherCombo;
+ private final ResourceBundle resourceBundle;
+ private final Dialogs dialogs;
public NewPasswordController newPasswordController;
+ public Button nextButton;
@Inject
- public RecoveryKeyResetPasswordController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, RecoveryKeyFactory recoveryKeyFactory, ExecutorService executor, @RecoveryKeyWindow StringProperty recoveryKey, @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD_SUCCESS) Lazy recoverResetPasswordSuccessScene, FxApplicationWindows appWindows) {
+ public RecoveryKeyResetPasswordController(@RecoveryKeyWindow Stage window, //
+ @RecoveryKeyWindow Vault vault, //
+ RecoveryKeyFactory recoveryKeyFactory, //
+ ExecutorService executor, //
+ @RecoveryKeyWindow StringProperty recoveryKey, //
+ @FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) Lazy recoverExpertSettingsScene, //
+ @FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy recoverykeyRecoverScene, //
+ FxApplicationWindows appWindows, //
+ MasterkeyFileAccess masterkeyFileAccess, //
+ VaultListManager vaultListManager, //
+ @Named("shorteningThreshold") IntegerProperty shorteningThreshold, //
+ @Named("recoverType") ObjectProperty recoverType, //
+ @Named("cipherCombo") ObjectProperty cipherCombo, //
+ ResourceBundle resourceBundle, //
+ Dialogs dialogs) {
this.window = window;
this.vault = vault;
this.recoveryKeyFactory = recoveryKeyFactory;
this.executor = executor;
this.recoveryKey = recoveryKey;
- this.recoverResetPasswordSuccessScene = recoverResetPasswordSuccessScene;
+ this.recoverExpertSettingsScene = recoverExpertSettingsScene;
+ this.recoverykeyRecoverScene = recoverykeyRecoverScene;
this.appWindows = appWindows;
+ this.masterkeyFileAccess = masterkeyFileAccess;
+ this.vaultListManager = vaultListManager;
+ this.shorteningThreshold = shorteningThreshold;
+ this.cipherCombo = cipherCombo;
+ this.recoverType = recoverType;
+ this.resourceBundle = resourceBundle;
+ this.dialogs = dialogs;
+ }
+
+ @FXML
+ public void initialize() {
+ switch (recoverType.get()) {
+ case RESTORE_MASTERKEY, RESTORE_ALL -> nextButton.setText(resourceBundle.getString("recoveryKey.recover.recoverBtn"));
+ case RESET_PASSWORD -> nextButton.setText(resourceBundle.getString("recoveryKey.recover.resetBtn"));
+ default -> nextButton.setText(resourceBundle.getString("recoveryKey.recover.recoverBtn")); // Fallback
+ }
}
@FXML
public void close() {
- window.close();
+ switch (recoverType.get()) {
+ case RESTORE_ALL -> window.setScene(recoverExpertSettingsScene.get());
+ case RESTORE_MASTERKEY, RESET_PASSWORD -> window.setScene(recoverykeyRecoverScene.get());
+ default -> window.close();
+ }
+ }
+
+ @FXML
+ public void next() {
+ switch (recoverType.get()) {
+ case RESTORE_ALL -> restorePassword();
+ case RESTORE_MASTERKEY, RESET_PASSWORD -> resetPassword();
+ default -> resetPassword(); // Fallback
+ }
+ }
+
+ @FXML
+ public void restorePassword() {
+ try (RecoveryDirectory recoveryDirectory = RecoveryDirectory.create(vault.getPath())) {
+ Path recoveryPath = recoveryDirectory.getRecoveryPath();
+ MasterkeyService.recoverFromRecoveryKey(recoveryKey.get(), recoveryKeyFactory, recoveryPath, newPasswordController.passwordField.getCharacters());
+
+ try (Masterkey masterkey = MasterkeyService.load(masterkeyFileAccess, recoveryPath.resolve(MASTERKEY_FILENAME), newPasswordController.passwordField.getCharacters())) {
+ CryptoFsInitializer.init(recoveryPath, masterkey, shorteningThreshold.get(), cipherCombo.get());
+ }
+
+ recoveryDirectory.moveRecoveredFile(MASTERKEY_FILENAME);
+ recoveryDirectory.moveRecoveredFile(VAULTCONFIG_FILENAME);
+
+ if (!vaultListManager.isAlreadyAdded(vault.getPath())) {
+ vaultListManager.add(vault.getPath());
+ }
+ window.close();
+ dialogs.prepareRecoverPasswordSuccess((Stage)window.getOwner()) //
+ .setTitleKey("recover.recoverVaultConfig.title") //
+ .setMessageKey("recoveryKey.recover.resetVaultConfigSuccess.message") //
+ .build().showAndWait();
+
+ } catch (IOException | CryptoException e) {
+ LOG.error("Recovery process failed", e);
+ appWindows.showErrorWindow(e, window, null);
+ }
}
@FXML
public void resetPassword() {
Task task = new ResetPasswordTask();
- task.setOnScheduled(event -> {
+
+ task.setOnScheduled(_ -> {
LOG.debug("Using recovery key to reset password for {}.", vault.getDisplayablePath());
});
- task.setOnSucceeded(event -> {
- LOG.info("Used recovery key to reset password for {}.", vault.getDisplayablePath());
- window.setScene(recoverResetPasswordSuccessScene.get());
+
+ task.setOnSucceeded(_ -> {
+ LOG.debug("Used recovery key to reset password for {}.", vault.getDisplayablePath());
+ window.close();
+ switch (recoverType.get()){
+ case RESET_PASSWORD -> dialogs.prepareRecoverPasswordSuccess((Stage)window.getOwner()).build().showAndWait();
+ case RESTORE_MASTERKEY -> dialogs.prepareRecoverPasswordSuccess((Stage)window.getOwner()).setTitleKey("recover.recoverMasterkey.title").setMessageKey("recoveryKey.recover.resetMasterkeyFileSuccess.message").build().showAndWait();
+ default -> dialogs.prepareRecoverPasswordSuccess(window).build().showAndWait(); // Fallback
+ }
});
- task.setOnFailed(event -> {
+
+ task.setOnFailed(_ -> {
LOG.error("Resetting password failed.", task.getException());
appWindows.showErrorWindow(task.getException(), window, null);
});
+
executor.submit(task);
}
private class ResetPasswordTask extends Task {
- private ResetPasswordTask() {
- setOnFailed(event -> LOG.error("Failed to reset password", getException()));
+ private static final Logger LOG = LoggerFactory.getLogger(ResetPasswordTask.class);
+
+ public ResetPasswordTask() {
+ setOnFailed(_ -> LOG.error("Failed to reset password", getException()));
}
@Override
@@ -79,7 +190,6 @@ public class RecoveryKeyResetPasswordController implements FxController {
recoveryKeyFactory.newMasterkeyFileWithPassphrase(vault.getPath(), recoveryKey.get(), newPasswordController.passwordField.getCharacters());
return null;
}
-
}
/* Getter/Setter */
diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordSuccessController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordSuccessController.java
deleted file mode 100644
index b8b106d8b..000000000
--- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordSuccessController.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package org.cryptomator.ui.recoverykey;
-
-import org.cryptomator.ui.common.FxController;
-
-import javax.inject.Inject;
-import javafx.fxml.FXML;
-import javafx.stage.Stage;
-
-@RecoveryKeyScoped
-public class RecoveryKeyResetPasswordSuccessController implements FxController {
-
- private final Stage window;
-
- @Inject
- public RecoveryKeyResetPasswordSuccessController(@RecoveryKeyWindow Stage window) {
- this.window = window;
- }
-
- @FXML
- public void close() {
- window.close();
- }
-
-}
diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyValidateController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyValidateController.java
index 4a8224ffe..35f4c15ed 100644
--- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyValidateController.java
+++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyValidateController.java
@@ -1,18 +1,23 @@
package org.cryptomator.ui.recoverykey;
-
import com.google.common.base.CharMatcher;
import com.google.common.base.Strings;
import org.cryptomator.common.Nullable;
import org.cryptomator.common.ObservableUtil;
+import org.cryptomator.common.recovery.MasterkeyService;
+import org.cryptomator.common.recovery.RecoveryActionType;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.VaultConfig;
import org.cryptomator.cryptofs.VaultConfigLoadException;
import org.cryptomator.cryptofs.VaultKeyInvalidException;
+import org.cryptomator.cryptolib.api.CryptoException;
+import org.cryptomator.cryptolib.api.CryptorProvider;
+import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
import org.cryptomator.ui.common.FxController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import javax.inject.Named;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.StringProperty;
@@ -22,10 +27,12 @@ import javafx.scene.control.TextArea;
import javafx.scene.control.TextFormatter;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
+import java.io.IOException;
+import java.util.NoSuchElementException;
public class RecoveryKeyValidateController implements FxController {
- private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyCreationController.class);
+ private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyValidateController.class);
private static final CharMatcher ALLOWED_CHARS = CharMatcher.inRange('a', 'z').or(CharMatcher.is(' '));
private final Vault vault;
@@ -36,13 +43,23 @@ public class RecoveryKeyValidateController implements FxController {
private final ObservableValue recoveryKeyInvalid;
private final RecoveryKeyFactory recoveryKeyFactory;
private final ObjectProperty recoveryKeyState;
+ private final ObjectProperty cipherCombo;
private final AutoCompleter autoCompleter;
+ private final ObjectProperty recoverType;
+ private final MasterkeyFileAccess masterkeyFileAccess;
private volatile boolean isWrongKey;
public TextArea textarea;
- public RecoveryKeyValidateController(Vault vault, @Nullable VaultConfig.UnverifiedVaultConfig vaultConfig, StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory) {
+ public RecoveryKeyValidateController(Vault vault, //
+ @Nullable VaultConfig.UnverifiedVaultConfig vaultConfig, //
+ StringProperty recoveryKey, //
+ RecoveryKeyFactory recoveryKeyFactory, //
+ MasterkeyFileAccess masterkeyFileAccess, //
+ @Named("recoverType") ObjectProperty recoverType, //
+ @Named("cipherCombo") ObjectProperty cipherCombo
+ ) {
this.vault = vault;
this.unverifiedVaultConfig = vaultConfig;
this.recoveryKey = recoveryKey;
@@ -52,6 +69,9 @@ public class RecoveryKeyValidateController implements FxController {
this.recoveryKeyCorrect = ObservableUtil.mapWithDefault(recoveryKeyState, RecoveryKeyState.CORRECT::equals, false);
this.recoveryKeyWrong = ObservableUtil.mapWithDefault(recoveryKeyState, RecoveryKeyState.WRONG::equals, false);
this.recoveryKeyInvalid = ObservableUtil.mapWithDefault(recoveryKeyState, RecoveryKeyState.INVALID::equals, false);
+ this.recoverType = recoverType;
+ this.cipherCombo = cipherCombo;
+ this.masterkeyFileAccess = masterkeyFileAccess;
}
@FXML
@@ -117,14 +137,37 @@ public class RecoveryKeyValidateController implements FxController {
}
private void validateRecoveryKey() {
- isWrongKey = false;
- var valid = recoveryKeyFactory.validateRecoveryKey(recoveryKey.get(), unverifiedVaultConfig != null ? this::checkKeyAgainstVaultConfig : null);
- if (valid) {
- recoveryKeyState.set(RecoveryKeyState.CORRECT);
- } else if (isWrongKey) { //set via side effect in checkKeyAgainstVaultConfig()
- recoveryKeyState.set(RecoveryKeyState.WRONG);
- } else {
- recoveryKeyState.set(RecoveryKeyState.INVALID);
+ switch (recoverType.get()) {
+ case RESTORE_ALL, RESTORE_VAULT_CONFIG -> {
+ try {
+ var scheme = MasterkeyService.validateRecoveryKeyAndDetectCombo(recoveryKeyFactory, vault, recoveryKey.get(), masterkeyFileAccess);
+ cipherCombo.set(scheme);
+ recoveryKeyState.set(RecoveryKeyState.CORRECT);
+ } catch (CryptoException e) {
+ LOG.info("Recovery key is valid but crypto scheme couldn't be determined", e);
+ recoveryKeyState.set(RecoveryKeyState.WRONG);
+ } catch (IllegalArgumentException e) {
+ LOG.info("Recovery key is syntactically invalid", e);
+ recoveryKeyState.set(RecoveryKeyState.INVALID);
+ } catch (IOException e) {
+ LOG.warn("IO error while validating recovery key", e);
+ recoveryKeyState.set(RecoveryKeyState.INVALID);
+ } catch (NoSuchElementException e) {
+ LOG.warn("Could not determine scheme from masterkey during recovery key validation, because no valid *.c9r file is present in vault", e);
+ recoveryKeyState.set(RecoveryKeyState.INVALID);
+ }
+ }
+ case RESTORE_MASTERKEY, RESET_PASSWORD, SHOW_KEY, CONVERT_VAULT -> {
+ isWrongKey = false;
+ boolean valid = recoveryKeyFactory.validateRecoveryKey(recoveryKey.get(), unverifiedVaultConfig != null ? this::checkKeyAgainstVaultConfig : null);
+ if (valid) {
+ recoveryKeyState.set(RecoveryKeyState.CORRECT);
+ } else if (isWrongKey) { //set via side effect in checkKeyAgainstVaultConfig()
+ recoveryKeyState.set(RecoveryKeyState.WRONG);
+ } else {
+ recoveryKeyState.set(RecoveryKeyState.INVALID);
+ }
+ }
}
}
diff --git a/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java b/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java
index 95f13d383..1c8d758fc 100644
--- a/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java
+++ b/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java
@@ -15,6 +15,7 @@ import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.StageFactory;
import org.cryptomator.ui.keyloading.KeyLoadingComponent;
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
+import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
import org.jetbrains.annotations.Nullable;
import javax.inject.Named;
@@ -27,7 +28,7 @@ import javafx.stage.Stage;
import java.util.Map;
import java.util.ResourceBundle;
-@Module(subcomponents = {KeyLoadingComponent.class})
+@Module(subcomponents = {KeyLoadingComponent.class, RecoveryKeyComponent.class})
abstract class UnlockModule {
@Provides
diff --git a/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java b/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java
index dd003d93d..67ae2f42d 100644
--- a/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java
+++ b/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java
@@ -1,16 +1,16 @@
package org.cryptomator.ui.vaultoptions;
import org.cryptomator.common.keychain.KeychainManager;
+import org.cryptomator.common.recovery.RecoveryActionType;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.changepassword.ChangePasswordComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.forgetpassword.ForgetPasswordComponent;
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.beans.property.SimpleBooleanProperty;
+import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.stage.Stage;
@@ -18,8 +18,6 @@ import javafx.stage.Stage;
@VaultOptionsScoped
public class MasterkeyOptionsController implements FxController {
- private static final Logger LOG = LoggerFactory.getLogger(MasterkeyOptionsController.class);
-
private final Vault vault;
private final Stage window;
private final ChangePasswordComponent.Builder changePasswordWindow;
@@ -51,12 +49,12 @@ public class MasterkeyOptionsController implements FxController {
@FXML
public void showRecoveryKey() {
- recoveryKeyWindow.create(vault, window).showRecoveryKeyCreationWindow();
+ recoveryKeyWindow.create(vault, window, new SimpleObjectProperty<>(RecoveryActionType.SHOW_KEY)).showRecoveryKeyCreationWindow();
}
@FXML
public void showRecoverVaultDialog() {
- recoveryKeyWindow.create(vault, window).showRecoveryKeyRecoverWindow();
+ recoveryKeyWindow.create(vault, window, new SimpleObjectProperty<>(RecoveryActionType.RESET_PASSWORD)).showRecoveryKeyRecoverWindow();
}
@FXML
diff --git a/src/main/resources/fxml/convertvault_hubtopassword_convert.fxml b/src/main/resources/fxml/convertvault_hubtopassword_convert.fxml
index 7ea190fd4..7797b41b2 100644
--- a/src/main/resources/fxml/convertvault_hubtopassword_convert.fxml
+++ b/src/main/resources/fxml/convertvault_hubtopassword_convert.fxml
@@ -6,7 +6,12 @@
-
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
@@ -26,7 +40,7 @@
-
-
-
+
+
diff --git a/src/main/resources/fxml/convertvault_hubtopassword_start.fxml b/src/main/resources/fxml/convertvault_hubtopassword_start.fxml
index d5c0a5e0b..27b6911f7 100644
--- a/src/main/resources/fxml/convertvault_hubtopassword_start.fxml
+++ b/src/main/resources/fxml/convertvault_hubtopassword_start.fxml
@@ -5,7 +5,10 @@
-
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/fxml/recoverykey_create.fxml b/src/main/resources/fxml/recoverykey_create.fxml
index 784ba49ed..2d9f0f418 100644
--- a/src/main/resources/fxml/recoverykey_create.fxml
+++ b/src/main/resources/fxml/recoverykey_create.fxml
@@ -40,7 +40,7 @@
-
+
@@ -49,8 +49,8 @@
-
-
+
+
diff --git a/src/main/resources/fxml/recoverykey_expert_settings.fxml b/src/main/resources/fxml/recoverykey_expert_settings.fxml
new file mode 100644
index 000000000..cceac8388
--- /dev/null
+++ b/src/main/resources/fxml/recoverykey_expert_settings.fxml
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/fxml/recoverykey_onboarding.fxml b/src/main/resources/fxml/recoverykey_onboarding.fxml
new file mode 100644
index 000000000..b1c701a9d
--- /dev/null
+++ b/src/main/resources/fxml/recoverykey_onboarding.fxml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/fxml/recoverykey_recover.fxml b/src/main/resources/fxml/recoverykey_recover.fxml
index 4c85f9356..5a9fddbdf 100644
--- a/src/main/resources/fxml/recoverykey_recover.fxml
+++ b/src/main/resources/fxml/recoverykey_recover.fxml
@@ -3,32 +3,46 @@
+
+
-
+
+
+
+
+ spacing="12">
+
+
+
+
+
+
+
+
+
-
+
+
-
+
-
-
+
-
+
-
+
diff --git a/src/main/resources/fxml/recoverykey_reset_password.fxml b/src/main/resources/fxml/recoverykey_reset_password.fxml
index 79827af35..8c28a1c9f 100644
--- a/src/main/resources/fxml/recoverykey_reset_password.fxml
+++ b/src/main/resources/fxml/recoverykey_reset_password.fxml
@@ -5,7 +5,12 @@
-
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
+
diff --git a/src/main/resources/fxml/recoverykey_reset_password_success.fxml b/src/main/resources/fxml/recoverykey_reset_password_success.fxml
deleted file mode 100644
index 16d96d21d..000000000
--- a/src/main/resources/fxml/recoverykey_reset_password_success.fxml
+++ /dev/null
@@ -1,53 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/main/resources/fxml/recoverykey_validate.fxml b/src/main/resources/fxml/recoverykey_validate.fxml
index 920dcd2b0..b1420a899 100644
--- a/src/main/resources/fxml/recoverykey_validate.fxml
+++ b/src/main/resources/fxml/recoverykey_validate.fxml
@@ -2,48 +2,40 @@
-
-
-
-
-
+ spacing="12">
-
-
-
+
diff --git a/src/main/resources/fxml/unlock_select_masterkeyfile.fxml b/src/main/resources/fxml/unlock_select_masterkeyfile.fxml
index bfc995ceb..60934cdba 100644
--- a/src/main/resources/fxml/unlock_select_masterkeyfile.fxml
+++ b/src/main/resources/fxml/unlock_select_masterkeyfile.fxml
@@ -12,6 +12,7 @@
+
+
-
+
diff --git a/src/main/resources/fxml/vault_detail.fxml b/src/main/resources/fxml/vault_detail.fxml
index 8ca11a34f..cfae48d8b 100644
--- a/src/main/resources/fxml/vault_detail.fxml
+++ b/src/main/resources/fxml/vault_detail.fxml
@@ -53,5 +53,6 @@
+
diff --git a/src/main/resources/fxml/vault_detail_missing_vault_config.fxml b/src/main/resources/fxml/vault_detail_missing_vault_config.fxml
new file mode 100644
index 000000000..26fa3e096
--- /dev/null
+++ b/src/main/resources/fxml/vault_detail_missing_vault_config.fxml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/fxml/vault_list.fxml b/src/main/resources/fxml/vault_list.fxml
index 6706ec293..e21237a43 100644
--- a/src/main/resources/fxml/vault_list.fxml
+++ b/src/main/resources/fxml/vault_list.fxml
@@ -13,8 +13,6 @@
-
-
+
diff --git a/src/main/resources/fxml/vault_list_contextmenu.fxml b/src/main/resources/fxml/vault_list_contextmenu.fxml
index bd8f19204..492905290 100644
--- a/src/main/resources/fxml/vault_list_contextmenu.fxml
+++ b/src/main/resources/fxml/vault_list_contextmenu.fxml
@@ -10,6 +10,7 @@
+
diff --git a/src/main/resources/i18n/strings.properties b/src/main/resources/i18n/strings.properties
index 5f7024849..3745b7c07 100644
--- a/src/main/resources/i18n/strings.properties
+++ b/src/main/resources/i18n/strings.properties
@@ -97,6 +97,7 @@ addvault.new.readme.accessLocation.4=Feel free to remove this file.
## Existing
addvaultwizard.existing.title=Add Existing Vault
addvaultwizard.existing.instruction=Choose the "vault.cryptomator" file of your existing vault. If only a file named "masterkey.cryptomator" exists, select that instead.
+addvaultwizard.existing.restore=Restore…
addvaultwizard.existing.chooseBtn=Choose…
addvaultwizard.existing.filePickerTitle=Select Vault File
addvaultwizard.existing.filePickerMimeDesc=Cryptomator Vault
@@ -128,6 +129,7 @@ unlock.unlockBtn=Unlock
## Select
unlock.chooseMasterkey.message=Masterkey file not found
unlock.chooseMasterkey.description=Cryptomator could not find the masterkey file for vault "%s". Please choose the key file manually.
+unlock.chooseMasterkey.restoreInstead=Restore the masterkey file instead
unlock.chooseMasterkey.filePickerTitle=Select Masterkey File
unlock.chooseMasterkey.filePickerMimeDesc=Cryptomator Masterkey
## Success
@@ -394,8 +396,10 @@ main.vaultlist.contextMenu.unlock=Unlock…
main.vaultlist.contextMenu.unlockNow=Unlock Now
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.contextMenu.share=Share…
+main.vaultlist.addVaultBtn.menuItemNew=Create New Vault…
+main.vaultlist.addVaultBtn.menuItemExisting=Open Existing Vault…
+main.vaultlist.addVaultBtn.menuItemRecover=Recover Existing Vault…
main.vaultlist.showEventsButton.tooltip=Open event view
##Notificaition
main.notification.updateAvailable=Update is available.
@@ -433,6 +437,9 @@ main.vaultDetail.missing.info=Cryptomator could not find a vault at this path.
main.vaultDetail.missing.recheck=Recheck
main.vaultDetail.missing.remove=Remove from Vault List…
main.vaultDetail.missing.changeLocation=Change Vault Location…
+### Missing Vault Config
+main.vaultDetail.missingVaultConfig.info=Vault config is missing.
+main.vaultDetail.missingVaultConfig.restore=Restore vault config
### Needs Migration
main.vaultDetail.migrateButton=Upgrade Vault
main.vaultDetail.migratePrompt=Your vault needs to be upgraded to a new format, before you can access it
@@ -497,6 +504,7 @@ vaultOptions.hub.convertBtn=Convert to Password-Based Vault
recoveryKey.display.title=Show Recovery Key
recoveryKey.create.message=Password required
recoveryKey.create.description=Enter the password for "%s" to show its recovery key.
+recoveryKey.recover.description=Enter the password for "%s" to recover the vault config.
recoveryKey.display.description=The following recovery key can be used to restore access to "%s":
recoveryKey.display.StorageHints=Keep it somewhere very secure, e.g.:\n • Store it using a password manager\n • Save it on a USB flash drive\n • Print it on paper
## Reset Password
@@ -509,9 +517,59 @@ recoveryKey.recover.invalidKey=This recovery key is not valid
recoveryKey.printout.heading=Cryptomator Recovery Key\n"%s"\n
### Reset Password
recoveryKey.recover.resetBtn=Reset
+recoveryKey.recover.recoverBtn=Recover
### Recovery Key Password Reset Success
recoveryKey.recover.resetSuccess.message=Password reset successful
recoveryKey.recover.resetSuccess.description=You can unlock your vault with the new password.
+### Recovery Key Vault Config Reset Success
+recoveryKey.recover.resetVaultConfigSuccess.message=Vault config reset successful
+recoveryKey.recover.resetMasterkeyFileSuccess.message=Masterkey file reset successful
+recoveryKey.recover.resetMasterkeyFileSuccess.description=You can unlock your vault with your password now.
+
+# Recover Vault Config File and/or Masterkey
+##Add Existing Vault without recovery - Dialog
+recover.existing.title=Vault Added
+recover.existing.message=The vault was added successfully
+recover.existing.description=Your vault "%s" has been added to the vault list. No recovery process was necessary.
+
+##Vault Already Exists - Dialog
+recover.alreadyExists.title=Vault Already Exists
+recover.alreadyExists.message=This vault has already been added
+recover.alreadyExists.description=Your vault "%s" is already present in your vault list and was therefore not added again.
+
+##Invalid Selection - Dialog
+recover.invalidSelection.title=Invalid Selection
+recover.invalidSelection.message=Your selection is not a vault
+recover.invalidSelection.description=The selected folder must be a valid Cryptomator vault.
+
+## Contact Hub Vault Owner - Dialog
+contactHubVaultOwner.title=Hub Vault
+contactHubVaultOwner.message=This vault was created with Cryptomator Hub
+contactHubVaultOwner.description=Please reach out to the vault owner to restore the missing file. They can download the vault template from Cryptomator Hub.
+
+##Dialog Title
+recover.recoverVaultConfig.title=Recover Vault Config
+recover.recoverMasterkey.title=Recover Masterkey
+
+## OnBoarding
+recover.onBoarding.chooseMethod=Choose recovery method:
+recover.onBoarding.useRecoveryKey=Use Recovery Key
+recover.onBoarding.usePassword=Use Password
+recover.onBoarding.intro=Make sure to check the following:
+recover.onBoarding.pleaseConfirm=Before proceeding, please confirm that:
+recover.onBoarding.otherwisePleaseConfirm=Otherwise, please confirm that:
+recover.onBoarding.allMissing.intro=If this vault is managed by Cryptomator Hub, the vault owner must restore it for you.
+recover.onBoarding.intro.ensure=All files are fully synced.
+recover.onBoarding.affirmation=I have read and understood these requirements
+
+###Vault Config Missing
+recover.onBoarding.intro.recoveryKey=You have the recovery key and know if expert settings were used.
+recover.onBoarding.intro.password=You have the vault password and know if expert settings were used.
+###Masterkey Missing
+recover.onBoarding.intro.masterkey.recoveryKey=You have the vault recovery key.
+
+## Expert Settings
+recover.expertSettings.shorteningThreshold.title=This value must match the one used before recovery to ensure compatibility with previously encrypted data.
# Convert Vault
convertVault.title=Convert Vault
diff --git a/src/main/resources/i18n/strings_ar.properties b/src/main/resources/i18n/strings_ar.properties
index f69f4b67c..ecf480be6 100644
--- a/src/main/resources/i18n/strings_ar.properties
+++ b/src/main/resources/i18n/strings_ar.properties
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=فتح…
main.vaultlist.contextMenu.unlockNow=افتح الان
main.vaultlist.contextMenu.vaultoptions=إظهار خيارات المخزن
main.vaultlist.contextMenu.reveal=اظهار القرص
+main.vaultlist.contextMenu.share=مشاركة…
main.vaultlist.addVaultBtn.menuItemNew=إنشاء مخزن جديد...
main.vaultlist.addVaultBtn.menuItemExisting=افتح مخزن موجود...
main.vaultlist.showEventsButton.tooltip=عرض الإشعارات
diff --git a/src/main/resources/i18n/strings_ca.properties b/src/main/resources/i18n/strings_ca.properties
index 64978397e..1a0ccbc57 100644
--- a/src/main/resources/i18n/strings_ca.properties
+++ b/src/main/resources/i18n/strings_ca.properties
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=Desbloca…
main.vaultlist.contextMenu.unlockNow=Desbloqueja ara
main.vaultlist.contextMenu.vaultoptions=Opcions de la caixa forta
main.vaultlist.contextMenu.reveal=Mostra la unitat
+main.vaultlist.contextMenu.share=Compateix…
main.vaultlist.addVaultBtn.menuItemNew=Crea una nova caixa forta…
main.vaultlist.addVaultBtn.menuItemExisting=Obri una caixa forta existent...
##Notificaition
diff --git a/src/main/resources/i18n/strings_cs.properties b/src/main/resources/i18n/strings_cs.properties
index 248e1a529..0fd46640a 100644
--- a/src/main/resources/i18n/strings_cs.properties
+++ b/src/main/resources/i18n/strings_cs.properties
@@ -364,6 +364,7 @@ main.vaultlist.contextMenu.unlock=Odemknout…
main.vaultlist.contextMenu.unlockNow=Odemknout nyní
main.vaultlist.contextMenu.vaultoptions=Zobrazit možnosti trezoru
main.vaultlist.contextMenu.reveal=Zobrazit jednotku
+main.vaultlist.contextMenu.share=Sdílet…
##Notificaition
## Vault Detail
### Welcome
diff --git a/src/main/resources/i18n/strings_da.properties b/src/main/resources/i18n/strings_da.properties
index 0ffdd6658..0490cdfef 100644
--- a/src/main/resources/i18n/strings_da.properties
+++ b/src/main/resources/i18n/strings_da.properties
@@ -283,6 +283,7 @@ preferences.title=Præferencer
## General
preferences.general=Generelt
preferences.general.startHidden=Skjul vinduet når Cryptomator starter
+preferences.general.autoCloseVaults=Lås bokse uden at spørge ved afslutning af applikationen
preferences.general.debugLogging=Aktivér fejllogning
preferences.general.debugDirectory=Vis logfiler
preferences.general.autoStart=Start Cryptomator automatisk ved opstart
@@ -301,6 +302,7 @@ preferences.interface.interfaceOrientation=Brugerflade retning
preferences.interface.interfaceOrientation.ltr=Venstre til højre
preferences.interface.interfaceOrientation.rtl=Højre til venstre
preferences.interface.showTrayIcon=Vis ikon i system-bakken (kræver genstart)
+preferences.interface.compactMode=Aktivér kompakt bokse-liste
## Volume
preferences.volume=Virtuelt drev
preferences.volume.type=Standard Drev Type
@@ -320,7 +322,13 @@ preferences.updates.currentVersion=Nuværende version: %s
preferences.updates.autoUpdateCheck=Søg automatisk efter opdateringer
preferences.updates.checkNowBtn=Kontrollér nu
preferences.updates.updateAvailable=Opdatering til version %s er tilgængelig.
+preferences.updates.lastUpdateCheck=Seneste tjek: %s
+preferences.updates.lastUpdateCheck.never=aldrig
+preferences.updates.lastUpdateCheck.recently=for nylig
+preferences.updates.lastUpdateCheck.daysAgo=%s dage siden
+preferences.updates.lastUpdateCheck.hoursAgo=%s timer siden
preferences.updates.checkFailed=Søgning efter opdateringer fejlede. Tjek din internetforbindelse eller forsøg igen senere.
+preferences.updates.upToDate=Cryptomator er opdateret.
## Contribution
preferences.contribute=Støt os
@@ -328,8 +336,14 @@ preferences.contribute.registeredFor=Registreret supporter-certifikat for %s
preferences.contribute.noCertificate=Hjælp Cryptomator, og modtag et supporter-certifikat. Det er ligesom en license-nøgle, men til fantastiske mennesker som bruger fri software. ;-)
preferences.contribute.getCertificate=Har du ikke et allerede? Se her hvordan du kan få et.
preferences.contribute.promptText=Indsæt koden for supporter-certifikatet her
+preferences.contribute.thankYou=Tak fordi du støtter Cryptomators open source-udvikling!
+preferences.contribute.donate=Donér
+preferences.contribute.sponsor=Sponsor
### Remove License Key Dialog
+removeCert.title=Fjern certifikat
+removeCert.message=Fjern supporter-certifikat?
+removeCert.description=Cryptomators kernefunktioner påvirkes ikke af dette. Hverken adgangen er begrænset, eller sikkerhedsniveauet sænket til dine bokse.
#<-- Add entries for donations and code/translation/documentation contribution -->
## About
@@ -379,6 +393,10 @@ main.vaultlist.contextMenu.unlock=Lås op…
main.vaultlist.contextMenu.unlockNow=Lås op nu
main.vaultlist.contextMenu.vaultoptions=Vis boksindstillinger
main.vaultlist.contextMenu.reveal=Vis drev
+main.vaultlist.contextMenu.share=Del…
+main.vaultlist.addVaultBtn.menuItemNew=Opret ny boks...
+main.vaultlist.addVaultBtn.menuItemExisting=Åbn eksisterende boks...
+main.vaultlist.showEventsButton.tooltip=Åbn begivenhedsvisning
##Notificaition
main.notification.updateAvailable=Opdatering er tilgængelig.
main.notification.support=Støt Cryptomator.
@@ -404,9 +422,12 @@ main.vaultDetail.throughput.idle=afventer
main.vaultDetail.throughput.kbps=%.1f KiB/s
main.vaultDetail.throughput.mbps=%.1f MiB/s
main.vaultDetail.stats=Boks statistik
-main.vaultDetail.locateEncryptedFileBtn=Find Krypteret Fil
+main.vaultDetail.locateEncryptedFileBtn=Find krypteret fil
main.vaultDetail.locateEncryptedFileBtn.tooltip=Vælg en fil fra din boks for at finde dens krypterede modpart
main.vaultDetail.encryptedPathsCopied=Stier kopieret!
+main.vaultDetail.locateEncrypted.filePickerTitle=Vælg fil inde i boks
+main.vaultDetail.decryptName.buttonLabel=Dekryptér filnavn
+main.vaultDetail.decryptName.tooltip=Vælg en krypteret fil i boks for at dekryptere dets navn
### Missing
main.vaultDetail.missing.info=Cryptomator kunne ikke finde en boks på denne sti.
main.vaultDetail.missing.recheck=Kontrollér igen
@@ -531,9 +552,15 @@ updateReminder.yesOnce=Ja, én gang
updateReminder.yesAutomatically=Ja, automatisk
#Dokany Support End
+dokanySupportEnd.title=Meddelelse om udfasning
+dokanySupportEnd.message=Understøttelse slutter for Dokany
+dokanySupportEnd.description=Drevtypen Dokany understøttes ikke længere af Cryptomator. Dine indstillinger er justeret for at bruge standard drevtypen nu. Du kan se standardtypen i indstillingerne.
dokanySupportEnd.preferencesBtn=Åbn Indstillinger
#Retry If Readonly
+retryIfReadonly.title=Begrænset boksadgang
+retryIfReadonly.message=Ingen skriveadgang til boksens mappe
+retryIfReadonly.description=Cryptomator kan ikke skrive til boksens mappe. Du kan ændre boksen til at være skrivebeskyttet og prøve igen. Denne indstilling kan være deaktiveret i boksens indstillinger.
# Share Vault
shareVault.title=Del Boks
@@ -556,7 +583,11 @@ shareVault.hub.instruction.2=2. Giv adgang til holdmedlem i Cryptomator Hub.
shareVault.hub.openHub=Åben Cryptomator Hub
# Decrypt File Names
+decryptNames.title=Dekryptér Filnavne
+decryptNames.copyHint=Kopiér celleindhold med %s
+decryptNames.dropZone.message=Slip filer eller klik for at vælge
# Event View
+eventView.title=Begivenheder
## event list entries
diff --git a/src/main/resources/i18n/strings_de.properties b/src/main/resources/i18n/strings_de.properties
index 1d0dcda87..a7bc6bb3d 100644
--- a/src/main/resources/i18n/strings_de.properties
+++ b/src/main/resources/i18n/strings_de.properties
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=Entsperren …
main.vaultlist.contextMenu.unlockNow=Jetzt entsperren
main.vaultlist.contextMenu.vaultoptions=Tresoroptionen anzeigen
main.vaultlist.contextMenu.reveal=Laufwerk anzeigen
+main.vaultlist.contextMenu.share=Teilen …
main.vaultlist.addVaultBtn.menuItemNew=Neuen Tresor erstellen...
main.vaultlist.addVaultBtn.menuItemExisting=Bestehenden Tresor öffnen...
main.vaultlist.showEventsButton.tooltip=Ereignis-Ansicht öffnen
diff --git a/src/main/resources/i18n/strings_el.properties b/src/main/resources/i18n/strings_el.properties
index f8214e9f9..1001d3912 100644
--- a/src/main/resources/i18n/strings_el.properties
+++ b/src/main/resources/i18n/strings_el.properties
@@ -393,8 +393,10 @@ main.vaultlist.contextMenu.unlock=Ξεκλείδωμα…
main.vaultlist.contextMenu.unlockNow=Ξεκλείδωμα τώρα
main.vaultlist.contextMenu.vaultoptions=Εμφάνιση επιλογών Vault
main.vaultlist.contextMenu.reveal=Αποκάλυψη εικονικού δίσκου
+main.vaultlist.contextMenu.share=Κοινοποίηση…
main.vaultlist.addVaultBtn.menuItemNew=Δημιουργία Νέας Κρύπτης...
main.vaultlist.addVaultBtn.menuItemExisting=Άνοιγμα Υπάρχοντος Κρύπτης...
+main.vaultlist.showEventsButton.tooltip=Άνοιγμα προβολής συμβάντων
##Notificaition
main.notification.updateAvailable=Η ενημέρωση είναι διαθέσιμη.
main.notification.support=Υποστήριξη Cryptomator.
@@ -423,6 +425,9 @@ main.vaultDetail.stats=Στατιστικά Vault
main.vaultDetail.locateEncryptedFileBtn=Εντοπισμός Κρυπτογραφημένου Αρχείου
main.vaultDetail.locateEncryptedFileBtn.tooltip=Επιλέξτε ένα αρχείο από την κρύπτη σας για να εντοπίσετε το κρυπτογραφημένο αντίστοιχο
main.vaultDetail.encryptedPathsCopied=Οι Διαδρομές Αντιγράφηκαν στο Πρόχειρο!
+main.vaultDetail.locateEncrypted.filePickerTitle=Επιλογή Αρχείου Μέσα Στην Κρύπτη
+main.vaultDetail.decryptName.buttonLabel=Αποκρυπτογράφηση Ονόματος Αρχείου
+main.vaultDetail.decryptName.tooltip=Επιλέξτε ένα κρυπτογραφημένο αρχείο κρύπτης για να αποκρυπτογραφήσετε το όνομά του
### Missing
main.vaultDetail.missing.info=Cryptomator δεν βρήκε vault σε αυτόν τον κατάλογο.
main.vaultDetail.missing.recheck=Επανέλεγχος
@@ -579,8 +584,40 @@ shareVault.hub.instruction.2=2. Παραχωρήστε πρόσβαση σε μ
shareVault.hub.openHub=Ανοίξτε το Cryptomator Hub
# Decrypt File Names
+decryptNames.title=Αποκρυπτογράφηση Ονομάτων Αρχείων
+decryptNames.filePicker.title=Επιλογή κρυπτογραφημένου αρχείου
+decryptNames.filePicker.extensionDescription=Κρυπτογραφημένο κρυπτογραφημένου αρχείο Cryptomator
+decryptNames.copyTable.tooltip=Αντιγραφή πίνακα
+decryptNames.clearTable.tooltip=Εκκαθάριση πίνακα
+decryptNames.copyHint=Αντιγραφή περιεχομένου κελιού με %s
+decryptNames.dropZone.message=Απόθεση αρχείων ή κλικ για επιλογή
+decryptNames.dropZone.error.vaultInternalFiles=Εσωτερικά αρχεία κρύπτης χωρίς να επιλεχθεί όνομα αποκρυπτογράφησης
+decryptNames.dropZone.error.foreignFiles=Τα αρχεία δεν ανήκουν στην κρύπτη "%s"
+decryptNames.dropZone.error.noDirIdBackup=Ο κατάλογος των επιλεγμένων αρχείων δεν περιέχει αρχείο dirId.c9r
+decryptNames.dropZone.error.generic=Αποτυχία αποκρυπτογράφησης ονομάτων αρχείων
# Event View
+eventView.title=Συμβάντα
eventView.filter.allVaults=Όλες
+eventView.clearListButton.tooltip=Εκκαθάριση λίστας
## event list entries
+eventView.entry.vaultLocked.description=Ξεκλείδωμα "%s" για λεπτομέρειες
+eventView.entry.conflictResolved.message=Επίλυση σύγκρουσης
+eventView.entry.conflictResolved.showDecrypted=Εμφάνιση αποκρυπτογραφημένου αρχείου
+eventView.entry.conflictResolved.copyDecrypted=Αντιγραφή αποκρυπτογραφημένης διαδρομής
+eventView.entry.conflict.message=Η επίλυση συγκρούσεων απέτυχε
+eventView.entry.conflict.showDecrypted=Εμφάνιση αποκρυπτογραφημένων, αρχικό αρχείο
+eventView.entry.conflict.copyDecrypted=Αντιγραφή αποκρυπτογραφημένων, αρχική διαδρομή
+eventView.entry.conflict.showEncrypted=Εμφάνιση αντικρουόμενου, κρυπτογραφημένο αρχείο
+eventView.entry.conflict.copyEncrypted=Αντιγραφή σύγκρουσης, κρυπτογραφημένη διαδρομή
+eventView.entry.decryptionFailed.message=Η αποκρυπτογράφηση απέτυχε
+eventView.entry.decryptionFailed.showEncrypted=Εμφάνιση κρυπτογραφημένου αρχείου
+eventView.entry.decryptionFailed.copyEncrypted=Αντιγραφή κρυπτογραφημένης διαδρομής
+eventView.entry.brokenDirFile.message=Μη λειτουργικός σύνδεσμος καταλόγου
+eventView.entry.brokenDirFile.showEncrypted=Εμφάνιση μη λειτουργικού, κρυπτογραφημένου συνδέσμου
+eventView.entry.brokenDirFile.copyEncrypted=Αντιγραφή διαδρομής του μη λειτουργικού συνδέσμου
+eventView.entry.brokenFileNode.message=Μη λειτουργικός κόμβος συστήματος αρχείων
+eventView.entry.brokenFileNode.showEncrypted=Εμφάνιση μη λειτουργικού, κρυπτογραφημένου κόμβου
+eventView.entry.brokenFileNode.copyEncrypted=Αντιγραφή διαδρομής του μη λειτουργικού, κρυπτογραφημένου κόμβου
+eventView.entry.brokenFileNode.copyDecrypted=Αντιγραφή αποκρυπτογραφημένης διαδρομής
diff --git a/src/main/resources/i18n/strings_es.properties b/src/main/resources/i18n/strings_es.properties
index 65ee6501b..ba47d0131 100644
--- a/src/main/resources/i18n/strings_es.properties
+++ b/src/main/resources/i18n/strings_es.properties
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=Desbloquear…
main.vaultlist.contextMenu.unlockNow=Desbloquear ahora
main.vaultlist.contextMenu.vaultoptions=Mostrar opciones de la bóveda
main.vaultlist.contextMenu.reveal=Revelar unidad
+main.vaultlist.contextMenu.share=Compartir…
main.vaultlist.addVaultBtn.menuItemNew=Crear Bóveda Nueva...
main.vaultlist.addVaultBtn.menuItemExisting=Abrir Bóveda Existente...
main.vaultlist.showEventsButton.tooltip=Abrir vista de evento
diff --git a/src/main/resources/i18n/strings_fi.properties b/src/main/resources/i18n/strings_fi.properties
index 3ab91964b..e6ec743e5 100644
--- a/src/main/resources/i18n/strings_fi.properties
+++ b/src/main/resources/i18n/strings_fi.properties
@@ -392,6 +392,7 @@ main.vaultlist.contextMenu.unlock=Avaa…
main.vaultlist.contextMenu.unlockNow=Avaa Nyt
main.vaultlist.contextMenu.vaultoptions=Näytä holvin asetukset
main.vaultlist.contextMenu.reveal=Paljasta Asema
+main.vaultlist.contextMenu.share=Jaa…
main.vaultlist.addVaultBtn.menuItemNew=Luo uusi holvi...
main.vaultlist.addVaultBtn.menuItemExisting=Avaa olemassa oleva holvi...
main.vaultlist.showEventsButton.tooltip=Avaa tapahtumanäkymä
diff --git a/src/main/resources/i18n/strings_fil.properties b/src/main/resources/i18n/strings_fil.properties
index 5067ee7db..b094e4943 100644
--- a/src/main/resources/i18n/strings_fil.properties
+++ b/src/main/resources/i18n/strings_fil.properties
@@ -384,6 +384,7 @@ main.vaultlist.contextMenu.unlock=I-unlock…
main.vaultlist.contextMenu.unlockNow=I-unlock Ngayon
main.vaultlist.contextMenu.vaultoptions=Ipakita ang Mga Opsyon sa Vault
main.vaultlist.contextMenu.reveal=Ibunyag ang Drive
+main.vaultlist.contextMenu.share=Ibahagi…
##Notificaition
## Vault Detail
### Welcome
diff --git a/src/main/resources/i18n/strings_fr.properties b/src/main/resources/i18n/strings_fr.properties
index 33dd242c1..081e79a45 100644
--- a/src/main/resources/i18n/strings_fr.properties
+++ b/src/main/resources/i18n/strings_fr.properties
@@ -361,7 +361,7 @@ stats.read.total.data.kib=Lecture des données : %.1f kio
stats.read.total.data.mib=Lecture des données : %.1f MiO
stats.read.total.data.gib=Lecture des données : %.1f GiO
stats.decr.total.data.none=Données déchiffrées: -
-stats.decr.total.data.kib=Données déchiffrées: %.1f kio
+stats.decr.total.data.kib=Données déchiffrées: %.1f Kio
stats.decr.total.data.mib=Données déchiffrées: %.1f MiO
stats.decr.total.data.gib=Données déchiffrées: %.1f GiO
stats.read.accessCount=Total des lectures: %d
@@ -374,7 +374,7 @@ stats.write.total.data.kib=Données écrites : %.1f kio
stats.write.total.data.mib=Données écrites : %.1f MiO
stats.write.total.data.gib=Données écrites : %.1f GiO
stats.encr.total.data.none=Données chiffrées : -
-stats.encr.total.data.kib=Données chiffrées: %.1f kio
+stats.encr.total.data.kib=Données chiffrées: %.1f Kio
stats.encr.total.data.mib=Données chiffrées: %.1f MiO
stats.encr.total.data.gib=Données chiffrées: %.1f GiO
stats.write.accessCount=Total des écritures: %d
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=Déverrouiller…
main.vaultlist.contextMenu.unlockNow=Déverrouiller maintenant
main.vaultlist.contextMenu.vaultoptions=Afficher les options du volume chiffré
main.vaultlist.contextMenu.reveal=Afficher le lecteur
+main.vaultlist.contextMenu.share=Partager…
main.vaultlist.addVaultBtn.menuItemNew=Créer un nouveau coffre...
main.vaultlist.addVaultBtn.menuItemExisting=Ouvrir un coffre existant...
main.vaultlist.showEventsButton.tooltip=Ouvrir la vue Événements
@@ -590,6 +591,7 @@ decryptNames.copyTable.tooltip=Copier le tableau
decryptNames.clearTable.tooltip=Effacer le tableau
decryptNames.copyHint=Copier le contenu de la cellule avec %s
decryptNames.dropZone.message=Déposer des fichiers ou cliquer pour en sélectionner
+decryptNames.dropZone.error.vaultInternalFiles=Fichiers internes du coffre sans nom déchiffrable sélectionnés
decryptNames.dropZone.error.foreignFiles=Les fichiers n'appartiennent pas au coffre « %s »
decryptNames.dropZone.error.noDirIdBackup=Le répertoire des fichiers sélectionnés ne contient pas de fichier dirId.c9r
decryptNames.dropZone.error.generic=Impossible de déchiffrer les noms de fichiers
diff --git a/src/main/resources/i18n/strings_hu.properties b/src/main/resources/i18n/strings_hu.properties
index 375261975..982e36fbd 100644
--- a/src/main/resources/i18n/strings_hu.properties
+++ b/src/main/resources/i18n/strings_hu.properties
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=Feloldás…
main.vaultlist.contextMenu.unlockNow=Azonnali feloldás
main.vaultlist.contextMenu.vaultoptions=Széf beállítások
main.vaultlist.contextMenu.reveal=Széf megjelenítése
+main.vaultlist.contextMenu.share=Megosztás…
main.vaultlist.addVaultBtn.menuItemNew=Új széf létrehozása...
main.vaultlist.addVaultBtn.menuItemExisting=Meglévő széf megnyitása...
##Notificaition
diff --git a/src/main/resources/i18n/strings_id.properties b/src/main/resources/i18n/strings_id.properties
index 57fa4ecb5..bfaa7afdb 100644
--- a/src/main/resources/i18n/strings_id.properties
+++ b/src/main/resources/i18n/strings_id.properties
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=Buka Kunci…
main.vaultlist.contextMenu.unlockNow=Buka Kunci Sekarang
main.vaultlist.contextMenu.vaultoptions=Tampilkan Opsi Vault
main.vaultlist.contextMenu.reveal=Buka Drive
+main.vaultlist.contextMenu.share=Bagikan…
main.vaultlist.addVaultBtn.menuItemNew=Buat Vault Baru...
main.vaultlist.addVaultBtn.menuItemExisting=Buka Vault yang Tersedia...
##Notificaition
diff --git a/src/main/resources/i18n/strings_it.properties b/src/main/resources/i18n/strings_it.properties
index a23ad1dad..7584e9bf1 100644
--- a/src/main/resources/i18n/strings_it.properties
+++ b/src/main/resources/i18n/strings_it.properties
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=Sblocca…
main.vaultlist.contextMenu.unlockNow=Sblocca Ora
main.vaultlist.contextMenu.vaultoptions=Mostra le Opzioni della Cassaforte
main.vaultlist.contextMenu.reveal=Rivela Unità
+main.vaultlist.contextMenu.share=Condividi…
main.vaultlist.addVaultBtn.menuItemNew=Crea una nuova cassaforte...
main.vaultlist.addVaultBtn.menuItemExisting=Apri una cassaforte esistente...
main.vaultlist.showEventsButton.tooltip=Apri vista eventi
diff --git a/src/main/resources/i18n/strings_ja.properties b/src/main/resources/i18n/strings_ja.properties
index b1559d5e4..8f636c3b4 100644
--- a/src/main/resources/i18n/strings_ja.properties
+++ b/src/main/resources/i18n/strings_ja.properties
@@ -390,6 +390,7 @@ main.vaultlist.contextMenu.unlock=解錠...
main.vaultlist.contextMenu.unlockNow=今すぐ解錠
main.vaultlist.contextMenu.vaultoptions=金庫のオプションを表示
main.vaultlist.contextMenu.reveal=ドライブを表示
+main.vaultlist.contextMenu.share=共有…
main.vaultlist.addVaultBtn.menuItemNew=新しい金庫を作成…
main.vaultlist.addVaultBtn.menuItemExisting=既存の金庫を開く…
##Notificaition
diff --git a/src/main/resources/i18n/strings_ko.properties b/src/main/resources/i18n/strings_ko.properties
index dd97c84e1..f1d558447 100644
--- a/src/main/resources/i18n/strings_ko.properties
+++ b/src/main/resources/i18n/strings_ko.properties
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=잠금 해제...
main.vaultlist.contextMenu.unlockNow=지금 잠금 해제
main.vaultlist.contextMenu.vaultoptions=Vault 옵션 보기
main.vaultlist.contextMenu.reveal=드라이브 표시
+main.vaultlist.contextMenu.share=공유하기…
main.vaultlist.addVaultBtn.menuItemNew=새 Vault 생성...
main.vaultlist.addVaultBtn.menuItemExisting=기존 Vault 열기...
main.vaultlist.showEventsButton.tooltip=이벤트 뷰어 열기
@@ -588,8 +589,10 @@ decryptNames.filePicker.title=암호화된 파일 선택
decryptNames.filePicker.extensionDescription=Cryptomator로 암호화된 파일
decryptNames.copyTable.tooltip=테이블 복사
decryptNames.clearTable.tooltip=테이블 비우기
+decryptNames.copyHint=%s로 셀 내용 복사하기
decryptNames.dropZone.message=파일을 여기에 드롭하거나 클릭하세요
decryptNames.dropZone.error.vaultInternalFiles=복호화 가능한 이름이 선택되지 않은 Vailt 내부 파일
+decryptNames.dropZone.error.foreignFiles=Vault "%s"에 포함되지 않은 파일
decryptNames.dropZone.error.noDirIdBackup=선택된 파일 폴더에 dirId.c9r 파일이 포함되어 있지 않습니다
decryptNames.dropZone.error.generic=파일 이름 복호화 실패
diff --git a/src/main/resources/i18n/strings_lv.properties b/src/main/resources/i18n/strings_lv.properties
index 46ed07b31..55657f1ef 100644
--- a/src/main/resources/i18n/strings_lv.properties
+++ b/src/main/resources/i18n/strings_lv.properties
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=Atslēgt…
main.vaultlist.contextMenu.unlockNow=Atslēgt tagad
main.vaultlist.contextMenu.vaultoptions=Rādīt glabātavas iespējas
main.vaultlist.contextMenu.reveal=Atklāt disku
+main.vaultlist.contextMenu.share=Kopīgot…
main.vaultlist.addVaultBtn.menuItemNew=Izveidot jaunu glabātavu...
main.vaultlist.addVaultBtn.menuItemExisting=Atvērt esošu glabātavu...
main.vaultlist.showEventsButton.tooltip=Atvērt notikumu skatu
diff --git a/src/main/resources/i18n/strings_nb.properties b/src/main/resources/i18n/strings_nb.properties
index b13a55c7d..31e302d87 100644
--- a/src/main/resources/i18n/strings_nb.properties
+++ b/src/main/resources/i18n/strings_nb.properties
@@ -386,6 +386,7 @@ main.vaultlist.contextMenu.unlock=Lås opp…
main.vaultlist.contextMenu.unlockNow=Lås opp nå
main.vaultlist.contextMenu.vaultoptions=Alternativer for hvelvet
main.vaultlist.contextMenu.reveal=Vis enheten
+main.vaultlist.contextMenu.share=Del…
##Notificaition
main.notification.support=Støtt Cryptomator.
## Vault Detail
diff --git a/src/main/resources/i18n/strings_nl.properties b/src/main/resources/i18n/strings_nl.properties
index 9875f5ef2..4b6f4ad82 100644
--- a/src/main/resources/i18n/strings_nl.properties
+++ b/src/main/resources/i18n/strings_nl.properties
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=Ontgrendelen…
main.vaultlist.contextMenu.unlockNow=Nu Ontgrendelen
main.vaultlist.contextMenu.vaultoptions=Laat kluisinstellingen zien
main.vaultlist.contextMenu.reveal=Toon Schijf
+main.vaultlist.contextMenu.share=Delen…
main.vaultlist.addVaultBtn.menuItemNew=Nieuwe Kluis Aanmaken...
main.vaultlist.addVaultBtn.menuItemExisting=Open Bestaande Kluis...
main.vaultlist.showEventsButton.tooltip=Afspraakweergave openen
diff --git a/src/main/resources/i18n/strings_pa.properties b/src/main/resources/i18n/strings_pa.properties
index bf7dfa025..c0a714bda 100644
--- a/src/main/resources/i18n/strings_pa.properties
+++ b/src/main/resources/i18n/strings_pa.properties
@@ -328,6 +328,7 @@ main.vaultlist.contextMenu.unlock=ਅਣ-ਲਾਕ ਕਰੋ…
main.vaultlist.contextMenu.unlockNow=ਹੁਣੇ ਅਣ-ਲਾਕ ਕਰੋ
main.vaultlist.contextMenu.vaultoptions=ਵਾਲਟ ਚੋਣਾਂ ਨੂੰ ਵੇਖਾਓ
main.vaultlist.contextMenu.reveal=ਡਰਾਇਵ ਦਿਖਾਓ
+main.vaultlist.contextMenu.share=…ਸਾਂਝਾ ਕਰੋ
main.vaultlist.addVaultBtn.menuItemNew=...ਨਵਾਂ ਵਾਲਟ ਬਣਾਓ
main.vaultlist.addVaultBtn.menuItemExisting=...ਮੌਜੂਦਾ ਵਾਲਟ ਨੂੰ ਖੋਲ੍ਹੋ
##Notificaition
diff --git a/src/main/resources/i18n/strings_pl.properties b/src/main/resources/i18n/strings_pl.properties
index 815975fb1..db7342d0e 100644
--- a/src/main/resources/i18n/strings_pl.properties
+++ b/src/main/resources/i18n/strings_pl.properties
@@ -393,8 +393,10 @@ main.vaultlist.contextMenu.unlock=Odblokuj…
main.vaultlist.contextMenu.unlockNow=Odblokuj teraz
main.vaultlist.contextMenu.vaultoptions=Pokaż opcje sejfu
main.vaultlist.contextMenu.reveal=Otwórz lokalizację
+main.vaultlist.contextMenu.share=Udostępnij…
main.vaultlist.addVaultBtn.menuItemNew=Utwórz Nowy Sejf...
main.vaultlist.addVaultBtn.menuItemExisting=Otwórz Istniejący Sejf...
+main.vaultlist.showEventsButton.tooltip=Otwórz widok wydarzeń
##Notificaition
main.notification.updateAvailable=Dostępna aktualizacja.
main.notification.support=Wspomóż Cryptomatora.
@@ -423,6 +425,9 @@ main.vaultDetail.stats=Statystyki sejfu
main.vaultDetail.locateEncryptedFileBtn=Zlokalizuj zaszyfrowany plik
main.vaultDetail.locateEncryptedFileBtn.tooltip=Wybierz plik z sejfu, aby zlokalizować jego zaszyfrowany odpowiednik
main.vaultDetail.encryptedPathsCopied=Ścieżki skopiowane do schowka!
+main.vaultDetail.locateEncrypted.filePickerTitle=Wybierz plik wewnątrz sejfu
+main.vaultDetail.decryptName.buttonLabel=Odszyfruj nazwę pliku
+main.vaultDetail.decryptName.tooltip=Wybierz zaszyfrowany plik sejfu do odszyfrowania jego nazwy
### Missing
main.vaultDetail.missing.info=Cryptomator nie mógł znaleźć sejfu w tej lokalizacji.
main.vaultDetail.missing.recheck=Ponów próbę
@@ -579,7 +584,40 @@ shareVault.hub.instruction.2=2. Udziel dostępu członkowi zespołu w Cryptomato
shareVault.hub.openHub=Otwórz Cryptomator Hub
# Decrypt File Names
+decryptNames.title=Odszyfruj nazwy plików
+decryptNames.filePicker.title=Wybierz zaszyfrowany plik
+decryptNames.filePicker.extensionDescription=Zaszyfrowany plik Cryptomator
+decryptNames.copyTable.tooltip=Kopiuj tabelę
+decryptNames.clearTable.tooltip=Wyczyść tabelę
+decryptNames.copyHint=Kopiuj zawartość komórki z %s
+decryptNames.dropZone.message=Upuść pliki lub kliknij, aby wybrać
+decryptNames.dropZone.error.vaultInternalFiles=Wybrano wewnętrzne pliki sejfu z nazwami niemożliwymi do odszyfrowania
+decryptNames.dropZone.error.foreignFiles=Pliki nie należą do sejfu "%s"
+decryptNames.dropZone.error.noDirIdBackup=Katalog wybranych plików nie zawiera pliku dirId.c9r
+decryptNames.dropZone.error.generic=Nie udało się odszyfrować nazw plików
# Event View
+eventView.title=Zdarzenia
+eventView.filter.allVaults=Wszystkie
+eventView.clearListButton.tooltip=Wyczyść listę
## event list entries
+eventView.entry.vaultLocked.description=Odblokuj "%s" po szczegóły
+eventView.entry.conflictResolved.message=Konflikt rozwiązany
+eventView.entry.conflictResolved.showDecrypted=Pokaż odszyfrowany plik
+eventView.entry.conflictResolved.copyDecrypted=Kopiuj odszyfrowaną ścieżkę
+eventView.entry.conflict.message=Rozwiązywanie konfliktu nie powiodło się
+eventView.entry.conflict.showDecrypted=Pokaż odszyfrowany, oryginalny plik
+eventView.entry.conflict.copyDecrypted=Kopiuj odszyfrowaną, oryginalną ścieżkę
+eventView.entry.conflict.showEncrypted=Pokaż sprzeczny, zaszyfrowany plik
+eventView.entry.conflict.copyEncrypted=Kopiuj sprzeczną, zaszyfrowaną ścieżkę
+eventView.entry.decryptionFailed.message=Odszyfrowywanie nie powiodło się
+eventView.entry.decryptionFailed.showEncrypted=Pokaż zaszyfrowany plik
+eventView.entry.decryptionFailed.copyEncrypted=Kopiuj zaszyfrowaną ścieżkę
+eventView.entry.brokenDirFile.message=Uszkodzony link do katalogu
+eventView.entry.brokenDirFile.showEncrypted=Pokaż uszkodzony, zaszyfrowany link
+eventView.entry.brokenDirFile.copyEncrypted=Kopiuj ścieżkę uszkodzonego linku
+eventView.entry.brokenFileNode.message=Uszkodzony węzeł systemu plików
+eventView.entry.brokenFileNode.showEncrypted=Pokaż uszkodzony, zaszyfrowany węzeł
+eventView.entry.brokenFileNode.copyEncrypted=Kopiuj ścieżkę uszkodzonego, zaszyfrowanego węzła
+eventView.entry.brokenFileNode.copyDecrypted=Kopiuj odszyfrowaną ścieżkę
diff --git a/src/main/resources/i18n/strings_pt.properties b/src/main/resources/i18n/strings_pt.properties
index bb048b2cf..fec088e48 100644
--- a/src/main/resources/i18n/strings_pt.properties
+++ b/src/main/resources/i18n/strings_pt.properties
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=Desbloquear…
main.vaultlist.contextMenu.unlockNow=Desbloquear agora
main.vaultlist.contextMenu.vaultoptions=Mostrar opções do Cofre
main.vaultlist.contextMenu.reveal=Revelar unidade
+main.vaultlist.contextMenu.share=Partilhar…
main.vaultlist.addVaultBtn.menuItemNew=Criar novo cofre...
main.vaultlist.addVaultBtn.menuItemExisting=Abrir cofre existente...
main.vaultlist.showEventsButton.tooltip=Abrir visualização do evento
diff --git a/src/main/resources/i18n/strings_pt_BR.properties b/src/main/resources/i18n/strings_pt_BR.properties
index 58f31d575..2dea3c44c 100644
--- a/src/main/resources/i18n/strings_pt_BR.properties
+++ b/src/main/resources/i18n/strings_pt_BR.properties
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=Desbloquear…
main.vaultlist.contextMenu.unlockNow=Desbloquear Agora
main.vaultlist.contextMenu.vaultoptions=Exibir Opções de Cofre
main.vaultlist.contextMenu.reveal=Revelar Volume
+main.vaultlist.contextMenu.share=Compartilhar…
main.vaultlist.addVaultBtn.menuItemNew=Novo Cofre...
main.vaultlist.addVaultBtn.menuItemExisting=Abrir Cofre Existente...
main.vaultlist.showEventsButton.tooltip=Abrir visualização de evento
diff --git a/src/main/resources/i18n/strings_ro.properties b/src/main/resources/i18n/strings_ro.properties
index e5812a6ec..308e09290 100644
--- a/src/main/resources/i18n/strings_ro.properties
+++ b/src/main/resources/i18n/strings_ro.properties
@@ -389,6 +389,7 @@ main.vaultlist.contextMenu.unlock=Deblochează…
main.vaultlist.contextMenu.unlockNow=Deblochează acum
main.vaultlist.contextMenu.vaultoptions=Arată opțiunile seifului
main.vaultlist.contextMenu.reveal=Dezvăluie unitatea
+main.vaultlist.contextMenu.share=Distribuie…
main.vaultlist.addVaultBtn.menuItemNew=Creare seif nou...
main.vaultlist.addVaultBtn.menuItemExisting=Deschide un seif existent...
##Notificaition
diff --git a/src/main/resources/i18n/strings_ru.properties b/src/main/resources/i18n/strings_ru.properties
index c0f3da76a..0ff9ea63a 100644
--- a/src/main/resources/i18n/strings_ru.properties
+++ b/src/main/resources/i18n/strings_ru.properties
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=Разблокировать…
main.vaultlist.contextMenu.unlockNow=Разблокировать
main.vaultlist.contextMenu.vaultoptions=Параметры хранилища
main.vaultlist.contextMenu.reveal=Показать диск
+main.vaultlist.contextMenu.share=Поделиться…
main.vaultlist.addVaultBtn.menuItemNew=Создать хранилище...
main.vaultlist.addVaultBtn.menuItemExisting=Открыть имеющееся хранилище...
main.vaultlist.showEventsButton.tooltip=Открыть просмотр события
diff --git a/src/main/resources/i18n/strings_sk.properties b/src/main/resources/i18n/strings_sk.properties
index b1b985b7a..ac8a91505 100644
--- a/src/main/resources/i18n/strings_sk.properties
+++ b/src/main/resources/i18n/strings_sk.properties
@@ -386,6 +386,7 @@ main.vaultlist.contextMenu.unlock=Odomknúť…
main.vaultlist.contextMenu.unlockNow=Odomknúť teraz
main.vaultlist.contextMenu.vaultoptions=Ukáž možnosti trezora
main.vaultlist.contextMenu.reveal=Odkry disk
+main.vaultlist.contextMenu.share=Zdieľať…
main.vaultlist.addVaultBtn.menuItemNew=Vytvoriť Nový trezor…
main.vaultlist.addVaultBtn.menuItemExisting=Otvoriť Existujúci trezor...
main.vaultlist.showEventsButton.tooltip=Otvoriť zobrazenie udalosti
diff --git a/src/main/resources/i18n/strings_sl.properties b/src/main/resources/i18n/strings_sl.properties
index bc19194d4..d62415d50 100644
--- a/src/main/resources/i18n/strings_sl.properties
+++ b/src/main/resources/i18n/strings_sl.properties
@@ -132,6 +132,7 @@ preferences.updates.lastUpdateCheck.daysAgo=%s dni nazaj
## Vault List
main.vaultlist.contextMenu.lock=Zakleni
main.vaultlist.contextMenu.unlockNow=Odkleni zdaj
+main.vaultlist.contextMenu.share=Deli…
##Notificaition
## Vault Detail
### Welcome
diff --git a/src/main/resources/i18n/strings_sv.properties b/src/main/resources/i18n/strings_sv.properties
index 67fc2fb7f..6b38e0d7c 100644
--- a/src/main/resources/i18n/strings_sv.properties
+++ b/src/main/resources/i18n/strings_sv.properties
@@ -283,6 +283,7 @@ preferences.title=Inställningar
## General
preferences.general=Allmänt
preferences.general.startHidden=Dölj fönster när Cryptomator startar
+preferences.general.autoCloseVaults=Lås valv utan att fråga när du avslutar applikationen
preferences.general.debugLogging=Aktivera loggning för felsökning
preferences.general.debugDirectory=Visa loggfiler
preferences.general.autoStart=Starta Cryptomator vid systemstart
@@ -392,6 +393,7 @@ main.vaultlist.contextMenu.unlock=Lås upp…
main.vaultlist.contextMenu.unlockNow=Lås upp nu
main.vaultlist.contextMenu.vaultoptions=Visa inställningar för valv
main.vaultlist.contextMenu.reveal=Visa enhet
+main.vaultlist.contextMenu.share=Dela…
main.vaultlist.addVaultBtn.menuItemNew=Skapa nytt valv...
main.vaultlist.addVaultBtn.menuItemExisting=Öppna befintligt valv...
main.vaultlist.showEventsButton.tooltip=Öppna händelsevy
diff --git a/src/main/resources/i18n/strings_th.properties b/src/main/resources/i18n/strings_th.properties
index 22f981768..d8c82a09d 100644
--- a/src/main/resources/i18n/strings_th.properties
+++ b/src/main/resources/i18n/strings_th.properties
@@ -335,6 +335,7 @@ main.vaultlist.contextMenu.unlock=ปลดล็อก...
main.vaultlist.contextMenu.unlockNow=ปลดล็อกตอนนี้
main.vaultlist.contextMenu.vaultoptions=แสดงตัวเลือก Vault
main.vaultlist.contextMenu.reveal=เปิดไดรฟ์
+main.vaultlist.contextMenu.share=แชร์...
##Notificaition
## Vault Detail
### Welcome
diff --git a/src/main/resources/i18n/strings_tr.properties b/src/main/resources/i18n/strings_tr.properties
index e77dfd64c..69d097492 100644
--- a/src/main/resources/i18n/strings_tr.properties
+++ b/src/main/resources/i18n/strings_tr.properties
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=Kilidi aç…
main.vaultlist.contextMenu.unlockNow=Kilidi Şimdi Aç
main.vaultlist.contextMenu.vaultoptions=Kasa Ayarlarını Göster
main.vaultlist.contextMenu.reveal=Sürücüyü Göster
+main.vaultlist.contextMenu.share=Paylaş…
main.vaultlist.addVaultBtn.menuItemNew=Yeni Kasa Oluştur...
main.vaultlist.addVaultBtn.menuItemExisting=Var Olan Kasayı Aç...
##Notificaition
diff --git a/src/main/resources/i18n/strings_ug.properties b/src/main/resources/i18n/strings_ug.properties
index 427c30f62..50624bf22 100644
--- a/src/main/resources/i18n/strings_ug.properties
+++ b/src/main/resources/i18n/strings_ug.properties
@@ -383,6 +383,7 @@ main.vaultlist.contextMenu.unlock=قۇلۇپسىزلا…
main.vaultlist.contextMenu.unlockNow=ھازىر قۇلۇپسىزلا
main.vaultlist.contextMenu.vaultoptions=ئامبار تاللانمىلىرىنى كۆرسەت
main.vaultlist.contextMenu.reveal=دىسكىنى كۆرسەت
+main.vaultlist.contextMenu.share=ھەمبەھىرلە…
##Notificaition
## Vault Detail
### Welcome
diff --git a/src/main/resources/i18n/strings_uk.properties b/src/main/resources/i18n/strings_uk.properties
index a3bd1290b..2b120b967 100644
--- a/src/main/resources/i18n/strings_uk.properties
+++ b/src/main/resources/i18n/strings_uk.properties
@@ -15,17 +15,17 @@ generic.button.copied=Скопійовано!
generic.button.done=Завершити
generic.button.next=Далі
generic.button.print=Друкувати
-generic.button.remove=Прибрати
+generic.button.remove=Вилучити
# Error
-error.message=Сталася помилка
-error.description=Ой! Cryptomator не очікував, що таке трапиться. Ви можете знайти існуючі рішення цієї помилки. Або, якщо про це ще не повідомили, то не соромтеся зробити це.
+error.message=Виникла помилка
+error.description=Ой! Cryptomator не очікував, що це станеться. Ви можете спробувати знайти рішення для цієї помилки. Або, якщо про неї ще не повідомляли, не соромтеся зробити це.
error.hyperlink.lookup=Дізнатися більше про цю помилку
error.hyperlink.report=Повідомити про цю помилку
error.technicalDetails=Докладно:
-error.existingSolutionDescription=Cryptomator не очікував, що це станеться. Але ми знайшли спосіб розв'язання цієї проблеми. Ознайомтесь з ним, будь ласка, далі.
+error.existingSolutionDescription=Cryptomator не очікував, що це станеться. Однак ми знайшли рішення для цієї помилки. Будь ласка, перегляньте наступне посилання.
error.hyperlink.solution=Ознайомтесь із рішенням
-error.lookupPermissionMessage=Cryptomator може спробувати знайти спосіб розв'язання цієї проблеми онлайн. Це передбачає відправку запиту до нашої бази даних проблем з вашої ІР.
+error.lookupPermissionMessage=Cryptomator може спробувати знайти рішення для цієї проблеми онлайн. Це передбачає відправку запиту до нашої бази даних проблем з вашої IP-адреси.
error.dismiss=Відхилити
error.lookUpSolution=Пошук рішення
@@ -35,14 +35,14 @@ defaults.vault.vaultName=Сховище
# Tray Menu
traymenu.showMainWindow=Показати
traymenu.showPreferencesWindow=Налаштування
-traymenu.lockAllVaults=Заблокувати все
+traymenu.lockAllVaults=Заблокувати всі
traymenu.quitApplication=Вийти
traymenu.vault.unlock=Розблокувати
traymenu.vault.lock=Заблокувати
-traymenu.vault.reveal=Розгорнути вікно
+traymenu.vault.reveal=Відкрити диск
# Add Vault Wizard
-addvaultwizard.title=Додавання сховища
+addvaultwizard.title=Додати сховище
## New
addvaultwizard.new.title=Додавання нового сховища
### Name
@@ -50,68 +50,68 @@ addvaultwizard.new.nameInstruction=Оберіть назву для сховищ
addvaultwizard.new.namePrompt=Назва сховища
### Location
addvaultwizard.new.locationInstruction=Де Cryptomator має зберігати зашифровані файли вашого сховища?
-addvaultwizard.new.locationLoading=Перевірка локальної файлової системи на предмет наявності папок клієнтів хмарних сховищ…
+addvaultwizard.new.locationLoading=Перевірка локальної файлової системи на наявність каталогів хмарних сховищ…
addvaultwizard.new.locationLabel=Розташування сховища
addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Власне розташування
addvaultwizard.new.directoryPickerButton=Обрати…
-addvaultwizard.new.directoryPickerTitle=Обрати папку
-addvaultwizard.new.fileAlreadyExists=Файл чи папка з вказаним іменем сховища вже існує
-addvaultwizard.new.locationDoesNotExist=Папка недоступна за вказаною адресою або не існує
-addvaultwizard.new.locationIsNotWritable=Вказана адреса доступна лише для читання
+addvaultwizard.new.directoryPickerTitle=Обрати каталог
+addvaultwizard.new.fileAlreadyExists=Файл або каталог з такою назвою сховища вже існує
+addvaultwizard.new.locationDoesNotExist=Каталог за вказаним шляхом не існує або недоступний
+addvaultwizard.new.locationIsNotWritable=Немає дозволу на запис за вказаним шляхом
addvaultwizard.new.locationIsOk=Це місце підходить для сховища
-addvaultwizard.new.invalidName=Недопустима назва сховища
-addvaultwizard.new.validName=Допустима назва сховища
+addvaultwizard.new.invalidName=Недійсна назва сховища
+addvaultwizard.new.validName=Дійсна назва сховища
addvaultwizard.new.validCharacters.message=Назва сховища може містити такі символи:
-addvaultwizard.new.validCharacters.chars=Літери (напр. a, ж or 수)
+addvaultwizard.new.validCharacters.chars=Літери (напр. a, ж або 수)
addvaultwizard.new.validCharacters.numbers=Цифри
addvaultwizard.new.validCharacters.dashes=Дефіс (%s) або підкреслення (%s)
### Expert Settings
addvaultwizard.new.expertSettings.enableExpertSettingsCheckbox=Увімкнути експертні налаштування
-addvaultwizard.new.expertSettings.shorteningThreshold.invalid=Введіть значення від 36 до 220 (типово 220)
+addvaultwizard.new.expertSettings.shorteningThreshold.invalid=Введіть значення від 36 до 220 (за замовчуванням 220)
addvaultwizard.new.expertSettings.shorteningThreshold.tooltip=Відкрийте документацію, щоб дізнатися більше.
-addvaultwizard.new.expertSettings.shorteningThreshold.title=Максимальна довжина зашифрованих імен файлів
+addvaultwizard.new.expertSettings.shorteningThreshold.title=Максимальна довжина зашифрованих назв файлів
addvaultwizard.new.expertSettings.shorteningThreshold.valid=Вірний
### Password
addvaultwizard.new.createVaultBtn=Створити сховище
addvaultwizard.new.generateRecoveryKeyChoice=Ви не зможете отримати доступ до своїх даних без пароля. Хочете створити ключ відновлення на випадок втрати паролю?
-addvaultwizard.new.generateRecoveryKeyChoice.yes=Так, будь ласка, береженого Бог береже
+addvaultwizard.new.generateRecoveryKeyChoice.yes=Так, будь ласка, краще перестрахуватися
addvaultwizard.new.generateRecoveryKeyChoice.no=Ні, дякую, я не втрачу свій пароль
### Information
addvault.new.readme.storageLocation.fileName=ВАЖЛИВО.rtf
addvault.new.readme.storageLocation.1=⚠️ ФАЙЛИ СХОВИЩА ⚠️
addvault.new.readme.storageLocation.2=Це місце, де зберігається ваше сховище.
-addvault.new.readme.storageLocation.3=НЕ
-addvault.new.readme.storageLocation.4=• змінюйте будь-які файли в цій папці, або
-addvault.new.readme.storageLocation.5=• додавайте безпосередньо до цієї папки будь-які файли для їх шифрування.
+addvault.new.readme.storageLocation.3=НЕ РОБІТЬ ЦЬОГО
+addvault.new.readme.storageLocation.4=• не змінюйте будь-які файли в цьому каталозі, та
+addvault.new.readme.storageLocation.5=• не додавайте безпосередньо до цього каталогу будь-які файли для їх шифрування.
addvault.new.readme.storageLocation.6=Якщо ви хочете зашифрувати файли та переглянути вміст сховища, зробіть так:
-addvault.new.readme.storageLocation.7=1. Додайте це сховище (папку) у Cryptomator.
+addvault.new.readme.storageLocation.7=1. Додайте це сховище у Cryptomator.
addvault.new.readme.storageLocation.8=2. Розблокуйте його через Cryptomator.
-addvault.new.readme.storageLocation.9=3. Отримайте доступ до вмісту сховища, натиснувши кнопку "Розгорнути".
-addvault.new.readme.storageLocation.10=Якщо вам потрібна допомога, почитайте документацію: %s
-addvault.new.readme.accessLocation.fileName=ПРОЧИТАЙ_МЕНЕ.rtf
+addvault.new.readme.storageLocation.9=3. Натисніть кнопку «Відкрити диск», щоб отримати доступ до вмісту сховища.
+addvault.new.readme.storageLocation.10=Якщо вам потрібна допомога, відвідайте документацію: %s
+addvault.new.readme.accessLocation.fileName=ПРИВІТ.rtf
addvault.new.readme.accessLocation.1=🔐️ ЗАШИФРОВАНИЙ ТОМ 🔐️
-addvault.new.readme.accessLocation.2=Це місце розташування вашого сховища.
+addvault.new.readme.accessLocation.2=Це розташування для доступу до вашого сховища.
addvault.new.readme.accessLocation.3=Будь-які файли, додані до цього тому, будуть зашифровані за допомогою Cryptomator. Ви можете працювати із ним як із будь-якою іншою директорією або накопичувачем. Це лише розшифрований вигляд вмісту сховища, ваші файли завжди знаходяться в зашифрованому вигляді на диску.
addvault.new.readme.accessLocation.4=Цей файл можна видалити, якщо бажаєте.
## Existing
addvaultwizard.existing.title=Додати наявне сховище
-addvaultwizard.existing.instruction=Виберіть файл "vault.cryptomator" вашого наявного сховища. Якщо існує лише файл з назвою "masterkey.cryptomator", виберіть його.
+addvaultwizard.existing.instruction=Оберіть файл «vault.cryptomator» свого наявного сховища. Якщо існує лише файл з назвою «masterkey.cryptomator», оберіть його.
addvaultwizard.existing.chooseBtn=Обрати…
addvaultwizard.existing.filePickerTitle=Виберіть файл сховища
addvaultwizard.existing.filePickerMimeDesc=Сховище Cryptomator
## Success
-addvaultwizard.success.nextStepsInstructions=Додано сховище "%s".\nЦе сховище необхідно розблокувати для доступу або додавання вмісту. Але ви можете розблокувати його пізніше.
+addvaultwizard.success.nextStepsInstructions=Сховище «%s» додано.\nЩоб отримати доступ або додати вміст, вам потрібно розблокувати це сховище. Також ви можете розблокувати його пізніше в будь-який час.
addvaultwizard.success.unlockNow=Розблокувати
# Remove Vault
-removeVault.title=Видалити "%s"
-removeVault.message=Видалити сховище?
-removeVault.description=Це лише змусить Cryptomator забути про це сховище. Його можна буде додати знову пізніше. Зашифровані файли на вашому жорсткому диску не буде втрачено.
+removeVault.title=Вилучити «%s»
+removeVault.message=Вилучити сховище?
+removeVault.description=Це лише змусить Cryptomator забути про сховище. Ви зможете додати його знову. Зашифровані файли не будуть видалені з вашого накопичувача.
# Change Password
changepassword.title=Зміна паролю
-changepassword.enterOldPassword=Введіть поточний пароль для "%s"
+changepassword.enterOldPassword=Введіть поточний пароль для «%s»
changepassword.finalConfirmation=Я розумію, що не зможу отримати доступ до даних, якщо забуду свій пароль
# Forget Password
@@ -121,201 +121,202 @@ forgetPassword.description=Ця дія видалить збережений п
forgetPassword.confirmBtn=Забути пароль
# Unlock
-unlock.title=Розблокування "%s"
-unlock.passwordPrompt=Введіть пароль для "%s":
+unlock.title=Розблокування «%s»
+unlock.passwordPrompt=Введіть пароль для «%s»:
unlock.savePassword=Запам'ятати пароль
unlock.unlockBtn=Розблокувати
## Select
unlock.chooseMasterkey.message=Файл майстер-ключа не знайдено
-unlock.chooseMasterkey.description=Cryptomator не зміг знайти файл майстер-ключа для сховища "%s". Будь ласка, виберіть ключ вручну.
+unlock.chooseMasterkey.description=Cryptomator не зміг знайти файл майстер-ключа для сховища «%s». Будь ласка, виберіть файл ключа вручну.
unlock.chooseMasterkey.filePickerTitle=Виберіть файл майстер-ключа
unlock.chooseMasterkey.filePickerMimeDesc=Майстер-ключ Cryptomator
## Success
unlock.success.message=Успішно розблоковано
-unlock.success.description=Вміст сховища "%s" тепер доступний через точку монтування.
+unlock.success.description=Вміст сховища «%s» тепер доступний через точку монтування.
unlock.success.rememberChoice=Запам'ятайте мій вибір та більше не запитуйте
-unlock.success.revealBtn=Розгорнути диск
+unlock.success.revealBtn=Відкрити диск
## Failure
unlock.error.customPath.message=Не вдалося змонтувати сховище за вказаним шляхом
unlock.error.customPath.description.notSupported=Якщо ви хочете надалі використовувати власний шлях, будь ласка, перейдіть до налаштувань та виберіть тип тому, що його підтримує. В іншому випадку перейдіть до параметрів сховища та оберіть точку монтування, що підтримується.
unlock.error.customPath.description.notExists=Вказаний шлях монтування не існує. Створіть його в локальній файловій системі або змініть його в параметрах сховища.
-unlock.error.customPath.description.inUse=Зараз використовується літера або користувацький шлях монтування "%s".
-unlock.error.customPath.description.hideawayNotDir=Цей тимчасовий прихований файл "%3$s" використовується для розблокування і не може бути видалений. Будь ласка, перевірте файл та видаліть його вручну.
-unlock.error.customPath.description.couldNotBeCleaned=Ваше сховище неможливо змонтувати за шляхом "%s". Будь ласка, спробуйте знову або оберіть інший шлях.
-unlock.error.customPath.description.notEmptyDir=Користувацький шлях монтування "%s" не є порожньою папкою. Будь ласка, оберіть порожню папку і спробуйте ще раз.
-unlock.error.customPath.description.generic=Ви вибрали користувацький шлях монтування для цього сховища, але його використання призвело до помилки з текстом: %2$s
+unlock.error.customPath.description.inUse=Літера диска або власний шлях монтування «%s» уже використовується.
+unlock.error.customPath.description.hideawayNotDir=Тимчасовий прихований файл «%3$s», що використовувався для розблокування, не вдалося видалити. Будь ласка, перевірте файл, а потім видаліть його вручну.
+unlock.error.customPath.description.couldNotBeCleaned=Ваше сховище неможливо змонтувати за шляхом «%s». Будь ласка, спробуйте знову або оберіть інший шлях.
+unlock.error.customPath.description.notEmptyDir=Власний шлях монтування «%s» не є порожньою папкою. Будь ласка, оберіть порожню папку та спробуйте ще раз.
+unlock.error.customPath.description.generic=Ви обрали власний шлях монтування для цього сховища, але його використання не вдалося з таким повідомленням: %2$s
unlock.error.restartRequired.message=Не вдалося розблокувати сховище
-unlock.error.restartRequired.description=Змініть тип носія в налаштуваннях сховища або перезапустіть Cryptomator.
-unlock.error.title=Розблокувати "%s" не вдалося
+unlock.error.restartRequired.description=Змініть тип тому в параметрах сховища або перезапустіть Cryptomator.
+unlock.error.title=Розблокувати «%s» не вдалося
## Hub
hub.noKeychain.message=Не вдалося отримати доступ до ключа пристрою
-hub.noKeychain.description=Щоб розблокувати сховища Hub, необхідний ключ пристрою, який захищено за допомогою зв'язки ключів. Щоб продовжити, увімкніть “%s” та виберіть зв'язку ключів у налаштуваннях.
+hub.noKeychain.description=Щоб розблокувати сховища Hub, необхідний ключ пристрою, який захищено за допомогою зв'язки ключів. Щоб продовжити, увімкніть «%s» та виберіть зв'язку ключів у налаштуваннях.
hub.noKeychain.openBtn=Відкрити налаштування
### Waiting
hub.auth.message=Очікування завершення автентифікації…
-hub.auth.description=Вас буде автоматично переспрямовано на сторінку входу.
-hub.auth.loginLink=Не переспрямовано? Натисніть тут, щоб відкрити її.
+hub.auth.description=Вас буде автоматично перенаправлено на сторінку входу.
+hub.auth.loginLink=Не перенаправлено? Натисніть тут, щоб відкрити її.
### Receive Key
hub.receive.message=Обробка відповіді…
hub.receive.description=Cryptomator отримує та опрацьовує відповідь від Hub. Будь ласка, зачекайте.
### Register Device
hub.register.message=Новий пристрій
-hub.register.description=Це перший доступ до Хабу Cryptomator з цього пристрою. Будь ласка, зареєструйтесь за допомогою вашого ключа облікового запису.
+hub.register.description=Це перший доступ до Hub з цього пристрою. Будь ласка, зареєструйтесь за допомогою вашого ключа облікового запису.
hub.register.nameLabel=Назва пристрою
hub.register.invalidAccountKeyLabel=Недійсний ключ облікового запису
hub.register.registerBtn=Зареєструватись
### Register Device Legacy
hub.register.legacy.occupiedMsg=Таке ім’я уже існує
-hub.register.legacy.description=Це перший доступ до Хабу Cryptomator з цього пристрою. Будь ласка, зареєструйте його.
+hub.register.legacy.description=Це перший доступ до Hub з цього пристрою. Будь ласка, зареєструйте його.
### Registration Success
hub.registerSuccess.message=Пристрій зареєстровано
hub.registerSuccess.description=Ваш пристрій успішно зареєстровано. Тепер ви можете продовжити розблокування сховища.
hub.registerSuccess.unlockBtn=Розблокувати
-hub.registerSuccess.legacy.description=Щоб отримати доступ до сховища, ваш пристрій повинен бути додатково авторизованим власником сховища.
+hub.registerSuccess.legacy.description=Щоб отримати доступ до сховища, ваш пристрій має бути додатково авторизованим власником сховища.
### Registration Failed
-hub.registerFailed.message=Помилка реєстрації пристрою
-hub.registerFailed.description.generic=Помилка виникла у процесі реєстрації. Для більш докладної інформації перегляньте журнал додатка.
-hub.registerFailed.description.deviceAlreadyExists=Цей пристрій вже зареєстровано для іншого користувача. Спробуйте змінити обліковий запис користувача або скористайтеся іншим пристроєм.
+hub.registerFailed.message=Не вдалося зареєструвати пристрій
+hub.registerFailed.description.generic=Під час реєстрації виникла помилка. Для отримання додаткової інформації перегляньте журнал програми.
+hub.registerFailed.description.deviceAlreadyExists=Цей пристрій вже зареєстровано для іншого користувача. Спробуйте змінити обліковий запис або скористайтеся іншим пристроєм.
### Unauthorized
hub.unauthorized.message=У доступі відмовлено
-hub.unauthorized.description=Ви не маєте права для відкриття цього сховища. Зверніться до власника сховища, щоб запитати доступ.
+hub.unauthorized.description=Ви не авторизовані для відкриття цього сховища. Зв'яжіться з його власником, щоб отримати доступ.
### Requires Account Initialization
hub.requireAccountInit.message=Необхідна дія
-hub.requireAccountInit.description.0=Для продовження, будь ласка, завершіть виконання обов'язкових кроків
-hub.requireAccountInit.description.1=Профіль користувача Хабу
+hub.requireAccountInit.description.0=Щоб продовжити, будь ласка, виконайте необхідні кроки у вашому
+hub.requireAccountInit.description.1=профілі користувача Hub
hub.requireAccountInit.description.2=.
### License Exceeded
hub.invalidLicense.message=Недійсна ліцензія Hub
-hub.invalidLicense.description=У вашого Cryptomator Hub недійсна ліцензія. Будь ласка, зв'яжіться з адміністратором Hub, щоб оновити або продовжити дію ліцензії.
+hub.invalidLicense.description=Ваш екземпляр Cryptomator Hub має недійсну ліцензію. Будь ласка, повідомте адміністратора Hub для оновлення або поновлення ліцензії.
# Lock
## Force
lock.forced.message=Помилка блокування
-lock.forced.description=Блокуванню "%s" заважає виконання операцій або відкриті файли. Ви можете примусово заблокувати це сховище, однак переривання операцій чинання/запису може призвести до втрати незбережених даних.
-lock.forced.retryBtn=Повторити
-lock.forced.forceBtn=Примусове блокування
+lock.forced.description=Блокуванню «%s» заважає виконання операцій або відкриті файли. Ви можете примусово заблокувати це сховище, однак переривання операцій читання/запису може призвести до втрати незбережених даних.
+lock.forced.retryBtn=Повторити спробу
+lock.forced.forceBtn=Примусово заблокувати
## Failure
lock.fail.message=Не вдалося заблокувати сховище
-lock.fail.description=Сховище "%s" не можна заблокувати. Переконайтеся, що незавершену роботу збережено в іншому місці, а також важливі операції читання/запису закінчені. Щоб закрити сховище, завершіть процес Cryptomator.
+lock.fail.description=Не вдалося заблокувати сховище «%s». Переконайтеся, що незбережена робота збережена в іншому місці, а важливі операції читання/запису завершені. Щоб закрити сховище, завершіть процес Cryptomator.
# Migration
migration.title=Покращення сховища
## Start
migration.start.header=Покращення сховища до наступної версії
-migration.start.text=Для того, щоб відкрити сховище "%s" у новій версії Cryptomator, воно має бути оновлене до новішого формату. Перед тим, як це зробити, ви повинні знати таке:
+migration.start.text=Для того, щоб відкрити сховище «%s» у новій версії Cryptomator, воно має бути оновлене до новішого формату. Перед тим, як це зробити, ви повинні знати таке:
migration.start.remarkUndone=Це оновлення не може бути скасовано.
migration.start.remarkVersions=Старіші версії Cryptomator не зможуть відкрити оновлене сховище.
migration.start.remarkCanRun=Ви повинні бути впевнені, що кожен пристрій, з якого ви маєте доступ до сховища, підтримує цю версію Cryptomator.
migration.start.remarkSynced=Ви маєте бути впевнені, що ваше сховище повністю синхронізовано на цьому пристрої та на інших ваших пристроях, перш ніж оновити його.
-migration.start.confirm=Я прочитав та зрозумів вищевказану інформацію
+migration.start.confirm=Я прочитав(ла) і зрозумів(ла) наведену вище інформацію
## Run
-migration.run.enterPassword=Введіть пароль для "%s"
-migration.run.startMigrationBtn=Розпочати міграцію
+migration.run.enterPassword=Введіть пароль для «%s»
+migration.run.startMigrationBtn=Розпочати оновлення
migration.run.progressHint=Це може зайняти деякий час…
## Success
-migration.success.nextStepsInstructions=Міграцію "%s" успішно завершено.\nТепер ви можете розблокувати ваше сховище.
+migration.success.nextStepsInstructions=Оновлення «%s» успішно завершено.\nТепер ви можете розблокувати ваше сховище.
migration.success.unlockNow=Розблокувати
## Missing file system capabilities
migration.error.missingFileSystemCapabilities.title=Файлова система не підтримується
-migration.error.missingFileSystemCapabilities.description=Перенесення не було розпочато, оскільки ваше сховище знаходиться в несумісній файловій системі.
+migration.error.missingFileSystemCapabilities.description=Оновлення не було розпочато, оскільки ваше сховище знаходиться в несумісній файловій системі.
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=Файлова система не підтримує довгі назви файлів.
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Файлова система не підтримує довгі шляхи.
migration.error.missingFileSystemCapabilities.reason.READ_ACCESS=Файлова система не дозволяє зчитувати дані.
migration.error.missingFileSystemCapabilities.reason.WRITE_ACCESS=Файлова система не дозволяє записувати дані.
## Impossible
-migration.impossible.heading=Не вдалося перенести сховище
-migration.impossible.reason=Сховище не можливо автоматично перенести через його розташуванням на диску або несумісність точки доступу.
-migration.impossible.moreInfo=Сховище все ще може бути відкрите старішою версією. Для інструкцій, як вручну перенести сховище, перейдіть
+migration.impossible.heading=Не вдалося оновити сховище
+migration.impossible.reason=Сховище не можливо автоматично оновити через його розташування на диску або несумісність точки доступу.
+migration.impossible.moreInfo=Сховище все ще можна відкрити старішою версією. Для інструкцій, як вручну оновити сховище, відвідайте
# Health Check
## Start
-health.title=Перевірка працездатності "%s"
-health.intro.header=Перевірка працездатності
-health.intro.text=Перевірка працездатності передбачає ряд тестів для виявлення та за можливості виправлення проблем у внутрішній структурі вашого сховища. Будь ласка, пам'ятайте:
+health.title=Перевірка стану «%s»
+health.intro.header=Перевірка стану
+health.intro.text=«Перевірка стану» — це набір перевірок для виявлення та, можливо, виправлення проблем у внутрішній структурі вашого сховища. Будь ласка, врахуйте:
health.intro.remarkSync=Переконайтеся, що всі пристрої повністю синхронізовані, це вирішує більшість проблем.
health.intro.remarkFix=Не всі проблеми можна виправити.
-health.intro.remarkBackup=Якщо дані пошкоджено, то може допомогти тільки резервна копія.
-health.intro.affirmation=Я прочитав та зрозумів вищевказану інформацію
+health.intro.remarkBackup=Якщо дані пошкоджено, допоможе лише резервна копія.
+health.intro.affirmation=Я прочитав(ла) і зрозумів(ла) наведену вище інформацію
## Start Failure
health.fail.header=Помилка при завантаженні конфігурації сховища
-health.fail.ioError=Сталася помилка під час спроби доступу до файлу конфігурації.
-health.fail.parseError=Сталася помилка під час опрацювання конфігурації сховища.
+health.fail.ioError=Під час доступу та читання файлу конфігурації сталася помилка.
+health.fail.parseError=Під час аналізу конфігурації сховища сталася помилка.
health.fail.moreInfo=Більше інформації
## Check Selection
-health.checkList.description=Оберіть прапорець зі списку ліворуч або використовуйте кнопки нижче.
+health.checkList.description=Виберіть перевірки зі списку ліворуч або скористайтеся кнопками нижче.
health.checkList.selectAllButton=Вибрати всі перевірки
health.checkList.deselectAllButton=Зняти вибір з усіх перевірок
health.check.runBatchBtn=Розпочати обрані перевірки
## Detail view
-health.check.detail.noSelectedCheck=Для перегляду результатів оберіть завершену перевірку працездатності зі списку ліворуч.
+health.check.detail.noSelectedCheck=Щоб переглянути результати, виберіть завершену перевірку стану зі списку ліворуч.
health.check.detail.checkScheduled=Перевірку заплановано.
health.check.detail.checkRunning=Перевірка триває…
-health.check.detail.checkSkipped=Перевірку не вибрано для запуску.
+health.check.detail.checkSkipped=Цю перевірку не було обрано для запуску.
health.check.detail.checkFinished=Перевірку завершено успішно.
health.check.detail.checkFinishedAndFound=Перевірку завершено. Будь ласка, ознайомтесь з результатами.
-health.check.detail.checkFailed=Перевірка завершилась через помилку.
+health.check.detail.checkFailed=Перевірку перервано через помилку.
health.check.detail.checkCancelled=Перевірку було скасовано.
health.check.detail.listFilters.label=Фільтр
-health.check.detail.fixAllSpecificBtn=Виправити все
+health.check.detail.fixAllSpecificBtn=Виправити всі цього типу
health.check.exportBtn=Експортувати звіт
## Result view
-health.result.severityFilter.all=Рівень критичності - Усі
+health.result.severityFilter.all=Ступінь серйозності — Усі
health.result.severityFilter.good=В нормі
health.result.severityFilter.info=Інформація
health.result.severityFilter.warn=Попередження
health.result.severityFilter.crit=Критично
-health.result.severityTip.good=Рівень критичності: в нормі\nЗвичайна структура сховища.
-health.result.severityTip.info=Рівень критичності: інформація\nСтруктура сховища цілісна, виправлення рекомендовані.
-health.result.severityTip.warn=Рівень критичності: попередження\nСтруктуру сховища пошкоджено, це слід виправити.
-health.result.severityTip.crit=Рівень критичності: критичний\nСтруктуру сховища пошкоджено, виявлено втрату даних.
-health.result.fixStateFilter.all=Статус виправлення - Всі
-health.result.fixStateFilter.fixable=Поправні
-health.result.fixStateFilter.notFixable=Не поправні
+health.result.severityTip.good=Ступінь серйозності: В нормі.\nЗвичайна структура сховища.
+health.result.severityTip.info=Ступінь серйозності: Інформація.\nСтруктура сховища ціла, запропоновано виправлення.
+health.result.severityTip.warn=Ступінь серйозності: Попередження.\nСтруктура сховища пошкоджена, наполегливо рекомендуємо виправити.
+health.result.severityTip.crit=Ступінь серйозності: Критичний.\nСтруктура сховища пошкоджена, виявлено втрату даних.
+health.result.fixStateFilter.all=Стан виправлення — Усі
+health.result.fixStateFilter.fixable=Можна виправити
+health.result.fixStateFilter.notFixable=Неможливо виправити
health.result.fixStateFilter.fixing=Виправляються…
-health.result.fixStateFilter.fixed=Виправлені
-health.result.fixStateFilter.fixFailed=Невдале виправлення
+health.result.fixStateFilter.fixed=Виправлено
+health.result.fixStateFilter.fixFailed=Не вдалося виправити
## Fix Application
health.fix.fixBtn=Виправити
-health.fix.successTip=Виправлення успішне
-health.fix.failTip=Виправити не вдалось, перегляньте журнал для ознайомлення з подробицями
+health.fix.successTip=Успішно виправлено
+health.fix.failTip=Не вдалося виправити, перегляньте журнал для отримання подробиць
# Preferences
preferences.title=Налаштування
## General
preferences.general=Загальні
-preferences.general.startHidden=Приховати вікно під час запуску Cryptomator
+preferences.general.startHidden=Приховувати вікно під час запуску Cryptomator
+preferences.general.autoCloseVaults=Блокувати сховища без запиту при виході з програми
preferences.general.debugLogging=Увімкнути ведення журналу налагодження
preferences.general.debugDirectory=Показати файли журналу
preferences.general.autoStart=Запускати Cryptomator під час запуску системи
preferences.general.keychainBackend=Зберігати паролі за допомогою
-preferences.general.quickAccessService=Додати до області швидкого доступу розблоковані сховища
+preferences.general.quickAccessService=Додавати розблоковані сховища до області швидкого доступу
## Interface
preferences.interface=Вигляд
-preferences.interface.theme=Колір і стиль
+preferences.interface.theme=Вигляд і поведінка
preferences.interface.theme.automatic=Автоматично
preferences.interface.theme.dark=Темний
preferences.interface.theme.light=Світлий
preferences.interface.unlockThemes=Розблокувати темний режим
-preferences.interface.language=Мова (потребує перезавантаження)
+preferences.interface.language=Мова (потрібен перезапуск)
preferences.interface.language.auto=Мова системи
-preferences.interface.interfaceOrientation=Відображення елементів
+preferences.interface.interfaceOrientation=Орієнтація інтерфейсу
preferences.interface.interfaceOrientation.ltr=Зліва направо
preferences.interface.interfaceOrientation.rtl=Справа наліво
-preferences.interface.showTrayIcon=Показувати піктограму на панелі завдань (потрібен перезапуск)
+preferences.interface.showTrayIcon=Показувати іконку в треї (потрібен перезапуск)
preferences.interface.compactMode=Активувати компактний список сховищ
## Volume
preferences.volume=Віртуальний диск
preferences.volume.type=Тип тому за замовчуванням
preferences.volume.type.automatic=Автоматично
preferences.volume.docsTooltip=Відкрийте документацію, щоб дізнатися більше про різні типи томів.
-preferences.volume.fuseRestartRequired=Слід перезапустити Cryptomator, щоб зміни набули чинності.
+preferences.volume.fuseRestartRequired=Щоб застосувати зміни, Cryptomator потрібно перезапустити.
preferences.volume.tcp.port=TCP порт за замовчуванням
-preferences.volume.supportedFeatures=Обраний тип тому підтримує такі функції:
+preferences.volume.supportedFeatures=Обраний тип тому підтримує такі можливості:
preferences.volume.feature.mountAuto=Автоматичний вибір точки монтування
-preferences.volume.feature.mountToDir=Користувацька папка як точка монтування
-preferences.volume.feature.mountToDriveLetter=Точка монтування у вигляді букви диска
-preferences.volume.feature.mountFlags=Користувацькі параметри монтування
-preferences.volume.feature.readOnly=Монтування в режимі "лише для читання"
+preferences.volume.feature.mountToDir=Власний каталог як точка монтування
+preferences.volume.feature.mountToDriveLetter=Літера диска як точка монтування
+preferences.volume.feature.mountFlags=Власні параметри монтування
+preferences.volume.feature.readOnly=Монтування лише для читання
## Updates
preferences.updates=Оновлення
preferences.updates.currentVersion=Поточна версія: %s
@@ -326,24 +327,24 @@ preferences.updates.lastUpdateCheck=Остання перевірка: %s
preferences.updates.lastUpdateCheck.never=ніколи
preferences.updates.lastUpdateCheck.recently=нещодавно
preferences.updates.lastUpdateCheck.daysAgo=%s дні(в) тому
-preferences.updates.lastUpdateCheck.hoursAgo=%s годин тому
+preferences.updates.lastUpdateCheck.hoursAgo=%s годин(и) тому
preferences.updates.checkFailed=Не вдалось перевірити наявність оновлень. Будь ласка, перевірте підключення до Інтернету або спробуйте ще раз пізніше.
preferences.updates.upToDate=Ваш Cryptomator не потребує оновлення.
## Contribution
preferences.contribute=Підтримайте нас
preferences.contribute.registeredFor=Сертифікат помічника зареєстровано для %s
-preferences.contribute.noCertificate=Підтримайте Cryptomator та отримайте сертифікат помічника - це як ліцензійний ключ, але для чудових людей, які користуються безкоштовним програмним забезпеченням. ;-)
+preferences.contribute.noCertificate=Підтримайте Cryptomator та отримайте сертифікат помічника. Це як ліцензійний ключ, але для чудових людей, які користуються безплатним програмним забезпеченням. ;-)
preferences.contribute.getCertificate=Ще немає такого? Дізнайтеся, як його отримати.
preferences.contribute.promptText=Вставте код сертифікату помічника тут
preferences.contribute.thankYou=Дякуємо, що підтримуєте розробку Cryptomator з відкритим вихідним кодом!
-preferences.contribute.donate=Підтримати
-preferences.contribute.sponsor=Спонсор
+preferences.contribute.donate=Зробити пожертву
+preferences.contribute.sponsor=Спонсори
### Remove License Key Dialog
-removeCert.title=Видалити сертифікат
-removeCert.message=Видалити сертифікат помічника?
-removeCert.description=Основний функціонал Cryptomator не змінюється. Ні доступ до вашого сховища, а ні рівень безпеки не буде понижено/обмежено.
+removeCert.title=Вилучити сертифікат
+removeCert.message=Вилучити сертифікат помічника?
+removeCert.description=Основні функції Cryptomator не будуть зачеплені. Ані доступ до ваших сховищ не буде обмежено, ані рівень безпеки не буде знижено.
#<-- Add entries for donations and code/translation/documentation contribution -->
## About
@@ -351,33 +352,33 @@ preferences.about=Про додаток
# Vault Statistics
stats.title=Статистика %s
-stats.cacheHitRate=Відсоток потрапляння до кешу
+stats.cacheHitRate=Коефіцієнт влучань кеша
## Read
-stats.read.throughput.idle=Читання: очікування
-stats.read.throughput.kibs=Зчитування: %.2f КіБ/с
-stats.read.throughput.mibs=Зчитування: %.2f МіБ/с
-stats.read.total.data.none=Зчитано даних: -
-stats.read.total.data.kib=Зчитано даних: %.1f Кб
-stats.read.total.data.mib=Зчитано даних: %.1f Мб
-stats.read.total.data.gib=Зчитано даних: %.1f Гб
-stats.decr.total.data.none=Розшифровано даних: -
-stats.decr.total.data.kib=Розшифровано даних: %.1f Кб
-stats.decr.total.data.mib=Розшифровано даних: %.1f Мб
-stats.decr.total.data.gib=Розшифровано даних: %.1f Гб
-stats.read.accessCount=Всього зчитувань: %d
+stats.read.throughput.idle=Читання: простоює
+stats.read.throughput.kibs=Читання: %.2f КіБ/с
+stats.read.throughput.mibs=Читання: %.2f МіБ/с
+stats.read.total.data.none=Прочитано даних: —
+stats.read.total.data.kib=Прочитано даних: %.1f КіБ
+stats.read.total.data.mib=Прочитано даних: %.1f МіБ
+stats.read.total.data.gib=Прочитано даних: %.1f ГіБ
+stats.decr.total.data.none=Розшифровано даних: —
+stats.decr.total.data.kib=Розшифровано даних: %.1f КіБ
+stats.decr.total.data.mib=Розшифровано даних: %.1f МіБ
+stats.decr.total.data.gib=Розшифровано даних: %.1f ГіБ
+stats.read.accessCount=Усього прочитань: %d
## Write
-stats.write.throughput.idle=Запис: очікування
+stats.write.throughput.idle=Запис: простоює
stats.write.throughput.kibs=Запис: %.2f KіБ/с
stats.write.throughput.mibs=Запис: %.2f MіБ/с
-stats.write.total.data.none=Записано даних: -
-stats.write.total.data.kib=Записано даних: %.1f Кб
-stats.write.total.data.mib=Записано даних: %.1f Мб
-stats.write.total.data.gib=Записано даних: %.1f Гб
-stats.encr.total.data.none=Зашифровано даних: -
-stats.encr.total.data.kib=Зашифровано даних: %.1f Кб
-stats.encr.total.data.mib=Зашифровано даних: %.1f Мб
-stats.encr.total.data.gib=Зашифровано даних: %.1f Гб
-stats.write.accessCount=Всього записувань: %d
+stats.write.total.data.none=Записано даних: —
+stats.write.total.data.kib=Записано даних: %.1f КіБ
+stats.write.total.data.mib=Записано даних: %.1f МіБ
+stats.write.total.data.gib=Записано даних: %.1f ГіБ
+stats.encr.total.data.none=Зашифровано даних: —
+stats.encr.total.data.kib=Зашифровано даних: %.1f КіБ
+stats.encr.total.data.mib=Зашифровано даних: %.1f МіБ
+stats.encr.total.data.gib=Зашифровано даних: %.1f ГіБ
+stats.write.accessCount=Усього записів: %d
## Accesses
stats.access.current=Отримано доступ: %d
@@ -387,42 +388,47 @@ stats.access.total=Усього отримано доступ: %d
# Main Window
## Vault List
main.vaultlist.emptyList.onboardingInstruction=Натисніть тут, щоб додати сховище
-main.vaultlist.contextMenu.remove=Видалити…
+main.vaultlist.contextMenu.remove=Вилучити…
main.vaultlist.contextMenu.lock=Заблокувати
main.vaultlist.contextMenu.unlock=Розблокувати…
main.vaultlist.contextMenu.unlockNow=Розблокувати
main.vaultlist.contextMenu.vaultoptions=Показати параметри сховища
-main.vaultlist.contextMenu.reveal=Розгорнути диск
+main.vaultlist.contextMenu.reveal=Відкрити диск
+main.vaultlist.contextMenu.share=Поділитися…
main.vaultlist.addVaultBtn.menuItemNew=Створити нове сховище...
main.vaultlist.addVaultBtn.menuItemExisting=Відкрити наявне сховище...
+main.vaultlist.showEventsButton.tooltip=Відкрити журнал подій
##Notificaition
-main.notification.updateAvailable=Оновлення доступне.
+main.notification.updateAvailable=Доступне оновлення.
main.notification.support=Підтримайте Cryptomator.
## Vault Detail
### Welcome
-main.vaultDetail.welcomeOnboarding=Дякуємо, що обрали Cryptomator для захисту ваших файлів. Якщо вам потрібна допомога, перегляньте наші інструкції з роботи:
+main.vaultDetail.welcomeOnboarding=Дякуємо, що обрали Cryptomator для захисту ваших файлів. Якщо вам потрібна допомога, ознайомтеся з нашими посібниками для початку роботи:
### Locked
main.vaultDetail.lockedStatus=ЗАБЛОКОВАНО
main.vaultDetail.unlockBtn=Розблокувати…
main.vaultDetail.unlockNowBtn=Розблокувати
-main.vaultDetail.optionsBtn=Налаштування сховища
+main.vaultDetail.optionsBtn=Параметри сховища
main.vaultDetail.passwordSavedInKeychain=Пароль збережено
main.vaultDetail.share=Поділитися…
### Unlocked
main.vaultDetail.unlockedStatus=РОЗБЛОКОВАНО
-main.vaultDetail.accessLocation=Щоб отримати доступ до вмісту сховища - натисніть кнопку:
-main.vaultDetail.revealBtn=Розгорнути диск
-main.vaultDetail.copyUri=Скопіювати адресу
+main.vaultDetail.accessLocation=Вміст вашого сховища доступний тут:
+main.vaultDetail.revealBtn=Відкрити диск
+main.vaultDetail.copyUri=Копіювати URI
main.vaultDetail.lockBtn=Заблокувати
-main.vaultDetail.bytesPerSecondRead=Зчитування:
+main.vaultDetail.bytesPerSecondRead=Читання:
main.vaultDetail.bytesPerSecondWritten=Запис:
-main.vaultDetail.throughput.idle=простоювання
+main.vaultDetail.throughput.idle=простоює
main.vaultDetail.throughput.kbps=%.1f КіБ/c
main.vaultDetail.throughput.mbps=%.1f МіБ/c
main.vaultDetail.stats=Статистика сховища
main.vaultDetail.locateEncryptedFileBtn=Знайти зашифрований файл
-main.vaultDetail.locateEncryptedFileBtn.tooltip=Виберіть файл із вашого сховища, щоб знайти розташування його зашифрованого відповідника
+main.vaultDetail.locateEncryptedFileBtn.tooltip=Виберіть файл зі сховища, щоб знайти його зашифрований відповідник
main.vaultDetail.encryptedPathsCopied=Шляхи скопійовано в буфер обміну!
+main.vaultDetail.locateEncrypted.filePickerTitle=Виберіть файл у сховищі
+main.vaultDetail.decryptName.buttonLabel=Розшифрувати назву файлу
+main.vaultDetail.decryptName.tooltip=Виберіть зашифрований файл сховища, щоб розшифрувати його назву
### Missing
main.vaultDetail.missing.info=Cryptomator не зміг знайти сховище за цим шляхом.
main.vaultDetail.missing.recheck=Перевірити знову
@@ -430,7 +436,7 @@ main.vaultDetail.missing.remove=Вилучити зі списку сховищ
main.vaultDetail.missing.changeLocation=Змінити розташування сховища…
### Needs Migration
main.vaultDetail.migrateButton=Покращити сховище
-main.vaultDetail.migratePrompt=Ваше сховище слід покращити (перетворити) у новий формат, перш ніж ви зможете отримати до нього доступ
+main.vaultDetail.migratePrompt=Ваше сховище потрібно оновити до нового формату, перш ніж ви зможете отримати до нього доступ
### Error
main.vaultDetail.error.info=Виникла помилка під час завантаження сховища з диска.
main.vaultDetail.error.reload=Перезавантажити
@@ -439,10 +445,10 @@ main.vaultDetail.error.windowTitle=Помилка завантаження сх
# Wrong File Alert
wrongFileAlert.title=Як зашифрувати файли
wrongFileAlert.message=Ви намагалися зашифрувати ці файли?
-wrongFileAlert.description=Для цього Cryptomator додає том у ваш системний файловий менеджер.
+wrongFileAlert.description=Для цього Cryptomator надає том у системному файловому менеджері.
wrongFileAlert.instruction.0=Щоб зашифрувати файли, виконайте такі кроки:
wrongFileAlert.instruction.1=1. Розблокуйте ваше сховище.
-wrongFileAlert.instruction.2=2. Натисніть "Розгорнути", щоб відкрити том у вашому файловому менеджері.
+wrongFileAlert.instruction.2=2. Натисніть «Відкрити диск», щоб відкрити том у вашому файловому менеджері.
wrongFileAlert.instruction.3=3. Додайте ваші файли до цього тому.
wrongFileAlert.link=Для отримання подальшої допомоги, відвідайте
@@ -450,27 +456,27 @@ wrongFileAlert.link=Для отримання подальшої допомог
## General
vaultOptions.general=Загальні
vaultOptions.general.vaultName=Назва сховища
-vaultOptions.general.autoLock.lockAfterTimePart1=Заблокувати коли не використовується протягом
+vaultOptions.general.autoLock.lockAfterTimePart1=Блокувати після простою протягом
vaultOptions.general.autoLock.lockAfterTimePart2=хвилин
vaultOptions.general.unlockAfterStartup=Розблоковувати сховище під час запуску Cryptomator
vaultOptions.general.actionAfterUnlock=Після успішного розблокування
vaultOptions.general.actionAfterUnlock.ignore=Нічого не робити
-vaultOptions.general.actionAfterUnlock.reveal=Розгорнути диск
-vaultOptions.general.actionAfterUnlock.ask=Запитати
-vaultOptions.general.startHealthCheckBtn=Розпочати перевірку працездатності
+vaultOptions.general.actionAfterUnlock.reveal=Відкривати диск
+vaultOptions.general.actionAfterUnlock.ask=Запитувати
+vaultOptions.general.startHealthCheckBtn=Розпочати перевірку стану
## Mount
vaultOptions.mount=Монтування
-vaultOptions.mount.info=Відкрийте вибір віртуального носія, щоб змінити налаштування за замовчуванням.
+vaultOptions.mount.info=Відкрийте налаштування віртуального диска, щоб змінити параметри за замовчуванням.
vaultOptions.mount.readonly=Тільки для перегляду
-vaultOptions.mount.customMountFlags=Користувацькі опції монтування
+vaultOptions.mount.customMountFlags=Власні прапори монтування
vaultOptions.mount.winDriveLetterOccupied=зайнято
vaultOptions.mount.mountPoint=Точка монтування
-vaultOptions.mount.mountPoint.auto=Автоматично підбирати зручне розташування
-vaultOptions.mount.mountPoint.driveLetter=Використовувати призначену літеру для диска
-vaultOptions.mount.mountPoint.custom=Використовувати обрану папку
+vaultOptions.mount.mountPoint.auto=Автоматично вибрати відповідне розташування
+vaultOptions.mount.mountPoint.driveLetter=Використовувати призначену літеру диска
+vaultOptions.mount.mountPoint.custom=Використовувати обраний каталог
vaultOptions.mount.mountPoint.directoryPickerButton=Обрати…
-vaultOptions.mount.mountPoint.directoryPickerTitle=Виберіть папку
+vaultOptions.mount.mountPoint.directoryPickerTitle=Виберіть каталог
vaultOptions.mount.volumeType.default=За замовчуванням (%s)
vaultOptions.mount.volumeType.restartRequired=Щоб використовувати цей тип тому, необхідно перезапустити Cryptomator.
vaultOptions.mount.volume.tcp.port=TCP порт
@@ -479,107 +485,140 @@ vaultOptions.mount.volume.type=Тип тому
vaultOptions.masterkey=Пароль
vaultOptions.masterkey.changePasswordBtn=Змінити пароль
vaultOptions.masterkey.forgetSavedPasswordBtn=Забути збережений пароль
-vaultOptions.masterkey.recoveryKeyExplanation=У разі втрати пароля, лише ключ відновлення залишиться єдиним способом отримання доступу до сховища.
+vaultOptions.masterkey.recoveryKeyExplanation=Ключ відновлення — це ваш єдиний спосіб відновити доступ до сховища, якщо ви втратите пароль.
vaultOptions.masterkey.showRecoveryKeyBtn=Показати ключ відновлення
vaultOptions.masterkey.recoverPasswordBtn=Скинути пароль
## Hub
vaultOptions.hub=Відновлення
-vaultOptions.hub.convertInfo=Ви можете використати ключ відновлення, щоб перетворити це сховище Hub у сховище на основі пароля в разі крайньої потреби.
-vaultOptions.hub.convertBtn=Перетворити на сховище з паролем
+vaultOptions.hub.convertInfo=Ви можете використати ключ відновлення, щоб у надзвичайній ситуації перетворити це Hub-сховище на сховище, захищене паролем.
+vaultOptions.hub.convertBtn=Перетворити на сховище, захищене паролем
# Recovery Key
## Display Recovery Key
recoveryKey.display.title=Показати ключ відновлення
recoveryKey.create.message=Потрібен пароль
-recoveryKey.create.description=Введіть пароль для "%s", щоб показати його ключ відновлення.
-recoveryKey.display.description=Цей ключ можна використати для відновлення доступу до "%s":
-recoveryKey.display.StorageHints=Зберігайте його у дуже безпечному місці, наприклад:\n • в менеджері паролів\n • на захищеному USB-носії\n • роздрукуйте на папері та зберігайте у недоступному для інших місці
+recoveryKey.create.description=Введіть пароль для «%s», щоб показати його ключ відновлення.
+recoveryKey.display.description=Зазначений ключ відновлення можна використовувати для відновлення доступу до «%s»:
+recoveryKey.display.StorageHints=Зберігайте його в дуже надійному місці, наприклад:\n • Збережіть його за допомогою менеджера паролів\n • Збережіть його на USB-накопичувачі\n • Роздрукуйте його на папері
## Reset Password
### Enter Recovery Key
recoveryKey.recover.title=Скинути пароль
-recoveryKey.recover.prompt=Введіть ключ відновлення для "%s:
-recoveryKey.recover.correctKey=Цей ключ відновлення є правильним
-recoveryKey.recover.wrongKey=Цей ключ відновлення належить до іншого сховища
+recoveryKey.recover.prompt=Введіть ключ відновлення для «%s»:
+recoveryKey.recover.correctKey=Цей ключ відновлення правильний
+recoveryKey.recover.wrongKey=Цей ключ відновлення належить іншому сховищу
recoveryKey.recover.invalidKey=Невірний ключ відновлення
-recoveryKey.printout.heading=Ключ відновлення Cryptomator\n"%s"\n
+recoveryKey.printout.heading=Ключ відновлення Cryptomator\n«%s»\n
### Reset Password
recoveryKey.recover.resetBtn=Скинути
### Recovery Key Password Reset Success
-recoveryKey.recover.resetSuccess.message=Пароль скинуто успішно
+recoveryKey.recover.resetSuccess.message=Пароль успішно скинуто
recoveryKey.recover.resetSuccess.description=Ви можете розблокувати своє сховище за допомогою нового пароля.
# Convert Vault
convertVault.title=Перетворити сховище
convertVault.convert.convertBtn.before=Перетворити
convertVault.convert.convertBtn.processing=Перетворення…
-convertVault.success.message=Перетворення виконано успішно
-convertVault.hubToPassword.success.description=Тепер ви можете розблокувати сховище за допомогою обраного пароля без необхідності доступу до Hub.
+convertVault.success.message=Перетворення успішно виконано
+convertVault.hubToPassword.success.description=Тепер ви можете розблокувати сховище за допомогою обраного пароля, не вимагаючи доступу до Hub.
# New Password
newPassword.promptText=Введіть новий пароль
newPassword.reenterPassword=Підтвердіть новий пароль
newPassword.passwordsMatch=Паролі збігаються!
newPassword.passwordsDoNotMatch=Паролі не збігаються
-passwordStrength.messageLabel.tooShort=Введіть принаймні %d символів
+passwordStrength.messageLabel.tooShort=Використайте щонайменше %d символів
passwordStrength.messageLabel.0=Дуже слабкий
passwordStrength.messageLabel.1=Слабкий
-passwordStrength.messageLabel.2=Посередній
+passwordStrength.messageLabel.2=Задовільний
passwordStrength.messageLabel.3=Надійний
passwordStrength.messageLabel.4=Дуже надiйний
# Quit
quit.title=Вийти з додатка
quit.message=Є розблоковані сховища
-quit.description=Будь ласка, підтвердіть що ви хочете вийти. Cryptomator правильно заблокує всі розблоковані сховища, щоб запобігти втраті даних.
-quit.lockAndQuitBtn=Заблокувати і вийти
+quit.description=Ви дійсно хочете вийти? Cryptomator коректно заблокує всі розблоковані сховища, щоб запобігти втраті даних.
+quit.lockAndQuitBtn=Заблокувати та вийти
# Forced Quit
-quit.forced.message=Деякі сховища неможливо заблокувати
-quit.forced.description=Блокування сховищ було перерване через операції з ними або відкриті файли. Ви можете примусово заблокувати сховища, проте переривання операцій зчитування-запису може призвести до втрати незбережених даних.
-quit.forced.forceAndQuitBtn=Завершити роботу примусово
+quit.forced.message=Деякі сховища не вдалося заблокувати
+quit.forced.description=Блокування сховищ було заблоковано через незавершені операції або відкриті файли. Ви можете примусово заблокувати сховища, що залишилися, однак переривання операцій читання/запису може призвести до втрати незбережених даних.
+quit.forced.forceAndQuitBtn=Примусово заблокувати та вийти
# Update Reminder
updateReminder.title=Перевірити наявність оновлень
updateReminder.message=Перевірити наявність оновлень?
-updateReminder.description=Будьте в курсі нових функцій, виправлення помилок і вдосконалення безпеки. Ми рекомендуємо автоматично перевіряти наявність оновлень.
+updateReminder.description=Залишайтеся в курсі нових функцій, виправлень помилок та вдосконалень безпеки. Ми рекомендуємо автоматично перевіряти наявність оновлень.
updateReminder.notNow=Не зараз
updateReminder.yesOnce=Так, лише раз
updateReminder.yesAutomatically=Так, автоматично
#Dokany Support End
-dokanySupportEnd.title=Застаріле повідомлення
+dokanySupportEnd.title=Повідомлення про припинення підтримки
dokanySupportEnd.message=Закінчення підтримки Dokany
-dokanySupportEnd.description=Тип сховища Dokany більше не підтримується в Cryptomator. Тепер ваші налаштування змінено для використання типового типу сховища. Ви можете переглянути тип сховища за замовчуванням в налаштуваннях.
+dokanySupportEnd.description=Тип тому Dokany більше не підтримується Cryptomator. Ваші налаштування змінено на використання типу тому за замовчуванням. Переглянути стандартний тип можна в налаштуваннях.
dokanySupportEnd.preferencesBtn=Відкрити налаштування
#Retry If Readonly
retryIfReadonly.title=Обмежений доступ до сховища
-retryIfReadonly.message=Немає прав на запис до папки сховища
-retryIfReadonly.description=Cryptomator не може записувати в папку сховища. Ви можете змінити режим доступу до сховища на "лише для читання" і повторити спробу. Цей параметр можна вимкнути у налаштуваннях сховища.
-retryIfReadonly.retry=Змінити і повторити спробу
+retryIfReadonly.message=Немає доступу для запису до каталогу сховища
+retryIfReadonly.description=Cryptomator не може здійснити запис до каталогу сховища. Ви можете перевести сховище в режим «лише для читання» і спробувати знову. Цю опцію можна вимкнути в параметрах сховища.
+retryIfReadonly.retry=Змінити та повторити спробу
# Share Vault
shareVault.title=Поділитися сховищем
-shareVault.message=Ви хочете поділитися своїм сховищем з іншими?
-shareVault.description=Завжди будьте обережні, коли ділитесь своїм сховищем з іншими людьми. Якщо коротко, виконайте ці кроки:
-shareVault.instruction.1=1. Діліться доступом до зашифрованої папки сховища через хмарне сховище.
-shareVault.instruction.2=2. Передавайте пароль сховища безпечним способом.
-shareVault.remarkBestPractices=Для отримання додаткової інформації - ознайомтесь з кращим досвідом з наших документів.
-shareVault.docsTooltip=Відкрийте документацію, щоб дізнатися більше про надання доступу до сховищ.
-shareVault.hubAd.description=Безпечний спосіб роботи в командах
-shareVault.hubAd.keyManagement=• Управління ключами без інформації про їх значення
-shareVault.hubAd.authentication=• Сильні механізми перевірки особи (автентифікації)
+shareVault.message=Хочете поділитися сховищем з іншими?
+shareVault.description=Завжди будьте обережні, коли ділитесь своїм сховищем з іншими людьми. Якщо коротко, дотримуйтеся таких кроків:
+shareVault.instruction.1=1. Надайте спільний доступ до зашифрованої папки сховища через хмарне сховище.
+shareVault.instruction.2=2. Передайте пароль сховища безпечним способом.
+shareVault.remarkBestPractices=Для отримання додаткової інформації перегляньте поради щодо найкращих практик у нашій документації.
+shareVault.docsTooltip=Відкрийте документацію, щоб дізнатися більше про спільний доступ до сховищ.
+shareVault.hubAd.description=Надійний спосіб для командної роботи
+shareVault.hubAd.keyManagement=• Управління ключами за принципом «zero-knowledge»
+shareVault.hubAd.authentication=• Надійна автентифікація
shareVault.hubAd.encryption=• Наскрізне шифрування
shareVault.visitHub=Відвідати Cryptomator Hub
-shareVault.hub.message=Як поділитись сховищем у хабі (Hub)
-shareVault.hub.description=Щоб поділитися вмістом сховища з іншим членом команди, ви повинні виконати два кроки:
-shareVault.hub.instruction.1=1. Поділіться доступом до зашифрованої папки сховища через хмарне сховище.
+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
+decryptNames.title=Розшифрування назв файлів
+decryptNames.filePicker.title=Виберіть зашифрований файл
+decryptNames.filePicker.extensionDescription=Зашифрований файл Cryptomator
+decryptNames.copyTable.tooltip=Копіювати таблицю
+decryptNames.clearTable.tooltip=Очистити таблицю
+decryptNames.copyHint=Скопіювати вміст комірки за допомогою %s
+decryptNames.dropZone.message=Перетягніть файли або натисніть, щоб вибрати
+decryptNames.dropZone.error.vaultInternalFiles=Вибрано службові файли сховища, які не можна розшифрувати
+decryptNames.dropZone.error.foreignFiles=Файли не належать до сховища «%s»
+decryptNames.dropZone.error.noDirIdBackup=Каталог вибраних файлів не містить файл dirId.c9r
+decryptNames.dropZone.error.generic=Не вдалося розшифрувати назви файлів
# Event View
+eventView.title=Події
+eventView.filter.allVaults=Усі
+eventView.clearListButton.tooltip=Очистити список
## event list entries
+eventView.entry.vaultLocked.description=Розблокуйте «%s» для отримання деталей
+eventView.entry.conflictResolved.message=Конфлікт вирішено
+eventView.entry.conflictResolved.showDecrypted=Показати розшифрований файл
+eventView.entry.conflictResolved.copyDecrypted=Копіювати розшифрований шлях
+eventView.entry.conflict.message=Не вдалося вирішити конфлікт
+eventView.entry.conflict.showDecrypted=Показати розшифрований оригінальний файл
+eventView.entry.conflict.copyDecrypted=Скопіювати розшифрований оригінальний шлях
+eventView.entry.conflict.showEncrypted=Показати конфліктуючий зашифрований файл
+eventView.entry.conflict.copyEncrypted=Скопіювати конфліктуючий зашифрований шлях
+eventView.entry.decryptionFailed.message=Не вдалося розшифрувати
+eventView.entry.decryptionFailed.showEncrypted=Показати зашифрований файл
+eventView.entry.decryptionFailed.copyEncrypted=Копіювати зашифрований шлях
+eventView.entry.brokenDirFile.message=Пошкоджене посилання на каталог
+eventView.entry.brokenDirFile.showEncrypted=Показати пошкоджене зашифроване посилання
+eventView.entry.brokenDirFile.copyEncrypted=Копіювати шлях пошкодженого посилання
+eventView.entry.brokenFileNode.message=Пошкоджений вузол файлової системи
+eventView.entry.brokenFileNode.showEncrypted=Показати пошкоджений зашифрований вузол
+eventView.entry.brokenFileNode.copyEncrypted=Скопіювати шлях до пошкодженого зашифрованого вузла
+eventView.entry.brokenFileNode.copyDecrypted=Копіювати розшифрований шлях
diff --git a/src/main/resources/i18n/strings_vi.properties b/src/main/resources/i18n/strings_vi.properties
index 2254972b3..f0806b21a 100644
--- a/src/main/resources/i18n/strings_vi.properties
+++ b/src/main/resources/i18n/strings_vi.properties
@@ -291,7 +291,7 @@ preferences.general.keychainBackend=Lưu mật khẩu với
preferences.general.quickAccessService=Thêm các vault đã mở khóa vào khu vực truy cập nhanh
## Interface
preferences.interface=Giao diện
-preferences.interface.theme=Cái nhìn và cảm nhận
+preferences.interface.theme=Chủ đề giao diện
preferences.interface.theme.automatic=Tự động
preferences.interface.theme.dark=Tối
preferences.interface.theme.light=Sáng
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=Mở khoá…
main.vaultlist.contextMenu.unlockNow=Mở khóa bây giờ
main.vaultlist.contextMenu.vaultoptions=Hiện tùy chọn vault
main.vaultlist.contextMenu.reveal=Hiển thị Ổ đĩa
+main.vaultlist.contextMenu.share=Chia sẻ…
main.vaultlist.addVaultBtn.menuItemNew=Tạo Vault Mới...
main.vaultlist.addVaultBtn.menuItemExisting=Mở Vault Hiện Có...
main.vaultlist.showEventsButton.tooltip=Mở xem sự kiện
diff --git a/src/main/resources/i18n/strings_zh.properties b/src/main/resources/i18n/strings_zh.properties
index 3e97a80de..caa74221c 100644
--- a/src/main/resources/i18n/strings_zh.properties
+++ b/src/main/resources/i18n/strings_zh.properties
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=解锁…
main.vaultlist.contextMenu.unlockNow=立即解锁
main.vaultlist.contextMenu.vaultoptions=显示保险库选项
main.vaultlist.contextMenu.reveal=显示驱动器
+main.vaultlist.contextMenu.share=共享…
main.vaultlist.addVaultBtn.menuItemNew=新建保险库...
main.vaultlist.addVaultBtn.menuItemExisting=打开现有的保险库...
##Notificaition
diff --git a/src/main/resources/i18n/strings_zh_HK.properties b/src/main/resources/i18n/strings_zh_HK.properties
index 7ee362b37..984fbf1f6 100644
--- a/src/main/resources/i18n/strings_zh_HK.properties
+++ b/src/main/resources/i18n/strings_zh_HK.properties
@@ -391,6 +391,7 @@ main.vaultlist.contextMenu.unlock=解鎖…
main.vaultlist.contextMenu.unlockNow=立即解鎖
main.vaultlist.contextMenu.vaultoptions=顯示加密庫選項
main.vaultlist.contextMenu.reveal=展示磁碟
+main.vaultlist.contextMenu.share=分享…
main.vaultlist.addVaultBtn.menuItemNew=新建加密檔案庫...
main.vaultlist.addVaultBtn.menuItemExisting=開啟現有的加密檔案庫...
##Notificaition
diff --git a/src/main/resources/i18n/strings_zh_TW.properties b/src/main/resources/i18n/strings_zh_TW.properties
index 075e43fdd..9d03cfa49 100644
--- a/src/main/resources/i18n/strings_zh_TW.properties
+++ b/src/main/resources/i18n/strings_zh_TW.properties
@@ -393,6 +393,7 @@ main.vaultlist.contextMenu.unlock=解鎖…
main.vaultlist.contextMenu.unlockNow=立即解鎖
main.vaultlist.contextMenu.vaultoptions=顯示加密檔案庫選項
main.vaultlist.contextMenu.reveal=顯示磁碟
+main.vaultlist.contextMenu.share=分享…
main.vaultlist.addVaultBtn.menuItemNew=新建加密檔案庫...
main.vaultlist.addVaultBtn.menuItemExisting=開啟現有的加密檔案庫...
main.vaultlist.showEventsButton.tooltip=打開事件檢視
@@ -583,21 +584,39 @@ shareVault.hub.instruction.2=2. 在Cryptomator Hub中允許團隊成員對加密
shareVault.hub.openHub=打開 Cryptomator Hub
# Decrypt File Names
+decryptNames.title=解密檔案名稱
decryptNames.filePicker.title=選擇已加密的檔案
decryptNames.filePicker.extensionDescription=Cryptomator 加密檔案
decryptNames.copyTable.tooltip=複製表格
decryptNames.clearTable.tooltip=清除表格
+decryptNames.dropZone.message=拖放檔案或點擊選取
+decryptNames.dropZone.error.vaultInternalFiles=所選取的加密檔案庫內部檔案並沒有可解密的檔名
decryptNames.dropZone.error.foreignFiles=檔案不屬於加密檔案庫「%s」
+decryptNames.dropZone.error.noDirIdBackup=選取的檔案的資料夾並不包含dirId.c9r檔案
decryptNames.dropZone.error.generic=解密檔案名稱失敗
# Event View
eventView.title=事件
eventView.filter.allVaults=全部
+eventView.clearListButton.tooltip=清空列表
## event list entries
+eventView.entry.vaultLocked.description=解鎖「%s」以顯示詳細資訊
+eventView.entry.conflictResolved.message=以解決的衝突
+eventView.entry.conflictResolved.showDecrypted=顯示解密的檔案
+eventView.entry.conflictResolved.copyDecrypted=複製解密路徑
+eventView.entry.conflict.message=解決衝突失敗
+eventView.entry.conflict.showDecrypted=顯示解密、原始的檔案
+eventView.entry.conflict.copyDecrypted=複製解密、原始的路徑
+eventView.entry.conflict.showEncrypted=顯示衝突的加密檔案
+eventView.entry.conflict.copyEncrypted=複製衝突的加密檔案
eventView.entry.decryptionFailed.message=解密失敗
eventView.entry.decryptionFailed.showEncrypted=顯示加密的檔案
eventView.entry.decryptionFailed.copyEncrypted=複製加密路徑
eventView.entry.brokenDirFile.message=損壞的目錄連結
eventView.entry.brokenDirFile.showEncrypted=顯示損壞的加密路徑
eventView.entry.brokenDirFile.copyEncrypted=複製損壞的路徑連結
+eventView.entry.brokenFileNode.message=損壞的檔案系統節點
+eventView.entry.brokenFileNode.showEncrypted=顯示損壞的加密節點
+eventView.entry.brokenFileNode.copyEncrypted=複製損壞的加密節點路徑
+eventView.entry.brokenFileNode.copyDecrypted=複製解密路徑