Merge branch 'release/1.12.0'

This commit is contained in:
Armin Schrenk
2024-02-06 14:28:08 +01:00
117 changed files with 2238 additions and 507 deletions

View File

@@ -26,6 +26,7 @@ body:
Examples:
- Operating System: Windows 10
- Cryptomator: 1.5.16
- OneDrive: 23.226
- LibreOffice: 7.1.4
value: |
- Operating System:

View File

@@ -6,7 +6,7 @@ updates:
interval: "weekly"
day: "monday"
time: "06:00"
timezone: "UTC"
timezone: "Etc/UTC"
groups:
java-test-dependencies:
patterns:

View File

@@ -11,7 +11,7 @@ on:
env:
JAVA_DIST: 'zulu'
JAVA_VERSION: '21.0.1+12'
JAVA_VERSION: '21.0.2+13'
jobs:
get-version:
@@ -29,16 +29,16 @@ jobs:
include:
- os: ubuntu-latest
appimage-suffix: x86_64
openjfx-url: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-x64_bin-jmods.zip'
openjfx-sha: 'f522ac2ae4bdd61f0219b7b8d2058ff72a22f36a44378453bcfdcd82f8f5e08c'
openjfx-url: 'https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_linux-x64_bin-jmods.zip'
openjfx-sha: '7baed11ca56d5fee85995fa6612d4299f1e8b7337287228f7f12fd50407c56f8'
- os: [self-hosted, Linux, ARM64]
appimage-suffix: aarch64
openjfx-url: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-aarch64_bin-jmods.zip'
openjfx-sha: 'c0d80ebbe0aab404ef9ad8b46c05bf533a1e40b39b2720eebd9238d81f6326ca'
openjfx-url: 'https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_linux-aarch64_bin-jmods.zip'
openjfx-sha: '871e7b9d7af16aef2e55c1b7830d0e0b2503b13dd8641374ba7e55ecb81d2ef9'
steps:
- uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
@@ -74,6 +74,7 @@ jobs:
cp LICENSE.txt target
cp target/cryptomator-*.jar target/mods
- name: Run jlink
#Remark: no compression is applied for improved build compression later (here appimage)
run: >
${JAVA_HOME}/bin/jlink
--verbose
@@ -84,7 +85,7 @@ jobs:
--no-header-files
--no-man-pages
--strip-debug
--compress=1
--compress zip-0
- name: Prepare additional launcher
run: envsubst '${SEMVER_STR} ${REVISION_NUM}' < dist/linux/launcher-gtk2.properties > launcher-gtk2.properties
env:
@@ -102,7 +103,7 @@ jobs:
--dest appdir
--name Cryptomator
--vendor "Skymatic GmbH"
--copyright "(C) 2016 - 2023 Skymatic GmbH"
--copyright "(C) 2016 - 2024 Skymatic GmbH"
--app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}"
--java-options "--enable-preview"
--java-options "--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator"
@@ -163,7 +164,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@v3
uses: actions/upload-artifact@v4
with:
name: appimage-${{ matrix.appimage-suffix }}
path: |

View File

@@ -19,13 +19,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v3
- uses: actions/setup-java@v4
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
cache: 'maven'
- name: Cache SonarCloud packages
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar

View File

@@ -15,7 +15,7 @@ jobs:
outputs:
jdk-date: ${{ steps.get-data.outputs.jdk-date}}
steps:
- uses: actions/setup-java@v3
- uses: actions/setup-java@v4
with:
java-version: ${{ env.JDK_VERSION }}
distribution: ${{ env.JDK_VENDOR }}
@@ -32,7 +32,7 @@ jobs:
jdk-date: ${{ steps.get-data.outputs.jdk-date}}
jdk-version: ${{ steps.get-data.outputs.jdk-version}}
steps:
- uses: actions/setup-java@v3
- uses: actions/setup-java@v4
with:
java-version: 21
distribution: ${{ env.JDK_VENDOR }}

View File

@@ -17,13 +17,13 @@ on:
env:
JAVA_DIST: 'zulu'
JAVA_VERSION: '21.0.1+12'
JAVA_VERSION: '21.0.2+13'
COFFEELIBS_JDK: 21
COFFEELIBS_JDK_VERSION: '21.0.1+12-0ppa1'
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-x64_bin-jmods.zip'
OPENJFX_JMODS_AMD64_HASH: 'f522ac2ae4bdd61f0219b7b8d2058ff72a22f36a44378453bcfdcd82f8f5e08c'
OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-aarch64_bin-jmods.zip'
OPENJFX_JMODS_AARCH64_HASH: 'c0d80ebbe0aab404ef9ad8b46c05bf533a1e40b39b2720eebd9238d81f6326ca'
COFFEELIBS_JDK_VERSION: '21.0.2+13-0ppa1'
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_linux-x64_bin-jmods.zip'
OPENJFX_JMODS_AMD64_HASH: '7baed11ca56d5fee85995fa6612d4299f1e8b7337287228f7f12fd50407c56f8'
OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_linux-aarch64_bin-jmods.zip'
OPENJFX_JMODS_AARCH64_HASH: '871e7b9d7af16aef2e55c1b7830d0e0b2503b13dd8641374ba7e55ecb81d2ef9'
jobs:
build:
@@ -46,7 +46,7 @@ jobs:
sudo apt-get update
sudo apt-get install debhelper devscripts dput coffeelibs-jdk-${{ env.COFFEELIBS_JDK }}=${{ env.COFFEELIBS_JDK_VERSION }} libgtk2.0-0
- name: Setup Java
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
@@ -128,7 +128,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@v3
uses: actions/upload-artifact@v4
with:
name: linux-deb-package
path: |

17
.github/workflows/dependency-check.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
name: OWASP Maven Dependency Check
on:
schedule:
- cron: '0 8 * * 0'
workflow_dispatch:
jobs:
check-dependencies:
uses: skymatic/workflows/.github/workflows/run-dependency-check.yml@main
with:
runner-os: 'ubuntu-latest'
java-distribution: 'temurin'
java-version: 21
secrets:
nvd-api-key: ${{ secrets.NVD_API_KEY }}
slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}

View File

@@ -10,7 +10,7 @@ jobs:
steps:
- name: Get download count of latest releases
id: get-stats
uses: actions/github-script@v6
uses: actions/github-script@v7
with:
script: |
const query = `query($owner:String!, $name:String!) {

View File

@@ -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@v6
uses: actions/github-script@v7
with:
script: |
const query = `query ($owner: String!, $name: String!, $discussionNumber: Int!) {

View File

@@ -39,7 +39,7 @@ jobs:
with:
fetch-depth: 0
- name: Setup Java
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}

View File

@@ -16,7 +16,7 @@ on:
env:
JAVA_DIST: 'zulu'
JAVA_VERSION: '21.0.1+12'
JAVA_VERSION: '21.0.2+13'
jobs:
get-version:
@@ -37,19 +37,19 @@ jobs:
output-suffix: x64
xcode-path: '/Applications/Xcode_13.2.1.app'
fuse-lib: macFUSE
openjfx-url: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_osx-x64_bin-jmods.zip'
openjfx-sha: '55b8ff7453d59c89ae129f6c9c5ad7b09a5d359568811b376ac1766c14d6a17c'
openjfx-url: 'https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_osx-x64_bin-jmods.zip'
openjfx-sha: 'bd6abab20da73d5a968dcf2fd915d81b5fb919340e3bb84979ee9a888a829939'
- os: [self-hosted, macOS, ARM64]
architecture: aarch64
output-suffix: arm64
xcode-path: '/Applications/Xcode_13.2.1.app'
fuse-lib: FUSE-T
openjfx-url: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_osx-aarch64_bin-jmods.zip'
openjfx-sha: 'c60f5f19aa847e0e620e0b011e5de68f2c6755641c2141cec27a0b89f612beaf'
openjfx-url: 'https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_osx-aarch64_bin-jmods.zip'
openjfx-sha: '7afaa1c57a6cc3c384d636e597b9a5364693e2db4aaec0a6e63d2fa964400b58'
steps:
- uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
@@ -85,6 +85,7 @@ jobs:
cp LICENSE.txt target
cp target/cryptomator-*.jar target/mods
- name: Run jlink
#Remark: no compression is applied for improved build compression later (here dmg)
run: >
${JAVA_HOME}/bin/jlink
--verbose
@@ -95,7 +96,7 @@ jobs:
--no-header-files
--no-man-pages
--strip-debug
--compress=1
--compress zip-0
- name: Run jpackage
run: >
${JAVA_HOME}/bin/jpackage
@@ -108,7 +109,7 @@ jobs:
--dest appdir
--name Cryptomator
--vendor "Skymatic GmbH"
--copyright "(C) 2016 - 2023 Skymatic GmbH"
--copyright "(C) 2016 - 2024 Skymatic GmbH"
--app-version "${{ needs.get-version.outputs.semVerNum }}"
--java-options "--enable-preview"
--java-options "--enable-native-access=org.cryptomator.jfuse.mac"
@@ -249,7 +250,7 @@ jobs:
run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db
continue-on-error: true
- name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: dmg-${{ matrix.output-suffix }}
path: Cryptomator-*.dmg

View File

@@ -12,7 +12,7 @@ jobs:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v8
- uses: actions/stale@v9
with:
days-before-stale: 14
days-before-close: 0

View File

@@ -18,7 +18,7 @@ jobs:
if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v3
- uses: actions/setup-java@v4
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}

View File

@@ -10,12 +10,22 @@ defaults:
run:
shell: bash
env:
JAVA_DIST: 'zulu'
JAVA_VERSION: 21
jobs:
release-check-precondition:
check-preconditions:
name: Validate commits pushed to release/hotfix branch to fulfill release requirements
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
cache: 'maven'
- id: validate-pom-version
name: Validate POM version
run: |
@@ -37,4 +47,19 @@ jobs:
if ! grep -q "<release date=\".*\" version=\"${{ steps.validate-pom-version.outputs.semVerStr }}\"/>" dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml; then
echo "Release not set in dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml"
exit 1
fi
fi
- name: Cache NVD DB
uses: actions/cache@v4
with:
path: ~/.m2/repository/org/owasp/dependency-check-data/
key: dependency-check-${{ github.run_id }}
restore-keys: |
dependency-check
env:
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 5
- name: Run org.owasp:dependency-check plugin
id: dependency-check
continue-on-error: true
run: mvn -B verify -Pdependency-check -DskipTests
env:
NVD_API_KEY: ${{ secrets.NVD_API_KEY }}

View File

@@ -12,7 +12,7 @@ jobs:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v8
- uses: actions/stale@v9
with:
days-before-stale: 365
days-before-close: 90

View File

@@ -15,11 +15,11 @@ on:
env:
JAVA_DIST: 'zulu'
JAVA_VERSION: '21.0.1+12'
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_windows-x64_bin-jmods.zip'
OPENJFX_JMODS_AMD64_HASH: '18625bbc13c57dbf802486564247a8d8cab72ec558c240a401bf6440384ebd77'
JAVA_VERSION: '21.0.2+13'
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_windows-x64_bin-jmods.zip'
OPENJFX_JMODS_AMD64_HASH: 'daf8acae631c016c24cfe23f88469400274d3441dd890615a42dfb501f3eb94a'
WINFSP_MSI: 'https://github.com/winfsp/winfsp/releases/download/v2.0/winfsp-2.0.23075.msi'
WINFSP_UNINSTALLER: 'https://github.com/cryptomator/winfsp-uninstaller/releases/download/1.0.0-beta9/winfsp-uninstaller.exe'
WINFSP_UNINSTALLER: 'https://github.com/cryptomator/winfsp-uninstaller/releases/download/1.0.0/winfsp-uninstaller.exe'
defaults:
run:
@@ -41,7 +41,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
@@ -79,6 +79,7 @@ jobs:
cp LICENSE.txt target
cp target/cryptomator-*.jar target/mods
- name: Run jlink
#Remark: no compression is applied for improved build compression later (here msi)
run: >
${JAVA_HOME}/bin/jlink
--verbose
@@ -89,7 +90,7 @@ jobs:
--no-header-files
--no-man-pages
--strip-debug
--compress=1
--compress zip-0
- name: Change win-console flag if debug is active
if: ${{ inputs.isDebug }}
run: echo "WIN_CONSOLE_FLAG=--win-console" >> $GITHUB_ENV
@@ -105,7 +106,7 @@ jobs:
--dest appdir
--name Cryptomator
--vendor "Skymatic GmbH"
--copyright "(C) 2016 - 2023 Skymatic GmbH"
--copyright "(C) 2016 - 2024 Skymatic GmbH"
--app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}"
--java-options "--enable-preview"
--java-options "--enable-native-access=org.cryptomator.jfuse.win"
@@ -213,7 +214,7 @@ jobs:
--dest installer
--name Cryptomator
--vendor "Skymatic GmbH"
--copyright "(C) 2016 - 2023 Skymatic GmbH"
--copyright "(C) 2016 - 2024 Skymatic GmbH"
--app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum}}"
--win-menu
--win-dir-chooser
@@ -245,7 +246,7 @@ jobs:
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: msi
path: |
@@ -269,13 +270,13 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Download .msi
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: msi
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
- uses: actions/setup-java@v3
- uses: actions/setup-java@v4
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
@@ -308,7 +309,7 @@ jobs:
-out dist/win/bundle/
-dBundleVersion="${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}"
-dBundleVendor="Skymatic GmbH"
-dBundleCopyright="(C) 2016 - 2023 Skymatic GmbH"
-dBundleCopyright="(C) 2016 - 2024 Skymatic GmbH"
-dAboutUrl="https://cryptomator.org"
-dHelpUrl="https://cryptomator.org/contact"
-dUpdateUrl="https://cryptomator.org/downloads/"
@@ -356,7 +357,7 @@ jobs:
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: exe
path: |
@@ -380,12 +381,12 @@ jobs:
needs: [build-msi, build-exe]
steps:
- name: Download .msi
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: msi
path: msi
- name: Download .exe
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: exe
path: exe

View File

@@ -86,7 +86,7 @@ For more information on the security details visit [cryptomator.org](https://doc
### Dependencies
* JDK 19 (e.g. temurin)
* JDK 21 (e.g. temurin, zulu)
* Maven 3
### Run Maven

View File

@@ -8,11 +8,14 @@ REVISION_NO=`git rev-list --count HEAD`
if [ -z "${JAVA_HOME}" ]; then echo "JAVA_HOME not set. Run using JAVA_HOME=/path/to/jdk ./build.sh"; exit 1; fi
command -v mvn >/dev/null 2>&1 || { echo >&2 "mvn not found."; exit 1; }
command -v curl >/dev/null 2>&1 || { echo >&2 "curl not found."; exit 1; }
command -v unzip >/dev/null 2>&1 || { echo >&2 "unzip not found."; exit 1; }
VERSION=$(mvn -f ../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout)
SEMVER_STR=${VERSION}
MACHINE_TYPE=$(uname -m)
if [[ ! "${MACHINE_TYPE}" =~ x86_64|aarch64 ]]; then echo "Platform ${MACHINE_TYPE} not supported"; exit 1; fi
mvn -f ../../../pom.xml versions:set -DnewVersion=${SEMVER_STR}
# compile
@@ -20,17 +23,45 @@ mvn -B -f ../../../pom.xml clean package -Plinux -DskipTests
cp ../../../LICENSE.txt ../../../target
cp ../../../target/cryptomator-*.jar ../../../target/mods
# download javaFX jmods
OPENJFX_URL='https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_linux-x64_bin-jmods.zip'
OPENJFX_SHA='7baed11ca56d5fee85995fa6612d4299f1e8b7337287228f7f12fd50407c56f8'
OPENJFX_URL_aarch64='https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_linux-aarch64_bin-jmods.zip'
OPENJFX_SHA_aarch64='871e7b9d7af16aef2e55c1b7830d0e0b2503b13dd8641374ba7e55ecb81d2ef9'
if [[ "${MACHINE_TYPE}" = "aarch64" ]]; then
OPENJFX_URL="${OPENJFX_URL_aarch64}";
OPENJFX_SHA="${OPENJFX_SHA_aarch64}";
fi
curl -L ${OPENJFX_URL} -o openjfx-jmods.zip
echo "${OPENJFX_SHA} openjfx-jmods.zip" | shasum -a256 --check
mkdir -p openjfx-jmods
unzip -j openjfx-jmods.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d openjfx-jmods
JMOD_VERSION=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1)
JMOD_VERSION=${JMOD_VERSION#*@}
JMOD_VERSION=${JMOD_VERSION%%.*}
POM_JFX_VERSION=$(mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout)
POM_JFX_VERSION=${POM_JFX_VERSION#*@}
POM_JFX_VERSION=${POM_JFX_VERSION%%.*}
if [ $POM_JFX_VERSION -ne $JMOD_VERSION_AMD64 ]; then
>&2 echo "Major JavaFX version in pom.xml (${POM_JFX_VERSION}) != amd64 jmod version (${JMOD_VERSION})"
exit 1
fi
# add runtime
${JAVA_HOME}/bin/jlink \
--verbose \
--output runtime \
--module-path "${JAVA_HOME}/jmods" \
--module-path "${JAVA_HOME}/jmods:openjfx-jmods" \
--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.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net \
--strip-native-commands \
--no-header-files \
--no-man-pages \
--strip-debug \
--compress=1
--compress zip-0
# create app dir
envsubst '${SEMVER_STR} ${REVISION_NUM}' < ../launcher-gtk2.properties > launcher-gtk2.properties
@@ -46,7 +77,7 @@ ${JAVA_HOME}/bin/jpackage \
--vendor "Skymatic GmbH" \
--java-options "--enable-preview" \
--java-options "--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator" \
--copyright "(C) 2016 - 2023 Skymatic GmbH" \
--copyright "(C) 2016 - 2024 Skymatic GmbH" \
--java-options "-Xss5m" \
--java-options "-Xmx256m" \
--app-version "${VERSION}.${REVISION_NO}" \
@@ -97,5 +128,5 @@ chmod +x /tmp/appimagetool.AppImage
echo ""
echo "Done. AppImage successfully created: cryptomator-${SEMVER_STR}-${MACHINE_TYPE}.AppImage"
echo ""
echo >&2 "To clean up, run: rm -rf Cryptomator.AppDir appdir jni runtime squashfs-root; rm launcher-gtk2.properties /tmp/appimagetool.AppImage"
echo ""
echo >&2 "To clean up, run: rm -rf Cryptomator.AppDir appdir runtime squashfs-root openjfx-jmods; rm launcher-gtk2.properties /tmp/appimagetool.AppImage openjfx-jmods.zip"
echo ""

View File

@@ -66,6 +66,7 @@
</content_rating>
<releases>
<release date="2024-02-06" version="1.12.0"/>
<release date="2023-12-05" version="1.11.1"/>
<release date="2023-11-08" version="1.11.0"/>
<release date="2023-09-20" version="1.10.1"/>

View File

@@ -4,11 +4,11 @@ Upstream-Contact: Cryptomator <info@cryptomator.org>
Source: https://cryptomator.org
Files: *
Copyright: 2016-2023 Skymatic GmbH
Copyright: 2016-2024 Skymatic GmbH
License: GPL-3+
Files: debian/org.cryptomator.Cryptomator.appdata.xml
Copyright: 2016-2023 Skymatic GmbH
Copyright: 2016-2024 Skymatic GmbH
License: FSFAP
License: GPL-3+

View File

@@ -24,6 +24,7 @@ override_dh_auto_clean:
override_dh_auto_build:
mkdir resources
ln -s ../common/org.cryptomator.Cryptomator512.png resources/cryptomator.png
# Remark: no compression is applied for improved build compression later (here deb)
$(JAVA_HOME)/bin/jlink \
--output runtime \
--module-path "${JMODS_PATH}" \
@@ -32,7 +33,7 @@ override_dh_auto_build:
--no-header-files \
--no-man-pages \
--strip-debug \
--compress=2
--compress zip-0
$(JAVA_HOME)/bin/jpackage \
--type app-image \
--runtime-image runtime \
@@ -44,7 +45,7 @@ override_dh_auto_build:
--vendor "Skymatic GmbH" \
--java-options "--enable-preview" \
--java-options "--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator" \
--copyright "(C) 2016 - 2023 Skymatic GmbH" \
--copyright "(C) 2016 - 2024 Skymatic GmbH" \
--java-options "-Xss5m" \
--java-options "-Xmx256m" \
--java-options "-Dfile.encoding=\"utf-8\"" \

View File

@@ -21,7 +21,7 @@ rm -rf runtime dmg *.app *.dmg
# set variables
APP_NAME="Cryptomator"
VENDOR="Skymatic GmbH"
COPYRIGHT_YEARS="2016 - 2023"
COPYRIGHT_YEARS="2016 - 2024"
PACKAGE_IDENTIFIER="org.cryptomator"
MAIN_JAR_GLOB="cryptomator-*.jar"
MODULE_AND_MAIN_CLASS="org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator"
@@ -35,7 +35,7 @@ if [ "$(machine)" = "arm64e" ]; then
else
ARCH="x64"
fi
OPENJFX_JMODS="https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_osx-${ARCH}_bin-jmods.zip"
OPENJFX_JMODS="https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_osx-${ARCH}_bin-jmods.zip"
# check preconditions
if [ -z "${JAVA_HOME}" ]; then echo "JAVA_HOME not set. Run using JAVA_HOME=/path/to/jdk ./build.sh"; exit 1; fi
@@ -76,7 +76,7 @@ ${JAVA_HOME}/bin/jlink \
--no-header-files \
--no-man-pages \
--strip-debug \
--compress=1
--compress zip-0
# create app dir
${JAVA_HOME}/bin/jpackage \

View File

@@ -17,7 +17,7 @@
\f1\b0 \
\
\f0\b \'a9 2016 \'96 2023 Skymatic GmbH
\f0\b \'a9 2016 \'96 2024 Skymatic GmbH
\f1\b0 \
\
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\

10
dist/win/build.ps1 vendored
View File

@@ -51,9 +51,9 @@ if ($clean -and (Test-Path -Path $runtimeImagePath)) {
}
## download jfx jmods
$jmodsVersion='20.0.2'
$jmodsVersion='21.0.1'
$jmodsUrl = "https://download2.gluonhq.com/openjfx/${jmodsVersion}/openjfx-${jmodsVersion}_windows-x64_bin-jmods.zip"
$jfxJmodsChecksum = '18625bbc13c57dbf802486564247a8d8cab72ec558c240a401bf6440384ebd77'
$jfxJmodsChecksum = 'daf8acae631c016c24cfe23f88469400274d3441dd890615a42dfb501f3eb94a'
$jfxJmodsZip = '.\resources\jfxJmods.zip'
if( !(Test-Path -Path $jfxJmodsZip) ) {
Write-Output "Downloading ${jmodsUrl}..."
@@ -69,7 +69,7 @@ Expand-Archive -Path $jfxJmodsZip -Force -DestinationPath ".\resources\"
Remove-Item -Recurse -Force -Path ".\resources\javafx-jmods"
Move-Item -Force -Path ".\resources\javafx-jmods-*" -Destination ".\resources\javafx-jmods" -ErrorAction Stop
## create custom runtime
& "$Env:JAVA_HOME\bin\jlink" `
--verbose `
--output runtime `
@@ -79,7 +79,7 @@ Move-Item -Force -Path ".\resources\javafx-jmods-*" -Destination ".\resources\ja
--no-header-files `
--no-man-pages `
--strip-debug `
--compress=1
--compress "zip-0" #do not compress to have improved msi compression
$appPath = ".\$AppName"
if ($clean -and (Test-Path -Path $appPath)) {
@@ -181,7 +181,7 @@ Write-Output "Downloading ${winfspMsiUrl}..."
Invoke-WebRequest $winfspMsiUrl -OutFile ".\bundle\resources\winfsp.msi" # redirects are followed by default
# download legacy-winfsp uninstaller
$winfspUninstaller= 'https://github.com/cryptomator/winfsp-uninstaller/releases/download/1.0.0-beta9/winfsp-uninstaller.exe'
$winfspUninstaller= 'https://github.com/cryptomator/winfsp-uninstaller/releases/download/1.0.0/winfsp-uninstaller.exe'
Write-Output "Downloading ${winfspUninstaller}..."
Invoke-WebRequest $winfspUninstaller -OutFile ".\bundle\resources\winfsp-uninstaller.exe" # redirects are followed by default

View File

@@ -10,7 +10,7 @@
\vieww12000\viewh15840\viewkind0
\pard\tx283\tx567\tx850\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\b\fs16\lang7 Cryptomator is distributed under the GPLv3 License, found below. Please see the bottom of this document for any other license applicable to code used within Cryptomator.\b0\par
\par
\b\'a9 2016 \'96 2023 Skymatic GmbH \b0\par
\b\'a9 2016 \'96 2024 Skymatic GmbH \b0\par
\par
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\par
\par

View File

@@ -10,7 +10,7 @@
\vieww12000\viewh15840\viewkind0
\pard\tx283\tx567\tx850\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\b\fs16\lang7 Cryptomator is distributed under the GPLv3 License, found below. Please see the bottom of this document for any other license applicable to code used within Cryptomator.\b0\par
\par
\b\'a9 2016 \'96 2023 Skymatic GmbH \b0\par
\b\'a9 2016 \'96 2024 Skymatic GmbH \b0\par
\par
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\par
\par

42
pom.xml
View File

@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId>
<artifactId>cryptomator</artifactId>
<version>1.11.1</version>
<version>1.12.0</version>
<name>Cryptomator Desktop App</name>
<organization>
@@ -35,42 +35,42 @@
<!-- cryptomator dependencies -->
<cryptomator.cryptofs.version>2.6.8</cryptomator.cryptofs.version>
<cryptomator.integrations.version>1.3.0</cryptomator.integrations.version>
<cryptomator.integrations.win.version>1.2.4</cryptomator.integrations.win.version>
<cryptomator.integrations.mac.version>1.2.2</cryptomator.integrations.mac.version>
<cryptomator.integrations.linux.version>1.4.0-beta2</cryptomator.integrations.linux.version>
<cryptomator.fuse.version>4.0.0-beta5</cryptomator.fuse.version>
<cryptomator.integrations.win.version>1.2.5</cryptomator.integrations.win.version>
<cryptomator.integrations.mac.version>1.2.3</cryptomator.integrations.mac.version>
<cryptomator.integrations.linux.version>1.4.2</cryptomator.integrations.linux.version>
<cryptomator.fuse.version>4.0.0</cryptomator.fuse.version>
<cryptomator.dokany.version>2.0.0</cryptomator.dokany.version>
<cryptomator.webdav.version>2.0.5</cryptomator.webdav.version>
<cryptomator.webdav.version>2.0.6</cryptomator.webdav.version>
<!-- 3rd party dependencies -->
<commons-lang3.version>3.14.0</commons-lang3.version>
<dagger.version>2.48.1</dagger.version>
<dagger.version>2.50</dagger.version>
<easybind.version>2.2</easybind.version>
<guava.version>32.1.3-jre</guava.version>
<jackson.version>2.16.0</jackson.version>
<javafx.version>20.0.2</javafx.version>
<guava.version>33.0.0-jre</guava.version>
<jackson.version>2.16.1</jackson.version>
<javafx.version>21.0.1</javafx.version>
<jwt.version>4.4.0</jwt.version>
<nimbus-jose.version>9.37.1</nimbus-jose.version>
<logback.version>1.4.12</logback.version>
<slf4j.version>2.0.9</slf4j.version>
<nimbus-jose.version>9.37.3</nimbus-jose.version>
<logback.version>1.4.14</logback.version>
<slf4j.version>2.0.11</slf4j.version>
<tinyoauth2.version>0.8.0</tinyoauth2.version>
<zxcvbn.version>1.8.2</zxcvbn.version>
<!-- test dependencies -->
<junit.jupiter.version>5.10.1</junit.jupiter.version>
<mockito.version>5.7.0</mockito.version>
<junit.jupiter.version>5.10.2</junit.jupiter.version>
<mockito.version>5.10.0</mockito.version>
<hamcrest.version>2.2</hamcrest.version>
<!-- build-time dependencies -->
<jetbrains.annotations.version>24.1.0</jetbrains.annotations.version>
<dependency-check.version>9.0.1</dependency-check.version>
<dependency-check.version>9.0.9</dependency-check.version>
<jacoco.version>0.8.11</jacoco.version>
<license-generator.version>2.3.0</license-generator.version>
<license-generator.version>2.4.0</license-generator.version>
<junit-tree-reporter.version>1.2.1</junit-tree-reporter.version>
<mvn-compiler.version>3.11.0</mvn-compiler.version>
<mvn-compiler.version>3.12.1</mvn-compiler.version>
<mvn-resources.version>3.3.1</mvn-resources.version>
<mvn-dependency.version>3.6.1</mvn-dependency.version>
<mvn-surefire.version>3.2.2</mvn-surefire.version>
<mvn-surefire.version>3.2.5</mvn-surefire.version>
<mvn-jar.version>3.3.0</mvn-jar.version>
</properties>
@@ -460,17 +460,19 @@
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<configuration>
<cveValidForHours>24</cveValidForHours>
<nvdValidForHours>24</nvdValidForHours>
<failBuildOnCVSS>0</failBuildOnCVSS>
<skipTestScope>true</skipTestScope>
<detail>true</detail>
<suppressionFile>suppression.xml</suppressionFile>
<nvdApiKey>${env.NVD_API_KEY}</nvdApiKey>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
<phase>validate</phase>
</execution>
</executions>
</plugin>

View File

@@ -5,10 +5,8 @@
*******************************************************************************/
package org.cryptomator.common;
import com.tobiasdiez.easybind.EasyBind;
import dagger.Module;
import dagger.Provides;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.keychain.KeychainModule;
import org.cryptomator.common.mount.MountModule;
import org.cryptomator.common.settings.Settings;
@@ -22,8 +20,6 @@ import org.slf4j.LoggerFactory;
import javax.inject.Named;
import javax.inject.Singleton;
import javafx.beans.value.ObservableValue;
import java.net.InetSocketAddress;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Comparator;
@@ -136,13 +132,4 @@ public abstract class CommonsModule {
LOG.error("Uncaught exception in " + thread.getName(), throwable);
}
@Provides
@Singleton
static ObservableValue<InetSocketAddress> provideServerSocketAddressBinding(Settings settings) {
return settings.port.map(port -> {
String host = SystemUtils.IS_OS_WINDOWS ? "127.0.0.1" : "localhost";
return InetSocketAddress.createUnresolved(host, settings.port.intValue());
});
}
}

View File

@@ -1,6 +0,0 @@
package org.cryptomator.common.mount;
import org.cryptomator.integrations.mount.MountService;
public record ActualMountService(MountService service, boolean isDesired) {
}

View File

@@ -0,0 +1,14 @@
package org.cryptomator.common.mount;
import org.cryptomator.integrations.mount.MountFailedException;
/**
* Thrown by {@link Mounter} to indicate that the selected mount service can not be used
* due to incompatibilities with a different mount service that is already in use.
*/
public class ConflictingMountServiceException extends MountFailedException {
public ConflictingMountServiceException(String msg) {
super(msg);
}
}

View File

@@ -4,21 +4,18 @@ import dagger.Module;
import dagger.Provides;
import org.cryptomator.common.ObservableUtil;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.integrations.mount.Mount;
import org.cryptomator.integrations.mount.MountService;
import javax.inject.Named;
import javax.inject.Singleton;
import javafx.beans.value.ObservableValue;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@Module
public class MountModule {
private static final AtomicReference<MountService> formerSelectedMountService = new AtomicReference<>(null);
private static final List<String> problematicFuseMountServices = List.of("org.cryptomator.frontend.fuse.mount.MacFuseMountProvider", "org.cryptomator.frontend.fuse.mount.FuseTMountProvider");
@Provides
@Singleton
static List<MountService> provideSupportedMountServices() {
@@ -27,46 +24,18 @@ public class MountModule {
@Provides
@Singleton
@Named("FUPFMS")
static AtomicReference<MountService> provideFirstUsedProblematicFuseMountService() {
return new AtomicReference<>(null);
static ObservableValue<MountService> provideDefaultMountService(List<MountService> mountProviders, Settings settings) {
var fallbackProvider = mountProviders.stream().findFirst().get(); //there should always be a mount provider, at least webDAV
return ObservableUtil.mapWithDefault(settings.mountService, //
serviceName -> mountProviders.stream().filter(s -> s.getClass().getName().equals(serviceName)).findFirst().orElse(fallbackProvider), //
fallbackProvider);
}
@Provides
@Singleton
static ObservableValue<ActualMountService> provideMountService(Settings settings, List<MountService> serviceImpls, @Named("FUPFMS") AtomicReference<MountService> fupfms) {
var fallbackProvider = serviceImpls.stream().findFirst().orElse(null);
var observableMountService = ObservableUtil.mapWithDefault(settings.mountService, //
desiredServiceImpl -> { //
var serviceFromSettings = serviceImpls.stream().filter(serviceImpl -> serviceImpl.getClass().getName().equals(desiredServiceImpl)).findAny(); //
var targetedService = serviceFromSettings.orElse(fallbackProvider);
return applyWorkaroundForProblematicFuse(targetedService, serviceFromSettings.isPresent(), fupfms);
}, //
() -> { //
return applyWorkaroundForProblematicFuse(fallbackProvider, true, fupfms);
});
return observableMountService;
@Named("usedMountServices")
static Set<MountService> provideSetOfUsedMountServices() {
return ConcurrentHashMap.newKeySet();
}
//see https://github.com/cryptomator/cryptomator/issues/2786
private synchronized static ActualMountService applyWorkaroundForProblematicFuse(MountService targetedService, boolean isDesired, AtomicReference<MountService> firstUsedProblematicFuseMountService) {
//set the first used problematic fuse service if applicable
var targetIsProblematicFuse = isProblematicFuseService(targetedService);
if (targetIsProblematicFuse && firstUsedProblematicFuseMountService.get() == null) {
firstUsedProblematicFuseMountService.set(targetedService);
}
//do not use the targeted mount service and fallback to former one, if the service is problematic _and_ not the first problematic one used.
if (targetIsProblematicFuse && !firstUsedProblematicFuseMountService.get().equals(targetedService)) {
return new ActualMountService(formerSelectedMountService.get(), false);
} else {
formerSelectedMountService.set(targetedService);
return new ActualMountService(targetedService, isDesired);
}
}
public static boolean isProblematicFuseService(MountService service) {
return problematicFuseMountServices.contains(service.getClass().getName());
}
}
}

View File

@@ -9,11 +9,15 @@ import org.cryptomator.integrations.mount.MountFailedException;
import org.cryptomator.integrations.mount.MountService;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import javafx.beans.value.ObservableValue;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_AS_DRIVE_LETTER;
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_TO_EXISTING_DIR;
@@ -24,24 +28,39 @@ import static org.cryptomator.integrations.mount.MountCapability.UNMOUNT_FORCED;
@Singleton
public class Mounter {
private final Settings settings;
// mount providers (key) can not be used if any of the conflicting mount providers (values) are already in use
private static final Map<String, Set<String>> CONFLICTING_MOUNT_SERVICES = Map.of(
"org.cryptomator.frontend.fuse.mount.MacFuseMountProvider", Set.of("org.cryptomator.frontend.fuse.mount.FuseTMountProvider"),
"org.cryptomator.frontend.fuse.mount.FuseTMountProvider", Set.of("org.cryptomator.frontend.fuse.mount.MacFuseMountProvider")
);
private final Environment env;
private final Settings settings;
private final WindowsDriveLetters driveLetters;
private final ObservableValue<ActualMountService> mountServiceObservable;
private final List<MountService> mountProviders;
private final Set<MountService> usedMountServices;
private final ObservableValue<MountService> defaultMountService;
@Inject
public Mounter(Settings settings, Environment env, WindowsDriveLetters driveLetters, ObservableValue<ActualMountService> mountServiceObservable) {
this.settings = settings;
public Mounter(Environment env, //
Settings settings, //
WindowsDriveLetters driveLetters, //
List<MountService> mountProviders, //
@Named("usedMountServices") Set<MountService> usedMountServices, //
ObservableValue<MountService> defaultMountService) {
this.env = env;
this.settings = settings;
this.driveLetters = driveLetters;
this.mountServiceObservable = mountServiceObservable;
this.mountProviders = mountProviders;
this.usedMountServices = usedMountServices;
this.defaultMountService = defaultMountService;
}
private class SettledMounter {
private MountService service;
private MountBuilder builder;
private VaultSettings vaultSettings;
private final MountService service;
private final MountBuilder builder;
private final VaultSettings vaultSettings;
public SettledMounter(MountService service, MountBuilder builder, VaultSettings vaultSettings) {
this.service = service;
@@ -53,8 +72,13 @@ public class Mounter {
for (var capability : service.capabilities()) {
switch (capability) {
case FILE_SYSTEM_NAME -> builder.setFileSystemName("cryptoFs");
case LOOPBACK_PORT ->
builder.setLoopbackPort(settings.port.get()); //TODO: move port from settings to vaultsettings (see https://github.com/cryptomator/cryptomator/tree/feature/mount-setting-per-vault)
case LOOPBACK_PORT -> {
if (vaultSettings.mountService.getValue() == null) {
builder.setLoopbackPort(settings.port.get());
} else {
builder.setLoopbackPort(vaultSettings.port.get());
}
}
case LOOPBACK_HOST_NAME -> env.getLoopbackAlias().ifPresent(builder::setLoopbackHostName);
case READ_ONLY -> builder.setReadOnly(vaultSettings.usesReadOnlyMode.get());
case MOUNT_FLAGS -> {
@@ -131,13 +155,26 @@ public class Mounter {
}
public MountHandle mount(VaultSettings vaultSettings, Path cryptoFsRoot) throws IOException, MountFailedException {
var mountService = this.mountServiceObservable.getValue().service();
var mountService = mountProviders.stream().filter(s -> s.getClass().getName().equals(vaultSettings.mountService.getValue())).findFirst().orElse(defaultMountService.getValue());
if (isConflictingMountService(mountService)) {
var msg = STR."\{mountService.getClass()} unavailable due to conflict with either of \{CONFLICTING_MOUNT_SERVICES.get(mountService.getClass().getName())}";
throw new ConflictingMountServiceException(msg);
}
usedMountServices.add(mountService);
var builder = mountService.forFileSystem(cryptoFsRoot);
var internal = new SettledMounter(mountService, builder, vaultSettings);
var internal = new SettledMounter(mountService, builder, vaultSettings); // FIXME: no need for an inner class
var cleanup = internal.prepare();
return new MountHandle(builder.mount(), mountService.hasCapability(UNMOUNT_FORCED), cleanup);
}
public boolean isConflictingMountService(MountService service) {
var conflictingServices = CONFLICTING_MOUNT_SERVICES.getOrDefault(service.getClass().getName(), Set.of());
return usedMountServices.stream().map(MountService::getClass).map(Class::getName).anyMatch(conflictingServices::contains);
}
public record MountHandle(Mount mountObj, boolean supportsUnmountForced, Runnable specialCleanup) {
}

View File

@@ -8,7 +8,6 @@ package org.cryptomator.common.settings;
import com.google.common.base.CharMatcher;
import com.google.common.base.Strings;
import com.google.common.io.BaseEncoding;
import org.apache.commons.lang3.SystemUtils;
import org.jetbrains.annotations.VisibleForTesting;
import javafx.beans.Observable;
@@ -40,6 +39,7 @@ public class VaultSettings {
static final WhenUnlocked DEFAULT_ACTION_AFTER_UNLOCK = WhenUnlocked.ASK;
static final boolean DEFAULT_AUTOLOCK_WHEN_IDLE = false;
static final int DEFAULT_AUTOLOCK_IDLE_SECONDS = 30 * 60;
static final int DEFAULT_PORT = 42427;
private static final Random RNG = new Random();
@@ -56,6 +56,8 @@ public class VaultSettings {
public final IntegerProperty autoLockIdleSeconds;
public final ObjectProperty<Path> mountPoint;
public final StringExpression mountName;
public final StringProperty mountService;
public final IntegerProperty port;
VaultSettings(VaultSettingsJson json) {
this.id = json.id;
@@ -70,6 +72,8 @@ public class VaultSettings {
this.autoLockWhenIdle = new SimpleBooleanProperty(this, "autoLockWhenIdle", json.autoLockWhenIdle);
this.autoLockIdleSeconds = new SimpleIntegerProperty(this, "autoLockIdleSeconds", json.autoLockIdleSeconds);
this.mountPoint = new SimpleObjectProperty<>(this, "mountPoint", json.mountPoint == null ? null : Path.of(json.mountPoint));
this.mountService = new SimpleStringProperty(this, "mountService", json.mountService);
this.port = new SimpleIntegerProperty(this, "port", json.port);
// mount name is no longer an explicit setting, see https://github.com/cryptomator/cryptomator/pull/1318
this.mountName = StringExpression.stringExpression(Bindings.createStringBinding(() -> {
final String name;
@@ -95,7 +99,7 @@ public class VaultSettings {
}
Observable[] observables() {
return new Observable[]{actionAfterUnlock, autoLockIdleSeconds, autoLockWhenIdle, displayName, maxCleartextFilenameLength, mountFlags, mountPoint, path, revealAfterMount, unlockAfterStartup, usesReadOnlyMode};
return new Observable[]{actionAfterUnlock, autoLockIdleSeconds, autoLockWhenIdle, displayName, maxCleartextFilenameLength, mountFlags, mountPoint, path, revealAfterMount, unlockAfterStartup, usesReadOnlyMode, port, mountService};
}
public static VaultSettings withRandomId() {
@@ -124,6 +128,8 @@ public class VaultSettings {
json.autoLockWhenIdle = autoLockWhenIdle.get();
json.autoLockIdleSeconds = autoLockIdleSeconds.get();
json.mountPoint = mountPoint.map(Path::toString).getValue();
json.mountService = mountService.get();
json.port = port.get();
return json;
}

View File

@@ -45,6 +45,12 @@ class VaultSettingsJson {
@JsonProperty("autoLockIdleSeconds")
int autoLockIdleSeconds = VaultSettings.DEFAULT_AUTOLOCK_IDLE_SECONDS;
@JsonProperty("mountService")
String mountService;
@JsonProperty("port")
int port = VaultSettings.DEFAULT_PORT;
@Deprecated(since = "1.7.0")
@JsonProperty(value = "winDriveLetter", access = JsonProperty.Access.WRITE_ONLY) // WRITE_ONLY means value is "written" into the java object during deserialization. Upvote this: https://github.com/FasterXML/jackson-annotations/issues/233
String winDriveLetter;

View File

@@ -11,7 +11,6 @@ package org.cryptomator.common.vaults;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Constants;
import org.cryptomator.common.mount.Mounter;
import org.cryptomator.common.mount.WindowsDriveLetters;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.cryptofs.CryptoFileSystem;
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
@@ -73,7 +72,13 @@ public class Vault {
private final AtomicReference<Mounter.MountHandle> mountHandle = new AtomicReference<>(null);
@Inject
Vault(VaultSettings vaultSettings, VaultConfigCache configCache, AtomicReference<CryptoFileSystem> cryptoFileSystem, VaultState state, @Named("lastKnownException") ObjectProperty<Exception> lastKnownException, VaultStats stats, WindowsDriveLetters windowsDriveLetters, Mounter mounter) {
Vault(VaultSettings vaultSettings, //
VaultConfigCache configCache, //
AtomicReference<CryptoFileSystem> cryptoFileSystem, //
VaultState state, //
@Named("lastKnownException") ObjectProperty<Exception> lastKnownException, //
VaultStats stats, //
Mounter mounter) {
this.vaultSettings = vaultSettings;
this.configCache = configCache;
this.cryptoFileSystem = cryptoFileSystem;

View File

@@ -21,9 +21,11 @@ public enum FxmlFile {
HUB_INVALID_LICENSE("/fxml/hub_invalid_license.fxml"), //
HUB_RECEIVE_KEY("/fxml/hub_receive_key.fxml"), //
HUB_LEGACY_REGISTER_DEVICE("/fxml/hub_legacy_register_device.fxml"), //
HUB_LEGACY_REGISTER_SUCCESS("/fxml/hub_legacy_register_success.fxml"), //
HUB_REGISTER_SUCCESS("/fxml/hub_register_success.fxml"), //
HUB_REGISTER_DEVICE_ALREADY_EXISTS("/fxml/hub_register_device_already_exists.fxml"), //
HUB_REGISTER_FAILED("/fxml/hub_register_failed.fxml"), //
HUB_SETUP_DEVICE("/fxml/hub_setup_device.fxml"), //
HUB_REGISTER_DEVICE("/fxml/hub_register_device.fxml"), //
HUB_UNAUTHORIZED_DEVICE("/fxml/hub_unauthorized_device.fxml"), //
HUB_REQUIRE_ACCOUNT_INIT("/fxml/hub_require_account_init.fxml"), //
LOCK_FORCED("/fxml/lock_forced.fxml"), //
@@ -43,8 +45,10 @@ public enum FxmlFile {
RECOVERYKEY_RESET_PASSWORD_SUCCESS("/fxml/recoverykey_reset_password_success.fxml"), //
RECOVERYKEY_SUCCESS("/fxml/recoverykey_success.fxml"), //
REMOVE_VAULT("/fxml/remove_vault.fxml"), //
SHARE_VAULT("/fxml/share_vault.fxml"), //
UPDATE_REMINDER("/fxml/update_reminder.fxml"), //
UNLOCK_ENTER_PASSWORD("/fxml/unlock_enter_password.fxml"),
UNLOCK_REQUIRES_RESTART("/fxml/unlock_requires_restart.fxml"), //
UNLOCK_INVALID_MOUNT_POINT("/fxml/unlock_invalid_mount_point.fxml"), //
UNLOCK_SELECT_MASTERKEYFILE("/fxml/unlock_select_masterkeyfile.fxml"), //
UNLOCK_SUCCESS("/fxml/unlock_success.fxml"), //

View File

@@ -47,6 +47,7 @@ public enum FontAwesome5Icon {
QUESTION_CIRCLE("\uf059"), //
REDO("\uF01E"), //
SEARCH("\uF002"), //
SHARE("\uF064"), //
SPINNER("\uF110"), //
STETHOSCOPE("\uF0f1"), //
SYNC("\uF021"), //

View File

@@ -13,6 +13,7 @@ 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.sharevault.ShareVaultComponent;
import org.cryptomator.ui.traymenu.TrayMenuComponent;
import org.cryptomator.ui.unlock.UnlockComponent;
import org.cryptomator.ui.updatereminder.UpdateReminderComponent;
@@ -22,7 +23,17 @@ import javafx.scene.image.Image;
import java.io.IOException;
import java.io.InputStream;
@Module(includes = {UpdateCheckerModule.class}, subcomponents = {TrayMenuComponent.class, MainWindowComponent.class, PreferencesComponent.class, VaultOptionsComponent.class, UnlockComponent.class, LockComponent.class, QuitComponent.class, ErrorComponent.class, HealthCheckComponent.class, UpdateReminderComponent.class})
@Module(includes = {UpdateCheckerModule.class}, subcomponents = {TrayMenuComponent.class, //
MainWindowComponent.class, //
PreferencesComponent.class, //
VaultOptionsComponent.class, //
UnlockComponent.class, //
LockComponent.class, //
QuitComponent.class, //
ErrorComponent.class, //
HealthCheckComponent.class, //
UpdateReminderComponent.class, //
ShareVaultComponent.class})
abstract class FxApplicationModule {
private static Image createImageFromResource(String resourceName) throws IOException {

View File

@@ -11,6 +11,7 @@ import org.cryptomator.ui.mainwindow.MainWindowComponent;
import org.cryptomator.ui.preferences.PreferencesComponent;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.cryptomator.ui.quit.QuitComponent;
import org.cryptomator.ui.sharevault.ShareVaultComponent;
import org.cryptomator.ui.unlock.UnlockComponent;
import org.cryptomator.ui.unlock.UnlockWorkflow;
import org.cryptomator.ui.updatereminder.UpdateReminderComponent;
@@ -51,6 +52,7 @@ public class FxApplicationWindows {
private final ErrorComponent.Factory errorWindowFactory;
private final ExecutorService executor;
private final VaultOptionsComponent.Factory vaultOptionsWindow;
private final ShareVaultComponent.Factory shareVaultWindow;
private final FilteredList<Window> visibleWindows;
@Inject
@@ -64,6 +66,7 @@ public class FxApplicationWindows {
LockComponent.Factory lockWorkflowFactory, //
ErrorComponent.Factory errorWindowFactory, //
VaultOptionsComponent.Factory vaultOptionsWindow, //
ShareVaultComponent.Factory shareVaultWindow, //
ExecutorService executor) {
this.primaryStage = primaryStage;
this.trayIntegration = trayIntegration;
@@ -76,6 +79,7 @@ public class FxApplicationWindows {
this.errorWindowFactory = errorWindowFactory;
this.executor = executor;
this.vaultOptionsWindow = vaultOptionsWindow;
this.shareVaultWindow = shareVaultWindow;
this.visibleWindows = Window.getWindows().filtered(Window::isShowing);
}
@@ -122,6 +126,10 @@ public class FxApplicationWindows {
return CompletableFuture.supplyAsync(() -> preferencesWindow.get().showPreferencesWindow(selectedTab), Platform::runLater).whenComplete(this::reportErrors);
}
public void showShareVaultWindow(Vault vault) {
CompletableFuture.runAsync(() -> shareVaultWindow.create(vault).showShareVaultWindow(), Platform::runLater);
}
public CompletionStage<Stage> showVaultOptionsWindow(Vault vault, SelectedVaultOptionsTab tab) {
return showMainWindow().thenApplyAsync((window) -> vaultOptionsWindow.create(vault).showVaultOptionsWindow(tab), Platform::runLater).whenComplete(this::reportErrors);
}

View File

@@ -0,0 +1,12 @@
package org.cryptomator.ui.keyloading.hub;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
/**
* Thrown, when Hub registerDevice-Request returns with 409
*/
class DeviceAlreadyExistsException extends MasterkeyLoadingFailedException {
public DeviceAlreadyExistsException() {
super("Device already registered on this Hub instance");
}
}

View File

@@ -1,7 +1,7 @@
package org.cryptomator.ui.keyloading.hub;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.net.URI;
@@ -19,6 +19,19 @@ public class HubConfig {
@Deprecated // use apiBaseUrl + "/devices/"
public String devicesResourceUrl;
/**
* A collection of String template processors to construct URIs related to this Hub instance.
*/
@JsonIgnore
public final URIProcessors URIs = new URIProcessors();
/**
* Get the URI pointing to the <code>/api/</code> base resource.
*
* @return <code>/api/</code> URI
* @apiNote URI is guaranteed to end on <code>/</code>
* @see #URIs
*/
public URI getApiBaseUrl() {
if (apiBaseUrl != null) {
// make sure to end on "/":
@@ -33,4 +46,17 @@ public class HubConfig {
public URI getWebappBaseUrl() {
return getApiBaseUrl().resolve("../app/");
}
public class URIProcessors {
/**
* Resolves paths relative to the <code>/api/</code> endpoint of this Hub instance.
*/
public final StringTemplate.Processor<URI, RuntimeException> API = template -> {
var path = template.interpolate();
var relPath = path.startsWith("/") ? path.substring(1) : path;
return getApiBaseUrl().resolve(relPath);
};
}
}

View File

@@ -119,6 +119,12 @@ public abstract class HubKeyLoadingModule {
return fxmlLoaders.createScene(FxmlFile.HUB_LEGACY_REGISTER_DEVICE);
}
@Provides
@FxmlScene(FxmlFile.HUB_LEGACY_REGISTER_SUCCESS)
@KeyLoadingScoped
static Scene provideHubLegacyRegisterSuccessScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.HUB_LEGACY_REGISTER_SUCCESS);
}
@Provides
@FxmlScene(FxmlFile.HUB_REGISTER_SUCCESS)
@@ -135,10 +141,17 @@ public abstract class HubKeyLoadingModule {
}
@Provides
@FxmlScene(FxmlFile.HUB_SETUP_DEVICE)
@FxmlScene(FxmlFile.HUB_REGISTER_DEVICE)
@KeyLoadingScoped
static Scene provideHubRegisterDeviceScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.HUB_SETUP_DEVICE);
return fxmlLoaders.createScene(FxmlFile.HUB_REGISTER_DEVICE);
}
@Provides
@FxmlScene(FxmlFile.HUB_REGISTER_DEVICE_ALREADY_EXISTS)
@KeyLoadingScoped
static Scene provideHubRegisterDeviceAlreadyExistsScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.HUB_REGISTER_DEVICE_ALREADY_EXISTS);
}
@Provides
@@ -185,6 +198,11 @@ public abstract class HubKeyLoadingModule {
@FxControllerKey(LegacyRegisterDeviceController.class)
abstract FxController bindLegacyRegisterDeviceController(LegacyRegisterDeviceController controller);
@Binds
@IntoMap
@FxControllerKey(LegacyRegisterSuccessController.class)
abstract FxController bindLegacyRegisterSuccessController(LegacyRegisterSuccessController controller);
@Binds
@IntoMap
@FxControllerKey(RegisterSuccessController.class)

View File

@@ -1,7 +1,6 @@
package org.cryptomator.ui.keyloading.hub;
import com.google.common.base.Preconditions;
import com.nimbusds.jose.JWEObject;
import dagger.Lazy;
import org.cryptomator.common.keychain.KeychainManager;
import org.cryptomator.common.keychain.NoKeychainAccessProviderException;
@@ -44,6 +43,7 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
this.window = window;
this.keychainManager = keychainManager;
window.setTitle(windowTitle);
window.setOnCloseRequest(_ -> result.cancel(true));
this.authFlowScene = authFlowScene;
this.noKeychainScene = noKeychainScene;
this.result = result;

View File

@@ -1,7 +1,6 @@
package org.cryptomator.ui.keyloading.hub;
import com.google.common.base.Preconditions;
import com.google.common.io.BaseEncoding;
import com.nimbusds.jose.EncryptionMethod;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWEAlgorithm;
@@ -13,19 +12,20 @@ import com.nimbusds.jose.crypto.ECDHEncrypter;
import com.nimbusds.jose.crypto.PasswordBasedDecrypter;
import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.jwk.gen.ECKeyGenerator;
import com.nimbusds.jose.jwk.gen.JWKGenerator;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Base64;
import java.util.Map;
@@ -37,26 +37,16 @@ class JWEHelper {
private static final String JWE_PAYLOAD_KEY_FIELD = "key";
private static final String EC_ALG = "EC";
private JWEHelper(){}
private JWEHelper() {}
public static JWEObject encryptUserKey(ECPrivateKey userKey, ECPublicKey deviceKey) {
try {
var encodedUserKey = Base64.getEncoder().encodeToString(userKey.getEncoded());
var keyGen = new ECKeyGenerator(Curve.P_384);
var ephemeralKeyPair = keyGen.generate();
var header = new JWEHeader.Builder(JWEAlgorithm.ECDH_ES, EncryptionMethod.A256GCM).ephemeralPublicKey(ephemeralKeyPair.toPublicJWK()).build();
var payload = new Payload(Map.of(JWE_PAYLOAD_KEY_FIELD, encodedUserKey));
var jwe = new JWEObject(header, payload);
jwe.encrypt(new ECDHEncrypter(deviceKey));
return jwe;
} catch (JOSEException e) {
throw new RuntimeException(e);
}
return encryptKey(userKey, deviceKey);
}
public static ECPrivateKey decryptUserKey(JWEObject jwe, String setupCode) throws InvalidJweKeyException {
try {
jwe.decrypt(new PasswordBasedDecrypter(setupCode));
return decodeUserKey(jwe);
return readKey(jwe, JWE_PAYLOAD_KEY_FIELD, JWEHelper::decodeECPrivateKey);
} catch (JOSEException e) {
throw new InvalidJweKeyException(e);
}
@@ -65,17 +55,23 @@ class JWEHelper {
public static ECPrivateKey decryptUserKey(JWEObject jwe, ECPrivateKey deviceKey) throws InvalidJweKeyException {
try {
jwe.decrypt(new ECDHDecrypter(deviceKey));
return decodeUserKey(jwe);
return readKey(jwe, JWE_PAYLOAD_KEY_FIELD, JWEHelper::decodeECPrivateKey);
} catch (JOSEException e) {
throw new InvalidJweKeyException(e);
}
}
private static ECPrivateKey decodeUserKey(JWEObject decryptedJwe) {
/**
* Attempts to decode a DER-encoded EC private key.
*
* @param encoded DER-encoded EC private key
* @return the decoded key
* @throws KeyDecodeFailedException On malformed input
*/
public static ECPrivateKey decodeECPrivateKey(byte[] encoded) throws KeyDecodeFailedException {
try {
var keySpec = readKey(decryptedJwe, JWE_PAYLOAD_KEY_FIELD, PKCS8EncodedKeySpec::new);
var factory = KeyFactory.getInstance(EC_ALG);
var privateKey = factory.generatePrivate(keySpec);
KeyFactory factory = KeyFactory.getInstance(EC_ALG);
var privateKey = factory.generatePrivate(new PKCS8EncodedKeySpec(encoded));
if (privateKey instanceof ECPrivateKey ecPrivateKey) {
return ecPrivateKey;
} else {
@@ -84,8 +80,49 @@ class JWEHelper {
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(EC_ALG + " not supported");
} catch (InvalidKeySpecException e) {
LOG.warn("Unexpected JWE payload: {}", decryptedJwe.getPayload());
throw new MasterkeyLoadingFailedException("Unexpected JWE payload", e);
throw new KeyDecodeFailedException(e);
}
}
/**
* Attempts to decode a DER-encoded EC public key.
*
* @param encoded DER-encoded EC public key
* @return the decoded key
* @throws KeyDecodeFailedException On malformed input
*/
public static ECPublicKey decodeECPublicKey(byte[] encoded) throws KeyDecodeFailedException {
try {
KeyFactory factory = KeyFactory.getInstance(EC_ALG);
var publicKey = factory.generatePublic(new X509EncodedKeySpec(encoded));
if (publicKey instanceof ECPublicKey ecPublicKey) {
return ecPublicKey;
} else {
throw new IllegalStateException(EC_ALG + " key factory not generating ECPublicKeys");
}
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(EC_ALG + " not supported");
} catch (InvalidKeySpecException e) {
throw new KeyDecodeFailedException(e);
}
}
public static JWEObject encryptVaultKey(Masterkey vaultKey, ECPublicKey userKey) {
return encryptKey(vaultKey, userKey);
}
private static JWEObject encryptKey(Key key, ECPublicKey userKey) {
try {
var encodedVaultKey = Base64.getEncoder().encodeToString(key.getEncoded());
var keyGen = new ECKeyGenerator(Curve.P_384);
var ephemeralKeyPair = keyGen.generate();
var header = new JWEHeader.Builder(JWEAlgorithm.ECDH_ES, EncryptionMethod.A256GCM).ephemeralPublicKey(ephemeralKeyPair.toPublicJWK()).build();
var payload = new Payload(Map.of(JWE_PAYLOAD_KEY_FIELD, encodedVaultKey));
var jwe = new JWEObject(header, payload);
jwe.encrypt(new ECDHEncrypter(userKey));
return jwe;
} catch (JOSEException e) {
throw new RuntimeException(e);
}
}
@@ -108,12 +145,12 @@ class JWEHelper {
var keyBytes = new byte[0];
try {
if (fields.get(keyField) instanceof String key) {
keyBytes = BaseEncoding.base64().decode(key);
keyBytes = Base64.getDecoder().decode(key);
return rawKeyFactory.apply(keyBytes);
} else {
throw new IllegalArgumentException("JWE payload doesn't contain field " + keyField);
}
} catch (IllegalArgumentException e) {
} catch (IllegalArgumentException | KeyDecodeFailedException e) {
LOG.error("Unexpected JWE payload: {}", jwe.getPayload());
throw new MasterkeyLoadingFailedException("Unexpected JWE payload", e);
} finally {
@@ -127,4 +164,11 @@ class JWEHelper {
super("Invalid key", cause);
}
}
public static class KeyDecodeFailedException extends CryptoException {
public KeyDecodeFailedException(Throwable cause) {
super("Malformed key", cause);
}
}
}

View File

@@ -64,7 +64,7 @@ public class LegacyRegisterDeviceController implements FxController {
public Button registerBtn;
@Inject
public LegacyRegisterDeviceController(@KeyLoading Stage window, ExecutorService executor, HubConfig hubConfig, @Named("deviceId") String deviceId, DeviceKey deviceKey, CompletableFuture<ReceivedKey> result, @Named("bearerToken") AtomicReference<String> bearerToken, @FxmlScene(FxmlFile.HUB_REGISTER_SUCCESS) Lazy<Scene> registerSuccessScene, @FxmlScene(FxmlFile.HUB_REGISTER_FAILED) Lazy<Scene> registerFailedScene) {
public LegacyRegisterDeviceController(@KeyLoading Stage window, ExecutorService executor, HubConfig hubConfig, @Named("deviceId") String deviceId, DeviceKey deviceKey, CompletableFuture<ReceivedKey> result, @Named("bearerToken") AtomicReference<String> bearerToken, @FxmlScene(FxmlFile.HUB_LEGACY_REGISTER_SUCCESS) Lazy<Scene> registerSuccessScene, @FxmlScene(FxmlFile.HUB_REGISTER_FAILED) Lazy<Scene> registerFailedScene) {
this.window = window;
this.hubConfig = hubConfig;
this.deviceId = deviceId;

View File

@@ -0,0 +1,24 @@
package org.cryptomator.ui.keyloading.hub;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.keyloading.KeyLoading;
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.stage.Stage;
@KeyLoadingScoped
public class LegacyRegisterSuccessController implements FxController {
private final Stage window;
@Inject
public LegacyRegisterSuccessController(@KeyLoading Stage window) {
this.window = window;
}
@FXML
public void close() {
window.close();
}
}

View File

@@ -3,6 +3,7 @@ package org.cryptomator.ui.keyloading.hub;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Preconditions;
import com.nimbusds.jose.JWEObject;
import dagger.Lazy;
import org.cryptomator.common.vaults.Vault;
@@ -11,7 +12,6 @@ import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.keyloading.KeyLoading;
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -32,7 +32,6 @@ import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.time.Duration;
import java.time.Instant;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
@@ -48,29 +47,29 @@ public class ReceiveKeyController implements FxController {
private final Stage window;
private final HubConfig hubConfig;
private final String vaultId;
private final String deviceId;
private final String bearerToken;
private final CompletableFuture<ReceivedKey> result;
private final Lazy<Scene> setupDeviceScene;
private final Lazy<Scene> registerDeviceScene;
private final Lazy<Scene> legacyRegisterDeviceScene;
private final Lazy<Scene> unauthorizedScene;
private final Lazy<Scene> accountInitializationScene;
private final URI vaultBaseUri;
private final Lazy<Scene> invalidLicenseScene;
private final HttpClient httpClient;
@Inject
public ReceiveKeyController(@KeyLoading Vault vault, ExecutorService executor, @KeyLoading Stage window, HubConfig hubConfig, @Named("deviceId") String deviceId, @Named("bearerToken") AtomicReference<String> tokenRef, CompletableFuture<ReceivedKey> result, @FxmlScene(FxmlFile.HUB_SETUP_DEVICE) Lazy<Scene> setupDeviceScene, @FxmlScene(FxmlFile.HUB_LEGACY_REGISTER_DEVICE) Lazy<Scene> legacyRegisterDeviceScene, @FxmlScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE) Lazy<Scene> unauthorizedScene, @FxmlScene(FxmlFile.HUB_REQUIRE_ACCOUNT_INIT) Lazy<Scene> accountInitializationScene, @FxmlScene(FxmlFile.HUB_INVALID_LICENSE) Lazy<Scene> invalidLicenseScene) {
public ReceiveKeyController(@KeyLoading Vault vault, ExecutorService executor, @KeyLoading Stage window, HubConfig hubConfig, @Named("deviceId") String deviceId, @Named("bearerToken") AtomicReference<String> tokenRef, CompletableFuture<ReceivedKey> result, @FxmlScene(FxmlFile.HUB_REGISTER_DEVICE) Lazy<Scene> registerDeviceScene, @FxmlScene(FxmlFile.HUB_LEGACY_REGISTER_DEVICE) Lazy<Scene> legacyRegisterDeviceScene, @FxmlScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE) Lazy<Scene> unauthorizedScene, @FxmlScene(FxmlFile.HUB_REQUIRE_ACCOUNT_INIT) Lazy<Scene> accountInitializationScene, @FxmlScene(FxmlFile.HUB_INVALID_LICENSE) Lazy<Scene> invalidLicenseScene) {
this.window = window;
this.hubConfig = hubConfig;
this.vaultId = extractVaultId(vault.getVaultConfigCache().getUnchecked().getKeyId()); // TODO: access vault config's JTI directly (requires changes in cryptofs)
this.deviceId = deviceId;
this.bearerToken = Objects.requireNonNull(tokenRef.get());
this.result = result;
this.setupDeviceScene = setupDeviceScene;
this.registerDeviceScene = registerDeviceScene;
this.legacyRegisterDeviceScene = legacyRegisterDeviceScene;
this.unauthorizedScene = unauthorizedScene;
this.accountInitializationScene = accountInitializationScene;
this.vaultBaseUri = getVaultBaseUri(vault);
this.invalidLicenseScene = invalidLicenseScene;
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
this.httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).executor(executor).build();
@@ -78,70 +77,76 @@ public class ReceiveKeyController implements FxController {
@FXML
public void initialize() {
requestVaultMasterkey();
receiveKey();
}
public void receiveKey() {
requestApiConfig();
}
/**
* STEP 1 (Request): GET vault key for this user
* STEP 0 (Request): GET /api/config
*/
private void requestVaultMasterkey() {
var accessTokenUri = appendPath(vaultBaseUri, "/access-token");
var request = HttpRequest.newBuilder(accessTokenUri) //
.header("Authorization", "Bearer " + bearerToken) //
private void requestApiConfig() {
var configUri = hubConfig.URIs.API."config";
var request = HttpRequest.newBuilder(configUri) //
.GET() //
.timeout(REQ_TIMEOUT) //
.build();
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.US_ASCII)) //
.thenAcceptAsync(this::receivedVaultMasterkey, Platform::runLater) //
.thenAcceptAsync(this::receivedApiConfig, Platform::runLater) //
.exceptionally(this::retrievalFailed);
}
/**
* STEP 1 (Response): GET vault key for this user
* STEP 0 (Response): GET /api/config
*
* @param response Response
*/
private void receivedVaultMasterkey(HttpResponse<String> response) {
private void receivedApiConfig(HttpResponse<String> response) {
LOG.debug("GET {} -> Status Code {}", response.request().uri(), response.statusCode());
switch (response.statusCode()) {
case 200 -> requestUserKey(response.body());
case 402 -> licenseExceeded();
case 403, 410 -> accessNotGranted(); // or vault has been archived, effectively disallowing access - TODO: add specific dialog?
case 449 -> accountInitializationRequired();
case 404 -> requestLegacyAccessToken();
default -> throw new IllegalStateException("Unexpected response " + response.statusCode());
Preconditions.checkState(response.statusCode() == 200, "Unexpected response " + response.statusCode());
try {
var config = JSON.reader().readValue(response.body(), ConfigDto.class);
if (config.apiLevel >= 1) {
requestDeviceData();
} else {
requestLegacyAccessToken();
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
/**
* STEP 2 (Request): GET user key for this device
* STEP 1 (Request): GET user key for this device
*/
private void requestUserKey(String encryptedVaultKey) {
var deviceTokenUri = URI.create(hubConfig.getApiBaseUrl() + "/devices/" + deviceId);
var request = HttpRequest.newBuilder(deviceTokenUri) //
private void requestDeviceData() {
var deviceUri = hubConfig.URIs.API."devices/\{deviceId}";
var request = HttpRequest.newBuilder(deviceUri) //
.header("Authorization", "Bearer " + bearerToken) //
.GET() //
.timeout(REQ_TIMEOUT) //
.build();
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)) //
.thenAcceptAsync(response -> receivedUserKey(encryptedVaultKey, response), Platform::runLater) //
.thenAcceptAsync(this::receivedDeviceData) //
.exceptionally(this::retrievalFailed);
}
/**
* STEP 2 (Response): GET user key for this device
* STEP 1 (Response): GET user key for this device
*
* @param response Response
*/
private void receivedUserKey(String encryptedVaultKey, HttpResponse<String> response) {
private void receivedDeviceData(HttpResponse<String> response) {
LOG.debug("GET {} -> Status Code {}", response.request().uri(), response.statusCode());
try {
switch (response.statusCode()) {
case 200 -> {
var device = JSON.reader().readValue(response.body(), DeviceDto.class);
receivedBothEncryptedKeys(encryptedVaultKey, device.userPrivateKey);
requestVaultMasterkey(device.userPrivateKey);
}
case 404 -> needsDeviceSetup(); // TODO: using the setup code, we can theoretically immediately unlock
case 404 -> Platform.runLater(this::needsDeviceRegistration);
default -> throw new IllegalStateException("Unexpected response " + response.statusCode());
}
} catch (IOException e) {
@@ -149,18 +154,49 @@ public class ReceiveKeyController implements FxController {
}
}
private void needsDeviceSetup() {
window.setScene(setupDeviceScene.get());
private void needsDeviceRegistration() {
window.setScene(registerDeviceScene.get());
}
private void receivedBothEncryptedKeys(String encryptedVaultKey, String encryptedUserKey) throws IOException {
/**
* STEP 2 (Request): GET vault key for this user
*/
private void requestVaultMasterkey(String encryptedUserKey) {
var vaultKeyUri = hubConfig.URIs.API."vaults/\{vaultId}/access-token";
var request = HttpRequest.newBuilder(vaultKeyUri) //
.header("Authorization", "Bearer " + bearerToken) //
.GET() //
.timeout(REQ_TIMEOUT) //
.build();
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.US_ASCII)) //
.thenAcceptAsync(response -> receivedVaultMasterkey(encryptedUserKey, response), Platform::runLater) //
.exceptionally(this::retrievalFailed);
}
/**
* STEP 2 (Response): GET vault key for this user
*
* @param response Response
*/
private void receivedVaultMasterkey(String encryptedUserKey, HttpResponse<String> response) {
LOG.debug("GET {} -> Status Code {}", response.request().uri(), response.statusCode());
switch (response.statusCode()) {
case 200 -> receivedBothEncryptedKeys(response.body(), encryptedUserKey);
case 402 -> licenseExceeded();
case 403, 410 -> accessNotGranted(); // or vault has been archived, effectively disallowing access - TODO: add specific dialog?
case 449 -> accountInitializationRequired();
default -> throw new IllegalStateException("Unexpected response " + response.statusCode());
}
}
private void receivedBothEncryptedKeys(String encryptedVaultKey, String encryptedUserKey) {
try {
var vaultKeyJwe = JWEObject.parse(encryptedVaultKey);
var userKeyJwe = JWEObject.parse(encryptedUserKey);
result.complete(ReceivedKey.vaultKeyAndUserKey(vaultKeyJwe, userKeyJwe));
window.close();
} catch (ParseException e) {
throw new IOException("Failed to parse JWE", e);
retrievalFailed(e);
}
}
@@ -169,7 +205,7 @@ public class ReceiveKeyController implements FxController {
*/
@Deprecated
private void requestLegacyAccessToken() {
var legacyAccessTokenUri = appendPath(vaultBaseUri, "/keys/" + deviceId);
var legacyAccessTokenUri = hubConfig.URIs.API."vaults/\{vaultId}/keys/\{deviceId}";
var request = HttpRequest.newBuilder(legacyAccessTokenUri) //
.header("Authorization", "Bearer " + bearerToken) //
.GET() //
@@ -251,19 +287,15 @@ public class ReceiveKeyController implements FxController {
}
}
private static URI getVaultBaseUri(Vault vault) {
try {
var url = vault.getVaultConfigCache().get().getKeyId();
assert url.getScheme().startsWith(SCHEME_PREFIX);
var correctedScheme = url.getScheme().substring(SCHEME_PREFIX.length());
return new URI(correctedScheme, url.getSchemeSpecificPart(), url.getFragment());
} catch (IOException e) {
throw new UncheckedIOException(e);
} catch (URISyntaxException e) {
throw new IllegalStateException("URI constructed from params known to be valid", e);
}
private static String extractVaultId(URI vaultKeyUri) {
assert vaultKeyUri.getScheme().startsWith(SCHEME_PREFIX);
var path = vaultKeyUri.getPath();
return path.substring(path.lastIndexOf('/') + 1);
}
@JsonIgnoreProperties(ignoreUnknown = true)
private record DeviceDto(@JsonProperty(value = "userPrivateKey", required = true) String userPrivateKey) {}
@JsonIgnoreProperties(ignoreUnknown = true)
private record ConfigDto(@JsonProperty(value = "apiLevel") int apiLevel) {}
}

View File

@@ -31,19 +31,25 @@ import javafx.scene.control.TextField;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.UncheckedIOException;
import java.net.InetAddress;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.security.interfaces.ECPublicKey;
import java.text.ParseException;
import java.time.Duration;
import java.time.Instant;
import java.util.Base64;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
@KeyLoadingScoped
public class RegisterDeviceController implements FxController {
@@ -57,12 +63,12 @@ public class RegisterDeviceController implements FxController {
private final String bearerToken;
private final Lazy<Scene> registerSuccessScene;
private final Lazy<Scene> registerFailedScene;
private final Lazy<Scene> deviceAlreadyExistsScene;
private final String deviceId;
private final P384KeyPair deviceKeyPair;
private final CompletableFuture<ReceivedKey> result;
private final HttpClient httpClient;
private final BooleanProperty deviceNameAlreadyExists = new SimpleBooleanProperty(false);
private final BooleanProperty invalidSetupCode = new SimpleBooleanProperty(false);
private final BooleanProperty workInProgress = new SimpleBooleanProperty(false);
public TextField setupCodeField;
@@ -70,7 +76,7 @@ public class RegisterDeviceController implements FxController {
public Button registerBtn;
@Inject
public RegisterDeviceController(@KeyLoading Stage window, ExecutorService executor, HubConfig hubConfig, @Named("deviceId") String deviceId, DeviceKey deviceKey, CompletableFuture<ReceivedKey> result, @Named("bearerToken") AtomicReference<String> bearerToken, @FxmlScene(FxmlFile.HUB_REGISTER_SUCCESS) Lazy<Scene> registerSuccessScene, @FxmlScene(FxmlFile.HUB_REGISTER_FAILED) Lazy<Scene> registerFailedScene) {
public RegisterDeviceController(@KeyLoading Stage window, ExecutorService executor, HubConfig hubConfig, @Named("deviceId") String deviceId, DeviceKey deviceKey, CompletableFuture<ReceivedKey> result, @Named("bearerToken") AtomicReference<String> bearerToken, @FxmlScene(FxmlFile.HUB_REGISTER_SUCCESS) Lazy<Scene> registerSuccessScene, @FxmlScene(FxmlFile.HUB_REGISTER_FAILED) Lazy<Scene> registerFailedScene, @FxmlScene(FxmlFile.HUB_REGISTER_DEVICE_ALREADY_EXISTS) Lazy<Scene> deviceAlreadyExistsScene) {
this.window = window;
this.hubConfig = hubConfig;
this.deviceId = deviceId;
@@ -79,13 +85,13 @@ public class RegisterDeviceController implements FxController {
this.bearerToken = Objects.requireNonNull(bearerToken.get());
this.registerSuccessScene = registerSuccessScene;
this.registerFailedScene = registerFailedScene;
this.deviceAlreadyExistsScene = deviceAlreadyExistsScene;
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
this.httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).executor(executor).build();
}
public void initialize() {
deviceNameField.setText(determineHostname());
deviceNameField.textProperty().addListener(observable -> deviceNameAlreadyExists.set(false));
deviceNameField.disableProperty().bind(workInProgress);
setupCodeField.textProperty().addListener(observable -> invalidSetupCode.set(false));
setupCodeField.disableProperty().bind(workInProgress);
@@ -108,9 +114,8 @@ public class RegisterDeviceController implements FxController {
public void register() {
workInProgress.set(true);
var apiRootUrl = hubConfig.getApiBaseUrl();
var userReq = HttpRequest.newBuilder(apiRootUrl.resolve("users/me")) //
var userReq = HttpRequest.newBuilder(hubConfig.URIs.API."users/me") //
.GET() //
.timeout(REQ_TIMEOUT) //
.header("Authorization", "Bearer " + bearerToken) //
@@ -126,17 +131,19 @@ public class RegisterDeviceController implements FxController {
}
}).thenApply(user -> {
try {
assert user.privateKey != null; // api/vaults/{v}/user-tokens/me would have returned 403, if user wasn't fully set up yet
assert user.privateKey != null && user.publicKey != null; // api/vaults/{v}/user-tokens/me would have returned 403, if user wasn't fully set up yet
var userPublicKey = JWEHelper.decodeECPublicKey(Base64.getDecoder().decode(user.publicKey));
migrateLegacyDevices(userPublicKey); // TODO: remove eventually, when most users have migrated to Hub 1.3.x or newer
var userKey = JWEHelper.decryptUserKey(JWEObject.parse(user.privateKey), setupCodeField.getText());
return JWEHelper.encryptUserKey(userKey, deviceKeyPair.getPublic());
} catch (ParseException e) {
} catch (ParseException | JWEHelper.KeyDecodeFailedException e) {
throw new RuntimeException("Server answered with unparsable user key", e);
}
}).thenCompose(jwe -> {
var now = Instant.now().toString();
var dto = new CreateDeviceDto(deviceId, deviceNameField.getText(), BaseEncoding.base64().encode(deviceKeyPair.getPublic().getEncoded()), "DESKTOP", jwe.serialize(), now);
var json = toJson(dto);
var deviceUri = apiRootUrl.resolve("devices/" + deviceId);
var deviceUri = hubConfig.URIs.API."devices/\{deviceId}";
var putDeviceReq = HttpRequest.newBuilder(deviceUri) //
.PUT(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8)) //
.timeout(REQ_TIMEOUT) //
@@ -146,7 +153,7 @@ public class RegisterDeviceController implements FxController {
return httpClient.sendAsync(putDeviceReq, HttpResponse.BodyHandlers.discarding());
}).whenCompleteAsync((response, throwable) -> {
if (response != null) {
this.handleResponse(response);
this.handleRegisterDeviceResponse(response);
} else {
this.setupFailed(throwable);
}
@@ -154,6 +161,46 @@ public class RegisterDeviceController implements FxController {
}, Platform::runLater);
}
private void migrateLegacyDevices(ECPublicKey userPublicKey) {
try {
// GET legacy access tokens
var getUri = hubConfig.URIs.API."devices/\{deviceId}/legacy-access-tokens";
var getReq = HttpRequest.newBuilder(getUri).GET().timeout(REQ_TIMEOUT).header("Authorization", "Bearer " + bearerToken).build();
var getRes = httpClient.send(getReq, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
if (getRes.statusCode() != 200) {
LOG.debug("GET {} resulted in status code {}. Skipping migration.", getUri, getRes.statusCode());
return;
}
Map<String, String> legacyAccessTokens = JSON.readerForMapOf(String.class).readValue(getRes.body());
if (legacyAccessTokens.isEmpty()) {
return; // no migration required
}
// POST new access tokens
Map<String, String> newAccessTokens = legacyAccessTokens.entrySet().stream().<Map.Entry<String, String>>mapMulti((entry, consumer) -> {
try (var vaultKey = JWEHelper.decryptVaultKey(JWEObject.parse(entry.getValue()), deviceKeyPair.getPrivate())) {
var newAccessToken = JWEHelper.encryptVaultKey(vaultKey, userPublicKey).serialize();
consumer.accept(Map.entry(entry.getKey(), newAccessToken));
} catch (ParseException | JWEHelper.InvalidJweKeyException e) {
LOG.warn("Failed to decrypt legacy access token for vault {}. Skipping migration.", entry.getKey());
}
}).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
var postUri = hubConfig.URIs.API."users/me/access-tokens";
var postBody = JSON.writer().writeValueAsString(newAccessTokens);
var postReq = HttpRequest.newBuilder(postUri).POST(HttpRequest.BodyPublishers.ofString(postBody)).timeout(REQ_TIMEOUT).header("Authorization", "Bearer " + bearerToken).build();
var postRes = httpClient.send(postReq, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
if (postRes.statusCode() != 200) {
throw new IOException(STR."Unexpected response from POST \{postUri}: \{postRes.statusCode()}");
}
} catch (IOException e) {
// log and ignore: this is merely a best-effort attempt of migrating legacy devices. Failure is uncritical as this is merely a convenience feature.
LOG.error("Legacy Device Migration failed.", e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new UncheckedIOException(new InterruptedIOException("Legacy Device Migration interrupted"));
}
}
private UserDto fromJson(String json) {
try {
return JSON.reader().readValue(json, UserDto.class);
@@ -170,12 +217,12 @@ public class RegisterDeviceController implements FxController {
}
}
private void handleResponse(HttpResponse<Void> response) {
private void handleRegisterDeviceResponse(HttpResponse<Void> response) {
if (response.statusCode() == 201) {
LOG.debug("Device registration for hub instance {} successful.", hubConfig.authSuccessUrl);
window.setScene(registerSuccessScene.get());
} else if (response.statusCode() == 409) {
deviceNameAlreadyExists.set(true);
setupFailed(new DeviceAlreadyExistsException());
} else {
setupFailed(new IllegalStateException("Unexpected http status code " + response.statusCode()));
}
@@ -184,10 +231,13 @@ public class RegisterDeviceController implements FxController {
private void setupFailed(Throwable cause) {
switch (cause) {
case CompletionException e when e.getCause() instanceof JWEHelper.InvalidJweKeyException -> invalidSetupCode.set(true);
case DeviceAlreadyExistsException e -> {
LOG.debug("Device already registered in hub instance {} for different user", hubConfig.authSuccessUrl);
window.setScene(deviceAlreadyExistsScene.get());
}
default -> {
LOG.warn("Device setup failed.", cause);
window.setScene(registerFailedScene.get());
result.completeExceptionally(cause);
}
}
}
@@ -202,15 +252,6 @@ public class RegisterDeviceController implements FxController {
}
//--- Getters & Setters
public BooleanProperty deviceNameAlreadyExistsProperty() {
return deviceNameAlreadyExists;
}
public boolean getDeviceNameAlreadyExists() {
return deviceNameAlreadyExists.get();
}
public BooleanProperty invalidSetupCodeProperty() {
return invalidSetupCode;
}

View File

@@ -1,6 +1,5 @@
package org.cryptomator.ui.keyloading.hub;
import com.nimbusds.jose.JWEObject;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.keyloading.KeyLoading;
@@ -22,8 +21,8 @@ public class RegisterFailedController implements FxController {
@FXML
public void close() {
result.cancel(true);
window.close();
}
}

View File

@@ -1,24 +1,43 @@
package org.cryptomator.ui.keyloading.hub;
import dagger.Lazy;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.keyloading.KeyLoading;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import java.util.concurrent.CompletableFuture;
public class RegisterSuccessController implements FxController {
private final Stage window;
private final CompletableFuture<ReceivedKey> result;
private final Lazy<Scene> receiveKeyScene;
private final ReceiveKeyController receiveKeyController;
@Inject
public RegisterSuccessController(@KeyLoading Stage window) {
public RegisterSuccessController(@KeyLoading Stage window, CompletableFuture<ReceivedKey> result, @FxmlScene(FxmlFile.HUB_RECEIVE_KEY) Lazy<Scene> receiveKeyScene, ReceiveKeyController receiveKeyController) {
this.window = window;
this.result = result;
this.receiveKeyScene = receiveKeyScene;
this.receiveKeyController = receiveKeyController;
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
}
@FXML
public void close() {
window.close();
public void complete() {
window.setScene(receiveKeyScene.get());
receiveKeyController.receiveKey();
}
private void windowClosed(WindowEvent windowEvent) {
result.cancel(true);
}
}

View File

@@ -44,6 +44,11 @@ public class VaultDetailLockedController implements FxController {
appWindows.startUnlockWorkflow(vault.get(), mainWindow);
}
@FXML
public void share() {
appWindows.showShareVaultWindow(vault.get());
}
@FXML
public void showVaultOptions() {
vaultOptionsWindow.create(vault.get()).showVaultOptionsWindow(SelectedVaultOptionsTab.ANY);

View File

@@ -2,17 +2,14 @@ package org.cryptomator.ui.preferences;
import dagger.Lazy;
import org.cryptomator.common.ObservableUtil;
import org.cryptomator.common.mount.MountModule;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.integrations.mount.MountCapability;
import org.cryptomator.integrations.mount.MountService;
import org.cryptomator.ui.common.FxController;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanExpression;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceBox;
@@ -21,24 +18,22 @@ import javafx.util.StringConverter;
import java.util.List;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.concurrent.atomic.AtomicReference;
@PreferencesScoped
public class VolumePreferencesController implements FxController {
private static final String DOCS_MOUNTING_URL = "https://docs.cryptomator.org/en/1.7/desktop/volume-type/";
private static final int MIN_PORT = 1024;
private static final int MAX_PORT = 65535;
public static final String DOCS_MOUNTING_URL = "https://docs.cryptomator.org/en/1.7/desktop/volume-type/";
public static final int MIN_PORT = 1024;
public static final int MAX_PORT = 65535;
private final Settings settings;
private final ObservableValue<MountService> selectedMountService;
private final ResourceBundle resourceBundle;
private final BooleanExpression loopbackPortSupported;
private final ObservableValue<Boolean> loopbackPortSupported;
private final ObservableValue<Boolean> mountToDirSupported;
private final ObservableValue<Boolean> mountToDriveLetterSupported;
private final ObservableValue<Boolean> mountFlagsSupported;
private final ObservableValue<Boolean> readonlySupported;
private final ObservableValue<Boolean> fuseRestartRequired;
private final Lazy<Application> application;
private final List<MountService> mountProviders;
public ChoiceBox<MountService> volumeTypeChoiceBox;
@@ -46,7 +41,10 @@ public class VolumePreferencesController implements FxController {
public Button loopbackPortApplyButton;
@Inject
VolumePreferencesController(Settings settings, Lazy<Application> application, List<MountService> mountProviders, @Named("FUPFMS") AtomicReference<MountService> firstUsedProblematicFuseMountService, ResourceBundle resourceBundle) {
VolumePreferencesController(Settings settings, //
Lazy<Application> application, //
List<MountService> mountProviders, //
ResourceBundle resourceBundle) {
this.settings = settings;
this.application = application;
this.mountProviders = mountProviders;
@@ -54,17 +52,11 @@ public class VolumePreferencesController implements FxController {
var fallbackProvider = mountProviders.stream().findFirst().orElse(null);
this.selectedMountService = ObservableUtil.mapWithDefault(settings.mountService, serviceName -> mountProviders.stream().filter(s -> s.getClass().getName().equals(serviceName)).findFirst().orElse(fallbackProvider), fallbackProvider);
this.loopbackPortSupported = BooleanExpression.booleanExpression(selectedMountService.map(s -> s.hasCapability(MountCapability.LOOPBACK_PORT)));
this.loopbackPortSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.LOOPBACK_PORT));
this.mountToDirSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_WITHIN_EXISTING_PARENT) || s.hasCapability(MountCapability.MOUNT_TO_EXISTING_DIR));
this.mountToDriveLetterSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_AS_DRIVE_LETTER));
this.mountFlagsSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_FLAGS));
this.readonlySupported = selectedMountService.map(s -> s.hasCapability(MountCapability.READ_ONLY));
this.fuseRestartRequired = selectedMountService.map(s -> {//
return firstUsedProblematicFuseMountService.get() != null //
&& MountModule.isProblematicFuseService(s) //
&& !firstUsedProblematicFuseMountService.get().equals(s);
});
}
public void initialize() {
@@ -101,12 +93,12 @@ public class VolumePreferencesController implements FxController {
/* Property Getters */
public BooleanExpression loopbackPortSupportedProperty() {
public ObservableValue<Boolean> loopbackPortSupportedProperty() {
return loopbackPortSupported;
}
public boolean isLoopbackPortSupported() {
return loopbackPortSupported.get();
return loopbackPortSupported.getValue();
}
public ObservableValue<Boolean> readonlySupportedProperty() {
@@ -141,14 +133,6 @@ public class VolumePreferencesController implements FxController {
return mountFlagsSupported.getValue();
}
public ObservableValue<Boolean> fuseRestartRequiredProperty() {
return fuseRestartRequired;
}
public boolean getFuseRestartRequired() {
return fuseRestartRequired.getValue();
}
/* Helpers */
private class MountServiceConverter extends StringConverter<MountService> {

View File

@@ -0,0 +1,34 @@
package org.cryptomator.ui.sharevault;
import dagger.BindsInstance;
import dagger.Lazy;
import dagger.Subcomponent;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import javafx.scene.Scene;
import javafx.stage.Stage;
@ShareVaultScoped
@Subcomponent(modules = {ShareVaultModule.class})
public interface ShareVaultComponent {
@ShareVaultWindow
Stage window();
@FxmlScene(FxmlFile.SHARE_VAULT)
Lazy<Scene> scene();
default void showShareVaultWindow(){
Stage stage = window();
stage.setScene(scene().get());
stage.show();
}
@Subcomponent.Factory
interface Factory {
ShareVaultComponent create(@BindsInstance @ShareVaultWindow Vault vault);
}
}

View File

@@ -0,0 +1,76 @@
package org.cryptomator.ui.sharevault;
import dagger.Lazy;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.keyloading.hub.HubKeyLoadingStrategy;
import javax.inject.Inject;
import javafx.application.Application;
import javafx.fxml.FXML;
import javafx.stage.Stage;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
import java.net.URISyntaxException;
@ShareVaultScoped
public class ShareVaultController implements FxController {
private static final String SCHEME_PREFIX = "hub+";
private static final String VISIT_HUB_URL = "https://cryptomator.org/hub/";
private static final String BEST_PRACTICES_URL = "https://docs.cryptomator.org/en/latest/security/best-practices/#sharing-of-vaults";
private final Stage window;
private final Lazy<Application> application;
private final Vault vault;
private final Boolean hubVault;
@Inject
ShareVaultController(@ShareVaultWindow Stage window, //
Lazy<Application> application, //
@ShareVaultWindow Vault vault) {
this.window = window;
this.application = application;
this.vault = vault;
var vaultScheme = vault.getVaultConfigCache().getUnchecked().getKeyId().getScheme();
this.hubVault = (vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTP) || vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTPS));
}
@FXML
public void close() {
window.close();
}
@FXML
public void visitHub() {
application.get().getHostServices().showDocument(VISIT_HUB_URL);
}
@FXML
public void openHub() {
application.get().getHostServices().showDocument(getHubUri(vault).toString());
}
@FXML
public void visitBestPractices() {
application.get().getHostServices().showDocument(BEST_PRACTICES_URL);
}
private static URI getHubUri(Vault vault) {
try {
var keyID = new URI(vault.getVaultConfigCache().get().getKeyId().toString());
assert keyID.getScheme().startsWith(SCHEME_PREFIX);
return new URI(keyID.getScheme().substring(SCHEME_PREFIX.length()) + "://" + keyID.getHost() + "/app/vaults");
} catch (IOException e) {
throw new UncheckedIOException(e);
} catch (URISyntaxException e) {
throw new IllegalStateException("URI constructed from params known to be valid", e);
}
}
public boolean isHubVault() {
return hubVault;
}
}

View File

@@ -0,0 +1,54 @@
package org.cryptomator.ui.sharevault;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
import org.cryptomator.ui.common.DefaultSceneFactory;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxControllerKey;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlLoaderFactory;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.StageFactory;
import javax.inject.Provider;
import javafx.scene.Scene;
import javafx.stage.Modality;
import javafx.stage.Stage;
import java.util.Map;
import java.util.ResourceBundle;
@Module
abstract class ShareVaultModule {
@Provides
@ShareVaultWindow
@ShareVaultScoped
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
}
@Provides
@ShareVaultWindow
@ShareVaultScoped
static Stage provideStage(StageFactory factory, ResourceBundle resourceBundle) {
Stage stage = factory.create();
stage.setResizable(false);
stage.initModality(Modality.APPLICATION_MODAL);
stage.setTitle(resourceBundle.getString("shareVault.title"));
return stage;
}
@Provides
@FxmlScene(FxmlFile.SHARE_VAULT)
@ShareVaultScoped
static Scene provideShareVaultScene(@ShareVaultWindow FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.SHARE_VAULT);
}
@Binds
@IntoMap
@FxControllerKey(ShareVaultController.class)
abstract FxController bindShareVaultController(ShareVaultController controller);
}

View File

@@ -0,0 +1,13 @@
package org.cryptomator.ui.sharevault;
import javax.inject.Scope;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
@interface ShareVaultScoped {
}

View File

@@ -0,0 +1,14 @@
package org.cryptomator.ui.sharevault;
import javax.inject.Qualifier;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Qualifier
@Documented
@Retention(RUNTIME)
@interface ShareVaultWindow {
}

View File

@@ -19,16 +19,8 @@ import java.util.concurrent.Future;
@Subcomponent(modules = {UnlockModule.class})
public interface UnlockComponent {
ExecutorService defaultExecutorService();
UnlockWorkflow unlockWorkflow();
default Future<Boolean> startUnlockWorkflow() {
UnlockWorkflow workflow = unlockWorkflow();
defaultExecutorService().submit(workflow);
return workflow;
}
@Subcomponent.Factory
interface Factory {
UnlockComponent create(@BindsInstance @UnlockWindow Vault vault, @BindsInstance @Named("unlockWindowOwner") @Nullable Stage owner);

View File

@@ -81,6 +81,13 @@ abstract class UnlockModule {
return fxmlLoaders.createScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT);
}
@Provides
@FxmlScene(FxmlFile.UNLOCK_REQUIRES_RESTART)
@UnlockScoped
static Scene provideRestartRequiredScene(@UnlockWindow FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.UNLOCK_REQUIRES_RESTART);
}
// ------------------
@Binds
@@ -93,4 +100,9 @@ abstract class UnlockModule {
@FxControllerKey(UnlockInvalidMountPointController.class)
abstract FxController bindUnlockInvalidMountPointController(UnlockInvalidMountPointController controller);
@Binds
@IntoMap
@FxControllerKey(UnlockRequiresRestartController.class)
abstract FxController bindUnlockRequiresRestartController(UnlockRequiresRestartController controller);
}

View File

@@ -0,0 +1,47 @@
package org.cryptomator.ui.unlock;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.stage.Stage;
import java.util.ResourceBundle;
@UnlockScoped
public class UnlockRequiresRestartController implements FxController {
private final Stage window;
private final ResourceBundle resourceBundle;
private final FxApplicationWindows appWindows;
private final Vault vault;
@Inject
UnlockRequiresRestartController(@UnlockWindow Stage window, //
ResourceBundle resourceBundle, //
FxApplicationWindows appWindows, //
@UnlockWindow Vault vault) {
this.window = window;
this.resourceBundle = resourceBundle;
this.appWindows = appWindows;
this.vault = vault;
}
public void initialize() {
window.setTitle(String.format(resourceBundle.getString("unlock.error.title"), vault.getDisplayName()));
}
@FXML
public void close() {
window.close();
}
@FXML
public void closeAndOpenVaultOptions() {
appWindows.showVaultOptionsWindow(vault, SelectedVaultOptionsTab.MOUNT);
window.close();
}
}

View File

@@ -1,7 +1,7 @@
package org.cryptomator.ui.unlock;
import com.google.common.base.Throwables;
import dagger.Lazy;
import org.cryptomator.common.mount.ConflictingMountServiceException;
import org.cryptomator.common.mount.IllegalMountPointException;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
@@ -29,7 +29,7 @@ import java.io.IOException;
* This class runs the unlock process and controls when to display which UI.
*/
@UnlockScoped
public class UnlockWorkflow extends Task<Boolean> {
public class UnlockWorkflow extends Task<Void> {
private static final Logger LOG = LoggerFactory.getLogger(UnlockWorkflow.class);
@@ -38,42 +38,44 @@ public class UnlockWorkflow extends Task<Boolean> {
private final VaultService vaultService;
private final Lazy<Scene> successScene;
private final Lazy<Scene> invalidMountPointScene;
private final Lazy<Scene> restartRequiredScene;
private final FxApplicationWindows appWindows;
private final KeyLoadingStrategy keyLoadingStrategy;
private final ObjectProperty<IllegalMountPointException> illegalMountPointException;
@Inject
UnlockWorkflow(@UnlockWindow Stage window, @UnlockWindow Vault vault, VaultService vaultService, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy<Scene> invalidMountPointScene, FxApplicationWindows appWindows, @UnlockWindow KeyLoadingStrategy keyLoadingStrategy, @UnlockWindow ObjectProperty<IllegalMountPointException> illegalMountPointException) {
UnlockWorkflow(@UnlockWindow Stage window, //
@UnlockWindow Vault vault, //
VaultService vaultService, //
@FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, //
@FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy<Scene> invalidMountPointScene, //
@FxmlScene(FxmlFile.UNLOCK_REQUIRES_RESTART) Lazy<Scene> restartRequiredScene, //
FxApplicationWindows appWindows, //
@UnlockWindow KeyLoadingStrategy keyLoadingStrategy, //
@UnlockWindow ObjectProperty<IllegalMountPointException> illegalMountPointException) {
this.window = window;
this.vault = vault;
this.vaultService = vaultService;
this.successScene = successScene;
this.invalidMountPointScene = invalidMountPointScene;
this.restartRequiredScene = restartRequiredScene;
this.appWindows = appWindows;
this.keyLoadingStrategy = keyLoadingStrategy;
this.illegalMountPointException = illegalMountPointException;
}
@Override
protected Boolean call() throws InterruptedException, IOException, CryptoException, MountFailedException {
try {
attemptUnlock();
return true;
} catch (UnlockCancelledException e) {
cancel(false); // set Tasks state to cancelled
return false;
}
}
private void attemptUnlock() throws IOException, CryptoException, MountFailedException {
protected Void call() throws InterruptedException, IOException, CryptoException, MountFailedException {
try {
keyLoadingStrategy.use(vault::unlock);
return null;
} catch (UnlockCancelledException e) {
cancel(false); // set Tasks state to cancelled
return null;
} catch (IOException | RuntimeException | MountFailedException e) {
throw e;
} catch (Exception e) {
Throwables.propagateIfPossible(e, IOException.class);
Throwables.propagateIfPossible(e, CryptoException.class);
Throwables.propagateIfPossible(e, IllegalMountPointException.class);
Throwables.propagateIfPossible(e, MountFailedException.class);
throw new IllegalStateException("unexpected exception type", e);
throw new IllegalStateException("Unexpected exception type", e);
}
}
@@ -85,6 +87,13 @@ public class UnlockWorkflow extends Task<Boolean> {
});
}
private void handleConflictingMountServiceException() {
Platform.runLater(() -> {
window.setScene(restartRequiredScene.get());
window.show();
});
}
private void handleGenericError(Throwable e) {
LOG.error("Unlock failed for technical reasons.", e);
appWindows.showErrorWindow(e, window, null);
@@ -113,10 +122,10 @@ public class UnlockWorkflow extends Task<Boolean> {
protected void failed() {
LOG.info("Unlock of '{}' failed.", vault.getDisplayName());
Throwable throwable = super.getException();
if(throwable instanceof IllegalMountPointException impe) {
handleIllegalMountPointError(impe);
} else {
handleGenericError(throwable);
switch (throwable) {
case IllegalMountPointException e -> handleIllegalMountPointError(e);
case ConflictingMountServiceException _ -> handleConflictingMountServiceException();
default -> handleGenericError(throwable);
}
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.LOCKED);
}

View File

@@ -1,18 +1,25 @@
package org.cryptomator.ui.vaultoptions;
import com.google.common.base.Strings;
import org.cryptomator.common.mount.ActualMountService;
import dagger.Lazy;
import org.cryptomator.common.ObservableUtil;
import org.cryptomator.common.mount.Mounter;
import org.cryptomator.common.mount.WindowsDriveLetters;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.integrations.mount.MountCapability;
import org.cryptomator.integrations.mount.MountService;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.cryptomator.ui.preferences.VolumePreferencesController;
import javax.inject.Inject;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.RadioButton;
@@ -26,6 +33,8 @@ import java.io.File;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.Set;
@@ -36,14 +45,21 @@ public class MountOptionsController implements FxController {
private final VaultSettings vaultSettings;
private final WindowsDriveLetters windowsDriveLetters;
private final ResourceBundle resourceBundle;
private final Lazy<Application> application;
private final ObservableValue<String> defaultMountFlags;
private final ObservableValue<Boolean> mountpointDirSupported;
private final ObservableValue<Boolean> mountpointDriveLetterSupported;
private final ObservableValue<Boolean> readOnlySupported;
private final ObservableValue<Boolean> mountFlagsSupported;
private final ObservableValue<Boolean> defaultMountServiceSelected;
private final ObservableValue<String> directoryPath;
private final FxApplicationWindows applicationWindows;
private final List<MountService> mountProviders;
private final ObservableValue<MountService> defaultMountService;
private final ObservableValue<MountService> selectedMountService;
private final ObservableValue<Boolean> selectedMountServiceRequiresRestart;
private final ObservableValue<Boolean> loopbackPortChangeable;
//-- FXML objects --
@@ -56,30 +72,58 @@ public class MountOptionsController implements FxController {
public RadioButton mountPointDirBtn;
public TextField directoryPathField;
public ChoiceBox<Path> driveLetterSelection;
public ChoiceBox<MountService> vaultVolumeTypeChoiceBox;
public TextField vaultLoopbackPortField;
public Button vaultLoopbackPortApplyButton;
@Inject
MountOptionsController(@VaultOptionsWindow Stage window, @VaultOptionsWindow Vault vault, ObservableValue<ActualMountService> mountService, WindowsDriveLetters windowsDriveLetters, ResourceBundle resourceBundle, FxApplicationWindows applicationWindows) {
MountOptionsController(@VaultOptionsWindow Stage window, //
@VaultOptionsWindow Vault vault, //
WindowsDriveLetters windowsDriveLetters, //
ResourceBundle resourceBundle, //
FxApplicationWindows applicationWindows, //
Lazy<Application> application, //
List<MountService> mountProviders, //
Mounter mounter, //
ObservableValue<MountService> defaultMountService) {
this.window = window;
this.vaultSettings = vault.getVaultSettings();
this.windowsDriveLetters = windowsDriveLetters;
this.resourceBundle = resourceBundle;
this.defaultMountFlags = mountService.map(as -> {
if (as.service().hasCapability(MountCapability.MOUNT_FLAGS)) {
return as.service().getDefaultMountFlags();
this.applicationWindows = applicationWindows;
this.directoryPath = vault.getVaultSettings().mountPoint.map(p -> isDriveLetter(p) ? null : p.toString());
this.application = application;
this.mountProviders = mountProviders;
this.defaultMountService = defaultMountService;
this.selectedMountService = Bindings.createObjectBinding(this::reselectMountService, defaultMountService, vaultSettings.mountService);
this.selectedMountServiceRequiresRestart = selectedMountService.map(mounter::isConflictingMountService);
this.defaultMountFlags = selectedMountService.map(s -> {
if (s.hasCapability(MountCapability.MOUNT_FLAGS)) {
return s.getDefaultMountFlags();
} else {
return "";
}
});
this.mountpointDirSupported = mountService.map(as -> as.service().hasCapability(MountCapability.MOUNT_TO_EXISTING_DIR) || as.service().hasCapability(MountCapability.MOUNT_WITHIN_EXISTING_PARENT));
this.mountpointDriveLetterSupported = mountService.map(as -> as.service().hasCapability(MountCapability.MOUNT_AS_DRIVE_LETTER));
this.mountFlagsSupported = mountService.map(as -> as.service().hasCapability(MountCapability.MOUNT_FLAGS));
this.readOnlySupported = mountService.map(as -> as.service().hasCapability(MountCapability.READ_ONLY));
this.directoryPath = vault.getVaultSettings().mountPoint.map(p -> isDriveLetter(p) ? null : p.toString());
this.applicationWindows = applicationWindows;
this.mountFlagsSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_FLAGS));
this.defaultMountServiceSelected = ObservableUtil.mapWithDefault(vaultSettings.mountService, _ -> false, true);
this.readOnlySupported = selectedMountService.map(s -> s.hasCapability(MountCapability.READ_ONLY));
this.mountpointDirSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_TO_EXISTING_DIR) || s.hasCapability(MountCapability.MOUNT_WITHIN_EXISTING_PARENT));
this.mountpointDriveLetterSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_AS_DRIVE_LETTER));
this.loopbackPortChangeable = selectedMountService.map(s -> s.hasCapability(MountCapability.LOOPBACK_PORT) && vaultSettings.mountService.getValue() != null);
}
private MountService reselectMountService() {
var desired = vaultSettings.mountService.getValue();
var defaultMS = defaultMountService.getValue();
return mountProviders.stream().filter(s -> s.getClass().getName().equals(desired)).findFirst().orElse(defaultMS);
}
@FXML
public void initialize() {
defaultMountService.addListener((_, _, _) -> vaultVolumeTypeChoiceBox.setConverter(new MountServiceConverter()));
// readonly:
readOnlyCheckbox.selectedProperty().bindBidirectional(vaultSettings.usesReadOnlyMode);
@@ -106,6 +150,20 @@ public class MountOptionsController implements FxController {
mountPointToggleGroup.selectToggle(mountPointDirBtn);
}
mountPointToggleGroup.selectedToggleProperty().addListener(this::selectedToggleChanged);
vaultVolumeTypeChoiceBox.getItems().add(null);
vaultVolumeTypeChoiceBox.getItems().addAll(mountProviders);
vaultVolumeTypeChoiceBox.setConverter(new MountServiceConverter());
vaultVolumeTypeChoiceBox.getSelectionModel().select(isDefaultMountServiceSelected() ? null : selectedMountService.getValue());
vaultVolumeTypeChoiceBox.valueProperty().addListener((_, _, newProvider) -> {
var toSet = Optional.ofNullable(newProvider).map(nP -> nP.getClass().getName()).orElse(null);
vaultSettings.mountService.set(toSet);
});
vaultLoopbackPortField.setText(String.valueOf(vaultSettings.port.get()));
vaultLoopbackPortApplyButton.visibleProperty().bind(vaultSettings.port.asString().isNotEqualTo(vaultLoopbackPortField.textProperty()));
vaultLoopbackPortApplyButton.disableProperty().bind(Bindings.createBooleanBinding(this::validateLoopbackPort, vaultLoopbackPortField.textProperty()).not());
}
@FXML
@@ -229,6 +287,26 @@ public class MountOptionsController implements FxController {
}
public void openDocs() {
application.get().getHostServices().showDocument(VolumePreferencesController.DOCS_MOUNTING_URL);
}
private boolean validateLoopbackPort() {
try {
int port = Integer.parseInt(vaultLoopbackPortField.getText());
return port == 0 // choose port automatically
|| port >= VolumePreferencesController.MIN_PORT && port <= VolumePreferencesController.MAX_PORT; // port within range
} catch (NumberFormatException e) {
return false;
}
}
public void doChangeLoopbackPort() {
if (validateLoopbackPort()) {
vaultSettings.port.set(Integer.parseInt(vaultLoopbackPortField.getText()));
}
}
//@formatter:off
private static class NoDirSelectedException extends Exception {}
//@formatter:on
@@ -243,6 +321,14 @@ public class MountOptionsController implements FxController {
return mountFlagsSupported.getValue();
}
public ObservableValue<Boolean> defaultMountServiceSelectedProperty() {
return defaultMountServiceSelected;
}
public boolean isDefaultMountServiceSelected() {
return defaultMountServiceSelected.getValue();
}
public ObservableValue<Boolean> mountpointDirSupportedProperty() {
return mountpointDirSupported;
}
@@ -274,4 +360,37 @@ public class MountOptionsController implements FxController {
public String getDirectoryPath() {
return directoryPath.getValue();
}
public ObservableValue<Boolean> selectedMountServiceRequiresRestartProperty() {
return selectedMountServiceRequiresRestart;
}
public boolean getSelectedMountServiceRequiresRestart() {
return selectedMountServiceRequiresRestart.getValue();
}
public ObservableValue<Boolean> loopbackPortChangeableProperty() {
return loopbackPortChangeable;
}
public boolean isLoopbackPortChangeable() {
return loopbackPortChangeable.getValue();
}
private class MountServiceConverter extends StringConverter<MountService> {
@Override
public String toString(MountService provider) {
if (provider == null) {
return String.format(resourceBundle.getString("vaultOptions.mount.volumeType.default"), defaultMountService.getValue().displayName());
} else {
return provider.displayName();
}
}
@Override
public MountService fromString(String string) {
throw new UnsupportedOperationException();
}
}
}

View File

@@ -1,6 +1,7 @@
package org.cryptomator.ui.vaultoptions;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.keyloading.hub.HubKeyLoadingStrategy;
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy;
@@ -48,6 +49,10 @@ public class VaultOptionsController implements FxController {
if(!(vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTP) || vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTPS))){
tabPane.getTabs().remove(hubTab);
}
vault.stateProperty().addListener(observable -> {
tabPane.setDisable(vault.getState().equals(VaultState.Value.UNLOCKED));
});
}
private void selectChosenTab() {

View File

@@ -936,3 +936,16 @@
-fx-padding: 0.5px;
-fx-background-color: CONTROL_BORDER_NORMAL;
}
/*******************************************************************************
* *
* Ad box *
* *
******************************************************************************/
.ad-box {
-fx-padding: 12px;
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
-fx-background-insets: 0, 1px;
-fx-background-radius: 4px;
}

View File

@@ -935,3 +935,16 @@
-fx-padding: 0.5px;
-fx-background-color: CONTROL_BORDER_NORMAL;
}
/*******************************************************************************
* *
* Ad box *
* *
******************************************************************************/
.ad-box {
-fx-padding: 12px;
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
-fx-background-insets: 0, 1px;
-fx-background-radius: 4px;
}

View File

@@ -41,7 +41,7 @@
<Insets bottom="6" top="6"/>
</padding>
</Label>
<Label text="%hub.register.description" wrapText="true"/>
<Label text="%hub.register.legacy.description" wrapText="true"/>
<HBox spacing="6" alignment="CENTER_LEFT">
<padding>
<Insets top="12"/>
@@ -50,7 +50,7 @@
<TextField fx:id="deviceNameField" HBox.hgrow="ALWAYS"/>
</HBox>
<HBox alignment="TOP_RIGHT">
<Label text="%hub.register.occupiedMsg" textAlignment="RIGHT" alignment="CENTER_RIGHT" visible="${controller.deviceNameAlreadyExists}" graphicTextGap="6">
<Label text="%hub.register.legacy.occupiedMsg" textAlignment="RIGHT" alignment="CENTER_RIGHT" visible="${controller.deviceNameAlreadyExists}" graphicTextGap="6">
<padding>
<Insets top="6"/>
</padding>

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.Group?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?>
<HBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.keyloading.hub.LegacyRegisterSuccessController"
minWidth="400"
maxWidth="400"
minHeight="145"
spacing="12"
alignment="TOP_LEFT">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<Group>
<StackPane>
<padding>
<Insets topRightBottomLeft="6"/>
</padding>
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="CHECK" glyphSize="24"/>
</StackPane>
</Group>
<VBox HBox.hgrow="ALWAYS">
<Label styleClass="label-large" text="%hub.registerSuccess.message" wrapText="true" textAlignment="LEFT">
<padding>
<Insets bottom="6" top="6"/>
</padding>
</Label>
<Label text="%hub.registerSuccess.legacy.description" wrapText="true"/>
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
<ButtonBar buttonMinWidth="120" buttonOrder="+C">
<buttons>
<Button text="%generic.button.close" ButtonBar.buttonData="CANCEL_CLOSE" defaultButton="true" onAction="#close"/>
</buttons>
</ButtonBar>
</VBox>
</children>
</HBox>

View File

@@ -57,15 +57,6 @@
<TextField fx:id="deviceNameField" HBox.hgrow="ALWAYS"/>
</HBox>
<HBox alignment="TOP_RIGHT">
<Label text="%hub.register.occupiedMsg" textAlignment="RIGHT" alignment="CENTER_RIGHT" visible="${controller.deviceNameAlreadyExists}" managed="${controller.deviceNameAlreadyExists}" graphicTextGap="6">
<padding>
<Insets top="6"/>
</padding>
<graphic>
<FontAwesome5IconView glyph="TIMES" styleClass="glyph-icon-red"/>
</graphic>
</Label>
<Label text="%hub.register.invalidAccountKeyLabel" textAlignment="RIGHT" alignment="CENTER_RIGHT" visible="${controller.invalidSetupCode}" managed="${controller.invalidSetupCode}" graphicTextGap="6">
<padding>
<Insets top="6"/>

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.Group?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?>
<HBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.keyloading.hub.RegisterFailedController"
minWidth="400"
maxWidth="400"
minHeight="145"
spacing="12"
alignment="TOP_LEFT">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<Group>
<StackPane>
<padding>
<Insets topRightBottomLeft="6"/>
</padding>
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="EXCLAMATION" glyphSize="24"/>
</StackPane>
</Group>
<VBox HBox.hgrow="ALWAYS">
<Label styleClass="label-large" text="%hub.registerFailed.message" wrapText="true" textAlignment="LEFT">
<padding>
<Insets bottom="6" top="6"/>
</padding>
</Label>
<Label text="%hub.registerFailed.description.deviceAlreadyExists" wrapText="true"/>
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
<ButtonBar buttonMinWidth="120" buttonOrder="+C">
<buttons>
<Button text="%generic.button.close" ButtonBar.buttonData="CANCEL_CLOSE" defaultButton="true" onAction="#close"/>
</buttons>
</ButtonBar>
</VBox>
</children>
</HBox>

View File

@@ -38,7 +38,7 @@
<Insets bottom="6" top="6"/>
</padding>
</Label>
<Label text="%hub.registerFailed.description" wrapText="true"/>
<Label text="%hub.registerFailed.description.generic" wrapText="true"/>
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
<ButtonBar buttonMinWidth="120" buttonOrder="+C">

View File

@@ -41,9 +41,9 @@
<Label text="%hub.registerSuccess.description" wrapText="true"/>
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
<ButtonBar buttonMinWidth="120" buttonOrder="+C">
<ButtonBar buttonMinWidth="120" buttonOrder="+X">
<buttons>
<Button text="%generic.button.close" ButtonBar.buttonData="CANCEL_CLOSE" defaultButton="true" onAction="#close"/>
<Button text="%hub.registerSuccess.unlockBtn" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#complete"/>
<!-- TODO: add request access button -->
</buttons>
</ButtonBar>

View File

@@ -22,7 +22,7 @@
</ImageView>
<VBox spacing="3" HBox.hgrow="ALWAYS" alignment="CENTER_LEFT">
<FormattedLabel styleClass="label-extra-large" format="Cryptomator %s" arg1="${controller.fullApplicationVersion}"/>
<Label text="© 2016 2023 Skymatic GmbH"/>
<Label text="© 2016 2024 Skymatic GmbH"/>
</VBox>
</HBox>

View File

@@ -32,8 +32,6 @@
</Hyperlink>
</HBox>
<Label styleClass="label-red" text="%preferences.volume.fuseRestartRequired" visible="${controller.fuseRestartRequired}" managed="${controller.fuseRestartRequired}"/>
<HBox spacing="12" alignment="CENTER_LEFT" visible="${controller.loopbackPortSupported}" managed="${controller.loopbackPortSupported}">
<Label text="%preferences.volume.tcp.port"/>
<NumericTextField fx:id="loopbackPortField"/>

View File

@@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.shape.Circle?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.control.Hyperlink?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.Group?>
<?import javafx.scene.text.Text?>
<?import javafx.scene.text.TextFlow?>
<HBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.sharevault.ShareVaultController"
prefWidth="540"
spacing="12">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<Group>
<StackPane>
<padding>
<Insets topRightBottomLeft="6"/>
</padding>
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="INFO" glyphSize="24"/>
</StackPane>
</Group>
<VBox>
<VBox HBox.hgrow="ALWAYS" visible="${controller.hubVault}" managed="${controller.hubVault}">
<Label text="%shareVault.hub.message" styleClass="label-large" wrapText="true">
<padding>
<Insets bottom="6" top="6"/>
</padding>
</Label>
<Label text="%shareVault.hub.description" wrapText="true"/>
<VBox>
<padding>
<Insets left="6"/>
</padding>
<Label text="%shareVault.hub.instruction.1" wrapText="true"/>
<Label text="%shareVault.hub.instruction.2" wrapText="true"/>
</VBox>
</VBox>
<VBox HBox.hgrow="ALWAYS" visible="${!controller.hubVault}" managed="${!controller.hubVault}">
<Label text="%shareVault.message" styleClass="label-large" wrapText="true">
<padding>
<Insets bottom="6" top="6"/>
</padding>
</Label>
<Label text="%shareVault.description" wrapText="true"/>
<VBox>
<padding>
<Insets left="6"/>
</padding>
<Label text="%shareVault.instruction.1" wrapText="true"/>
<Label text="%shareVault.instruction.2" wrapText="true"/>
</VBox>
<Region minHeight="6"/>
<TextFlow styleClass="text-flow">
<Text text="%shareVault.remarkBestPractices"/>
<Text text=" "/>
<Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#visitBestPractices">
<graphic>
<FontAwesome5IconView glyph="QUESTION_CIRCLE" styleClass="glyph-icon-muted"/>
</graphic>
<tooltip>
<Tooltip text="%shareVault.docsTooltip" showDelay="100ms"/>
</tooltip>
</Hyperlink>
</TextFlow>
<Region minHeight="12"/>
<HBox alignment="CENTER_LEFT" spacing="6" styleClass="ad-box">
<VBox spacing="6" alignment="CENTER_LEFT">
<ImageView HBox.hgrow="ALWAYS" fitWidth="180" preserveRatio="true" cache="true">
<Image url="@../img/hub_logo.png"/>
</ImageView>
<Label text="%shareVault.hubAd.description" style="-fx-font-weight: bold;" wrapText="true"/>
<VBox spacing="6" alignment="CENTER_LEFT">
<padding>
<Insets left="6"/>
</padding>
<Label text="%shareVault.hubAd.keyManagement" wrapText="true"/>
<Label text="%shareVault.hubAd.authentication" wrapText="true"/>
<Label text="%shareVault.hubAd.encryption" wrapText="true"/>
</VBox>
</VBox>
<Region HBox.hgrow="ALWAYS"/>
<ImageView HBox.hgrow="ALWAYS" fitWidth="180" preserveRatio="true" cache="true">
<Image url="@../img/group-magic.png"/>
</ImageView>
</HBox>
</VBox>
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
<ButtonBar buttonMinWidth="120" buttonOrder="+CX">
<buttons>
<Button text="%generic.button.close" ButtonBar.buttonData="CANCEL_CLOSE" onAction="#close"/>
<Button text="%shareVault.hub.openHub" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#openHub" visible="${controller.hubVault}" managed="${controller.hubVault}"/>
<Button text="%shareVault.visitHub" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#visitHub" visible="${!controller.hubVault}" managed="${!controller.hubVault}"/>
</buttons>
</ButtonBar>
</VBox>
</HBox>

View File

@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.Group?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?>
<HBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.unlock.UnlockRequiresRestartController"
minWidth="400"
maxWidth="400"
minHeight="145"
spacing="12"
alignment="TOP_LEFT">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<Group>
<StackPane>
<padding>
<Insets topRightBottomLeft="6"/>
</padding>
<Circle styleClass="glyph-icon-red" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="TIMES" glyphSize="24"/>
</StackPane>
</Group>
<VBox HBox.hgrow="ALWAYS">
<Label styleClass="label-large" text="%unlock.error.restartRequired.message" wrapText="true" textAlignment="LEFT">
<padding>
<Insets bottom="6" top="6"/>
</padding>
</Label>
<Label text="%unlock.error.restartRequired.description" wrapText="true" textAlignment="LEFT"/>
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
<ButtonBar buttonMinWidth="120" buttonOrder="+CI">
<buttons>
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
<Button text="%main.vaultlist.contextMenu.vaultoptions" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#closeAndOpenVaultOptions">
<tooltip>
<Tooltip text="%main.vaultlist.contextMenu.vaultoptions"/>
</tooltip>
</Button>
</buttons>
</ButtonBar>
</VBox>
</children>
</HBox>

View File

@@ -24,14 +24,17 @@
<FontAwesome5IconView glyph="KEY" glyphSize="15"/>
</graphic>
</Button>
<Hyperlink text="%main.vaultDetail.passwordSavedInKeychain" visible="${controller.passwordSaved}" onAction="#showKeyVaultOptions">
<Hyperlink text="%main.vaultDetail.passwordSavedInKeychain" visible="${controller.passwordSaved}" managed="${controller.passwordSaved}" onAction="#showKeyVaultOptions">
<graphic>
<FontAwesome5IconView glyph="LOCK"/>
</graphic>
</Hyperlink>
<Button text="%main.vaultDetail.share" minWidth="120" onAction="#share">
<graphic>
<FontAwesome5IconView glyph="SHARE" glyphSize="15"/>
</graphic>
</Button>
<Region VBox.vgrow="ALWAYS"/>
<HBox alignment="BOTTOM_RIGHT">
<Button text="%main.vaultDetail.optionsBtn" minWidth="120" onAction="#showVaultOptions">
<graphic>

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import org.cryptomator.ui.controls.NumericTextField?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
@@ -10,9 +11,9 @@
<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.ToggleGroup?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.TextFlow?>
<VBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.vaultoptions.MountOptionsController"
@@ -24,11 +25,35 @@
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<TextFlow>
<Label text="%vaultOptions.mount.info"/>
<Label text=" "/>
<Hyperlink styleClass="hyperlink-underline" text="%vaultOptions.mount.linkToPreferences" onAction="#openVolumePreferences" wrapText="true"/>
</TextFlow>
<HBox spacing="12" alignment="CENTER_LEFT">
<Label text="%vaultOptions.mount.volume.type"/>
<ChoiceBox fx:id="vaultVolumeTypeChoiceBox"/>
<Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#openVolumePreferences" visible="${controller.defaultMountServiceSelected}" managed="${controller.defaultMountServiceSelected}">
<graphic>
<FontAwesome5IconView glyph="COGS" styleClass="glyph-icon-muted"/>
</graphic>
<tooltip>
<Tooltip text="%vaultOptions.mount.info" showDelay="100ms"/>
</tooltip>
</Hyperlink>
<Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#openDocs" visible="${!controller.defaultMountServiceSelected}" managed="${!controller.defaultMountServiceSelected}">
<graphic>
<FontAwesome5IconView glyph="QUESTION_CIRCLE" styleClass="glyph-icon-muted"/>
</graphic>
<tooltip>
<Tooltip text="%preferences.volume.docsTooltip" showDelay="100ms"/>
</tooltip>
</Hyperlink>
</HBox>
<Label styleClass="label-red" text="%vaultOptions.mount.volumeType.restartRequired" visible="${controller.selectedMountServiceRequiresRestart}" managed="${controller.selectedMountServiceRequiresRestart}"/>
<HBox spacing="12" alignment="CENTER_LEFT" visible="${controller.loopbackPortChangeable}" managed="${controller.loopbackPortChangeable}">
<Label text="%vaultOptions.mount.volume.tcp.port"/>
<NumericTextField fx:id="vaultLoopbackPortField"/>
<Button text="%generic.button.apply" fx:id="vaultLoopbackPortApplyButton" onAction="#doChangeLoopbackPort"/>
</HBox>
<CheckBox fx:id="readOnlyCheckbox" text="%vaultOptions.mount.readonly" visible="${controller.readOnlySupported}" managed="${controller.readOnlySupported}"/>
<VBox visible="${controller.mountFlagsSupported}" managed="${controller.mountFlagsSupported}">

View File

@@ -48,6 +48,7 @@ addvaultwizard.new.nameInstruction=Choose a name for the vault
addvaultwizard.new.namePrompt=Vault Name
### Location
addvaultwizard.new.locationInstruction=Where should Cryptomator store the encrypted files of your vault?
addvaultwizard.new.locationLoading=Checking local filesystem for default cloud storage directories…
addvaultwizard.new.locationLabel=Storage location
addvaultwizard.new.locationPrompt=
addvaultwizard.new.directoryPickerLabel=Custom location
@@ -142,6 +143,9 @@ unlock.error.customPath.description.hideawayNotDir=The temporary, hidden file "%
unlock.error.customPath.description.couldNotBeCleaned=Your vault could not be mounted to the path "%s". Please try again or choose a different path.
unlock.error.customPath.description.notEmptyDir=The custom mount path "%s" is not an empty folder. Please choose an empty folder and try again.
unlock.error.customPath.description.generic=You have selected a custom mount path for this vault, but using it failed with the message: %2$s
unlock.error.restartRequired.message=Unable to unlock vault
unlock.error.restartRequired.description=Change the volume type in vault options or restart Cryptomator.
unlock.error.title=Unlock "%s" failed
## Hub
hub.noKeychain.message=Unable to access device key
hub.noKeychain.description=In order to unlock Hub vaults, a device key is required, which is secured using a keychain. To proceed, enable “%s” and select a keychain in the preferences.
@@ -155,17 +159,22 @@ hub.receive.message=Processing response…
hub.receive.description=Cryptomator is receiving and processing the response from Hub. Please wait.
### Register Device
hub.register.message=New Device
hub.register.description=This is the first Hub access from this device. Please authorize it using your Account Key.
hub.register.description=This is the first Hub access from this device. Please register it using your Account Key.
hub.register.nameLabel=Device Name
hub.register.invalidAccountKeyLabel=Invalid Account Key
hub.register.occupiedMsg=Name already in use
hub.register.registerBtn=Confirm
hub.register.registerBtn=Register
### Register Device Legacy
hub.register.legacy.occupiedMsg=Name already in use
hub.register.legacy.description=This is the first Hub access from this device. Please register it.
### Registration Success
hub.registerSuccess.message=Device named
hub.registerSuccess.description=To access the vault, your device needs to be authorized by the vault owner.
hub.registerSuccess.message=Device registered
hub.registerSuccess.description=Your device is successfully registered. You can now continue to unlock the vault.
hub.registerSuccess.unlockBtn=Unlock
hub.registerSuccess.legacy.description=To access the vault, your device needs to be additionally authorized by the vault owner.
### Registration Failed
hub.registerFailed.message=Device naming failed
hub.registerFailed.description=An error was thrown in the naming process. For more details, look into the application log.
hub.registerFailed.message=Device registration failed
hub.registerFailed.description.generic=An error was thrown in the registration process. For more details, look into the application log.
hub.registerFailed.description.deviceAlreadyExists=This device is already registered for a different user. Try to change the user account or use a different device.
### Unauthorized
hub.unauthorized.message=Access denied
hub.unauthorized.description=Your device has not yet been authorized to access this vault. Ask the vault owner to authorize it.
@@ -295,11 +304,11 @@ preferences.interface.showMinimizeButton=Show minimize button
preferences.interface.showTrayIcon=Show tray icon (requires restart)
## Volume
preferences.volume=Virtual Drive
preferences.volume.type=Volume Type
preferences.volume.type=Default Volume Type
preferences.volume.type.automatic=Automatic
preferences.volume.docsTooltip=Open the documentation to learn more about the different volume types.
preferences.volume.fuseRestartRequired=To apply the changes, Cryptomator needs to be restarted.
preferences.volume.tcp.port=TCP Port
preferences.volume.tcp.port=Default TCP Port
preferences.volume.supportedFeatures=The chosen volume type supports the following features:
preferences.volume.feature.mountAuto=Automatic mount point selection
preferences.volume.feature.mountToDir=Custom directory as mount point
@@ -384,6 +393,7 @@ main.vaultDetail.unlockBtn=Unlock…
main.vaultDetail.unlockNowBtn=Unlock Now
main.vaultDetail.optionsBtn=Vault Options
main.vaultDetail.passwordSavedInKeychain=Password saved
main.vaultDetail.share=Share…
### Unlocked
main.vaultDetail.unlockedStatus=UNLOCKED
main.vaultDetail.accessLocation=Your vault's contents are accessible here:
@@ -438,8 +448,7 @@ vaultOptions.general.startHealthCheckBtn=Start Health Check
## Mount
vaultOptions.mount=Mounting
vaultOptions.mount.info=Options depend on the selected volume type.
vaultOptions.mount.linkToPreferences=Open virtual drive preferences
vaultOptions.mount.info=Open virtual drive preferences to change default settings.
vaultOptions.mount.readonly=Read-only
vaultOptions.mount.customMountFlags=Custom mount flags
vaultOptions.mount.winDriveLetterOccupied=occupied
@@ -449,6 +458,10 @@ vaultOptions.mount.mountPoint.driveLetter=Use assigned drive letter
vaultOptions.mount.mountPoint.custom=Use chosen directory
vaultOptions.mount.mountPoint.directoryPickerButton=Choose…
vaultOptions.mount.mountPoint.directoryPickerTitle=Pick a directory
vaultOptions.mount.volumeType.default=Default (%s)
vaultOptions.mount.volumeType.restartRequired=To use this volume type, Cryptomator needs to be restarted.
vaultOptions.mount.volume.tcp.port=TCP Port
vaultOptions.mount.volume.type=Volume Type
## Master Key
vaultOptions.masterkey=Password
vaultOptions.masterkey.changePasswordBtn=Change Password
@@ -518,4 +531,24 @@ updateReminder.message=Check for Updates?
updateReminder.description=Stay updated with new features, bug fixes, and security improvements. We recommend to automatically check for updates.
updateReminder.notNow=Not Now
updateReminder.yesOnce=Yes, Once
updateReminder.yesAutomatically=Yes, Automatically
updateReminder.yesAutomatically=Yes, Automatically
# Share Vault
shareVault.title=Share Vault
shareVault.message=Would you like to share your vault with others?
shareVault.description=Always be careful when sharing your vault with other people. In short, follow these steps:
shareVault.instruction.1=1. Share access of the encrypted vault folder via cloud storage.
shareVault.instruction.2=2. Share the vault password in a secure way.
shareVault.remarkBestPractices=For more information, check out the best practices suggestions in our docs.
shareVault.docsTooltip=Open the documentation to learn more about sharing of vaults.
shareVault.hubAd.description=The secure way to work in teams
shareVault.hubAd.keyManagement=• Zero-knowledge key management
shareVault.hubAd.authentication=• Strong authentication
shareVault.hubAd.encryption=• End-to-end encryption
shareVault.visitHub=Visit Cryptomator Hub
shareVault.hub.message=How to share a Hub vault
shareVault.hub.description=In order to share the vault content with another team member, you have to perform two steps:
shareVault.hub.instruction.1=1. Share access of the encrypted vault folder via cloud storage.
shareVault.hub.instruction.2=2. Grant access to team member in Cryptomator Hub.
shareVault.hub.openHub=Open Cryptomator Hub

View File

@@ -287,11 +287,9 @@ preferences.interface.showMinimizeButton=إظهار زر التصغير
preferences.interface.showTrayIcon=إظهار أيقونة اللوحة (يتطلب إعادة تشغيل)
## Volume
preferences.volume=القرص الإفتراضي
preferences.volume.type=‮نوع وحدة التخزين
preferences.volume.type.automatic=تلقائي
preferences.volume.docsTooltip=افتح الوثائق لمعرفة المزيد عن مختلف أنواع وحدة التخزين.
preferences.volume.fuseRestartRequired=لتطبيق التغييرات، يحتاج Cryptomator إلى إعادة التشغيل.
preferences.volume.tcp.port=منفذ TCP
preferences.volume.supportedFeatures=يدعم نوع وحدة تخزين المختار الميزات التالية:
## Updates
preferences.updates=تحديثات

View File

@@ -0,0 +1,514 @@
# Locale Specific CSS files such as CJK, RTL,...
# Generics
## Button
generic.button.apply=Ҡуллан
generic.button.back=Артҡа
generic.button.cancel=Кире ал
generic.button.change=Үҙгәрт
generic.button.choose=Һайла…
generic.button.close=Яп
generic.button.copy=Күсермәһен ал
generic.button.copied=Күсермә алынды!
generic.button.done=Тамам
generic.button.next=Киләһе
generic.button.print=Баҫтыр
# Error
error.message=Хата килеп сыҡты
error.description=Cryptomator өсөн көтөлмәгән хәл. Был хатаның булған төҙәтеү варианттарын ҡарай алаһығыҙ. Әлегә тиклем белдерелмәгән булһа, был хаҡта хәбәр итә алаһығыҙ.
error.hyperlink.lookup=Был хатаны тикшерегеҙ
error.hyperlink.report=Был хатаны белдерегеҙ
error.technicalDetails=Тулыраҡ мәғлүмәт:
error.existingSolutionDescription=Cryptomator бының булырын көтмәгән. Әммә беҙ был хатаны хәл итер юл таптыҡ. Түбәндәге һылтанманы ҡарап сығығыҙ.
error.hyperlink.solution=Хәл итеү варианттарын ҡарарға
error.lookupPermissionMessage=Cryptomator был проблеманы онлайн хәл итә ала. Был ваҡытта IP-адресығыҙҙан беҙҙең проблемалар базаһына һоратыу ебәреләсәк.
error.dismiss=Кире ҡаҡ
error.lookUpSolution=Хәл итеү варианттарын ҡара
# Defaults
defaults.vault.vaultName=Һаҡлағыс
# Tray Menu
traymenu.showMainWindow=Күрһәт
traymenu.showPreferencesWindow=Көйләүҙәр
traymenu.lockAllVaults=Барыһын да биклә
traymenu.quitApplication=Сыҡ
traymenu.vault.unlock=Биген ас
traymenu.vault.lock=Биклә
traymenu.vault.reveal=Күрһәт
# Add Vault Wizard
addvaultwizard.title=Һаҡлағыс өҫтәү
## New
addvaultwizard.new.title=Яңы һаҡлағыс өҫтәү
### Name
addvaultwizard.new.nameInstruction=Һаҡлағыс өсөн исем һайлағыҙ
addvaultwizard.new.namePrompt=Һаҡлағыс исеме
### Location
addvaultwizard.new.locationInstruction=Cryptomator һаҡлағысығыҙҙың шифрланған файлдарын ҡайҙа һаҡларға тейеш?
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.locationIsOk=Һаҡлағыс өсөн яраҡлы урын
addvaultwizard.new.invalidName=Яраҡһыҙ һаҡлағыс исеме
addvaultwizard.new.validName=Яраҡлы һаҡлағыс исеме
addvaultwizard.new.validCharacters.message=Һаҡлағыс исемендә түбәндәге билдәләр була ала:
addvaultwizard.new.validCharacters.chars=Хәрефтәр (мәҫ. a, ж or 수)
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.tooltip=Тулыраҡ мәғлүмәт алыр өсөн документтарҙы ҡарағыҙ.
addvaultwizard.new.expertSettings.shorteningThreshold.title=Шифрланған файл исемдәренең максимум оҙонлоғо
addvaultwizard.new.expertSettings.shorteningThreshold.valid=Яраҡлы
### Password
addvaultwizard.new.createVaultBtn=Һаҡлағыс яһа
addvaultwizard.new.generateRecoveryKeyChoice=Серһүҙһеҙ мәғлүмәттәрегеҙгә инеп булмаясаҡ. Серһүҙ юғалтҡан осраҡ өсөн тергеҙеү асҡысы кәрәкме?
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.6=Әгәр файлдарҙы шифрларға һәм һаҡлағыстың эстәлеген ҡарарға теләһәгеҙ, түбәндәгеләрҙе эшләгеҙ:
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.accessLocation.1=🔐️ ШИФРЛАНҒАН ТОМ 🔐️
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.chooseBtn=Һайла…
addvaultwizard.existing.filePickerTitle=Һаҡлағыс файлын һайлау
addvaultwizard.existing.filePickerMimeDesc=Cryptomator Һаҡлағысы
## Success
addvaultwizard.success.nextStepsInstructions=\ "%s" һаҡлағысы өҫтәлде.\nЭстәлеккә инеү йәки өҫтәү өсөн был һаҡлағысты асыр кәрәк. Уны аҙаҡтан да асырға була.
addvaultwizard.success.unlockNow=Хәҙер бикте ас
# Remove Vault
removeVault.title="%s" һаҡлағысын алып ташла
removeVault.message=Һаҡлағысты алып ташларғамы?
removeVault.description=Cryptomator был һаҡлағысты ғына онотасаҡ. Һеҙ уны яңынан өҫтәй алаһығыҙ. Ҡаты дисктан шифрланған файлдар юйылмаясаҡ.
removeVault.confirmBtn=Һаҡлағысты алып ташла
# Change Password
changepassword.title=Серһүҙҙе үҙгәртеү
changepassword.enterOldPassword="%s" өсөн ағымдағы серһүҙҙе яҙығыҙ
changepassword.finalConfirmation=Серһүҙемде онотһам, мәғлүмәттәремә инә алмаясағымды аңлайым
# Forget Password
forgetPassword.title=Серһүҙҙе онот
forgetPassword.message=Һаҡланған серһүҙҙе оноторғамы?
forgetPassword.description=Был һаҡлағыстың һеҙҙең система асҡыс сылбырында һаҡланған серһүҙен юясаҡ.
forgetPassword.confirmBtn=Серһүҙҙе онот
# Unlock
unlock.title="%s" биген асыу
unlock.passwordPrompt="%s" серһүҙен яҙығыҙ:
unlock.savePassword=Серһүҙҙе һаҡла
unlock.unlockBtn=Биген ас
## Select
unlock.chooseMasterkey.message=Masterkey файлы табылманы
unlock.chooseMasterkey.description=Cryptomator "%s" һаҡлағысының masterkey файлын таба алманы. Асҡыс файлын ҡулдан һайлағыҙ.
unlock.chooseMasterkey.filePickerTitle=Masterkey файлын һайлағыҙ
unlock.chooseMasterkey.filePickerMimeDesc=Cryptomator Masterkey
## Success
unlock.success.message=Бикте сисеү уңышлы тамамланды
unlock.success.description="%s" һаҡлағысының эстәлеге хәҙер уның тоташтырыу нөктәһе аша асыҡ.
unlock.success.rememberChoice=Һайлауымды иҫтә тот, ҡабат һорама
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.fuseRestartRequired.message=Һаҡлағыс биген сисеп булманы
unlock.error.fuseRestartRequired.description=Һаҡлағыс параметрҙарында күләм төрөн үҙгәртеү йәки Cryptomator-ҙы яңынан башлатыу.
unlock.error.title="%s" биге асылманы
## Hub
hub.noKeychain.message=Йыһаз асҡысына инеү рөхсәте юҡ
hub.noKeychain.description=Хаб һаҡлағыстарын асыу өсөн йыһаз асҡысы кәрәк, ул иһә асҡыс сылбырында һаҡлана. Артабан “%s” мөмкинлеге бирегеҙ һәм көйләүҙәрҙә асҡыс сылбырын һайлағыҙ.
hub.noKeychain.openBtn=Көйләүҙәрҙе ас
### Waiting
hub.auth.message=Аутентиклау көтөлә…
hub.auth.description=Автоматик рәүештә инеү битенә йүнәлтелергә тейешһегеҙ.
hub.auth.loginLink=Йүнәлтелмәнегеҙме? Асыу өсөн ошонда баҫығыҙ.
### Receive Key
hub.receive.message=Яуапты эшкәртеү…
hub.receive.description=Cryptomator хабтан килгән яуапты ҡабул итә һәм эшкәртә. Зинһар, көтөгөҙ.
### Register Device
hub.register.message=Яңы йыһаз
hub.register.description=Был йыһаздан хабҡа тәү тапҡыр инеү. Зинһар, иҫәп яҙмаһы асҡысы менән рөхсәт бирегеҙ.
hub.register.nameLabel=Йыһаз исеме
hub.register.invalidAccountKeyLabel=Хаталы иҫәп яҙмаһы асҡысы
hub.register.occupiedMsg=Исем ҡулланыла инде
hub.register.registerBtn=Раҫла
### Registration Success
hub.registerSuccess.message=Йыһаз исемләнде
hub.registerSuccess.description=Һаҡлағысҡа инеү өсөн һаҡлағыс хужаһы йыһазығыҙға инеү рөхсәте бирергә тейеш.
### Registration Failed
hub.registerFailed.message=Йыһазға исем биреп булманы
hub.registerFailed.description=Исемләү процесында хата килеп сыҡты. Тулыраҡ мәғлүмәт өсөн ҡушымта журналын ҡарағыҙ.
### Unauthorized
hub.unauthorized.message=Инеү кире ҡағылды
hub.unauthorized.description=Һеҙҙең йыһаз әлегә был һаҡлағысҡа инеү хоҡуғына эйә түгел. Һаҡлағыс хужаһынан рөхсәт һорағыҙ.
### Requires Account Initialization
hub.requireAccountInit.message=Эш-хәрәкәт кәрәкле
hub.requireAccountInit.description.0=Дауам итер өсөн, кәрәкле аҙымдар тамамланырға тейеш урын:
hub.requireAccountInit.description.1=Хаб ҡулланыусы профиле
hub.requireAccountInit.description.2=.
### License Exceeded
hub.invalidLicense.message=Хаб рөхсәтнамәһе яраҡһыҙ
hub.invalidLicense.description=Һеҙҙең Cryptomator хабығыҙ ғәмәлдән тыш рөхсәтнамәгә эйә. Рөхсәтнамәне яңыртыу йәки оҙайтыу өсөн Хаб администраторына хәбәр итегеҙ.
# Lock
## Force
lock.forced.message=Бикләп булманы
lock.forced.description=Көтөп торған ғәмәлдәр йәки асыҡ файлдар "%s" бикләүҙе тотҡарланы. Был һаҡлағысты көсләп бикләп була, ләкин инеү-сығыуҙы өҙөү һаҡланмаған мәғлүмәттәрҙе юғалтыуға килтереүе ихтимал.
lock.forced.retryBtn=Ҡабатла
lock.forced.forceBtn=Көсләп биклә
## Failure
lock.fail.message=Һаҡлағысты бикләп булманы
lock.fail.description="%s" һаҡлағысын бикләп булмай. Һаҡланмаған эштең һаҡланыуын һәм мөһим уҡыу/яҙыу ғәмәлдәренең тамамланыуын тикшерегеҙ. һаҡлағысты ябыр өсөн Cryptomator процесын туҡтатығыҙ.
# Migration
migration.title=Һаҡлағысты яңыртыу
## Start
migration.start.header=Һаҡлағысты яңыртыу
migration.start.text="%s" һаҡлағысын Cryptomator-ҙың яңы версияһында асыу өсөн һаҡлағысты яңы форматҡа күсерергә кәрәк. Быны эшләр алдынан түбәндәгеләрҙе белер кәрәк:
migration.start.remarkUndone=Был яңыртыуҙы кире алып булмай.
migration.start.remarkVersions=Cryptomator-ҙың иҫке версиялары яңыртылған һаҡлағысты аса алмай.
migration.start.remarkCanRun=Һаҡлағысты ҡулланған һәр йыһаздың Cryptomator-ҙың был версияһы менән эшләй аласағын тикшереү кәрәк.
migration.start.remarkSynced=Яңыртыуҙан алда һаҡлағыстың был һәм башҡа йыһыздарҙа тулыһынса синхронлаштырылыуына тикшерер кәрәк.
migration.start.confirm=Юғарыла килтерелгән мәғлүмәтте уҡыным һәм аңланым
## Run
migration.run.enterPassword="%s" серһүҙен яҙығыҙ
migration.run.startMigrationBtn=Һаҡлағысты күсер
migration.run.progressHint=Был бер ни тиклем ваҡыт ала…
## Success
migration.success.nextStepsInstructions="%s" уңышлы күсерелде.\nХәҙер һаҡлағысығыҙҙы аса алаһығыҙ.
migration.success.unlockNow=Хәҙер бикте ас
## Missing file system capabilities
migration.error.missingFileSystemCapabilities.title=Терәкләнмәгән файл системаһы
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=Һаҡлағысты элекке версия менән асырға мөмкин. Ҡулдан күсереү буйынса инструкциялар өсөн, сайтҡа инегеҙ:
# Health Check
## Start
health.title="%s" хәлен тикшереү
health.intro.header=Хәлде тикшереү
health.intro.text=Хәлде тикшереү — көмбәҙҙең эске төҙөлөшөндәге проблемаларҙы асыҡлау һәм хәл итеү өсөн тикшереүҙәр тупланмаһы. Зинһар, иҫтә тотоғоҙ:
health.intro.remarkSync=Бөтә йыһаздарҙың тулыһынса синхронлаштырылғанын ҡарағыҙ, был күпселек проблемаларҙы хәл итә.
health.intro.remarkFix=Бөтә проблемаларҙы ла хәл итеү мөмкин түгел.
health.intro.remarkBackup=Мәғлүмәттәр боҙолған осраҡта һаҡлыҡ файлдары ғына ярҙам итә ала.
health.intro.affirmation=Юғарыла килтерелгән мәғлүмәтте уҡыным һәм аңланым
## Start Failure
health.fail.header=Һаҡлағыс конфигурацияһын тейәүҙәге хата
health.fail.ioError=Конфигурация файлын асыу һәм уҡыу ваҡытында хата килеп сыҡты.
health.fail.parseError=Һаҡлағыс конфигурацияһын тағатҡанда хата барлыҡҡа килде.
health.fail.moreInfo=Тулыраҡ мәғлүмәт
## Check Selection
health.checkList.description=Һул яҡтағы исемлектән һайлағыҙ йәки аҫтағы төймәләрҙе ҡулланығыҙ.
health.checkList.selectAllButton=Бөтәһен дә һайла
health.checkList.deselectAllButton=Береһен дә һайлама
health.check.runBatchBtn=Һайланған тикшереүҙәрҙе башлат
## Detail view
health.check.detail.noSelectedCheck=Һөҙөмтәләр өсөн һул яҡтағы исемлектә тамамланған хәл тикшереүен һайлағыҙ.
health.check.detail.checkScheduled=Тикшереү планлаштырылған.
health.check.detail.checkRunning=Әлеге ваҡытта тикшереү бара…
health.check.detail.checkSkipped=Тикшереү үткәреү өсөн һайланмаған.
health.check.detail.checkFinished=Тикшереү уңышлы тамамланды.
health.check.detail.checkFinishedAndFound=Тикшереү тамамланды. Һөҙөмтәләрҙе ҡарап сығығыҙ.
health.check.detail.checkFailed=Тикшереү хата арҡаһында тамамланманы.
health.check.detail.checkCancelled=Тикшереү өҙөлдө.
health.check.detail.listFilters.label=Фильтр
health.check.detail.fixAllSpecificBtn=Бөтә төрҙәрҙе төҙәтеү
health.check.exportBtn=Һөҙөмтәләрҙе экспортла
## Result view
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.fixStateFilter.fixing=Төҙәтеү…
health.result.fixStateFilter.fixed=Төҙәтелде
health.result.fixStateFilter.fixFailed=Төҙәтеп булманы
## Fix Application
health.fix.fixBtn=Төҙәт
health.fix.successTip=Төҙәтелде
health.fix.failTip=Төҙәтеп булманы, тулыраҡ мәғлүмәт өсөн журналды ҡарағыҙ
# Preferences
preferences.title=Көйләүҙәр
## General
preferences.general=Дөйөм
preferences.general.startHidden=Cryptomator башланған саҡта тәҙрәне йәшерергә
preferences.general.autoCloseVaults=Ҡушымтанан сыҡҡан ваҡытта һаҡлағыстарҙы автоматик рәүештә бикләргә
preferences.general.debugLogging=Төҙөкләндереү журналын асырға
preferences.general.debugDirectory=Журнал файлдарын күрһәт
preferences.general.autoStart=Система стартында Cryptomator-ҙы эшләтеп ебәрергә
preferences.general.keychainBackend=Серһүҙ һаҡлау өсөн
## Interface
preferences.interface=Интерфейс
preferences.interface.theme=Күренеш
preferences.interface.theme.automatic=Автоматик
preferences.interface.theme.dark=Ҡараңғы
preferences.interface.theme.light=Яҡты
preferences.interface.unlockThemes=Ҡараңғы режимды ас
preferences.interface.language=Тел (яңынан башлатыу кәрәк)
preferences.interface.language.auto=Ғәҙәттәге система көйләүҙәре
preferences.interface.interfaceOrientation=Интерфейс йүнәлеше
preferences.interface.interfaceOrientation.ltr=Һулдан уңға
preferences.interface.interfaceOrientation.rtl=Уңдан һулға
preferences.interface.showMinimizeButton=Бәләкәйләтеү төймәһен күрһәтергә
preferences.interface.showTrayIcon=Панелдә билдәне күрһәтергә (яңынан башлатыу кәрәк)
## Volume
preferences.volume=Виртуаль диск
preferences.volume.type=Ғәҙәттәге том төрө
preferences.volume.type.automatic=Автоматик
preferences.volume.docsTooltip=Төрлө том төрҙәре тураһында күберәк белер өсөн, документтарҙы ҡарағыҙ.
preferences.volume.fuseRestartRequired=Үҙгәрештәрҙе ҡулланыу өсөн Cryptomator-ҙы яңынан асыу кәрәк.
preferences.volume.tcp.port=Ғәҙәттәге TCP порты
preferences.volume.supportedFeatures=Һайланған том төрө түбәндәге үҙенсәлектәрҙе терәкләй:
preferences.volume.feature.mountAuto=Тоташтырыу нөктәһен автоматик рәүештә һайлау
preferences.volume.feature.mountToDir=Тоташтырыу нөктәһе өсөн махсус каталог һайлау
preferences.volume.feature.mountToDriveLetter=Тоташтырыу нөктәһе ите диск хәрефен ҡулланыу
preferences.volume.feature.mountFlags=Махсус тоташтырыу варианттары
preferences.volume.feature.readOnly=Уҡыу ғына режимында тоташтырыу
## Updates
preferences.updates=Яңыртыуҙар
preferences.updates.currentVersion=Ағымдағы версия: %s
preferences.updates.autoUpdateCheck=Яңыртыуҙарҙы автоматик рәүештә тикшереү
preferences.updates.checkNowBtn=Хәҙер тикшер
preferences.updates.updateAvailable=%s версияһына тиклем яңыртыу бар.
## Contribution
preferences.contribute=Ярҙам
preferences.contribute.registeredFor=%s өсөн теркәлгән ярҙамсы сертификаты
preferences.contribute.noCertificate=Cryptomator-ҡа ярҙам итегеҙ һәм ярҙамсы сертификаты алығыҙ. Был рөхсәтнамә асҡысы кеүек, әммә ирекле программа ҡулланған бик шәп кешеләр өсөн. ;-)
preferences.contribute.getCertificate=Юҡ мы әллә? Уны нисек алырға кәрәклеген ҡарағыҙ.
preferences.contribute.promptText=Ярҙамсы сертификатын бында йәбештерегеҙ
#<-- Add entries for donations and code/translation/documentation contribution -->
## About
preferences.about=Тураһында
# Vault Statistics
stats.title=%s статистикаһы
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
## Write
stats.write.throughput.idle=Яҙыу: буш
stats.write.throughput.kibs=Яҙыу: %.2f КиБ/с
stats.write.throughput.mibs=Яҙыу: %.2f МиБ/с
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
stats.access.total=Барлыҡ инеү: %d
# Main Window
main.closeBtn.tooltip=Яп
main.minimizeBtn.tooltip=Бәләкәйләт
main.preferencesBtn.tooltip=Көйләүҙәр
main.debugModeEnabled.tooltip=Төҙөкләндереү режимы асыҡ
main.supporterCertificateMissing.tooltip=Зинһар, иғәнә биреү тураһында уйлағыҙ
## Vault List
main.vaultlist.emptyList.onboardingInstruction=Һаҡлағыс өҫтәү өсөн бында баҫығыҙ
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.addVaultBtn=Өҫтәү
main.vaultlist.addVaultBtn.menuItemNew=Яңы Һаҡлағыс...
main.vaultlist.addVaultBtn.menuItemExisting=Булған һаҡлағыс...
## Vault Detail
### Welcome
main.vaultDetail.welcomeOnboarding=Файлдарығыҙҙы һаҡлау өсөн Cryptomator-ҙы һайлағанығыҙ өсөн рәхмәт. Әгәр һеҙгә ярҙам кәрәк булһа, башлау буйынса белешмәләребеҙҙе ҡарағыҙ:
### Locked
main.vaultDetail.lockedStatus=БИКЛЕ
main.vaultDetail.unlockBtn=Биген ас…
main.vaultDetail.unlockNowBtn=Хәҙер бикте ас
main.vaultDetail.optionsBtn=Һаҡлағыс варианттары
main.vaultDetail.passwordSavedInKeychain=Серһүҙ һаҡланды
### Unlocked
main.vaultDetail.unlockedStatus=АСЫҠ
main.vaultDetail.accessLocation=Һаҡлағыс эстәлеген бында ҡарарға була:
main.vaultDetail.revealBtn=Дискты күрһәт
main.vaultDetail.copyUri=URI күсермәһен ал
main.vaultDetail.lockBtn=Биклә
main.vaultDetail.bytesPerSecondRead=Уҡыу:
main.vaultDetail.bytesPerSecondWritten=Яҙыу:
main.vaultDetail.throughput.idle=буш
main.vaultDetail.throughput.kbps=%.1f КиБ/с
main.vaultDetail.throughput.mbps=%.1f МиБ/с
main.vaultDetail.stats=Һаҡлағыс статистикаһы
main.vaultDetail.locateEncryptedFileBtn=Шифрланған файлды тап
main.vaultDetail.locateEncryptedFileBtn.tooltip=Шифрланған аналогын табыр өсөн һаҡлағыстан файл һайлағыҙ
main.vaultDetail.encryptedPathsCopied=Юлдарҙын Clipboard-ҡа күсермәһе алынды!
main.vaultDetail.filePickerTitle=Һаҡлағыс эсендә файл һайлау
### Missing
main.vaultDetail.missing.info=Cryptomator был юлдан һаҡлағыс таба алманы.
main.vaultDetail.missing.recheck=Яңынан тикшер
main.vaultDetail.missing.remove=Һаҡлағыс исемлегенән алып ташла…
main.vaultDetail.missing.changeLocation=Һаҡлағыс урынын үҙгәрт…
### Needs Migration
main.vaultDetail.migrateButton=Һаҡлағысты яңыртыу
main.vaultDetail.migratePrompt=Һаҡлағысҡа инер алдынан уны яңы форматҡа тиклем яңыртыу кәрәк
### Error
main.vaultDetail.error.info=Һаҡлағысты дисктан тейәүҙә хата килеп сыҡты.
main.vaultDetail.error.reload=Яңынан тейә
main.vaultDetail.error.windowTitle=Һаҡлағыс тейәүҙә хата
# Wrong File Alert
wrongFileAlert.title=Файлдарҙы нисек шифрларға
wrongFileAlert.message=Был файлдарҙы шифрларға тырыштығыҙмы?
wrongFileAlert.description=Бының өсөн Cryptomator һеҙҙең система файл менеджерында том яһай.
wrongFileAlert.instruction.0=Файлдарҙы шифрлау өсөн ошо аҙымдарҙы үтәгеҙ:
wrongFileAlert.instruction.1=1. Һаҡлағысығыҙҙы асығыҙ.
wrongFileAlert.instruction.2=2. Файл менеджерығыҙҙа томды асыу өсөн «Күрһәт» төймәһенә баҫығыҙ.
wrongFileAlert.instruction.3=3. Файлдарығыҙҙы ошо томға өҫтәгеҙ.
wrongFileAlert.link=Өҫтәмә ярҙам өсөн, ҡарағыҙ:
# Vault Options
## General
vaultOptions.general=Дөйөм
vaultOptions.general.vaultName=Һаҡлағыс исеме
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=Хәл тикшереүен башлатырға
## Mount
vaultOptions.mount=Тоташтырыу
vaultOptions.mount.info=Ғәҙәттәге көйләүҙәрҙе үҙгәртеү өсөн виртуаль диск көйләүҙәрен асығыҙ.
vaultOptions.mount.readonly=Уҡыу ғына
vaultOptions.mount.customMountFlags=Махсус тоташтырыу флагтары
vaultOptions.mount.winDriveLetterOccupied=биләнгән
vaultOptions.mount.mountPoint=Тоташтырыу нөктәһе
vaultOptions.mount.mountPoint.auto=Автоматик рәүештә уңай урын һайларға
vaultOptions.mount.mountPoint.driveLetter=Тәғәйенләнгән диск хәрефен ҡулланырға
vaultOptions.mount.mountPoint.custom=Һайланған каталог ҡулланырға
vaultOptions.mount.mountPoint.directoryPickerButton=Һайла…
vaultOptions.mount.mountPoint.directoryPickerTitle=Каталог һайлау
vaultOptions.mount.volumeType.default=Ғәҙәттәгеләр (%s)
vaultOptions.mount.volumeType.fuseRestartRequired=Был том төрөн ҡулланыу өсөн Cryptomator-ҙы яңынан асыу кәрәк.
vaultOptions.mount.volume.tcp.port=TCP порты
vaultOptions.mount.volume.type=Том төрө
## Master Key
vaultOptions.masterkey=Серһүҙ
vaultOptions.masterkey.changePasswordBtn=Серһүҙҙе үҙгәртеү
vaultOptions.masterkey.forgetSavedPasswordBtn=Һаҡланған серһүҙҙе онот
vaultOptions.masterkey.recoveryKeyExplanation=Серһүҙ юғалғанда һаҡлағысҡа инеү мөмкинлеген ҡайтарыу асҡысы менән генә тергеҙергә мөмкин.
vaultOptions.masterkey.showRecoveryKeyBtn=Ҡайтарыу асҡысын күрһәт
vaultOptions.masterkey.recoverPasswordBtn=Серһүҙҙе яңырт
## Hub
vaultOptions.hub=Ҡайтарыу
vaultOptions.hub.convertInfo=Ашығыс осраҡта, был хаб һаҡлағысын серһүҙ нигеҙле һаҡлағысҡа әйләндереү өсөн тергеҙеү асҡысын ҡуллана алаһығыҙ.
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 • Ҡағыҙҙа баҫтырығыҙ
## Reset Password
### Enter Recovery Key
recoveryKey.recover.title=Серһүҙҙе яңырт
recoveryKey.recover.prompt="%s" өсөн ҡайтарыу асҡысын яҙығыҙ:
recoveryKey.recover.correctKey=Был ҡайтарыу асҡысы дөрөҫ
recoveryKey.recover.wrongKey=Был ҡайтарыу асҡысы башҡа һаҡлағысҡа ҡарай
recoveryKey.recover.invalidKey=Был ҡайтарыу асҡысы дөрөҫ түгел
recoveryKey.printout.heading=Cryptomator ҡайтарыу асҡысы\n"%s"\n
### Reset Password
recoveryKey.recover.resetBtn=Яңынан башла
### Recovery Key Password Reset Success
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=Хәҙер һеҙ һайланған серһүҙ менән, хабҡа инмәйенсә, һаҡлағысты аса алаһығыҙ.
# New Password
newPassword.promptText=Яңы серһүҙ яҙығыҙ
newPassword.reenterPassword=Яңы серһүҙҙе раҫлағыҙ
newPassword.passwordsMatch=Серһүҙҙәр тап килә!
# Quit
quit.title=Ҡушымтанан сығыу
quit.description=Сығырға теләүегеҙҙе раҫлағыҙ. Cryptomator мәғлүмәтте юғалтыуҙан һаҡлар өсөн бөтә һаҡлағыстарҙы бикләп ҡуя.
quit.lockAndQuitBtn=Биклә һәм сыҡ
# Forced Quit
quit.forced.description=Көтөп торған ғәмәлдәр йәки асыҡ файлдар һаҡлағыстарҙы бикләүҙе тотҡарланы. Был һаҡлағыстарҙы көсләп бикләп була, ләкин инеү-сығыуҙы өҙөү һаҡланмаған мәғлүмәттәрҙе юғалтыуға килтереүе ихтимал.
# Update Reminder
updateReminder.description=Яңы үҙенсәлектәр, хаталарҙы төҙәтеү, хәүефһеҙлекте яҡшыртыу менән яңыртылып торорға. Яңыртыуҙарҙы автоматик рәүештә тикшереүҙе кәңәш итәбеҙ.
updateReminder.notNow=Хәҙер түгел
updateReminder.yesOnce=Эйе, бер тапҡыр
updateReminder.yesAutomatically=Эйе, автоматик рәүештә

View File

@@ -284,11 +284,9 @@ preferences.interface.showMinimizeButton=Паказаць кнопку згор
preferences.interface.showTrayIcon=Паказваць іконку на інфармацыйнай панэлі (спатрэбіцца перазапуск)
## Volume
preferences.volume=Віртуальны дыск
preferences.volume.type=Тып тому
preferences.volume.type.automatic=Аўтаматычна
preferences.volume.docsTooltip=Адчыні дакумэнтацыю, каб даведацца больш пра розныя тыпы тому.
preferences.volume.fuseRestartRequired=Каб дастасаваць зьмены, Cryptomator патрэбна перазапусьціць.
preferences.volume.tcp.port=Порт TCP
preferences.volume.supportedFeatures=Абраны тып тому падтрымлівае наступныя функцыі:
preferences.volume.feature.mountAuto=Аўтаматычны выбар пункту мантажавання
preferences.volume.feature.mountToDir=Карыстальніцкая тэчка як пункт мантажавання
@@ -427,8 +425,6 @@ vaultOptions.general.startHealthCheckBtn=Пачаць тэст на цэласн
## Mount
vaultOptions.mount=Мантажаванне
vaultOptions.mount.info=Опцыі залежаць ад абранага тыпу тому.
vaultOptions.mount.linkToPreferences=Адчыніць налады віртуальнага дыску
vaultOptions.mount.readonly=Толькі для чытання
vaultOptions.mount.customMountFlags=Карыстальніцкія опцыі мантажавання
vaultOptions.mount.winDriveLetterOccupied=занята

View File

@@ -141,6 +141,9 @@ unlock.error.customPath.description.hideawayNotDir=Временният, скр
unlock.error.customPath.description.couldNotBeCleaned=Хранилището не може да бъде монтирано на „%s“. Опитайте отново или изберете друг път.
unlock.error.customPath.description.notEmptyDir=Потребителският път на монтиране „%s“ не е празна папка. Изберете празна папка и опитайте отново.
unlock.error.customPath.description.generic=Избрали сте потребителски път за монтиране на това хранилище, но при използването му възникна следната грешка: %2$s
unlock.error.fuseRestartRequired.message=Хранилището не може да бъде отключено
unlock.error.fuseRestartRequired.description=Променете вида на тома в настройките на хранилището или рестартирайте Криптоматор.
unlock.error.title=Неуспешно отключване на „%s“
## Hub
hub.noKeychain.message=Няма достъп до ключа на устройството
hub.noKeychain.description=За да отключите хранилищата в Hub е необходим ключ за устройството, който се защитава с помощта на ключодържател. За да продължите, разрешете „%s“ и изберете ключодържателя в настройките.
@@ -169,8 +172,8 @@ hub.registerFailed.description=В процеса на именуване е до
hub.unauthorized.message=Отказан достъп
hub.unauthorized.description=Устройството не е упълномощено за достъп до това хранилище. Поискайте достъп от собственика.
### Requires Account Initialization
hub.requireAccountInit.message=Необходимо действие
hub.requireAccountInit.description.0=За да продължите завършене необходимите стъпки в
hub.requireAccountInit.message=Необходимо е действие
hub.requireAccountInit.description.0=За да продължите завършете необходимите стъпки в
hub.requireAccountInit.description.1=профила в Hub
hub.requireAccountInit.description.2=.
### License Exceeded
@@ -294,11 +297,11 @@ preferences.interface.showMinimizeButton=Бутон за скриване
preferences.interface.showTrayIcon=Икона в областта за известия (изисква рестарт)
## Volume
preferences.volume=Виртуален диск
preferences.volume.type=Вид на тома
preferences.volume.type=Подразбиран вид на тома
preferences.volume.type.automatic=Автоматично
preferences.volume.docsTooltip=Вижте документацията относно видовете томове.
preferences.volume.fuseRestartRequired=За да бъдат приложени промените, Криптоматор трябва да бъде рестартиран.
preferences.volume.tcp.port=Порт на TCP
preferences.volume.tcp.port=Подразбиран порт на TCP
preferences.volume.supportedFeatures=Избрания вид на тома има следните възможности:
preferences.volume.feature.mountAuto=Автоматичен избор на точка за монтиране
preferences.volume.feature.mountToDir=Папка по избор като точка за монтиране
@@ -437,8 +440,7 @@ vaultOptions.general.startHealthCheckBtn=Проверка на състояни
## Mount
vaultOptions.mount=Монтиране
vaultOptions.mount.info=Възможностите зависят от вида на избрания том.
vaultOptions.mount.linkToPreferences=Настройки на виртуалния диск
vaultOptions.mount.info=Отворете настройките на виртуалния диск, за да промените стандартните настройки.
vaultOptions.mount.readonly=Само за четене
vaultOptions.mount.customMountFlags=Флагове на монтиране
vaultOptions.mount.winDriveLetterOccupied=използвана
@@ -448,6 +450,10 @@ vaultOptions.mount.mountPoint.driveLetter=Използване на опреде
vaultOptions.mount.mountPoint.custom=Използване на избрана папка
vaultOptions.mount.mountPoint.directoryPickerButton=Избиране…
vaultOptions.mount.mountPoint.directoryPickerTitle=Избиране на папка
vaultOptions.mount.volumeType.default=По подразбиране (%s)
vaultOptions.mount.volumeType.fuseRestartRequired=За да използвате този вид томове, трябва да рестартирате Криптоматор.
vaultOptions.mount.volume.tcp.port=Порт на TCP
vaultOptions.mount.volume.type=Вид на тома
## Master Key
vaultOptions.masterkey=Парола
vaultOptions.masterkey.changePasswordBtn=Промяна на парола

View File

@@ -288,11 +288,9 @@ preferences.interface.showMinimizeButton=Mostra el botó 'minimitzar'
preferences.interface.showTrayIcon=Mostra la icona en la barra (cal reiniciar)
## Volume
preferences.volume=Unitat virtual
preferences.volume.type=Tipus de volum
preferences.volume.type.automatic=Automàtic
preferences.volume.docsTooltip=Obre la documentació per aprendre més sobre els diferents tipus de volums.
preferences.volume.fuseRestartRequired=Per aplicar els canvis Cryptomator necessita reiniciar-se.
preferences.volume.tcp.port=Port TCP
preferences.volume.supportedFeatures=El tipus de volum escollit suporta les següents característiques:
preferences.volume.feature.mountAuto=Selecció automàtica del punt de muntatge
preferences.volume.feature.mountToDir=Directori personalitzat com a punt de muntatge
@@ -431,8 +429,6 @@ vaultOptions.general.startHealthCheckBtn=Inicia la comprovació
## Mount
vaultOptions.mount=Muntatge
vaultOptions.mount.info=Les opcions depenen del tipus de volum escollit.
vaultOptions.mount.linkToPreferences=Obre les preferències del disc virtual
vaultOptions.mount.readonly=Només lectura
vaultOptions.mount.customMountFlags=Senyaladors de muntatge personalitzats
vaultOptions.mount.winDriveLetterOccupied=ocupat

View File

@@ -269,7 +269,6 @@ preferences.interface.showTrayIcon=Zobrazit ikonu v liště (vyžaduje restart)
## Volume
preferences.volume=Virtuální jednotky
preferences.volume.type.automatic=Automatické
preferences.volume.tcp.port=TCP port
preferences.volume.feature.mountFlags=Vlastní možnosti připojení disku
preferences.volume.feature.readOnly=Připojení disku pouze pro čtení
## Updates

View File

@@ -288,11 +288,9 @@ preferences.interface.showMinimizeButton=Vis knap til minimering
preferences.interface.showTrayIcon=Vis ikon i system-bakken (kræver genstart)
## Volume
preferences.volume=Virtuelt drev
preferences.volume.type=Drev Type
preferences.volume.type.automatic=Automatisk
preferences.volume.docsTooltip=Åbn dokumentationen for at lære mere om de forskellige typer drev.
preferences.volume.fuseRestartRequired=For at anvende ændringerne skal Cryptomator genstartes.
preferences.volume.tcp.port=TCP port
preferences.volume.supportedFeatures=Den valgte type drev understøtter følgende funktioner:
preferences.volume.feature.mountAuto=Automatisk valg af monteringspunkt
preferences.volume.feature.mountToDir=Brugerdefineret mappe som monteringspunkt
@@ -431,8 +429,6 @@ vaultOptions.general.startHealthCheckBtn=Start sunhedstjek
## Mount
vaultOptions.mount=Montering
vaultOptions.mount.info=Valgmulighederne afhænger af den valgte type drev.
vaultOptions.mount.linkToPreferences=Åbn virtuelle drev præferencer
vaultOptions.mount.readonly=Skrivebeskyttet
vaultOptions.mount.customMountFlags=Brugerdefinerede monterings-flag
vaultOptions.mount.winDriveLetterOccupied=optaget

View File

@@ -141,6 +141,9 @@ unlock.error.customPath.description.hideawayNotDir=Die temporäre, versteckte Da
unlock.error.customPath.description.couldNotBeCleaned=Dein Tresor konnte nicht in den Pfad „%s“ eingehängt werden. Bitte versuche es erneut oder wähle einen anderen Pfad aus.
unlock.error.customPath.description.notEmptyDir=Der benutzerdefinierte Einhängepunkt „%s“ ist kein leerer Ordner. Bitte wähle einen leeren Ordner und versuche es erneut.
unlock.error.customPath.description.generic=Du hast für diesen Tresor einen benutzerdefinierten Einhängepunkt ausgewählt, aber dessen Verwendung ist mit folgender Meldung fehlgeschlagen: %2$s
unlock.error.fuseRestartRequired.message=Tresor konnte nicht entsperrt werden
unlock.error.fuseRestartRequired.description=Ändere den Laufwerkstyp in den Tresoroptionen oder starte Cryptomator neu.
unlock.error.title=„%s“ konnte nicht entsperrt werden
## Hub
hub.noKeychain.message=Zugriff auf Geräteschlüssel nicht möglich
hub.noKeychain.description=Zum Entsperren von Hub-Tresoren wird ein Geräteschlüssel benötigt, der in einem Schlüsselbund gesichert ist. Um fortzufahren, aktiviere „%s“ und wähle in den Einstellungen einen Schlüsselbund.
@@ -294,11 +297,11 @@ preferences.interface.showMinimizeButton=Schaltfläche zum Minimieren anzeigen
preferences.interface.showTrayIcon=Symbol im Infobereich anzeigen (Neustart erforderlich)
## Volume
preferences.volume=Virtuelles Laufwerk
preferences.volume.type=Laufwerkstyp
preferences.volume.type=Standard-Laufwerkstyp
preferences.volume.type.automatic=Automatisch
preferences.volume.docsTooltip=Öffne die Dokumentation, um mehr über die verschiedenen Laufwerkstypen zu erfahren.
preferences.volume.fuseRestartRequired=Um die Änderungen anzuwenden, muss Cryptomator neu gestartet werden.
preferences.volume.tcp.port=TCP-Port
preferences.volume.tcp.port=Standard-TCP-Port
preferences.volume.supportedFeatures=Der gewählte Laufwerkstyp unterstützt folgende Funktionen:
preferences.volume.feature.mountAuto=Automatische Auswahl des Einhängepunkts
preferences.volume.feature.mountToDir=Benutzerdefiniertes Verzeichnis als Einhängepunkt
@@ -437,8 +440,7 @@ vaultOptions.general.startHealthCheckBtn=Integritätsprüfung starten
## Mount
vaultOptions.mount=Laufwerk
vaultOptions.mount.info=Die Optionen hängen vom gewählten Laufwerkstyp ab.
vaultOptions.mount.linkToPreferences=Einstellungen für virtuelles Laufwerk öffnen
vaultOptions.mount.info=Öffne die Einstellungen des virtuellen Laufwerks, um die Standardeinstellungen zu ändern.
vaultOptions.mount.readonly=Schreibgeschützt
vaultOptions.mount.customMountFlags=Benutzerdefinierte Einhänge-Optionen
vaultOptions.mount.winDriveLetterOccupied=belegt
@@ -448,6 +450,10 @@ vaultOptions.mount.mountPoint.driveLetter=Laufwerksbuchstaben zuweisen
vaultOptions.mount.mountPoint.custom=Gewähltes Verzeichnis verwenden
vaultOptions.mount.mountPoint.directoryPickerButton=Durchsuchen 
vaultOptions.mount.mountPoint.directoryPickerTitle=Wähle ein Verzeichnis
vaultOptions.mount.volumeType.default=Standard (%s)
vaultOptions.mount.volumeType.fuseRestartRequired=Um diesen Laufwerkstyp verwenden zu können, muss Cryptomator neu gestartet werden.
vaultOptions.mount.volume.tcp.port=TCP-Port
vaultOptions.mount.volume.type=Laufwerkstyp
## Master Key
vaultOptions.masterkey=Passwort
vaultOptions.masterkey.changePasswordBtn=Passwort ändern

View File

@@ -141,6 +141,9 @@ unlock.error.customPath.description.hideawayNotDir=Το προσωρινό, κρ
unlock.error.customPath.description.couldNotBeCleaned=Η κρύπτη σας δεν μπορεί να τοποθετηθεί στη διαδρομή "%s". Παρακαλώ δοκιμάστε ξανά ή επιλέξτε διαφορετική διαδρομή.
unlock.error.customPath.description.notEmptyDir=Η προσαρμοσμένη διαδρομή προσάρτησης "%s" δεν είναι ένας άδειος φάκελος. Παρακαλώ επιλέξτε έναν άδειο φάκελο και προσπαθήστε ξανά.
unlock.error.customPath.description.generic=Έχετε επιλέξει μια προσαρμοσμένη διαδρομή προσάρτησης για αυτή την κρύπτη, αλλά η χρήση της απέτυχε με το μήνυμα: %2$s
unlock.error.fuseRestartRequired.message=Αδυναμία ξεκλειδώματος κρύπτης
unlock.error.fuseRestartRequired.description=Αλλάξτε τον τύπο τόμου στις επιλογές κρύπτης ή επανεκκινήστε το Cryptomator.
unlock.error.title=Ξεκλείδωμα "%s" απέτυχε
## Hub
hub.noKeychain.message=Δεν είναι δυνατή η πρόσβαση στο κλειδί της συσκευής
hub.noKeychain.description=Για να ξεκλειδώσετε τις κρύπτες Hub, απαιτείται ένα κλειδί συσκευής, το οποίο ασφαλίζεται με χρήση μπρελόκ. Για να συνεχίσετε, ενεργοποιήστε το "%s" και επιλέξτε ένα keychain στις προτιμήσεις.
@@ -294,11 +297,11 @@ preferences.interface.showMinimizeButton=Εμφάνιση κουμπιού ελ
preferences.interface.showTrayIcon=Εμφάνιση εικονιδίου tray (απαιτεί επανεκκίνηση)
## Volume
preferences.volume=Εικονικός δίσκος
preferences.volume.type=Τύπος Τόμου
preferences.volume.type=Προεπιλεγμένος Τύπος Τόμου
preferences.volume.type.automatic=Αυτόματα
preferences.volume.docsTooltip=Ανοίξτε τις οδηγίες για να μάθετε περισσότερα σχετικά με τους διαφορετικούς τύπους τόμων.
preferences.volume.fuseRestartRequired=Για να εφαρμοστούν οι αλλαγές, πρέπει να γίνει επανεκκίνηση του Cryptomator.
preferences.volume.tcp.port=Θύρα TCP
preferences.volume.tcp.port=Προεπιλεγμένη Θύρα TCP
preferences.volume.supportedFeatures=Ο επιλεγμένος τύπος τόμου υποστηρίζει τις ακόλουθες δυνατότητες:
preferences.volume.feature.mountAuto=Αυτόματη επιλογή σημείου προσάρτησης
preferences.volume.feature.mountToDir=Προσαρμοσμένος κατάλογος ως σημείο προσάρτησης
@@ -437,8 +440,7 @@ vaultOptions.general.startHealthCheckBtn=Έναρξη ελέγχου Υγεία
## Mount
vaultOptions.mount=Προσάρτηση
vaultOptions.mount.info=Οι επιλογές εξαρτώνται από τον επιλεγμένο τύπο τόμου.
vaultOptions.mount.linkToPreferences=Άνοιγμα προτιμήσεων εικονικής μονάδας δίσκου
vaultOptions.mount.info=Ανοίξτε τις προτιμήσεις εικονικής μονάδας δίσκου για να αλλάξετε τις προεπιλεγμένες ρυθμίσεις.
vaultOptions.mount.readonly=Μόνο για ανάγνωση
vaultOptions.mount.customMountFlags=Προσαρμοσμένες ετικέτες προσάρτησης
vaultOptions.mount.winDriveLetterOccupied=κατειλημμένο
@@ -448,6 +450,10 @@ vaultOptions.mount.mountPoint.driveLetter=Χρήση επιλεγμένου γρ
vaultOptions.mount.mountPoint.custom=Χρήση επιλεγμένου καταλόγου
vaultOptions.mount.mountPoint.directoryPickerButton=Επιλογή…
vaultOptions.mount.mountPoint.directoryPickerTitle=Επιλέξτε έναν κατάλογο
vaultOptions.mount.volumeType.default=Προεπιλογή (%s)
vaultOptions.mount.volumeType.fuseRestartRequired=Για να χρησιμοποιήσετε αυτόν τον τύπο τόμου, πρέπει να γίνει επανεκκίνηση του Cryptomator.
vaultOptions.mount.volume.tcp.port=Θύρα TCP
vaultOptions.mount.volume.type=Τύπος Τόμου
## Master Key
vaultOptions.masterkey=Κωδικός πρόσβασης
vaultOptions.masterkey.changePasswordBtn=Αλλαγή κωδικού πρόσβασης

View File

@@ -141,6 +141,9 @@ unlock.error.customPath.description.hideawayNotDir=El archivo oculto temporal "%
unlock.error.customPath.description.couldNotBeCleaned=Su bóveda no se pudo montar en la ruta "%s". Intente de nuevo o elija una ruta diferente.
unlock.error.customPath.description.notEmptyDir=La ruta de montaje personalizada "%s" no es una carpeta vacía. Elija una carpeta vacía y vuelva a intentarlo.
unlock.error.customPath.description.generic=Seleccionó una ruta de montaje personalizada para esta bóveda, pero falló al usarla con el mensaje: %2$s
unlock.error.fuseRestartRequired.message=No se puede desbloquear la bóveda
unlock.error.fuseRestartRequired.description=Cambie el tipo de volumen en las opciones de la bóveda o reinicie Cryptomator.
unlock.error.title=Error al desbloquear "%s"
## Hub
hub.noKeychain.message=No se puede acceder a la clave del dispositivo
hub.noKeychain.description=Para desbloquear las bóvedas de Hub, se requiere una clave de dispositivo que se asegura con un llavero. Para continuar, habilite "%s" y seleccione un llavero en las preferencias.
@@ -294,11 +297,11 @@ preferences.interface.showMinimizeButton=Mostrar botón minimizar
preferences.interface.showTrayIcon=Mostrar ícono de bandeja (requiere reiniciar)
## Volume
preferences.volume=Unidad virtual
preferences.volume.type=Tipo de volumen
preferences.volume.type=Tipo de volumen predeterminado
preferences.volume.type.automatic=Automático
preferences.volume.docsTooltip=Abra la documentación para saber más sobre los diferentes tipos de volumen.
preferences.volume.fuseRestartRequired=Para aplicar los cambios, Cryptomator necesita ser reiniciado.
preferences.volume.tcp.port=Puerto TCP
preferences.volume.tcp.port=Puerto TCP predeterminado
preferences.volume.supportedFeatures=El tipo de volumen elegido admite las siguientes funciones:
preferences.volume.feature.mountAuto=Selección automática del punto de montaje
preferences.volume.feature.mountToDir=Directorio personalizado como punto de montaje
@@ -437,8 +440,7 @@ vaultOptions.general.startHealthCheckBtn=Iniciar comprobación de estado
## Mount
vaultOptions.mount=Montaje
vaultOptions.mount.info=Las opciones dependen del tipo de volumen seleccionado.
vaultOptions.mount.linkToPreferences=Abrir preferencias de unidad virtual
vaultOptions.mount.info=Abra las preferencias de unidad virtual para cambiar la configuración predeterminada.
vaultOptions.mount.readonly=Sólo lectura
vaultOptions.mount.customMountFlags=Opciones de montaje personalizadas
vaultOptions.mount.winDriveLetterOccupied=ocupado
@@ -448,6 +450,10 @@ vaultOptions.mount.mountPoint.driveLetter=Usar letra de unidad asignada
vaultOptions.mount.mountPoint.custom=Usar directorio seleccionado
vaultOptions.mount.mountPoint.directoryPickerButton=Elegir…
vaultOptions.mount.mountPoint.directoryPickerTitle=Seleccionar un directorio
vaultOptions.mount.volumeType.default=Predeterminado (%s)
vaultOptions.mount.volumeType.fuseRestartRequired=Para utilizar este tipo de volumen, debe reiniciarse Criptomator.
vaultOptions.mount.volume.tcp.port=Puerto TCP
vaultOptions.mount.volume.type=Tipo de volumen
## Master Key
vaultOptions.masterkey=Contraseña
vaultOptions.masterkey.changePasswordBtn=Cambiar contraseña

View File

@@ -294,11 +294,9 @@ preferences.interface.showMinimizeButton=Ipakita ang pindutan ng minimize
preferences.interface.showTrayIcon=Ipakita ang icon ng tray (kailangan i-restart)
## Volume
preferences.volume=Virtual Drive
preferences.volume.type=Uri ng Dami
preferences.volume.type.automatic=Awtomatiko
preferences.volume.docsTooltip=Buksan ang dokumentasyon para matuto pa tungkol sa iba't ibang uri ng volume.
preferences.volume.fuseRestartRequired=Upang mailapat ang mga pagbabago, kailangang i-restart ang Cryptomator.
preferences.volume.tcp.port=TCP Port
preferences.volume.supportedFeatures=Sinusuportahan ng napiling uri ng volume ang mga sumusunod na tampok:
preferences.volume.feature.mountAuto=Awtomatikong pagpili ng mount point
preferences.volume.feature.mountToDir=Custom na direktoryo bilang mount point
@@ -437,8 +435,6 @@ vaultOptions.general.startHealthCheckBtn=Simulan ang Health Check
## Mount
vaultOptions.mount=Pag-mount
vaultOptions.mount.info=Ang mga opsyon ay depende sa napiling uri ng volume.
vaultOptions.mount.linkToPreferences=Buksan ang mga kagustuhan sa virtual drive
vaultOptions.mount.readonly=Basahin lamang
vaultOptions.mount.customMountFlags=Mga custom na naka-mount na flag
vaultOptions.mount.winDriveLetterOccupied=inookupahan

View File

@@ -32,7 +32,7 @@ defaults.vault.vaultName=Coffre
# Tray Menu
traymenu.showMainWindow=Montrer
traymenu.showPreferencesWindow=Préférences
traymenu.lockAllVaults=Tout Verrouiller
traymenu.lockAllVaults=Tout verrouiller
traymenu.quitApplication=Quitter
traymenu.vault.unlock=Déverrouiller
traymenu.vault.lock=Verrouiller
@@ -78,7 +78,7 @@ addvault.new.readme.storageLocation.fileName=IMPORTANT.rtf
addvault.new.readme.storageLocation.1=Fichiers de coffre-fort
addvault.new.readme.storageLocation.2=Ceci est l'emplacement de stockage de votre coffre.
addvault.new.readme.storageLocation.3=NE PAS
addvault.new.readme.storageLocation.4=Modifier n'importe quel fichier dans ce répertoire ou
addvault.new.readme.storageLocation.4=modifier les fichiers dans ce répertoire ni
addvault.new.readme.storageLocation.5=• coller de fichier à chiffrer dans ce répertoire.
addvault.new.readme.storageLocation.6=Si vous voulez chiffrer les fichiers et afficher le contenu du coffre, faites ce qui suit :
addvault.new.readme.storageLocation.7=1. Ajouter ce coffre à Cryptomator.
@@ -141,6 +141,9 @@ unlock.error.customPath.description.hideawayNotDir=Le fichier temporaire et cach
unlock.error.customPath.description.couldNotBeCleaned=Votre coffre n'a pas pu être monté au point "%s". Veuillez réessayer ou choisissez un autre point.
unlock.error.customPath.description.notEmptyDir=Le chemin de montage personnalisé "%s" n'est pas un dossier vide. Veuillez choisir un dossier vide et réessayez.
unlock.error.customPath.description.generic=Vous avez sélectionné un point de montage personnalisé pour ce coffre, mais son utilisation a échoué avec le message : %2$s
unlock.error.fuseRestartRequired.message=Impossible de déverrouiller le coffre
unlock.error.fuseRestartRequired.description=Changez le type de volume dans les options du coffre ou redémarrez Cryptomator.
unlock.error.title=Échec du déverrouillage de "%s"
## Hub
hub.noKeychain.message=Impossible d'accéder à la clé du périphérique
hub.noKeychain.description=Le déverrouillage des coffres Hub nécessite une clé de périphérique sécurisée à l'aide d'un trousseau. Pour continuer, activez « %s » et sélectionnez un trousseau dans les préférences.
@@ -294,11 +297,11 @@ preferences.interface.showMinimizeButton=Afficher le bouton Réduire
preferences.interface.showTrayIcon=Montrer l'icône de service (redémarrage nécessaire)
## Volume
preferences.volume=Disque virtuel
preferences.volume.type=Type de volume
preferences.volume.type=Type de volume par défaut
preferences.volume.type.automatic=Automatique
preferences.volume.docsTooltip=Consultez la documentation pour en savoir plus sur les différents types de volumes.
preferences.volume.fuseRestartRequired=Pour appliquer les modifications, Cryptomator doit être redémarré.
preferences.volume.tcp.port=Port TCP
preferences.volume.tcp.port=Port TCP par défaut
preferences.volume.supportedFeatures=Le type de volume choisi prend en charge les fonctionnalités suivantes :
preferences.volume.feature.mountAuto=Sélection automatique du point de montage
preferences.volume.feature.mountToDir=Répertoire personnalisé comme point de montage
@@ -437,8 +440,7 @@ vaultOptions.general.startHealthCheckBtn=Commencer la vérification de l'état
## Mount
vaultOptions.mount=Montage
vaultOptions.mount.info=Les options dépendent du type de volume sélectionné.
vaultOptions.mount.linkToPreferences=Ouvrir les préférences du lecteur virtuel
vaultOptions.mount.info=Ouvrez les préférences du lecteur virtuel pour changer les paramètres par défaut.
vaultOptions.mount.readonly=Lecture seule
vaultOptions.mount.customMountFlags=Paramètres personnalisés de montage
vaultOptions.mount.winDriveLetterOccupied=occupé
@@ -448,6 +450,10 @@ vaultOptions.mount.mountPoint.driveLetter=Utiliser la lettre du lecteur assigné
vaultOptions.mount.mountPoint.custom=Utiliser le répertoire choisi
vaultOptions.mount.mountPoint.directoryPickerButton=Choisir...
vaultOptions.mount.mountPoint.directoryPickerTitle=Choisissez un répertoire
vaultOptions.mount.volumeType.default=Par défaut (%s)
vaultOptions.mount.volumeType.fuseRestartRequired=Pour utiliser ce type de volume, Cryptomator doit être redémarré.
vaultOptions.mount.volume.tcp.port=Port TCP
vaultOptions.mount.volume.type=Type de volume
## Master Key
vaultOptions.masterkey=Mot de passe
vaultOptions.masterkey.changePasswordBtn=Modifier le mot de passe

View File

@@ -286,11 +286,9 @@ preferences.interface.showMinimizeButton=הצג כפתור מזעור
preferences.interface.showTrayIcon=הצג צלמית בשורה מטה (דורש הפעלה מחדש)
## Volume
preferences.volume=כונן וירטואלי
preferences.volume.type=סוג נפח
preferences.volume.type.automatic=אוטומטי
preferences.volume.docsTooltip=בכדי ללמוד עוד על סוגי volume ניתן לקרוא את הדוקומנטציה.
preferences.volume.fuseRestartRequired=בכדי להחיל את השינויים נדרשת הפעלה מחדש.
preferences.volume.tcp.port=פורט TCP
preferences.volume.supportedFeatures=סוג ה- volume שבחרת תומך ביכולות הבאות:
preferences.volume.feature.mountAuto=נבחרה בחירת נקודת קישור אוטומטית
preferences.volume.feature.mountToDir=הגדרת תיקיה כנקודת קישור
@@ -428,8 +426,6 @@ vaultOptions.general.startHealthCheckBtn=התחל בדיקת בריאות
## Mount
vaultOptions.mount=קישור
vaultOptions.mount.info=האפשרויות תלויות בסוג ה- volume הנבחר.
vaultOptions.mount.linkToPreferences=פתח את העדפות הכונן הוירטואלי
vaultOptions.mount.readonly=קריאה בלבד
vaultOptions.mount.customMountFlags=דגלים ידנים לקישור
vaultOptions.mount.winDriveLetterOccupied=בשימוש

View File

@@ -292,11 +292,9 @@ preferences.interface.showMinimizeButton=Kicsinyítés ikon megjelenítése
preferences.interface.showTrayIcon=Tálca ikon megjelenítése (újraindítás szükséges)
## Volume
preferences.volume=Virtuális meghajtó
preferences.volume.type=Kötet Típusa
preferences.volume.type.automatic=Automatikus
preferences.volume.docsTooltip=További információért a kötet típusokról kattintson ide, hogy megnyissa a dokumentációt.
preferences.volume.fuseRestartRequired=A változtatások alkalmazásához újra kell indítania a Cryptomatort.
preferences.volume.tcp.port=TCP Port
preferences.volume.supportedFeatures=A kiválaszott kötet típus az alábbi funkciókat támogatja:
preferences.volume.feature.mountAuto=Automatikus csatlakozási pont választás
preferences.volume.feature.mountToDir=Egyéni mappa csatlakozási pontként
@@ -435,8 +433,6 @@ vaultOptions.general.startHealthCheckBtn=Épség-ellenőrzés indítása
## Mount
vaultOptions.mount=Felcsatolás
vaultOptions.mount.info=Az opciók a kiválasztott kötet típustól függőek.
vaultOptions.mount.linkToPreferences=Virtuális meghajtó opciók megnyitása
vaultOptions.mount.readonly=Csak-olvasható
vaultOptions.mount.customMountFlags=Egyedi csatolási paraméterek
vaultOptions.mount.winDriveLetterOccupied=foglalt

View File

@@ -141,6 +141,9 @@ unlock.error.customPath.description.hideawayNotDir=Impossibile rimuovere il file
unlock.error.customPath.description.couldNotBeCleaned=La tua cassaforte non può essere montata sul percorso "%s". Riprova o scegli un percorso diverso.
unlock.error.customPath.description.notEmptyDir=Il percorso di montaggio selezionato "%s" non è una cartella vuota. Scegli una cartella vuota e riprova.
unlock.error.customPath.description.generic=Hai selezionato un percorso di montaggio personalizzato per questa cassaforte, ma il suo utilizzo non è riuscito con il messaggio: %2$s
unlock.error.fuseRestartRequired.message=Impossibile sbloccare la cassaforte
unlock.error.fuseRestartRequired.description=Cambia il tipo di unità nelle opzioni della cassaforte o riavvia Cryptomator.
unlock.error.title=Sblocco "%s" non riuscito
## Hub
hub.noKeychain.message=Impossibile accedere alla chiave del dispositivo
hub.noKeychain.description=Per sbloccare le casseforti Hub, è necessaria una chiave del dispositivo, che è protetta tramite un portachiavi. Per procedere, abilita "%s" e seleziona un portachiavi nelle preferenze.
@@ -294,11 +297,11 @@ preferences.interface.showMinimizeButton=Mostra il pulsante minimizza
preferences.interface.showTrayIcon=Mostra l'icona della barra d'applicazioni (richiede il riavvio)
## Volume
preferences.volume=Unità Virtuale
preferences.volume.type=Tipo di Volume
preferences.volume.type=Tipo di Unità Predefinita
preferences.volume.type.automatic=Automatico
preferences.volume.docsTooltip=Aprire la documentazione per saperne di più sui diversi tipi di volume.
preferences.volume.fuseRestartRequired=Per applicare le modifiche, Cryptomator deve essere riavviato.
preferences.volume.tcp.port=Porta TCP
preferences.volume.tcp.port=Porta TCP Predefinita
preferences.volume.supportedFeatures=Il tipo di volume scelto supporta le seguenti caratteristiche:
preferences.volume.feature.mountAuto=Selezione automatica del punto di montaggio
preferences.volume.feature.mountToDir=Directory personalizzata come punto di montaggio
@@ -437,8 +440,7 @@ vaultOptions.general.startHealthCheckBtn=Avvia il Controllo della Salute
## Mount
vaultOptions.mount=Montaggio
vaultOptions.mount.info=Le opzioni dipendono dal tipo di volume selezionato.
vaultOptions.mount.linkToPreferences=Apri le preferenze dell'unità virtuale
vaultOptions.mount.info=Apri le preferenze dell'unità virtuale per modificare le impostazioni predefinite.
vaultOptions.mount.readonly=Sola Lettura
vaultOptions.mount.customMountFlags=Flag di Montaggio Personalizzati
vaultOptions.mount.winDriveLetterOccupied=occupato
@@ -448,6 +450,10 @@ vaultOptions.mount.mountPoint.driveLetter=Usa la lettera del drive assegnata
vaultOptions.mount.mountPoint.custom=Usa la directory scelta
vaultOptions.mount.mountPoint.directoryPickerButton=Scegli…
vaultOptions.mount.mountPoint.directoryPickerTitle=Scegli una directory
vaultOptions.mount.volumeType.default=Predefinito (%s)
vaultOptions.mount.volumeType.fuseRestartRequired=Per utilizzare questo tipo di unità, Cryptomator deve essere riavviato.
vaultOptions.mount.volume.tcp.port=Porta TCP
vaultOptions.mount.volume.type=Tipo di Unità
## Master Key
vaultOptions.masterkey=Password
vaultOptions.masterkey.changePasswordBtn=Modifica password

View File

@@ -292,11 +292,9 @@ preferences.interface.showMinimizeButton=最小化ボタンを表示
preferences.interface.showTrayIcon=トレイアイコンを表示 (再起動が必要)
## Volume
preferences.volume=仮想ドライブ
preferences.volume.type=ボリュームの種類
preferences.volume.type.automatic=自動
preferences.volume.docsTooltip=異なるボリュームタイプの詳細については、ドキュメントを確認してください。
preferences.volume.fuseRestartRequired=変更を適用するには、Cryptomator を再起動する必要があります。
preferences.volume.tcp.port=TCP ポート
preferences.volume.supportedFeatures=選択したボリューム形式は、次の機能をサポートしています:
preferences.volume.feature.mountAuto=マウント先の自動選択
preferences.volume.feature.mountToDir=カスタム ディレクトリをマウント先に指定
@@ -435,8 +433,6 @@ vaultOptions.general.startHealthCheckBtn=正常性チェックを開始
## Mount
vaultOptions.mount=マウント
vaultOptions.mount.info=オプションは、選択されたボリュームタイプによって異なります。
vaultOptions.mount.linkToPreferences=仮想ドライブの環境設定を開く
vaultOptions.mount.readonly=読み取り専用
vaultOptions.mount.customMountFlags=カスタム マウント フラグ
vaultOptions.mount.winDriveLetterOccupied=使用中

View File

@@ -241,9 +241,7 @@ preferences.interface.showMinimizeButton=최소화 버튼 표시
preferences.interface.showTrayIcon=트레이 아이콘 보기 (재시작 필요)
## Volume
preferences.volume=가상 드라이브
preferences.volume.type=볼륨 유형
preferences.volume.type.automatic=자동
preferences.volume.tcp.port=TCP 포트
preferences.volume.supportedFeatures=현재 선택한 볼륨 타입은 다음과 같은 기능들을 지원합니다:
preferences.volume.feature.mountToDir=마운트할 폴더 지정
preferences.volume.feature.mountToDriveLetter=마운트할 드라이브 문자

View File

@@ -20,6 +20,7 @@ error.description=Cryptomator negaidīja, ka tas notiks. Varat meklēt esošos
error.hyperlink.lookup=Meklējiet šo kļūdu
error.hyperlink.report=Ziņojiet par šo kļūdu
error.technicalDetails=Detaļas:
error.existingSolutionDescription=Cryptomator negaidīja, ka tas notiks. Bet mēs atradām esošu risinājumu šai kļūdai. Lūdzu, apskatiet tālāk norādīto saiti.
# Defaults
defaults.vault.vaultName=Glabātava

View File

@@ -141,6 +141,9 @@ unlock.error.customPath.description.hideawayNotDir=Den midlertidige, skjulte fil
unlock.error.customPath.description.couldNotBeCleaned=Hvelvet ditt kan ikke monteres i banen "%s". Prøv igjen eller velg en annen sti.
unlock.error.customPath.description.notEmptyDir=Tilpasset monterings sti "%s" er ikke en tom mappe. Velg en tom mappe og prøv igjen.
unlock.error.customPath.description.generic=Du har valgt en egendefinert monterings sti for dette hvelvet, men bruk av den mislyktes med meldingen: %2$s
unlock.error.fuseRestartRequired.message=Kan ikke låse opp hvelvet
unlock.error.fuseRestartRequired.description=Endre volumtype i innstillinger for hvelvet eller start Cryptomator på nytt.
unlock.error.title=Lås opp "%s" mislyktes
## Hub
hub.noKeychain.message=Får ikke tilgang til enhetsnøkkel
hub.noKeychain.description=For å låse opp Hub-hvelv er det nødvendig med en enhetsnøkkel som er sikret med en nøkkelring. For å fortsette, aktiver "%s" og velg en nøkkelring i innstillingene.
@@ -294,11 +297,11 @@ preferences.interface.showMinimizeButton=Vis minimer-knapp
preferences.interface.showTrayIcon=Vis verktøykasseikon (krever omstart)
## Volume
preferences.volume=Virtuell enhet
preferences.volume.type=Volumtype
preferences.volume.type=Standard volumtype
preferences.volume.type.automatic=Automatisk
preferences.volume.docsTooltip=Åpne dokumentasjonen for å lære mer om de forskjellige volumtypene.
preferences.volume.fuseRestartRequired=For å iverksette endringene, må Cryptomator startes på nytt.
preferences.volume.tcp.port=TCP Port
preferences.volume.tcp.port=Standard TCP port
preferences.volume.supportedFeatures=Valgt volumtype støtter følgende funksjoner:
preferences.volume.feature.mountAuto=Valg av automatisk monteringspunkt
preferences.volume.feature.mountToDir=Egendefinert mappe som monteringspunkt
@@ -437,8 +440,7 @@ vaultOptions.general.startHealthCheckBtn=Start helsesjekk
## Mount
vaultOptions.mount=Montering
vaultOptions.mount.info=Alternativer avhenger av den valgte volumtypen.
vaultOptions.mount.linkToPreferences=Åpne virtuelle drev-innstillinger
vaultOptions.mount.info=Åpne innstillinger for virtuell enhet for å endre standardinnstillinger.
vaultOptions.mount.readonly=Skrivebeskyttet
vaultOptions.mount.customMountFlags=Tilpassede moteringsparametre
vaultOptions.mount.winDriveLetterOccupied=opptatt
@@ -448,6 +450,10 @@ vaultOptions.mount.mountPoint.driveLetter=Bruk tildelt drevbokstav
vaultOptions.mount.mountPoint.custom=Bruk valgt mappe
vaultOptions.mount.mountPoint.directoryPickerButton=Velg…
vaultOptions.mount.mountPoint.directoryPickerTitle=Velg en mappe
vaultOptions.mount.volumeType.default=Standard (%s)
vaultOptions.mount.volumeType.fuseRestartRequired=For å bruke denne volumtypen må Cryptomator startes på nytt.
vaultOptions.mount.volume.tcp.port=TCP Port
vaultOptions.mount.volume.type=Volumtype
## Master Key
vaultOptions.masterkey=Passord
vaultOptions.masterkey.changePasswordBtn=Endre passord

View File

@@ -141,6 +141,9 @@ unlock.error.customPath.description.hideawayNotDir=Het tijdelijk verborgen besta
unlock.error.customPath.description.couldNotBeCleaned=Uw kluis kon niet worden gekoppeld aan het pad "%s". Probeer het opnieuw of kies een ander pad.
unlock.error.customPath.description.notEmptyDir=Het aangepaste pad "%s" is geen lege map. Kies een lege map en probeer het opnieuw.
unlock.error.customPath.description.generic=Je hebt een aangepast koppelpad voor deze kluis geselecteerd, maar gebruik ervan is mislukt met het bericht: %2$s
unlock.error.fuseRestartRequired.message=Kan kluis niet ontgrendelen
unlock.error.fuseRestartRequired.description=Wijzig het volumetype in kluisopties of start Cryptomator opnieuw.
unlock.error.title=Ontgrendelen "%s" mislukt
## Hub
hub.noKeychain.message=Geen toegang tot de apparaatsleutel
hub.noKeychain.description=Om Hub kluizen te ontgrendelen is een apparaatsleutel vereist, die met een sleutelhanger wordt beveiligd. Om verder te gaan, schakel "%s" in en selecteer een sleutelhanger in de voorkeursinstellingen.
@@ -294,11 +297,11 @@ preferences.interface.showMinimizeButton=Knop minimaliseren weergeven
preferences.interface.showTrayIcon=Pictogram in systeemvak weergeven (herstart vereist)
## Volume
preferences.volume=Virtuele schijf
preferences.volume.type=Type volume
preferences.volume.type=Standaard volumetype
preferences.volume.type.automatic=Automatisch
preferences.volume.docsTooltip=Open de documentatie om meer te weten te komen over de verschillende volume types.
preferences.volume.fuseRestartRequired=Om de wijzigingen toe te passen, moet Cryptomator opnieuw worden gestart.
preferences.volume.tcp.port=TCP-poort
preferences.volume.tcp.port=Standaard TCP-poort
preferences.volume.supportedFeatures=Het gekozen volume type ondersteunt de volgende functies:
preferences.volume.feature.mountAuto=Automatische koppelpunt selectie
preferences.volume.feature.mountToDir=Aangepaste map als koppelpunt
@@ -437,8 +440,7 @@ vaultOptions.general.startHealthCheckBtn=Start controle
## Mount
vaultOptions.mount=Aankoppelen
vaultOptions.mount.info=Opties hangen af van het geselecteerde volume type.
vaultOptions.mount.linkToPreferences=Open voorkeuren virtuele schijf
vaultOptions.mount.info=Open 'Virtueel station' in systeemvoorkeuren om de standaardinstellingen te wijzigen.
vaultOptions.mount.readonly=Alleen-Lezen
vaultOptions.mount.customMountFlags=Aangepaste Aankoppel Parameters
vaultOptions.mount.winDriveLetterOccupied=in gebruik
@@ -448,6 +450,10 @@ vaultOptions.mount.mountPoint.driveLetter=Gebruik de toegewezen schijfletter
vaultOptions.mount.mountPoint.custom=Gebruik gekozen map
vaultOptions.mount.mountPoint.directoryPickerButton=Kies…
vaultOptions.mount.mountPoint.directoryPickerTitle=Selecteer een map
vaultOptions.mount.volumeType.default=Standaard (%s)
vaultOptions.mount.volumeType.fuseRestartRequired=Om dit volumetype te kunnen gebruiken, moet Cryptomator opnieuw worden opgestart.
vaultOptions.mount.volume.tcp.port=TCP-poort
vaultOptions.mount.volume.type=Volumetype
## Master Key
vaultOptions.masterkey=Wachtwoord
vaultOptions.masterkey.changePasswordBtn=Wijzig wachtwoord

View File

@@ -95,6 +95,9 @@ unlock.chooseMasterkey.filePickerTitle=ਮਾਸਟਰ-ਕੁੰਜੀ ਫਾਇ
unlock.success.rememberChoice=ਚੋਣਾਂ ਯਾਦ ਰੱਖੋ, ਇਹ ਮੁੜ ਕੇ ਨਾ ਵੇਖਾਓ
unlock.success.revealBtn=ਡਰਾਇਵ ਦਿਖਾਓ
## Failure
unlock.error.fuseRestartRequired.message=ਵਾਲੇਟ ਅਣ-ਲਾਕ ਕਰਨ ਲਈ ਅਸਮਰੱਥ
unlock.error.fuseRestartRequired.description=ਵਾਲਟ ਚੋਣਾਂ ਵਿੱਚ ਵਾਲੀਅਮ ਦੀ ਕਿਸਮ ਨੂੰ ਬਦਲੋ ਜਾਂ Cryptomator ਨੂੰ ਮੁੜ-ਚਾਲੂ ਕਰੋ।
unlock.error.title="%s" ਨੂੰ ਅਣ-ਲਾਕ ਕਰਨ ਲਈ ਅਸਫ਼ਲ ਹੈ
## Hub
### Waiting
### Receive Key
@@ -158,6 +161,8 @@ preferences.interface.theme.dark=ਗੂੜ੍ਹਾ
preferences.interface.theme.light=ਹਲਕਾ
## Volume
preferences.volume=ਵਰਚੁਅਲ ਡਰਾਇਵ
preferences.volume.type=ਮੂਲ ਵਾਲੀਅਮ ਕਿਸਮ
preferences.volume.tcp.port=ਮੂਲ TCP ਪੋਰਟ
## Updates
preferences.updates=ਅੱਪਡੇਟ
preferences.updates.currentVersion=ਮੌਜੂਦਾ ਵਰਜ਼ਨ: %s
@@ -260,6 +265,7 @@ vaultOptions.general.actionAfterUnlock.ask=ਪੁੱਛੋ
## Mount
vaultOptions.mount=ਮਾਊਂਟ ਕਰਨਾ
vaultOptions.mount.info=ਮੂਲ ਸੈਟਿੰਗਾਂ ਨੂੰ ਬਦਲਣ ਲਈ ਵਰਚੁਅਲ ਡਰਾਇਵ ਪਸੰਦਾਂ ਨੂੰ ਖੋਲ੍ਹੋ।
vaultOptions.mount.readonly=ਕੇਵਲ ਪੜ੍ਹਨ ਲਈ
vaultOptions.mount.customMountFlags=ਪਸੰਦੀਦਾ ਮਾਊਂਟ ਚਿੰਨ੍ਹ
vaultOptions.mount.winDriveLetterOccupied=ਮੱਲਿਆ
@@ -267,6 +273,10 @@ vaultOptions.mount.mountPoint=ਮਾਊਂਟ ਪੁਆਇੰਟ
vaultOptions.mount.mountPoint.auto=ਆਪਣੇ-ਆਪ ਢੁੱਕਵਾਂ ਟਿਕਾਣਾ ਚੁਣ ਲਵੋ
vaultOptions.mount.mountPoint.driveLetter=ਜਾਰੀ ਕੀਤਾ ਡਰਾਇਵ ਅੱਖਰ ਵਰਤੋਂ
vaultOptions.mount.mountPoint.directoryPickerButton=…ਚੁਣੋ
vaultOptions.mount.volumeType.default=ਮੂਲ (%s)
vaultOptions.mount.volumeType.fuseRestartRequired=ਇਸ ਵਾਲੀਅਮ ਕਿਸਮ ਨੂੰ ਵਰਤਣ ਲਈ Cryptomator ਨੂੰ ਮੁੜ-ਚਾਲੂ ਕਰਨ ਦੀ ਲੋੜ ਹੈ।
vaultOptions.mount.volume.tcp.port=TCP ਪੋਰਟ
vaultOptions.mount.volume.type=ਵਾਲੀਅਮ ਦੀ ਕਿਸਮ
## Master Key
vaultOptions.masterkey=ਪਾਸਵਰਡ
vaultOptions.masterkey.changePasswordBtn=ਪਾਸਵਰਡ ਬਦਲੋ

View File

@@ -141,6 +141,9 @@ unlock.error.customPath.description.hideawayNotDir=Nie można usunąć ukrytego
unlock.error.customPath.description.couldNotBeCleaned=Twój sejf nie mógł być zamontowany do ścieżki "%s". Spróbuj ponownie lub wybierz inną ścieżkę.
unlock.error.customPath.description.notEmptyDir=Wybrana ścieżka montowania "%s" nie jest pustym katalogiem. Wybierz pusty katalog i spróbuj ponownie.
unlock.error.customPath.description.generic=Wybrałeś własną ścieżkę montowania dla tego sejfu, ale użycie jej nie powiodło się. Powód: %2$s
unlock.error.fuseRestartRequired.message=Nie można odblokować sejfu
unlock.error.fuseRestartRequired.description=Zmień typ udziału w opcjach sejfu lub zrestartuj Cryptomator.
unlock.error.title=Błąd odblokowywania "%s"
## Hub
hub.noKeychain.message=Brak dostępu do klucza urządzenia
hub.noKeychain.description=Aby odblokować sejfy na Hubie, wymagany jest klucz urządzenia zabezpieczony za pomocą pęku kluczy. Aby kontynuować, włącz "%s" i wybierz Pęk kluczy w ustawieniach.
@@ -294,11 +297,11 @@ preferences.interface.showMinimizeButton=Pokaż przycisk minimalizacji
preferences.interface.showTrayIcon=Pokaż ikonę zasobnika (wymaga restartu)
## Volume
preferences.volume=Dysk wirtualny
preferences.volume.type=Typ woluminu
preferences.volume.type=Domyślny typ udziału
preferences.volume.type.automatic=Automatyczny
preferences.volume.docsTooltip=Sprawdź dokumentację, aby dowiedzieć się więcej o różnych typach udziałów.
preferences.volume.fuseRestartRequired=Aby zastosować zmiany, konieczne jest ponowne uruchomienie Cryptomatora.
preferences.volume.tcp.port=Port TCP
preferences.volume.tcp.port=Domyślny port TCP
preferences.volume.supportedFeatures=Wybrany typ udziału obsługuje następujące funkcje:
preferences.volume.feature.mountAuto=Automatyczny wybór punktu montowania
preferences.volume.feature.mountToDir=Niestandardowy katalog jako punkt montowania
@@ -437,8 +440,7 @@ vaultOptions.general.startHealthCheckBtn=Rozpocznij Test Spójności
## Mount
vaultOptions.mount=Montowanie
vaultOptions.mount.info=Opcje zależą od wybranego typu udziału.
vaultOptions.mount.linkToPreferences=Otwórz ustawienia dysku wirtualnego
vaultOptions.mount.info=Otwórz ustawienia dysku wirtualnego, aby zmienić wartości domyślne.
vaultOptions.mount.readonly=Tylko do odczytu
vaultOptions.mount.customMountFlags=Własne flagi montowania udziału
vaultOptions.mount.winDriveLetterOccupied=zajęty
@@ -448,6 +450,10 @@ vaultOptions.mount.mountPoint.driveLetter=Użyj przypisanej litery dysku
vaultOptions.mount.mountPoint.custom=Użyj wybranego katalogu
vaultOptions.mount.mountPoint.directoryPickerButton=Wybierz…
vaultOptions.mount.mountPoint.directoryPickerTitle=Wybierz katalog
vaultOptions.mount.volumeType.default=Domyślny (%s)
vaultOptions.mount.volumeType.fuseRestartRequired=Aby użyć tego typu udziału, należy zrestartować Cryptomator.
vaultOptions.mount.volume.tcp.port=Port TCP
vaultOptions.mount.volume.type=Typ udziału
## Master Key
vaultOptions.masterkey=Hasło
vaultOptions.masterkey.changePasswordBtn=Zmiana Hasła

Some files were not shown because too many files have changed in this diff Show More