mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-17 10:11:27 +00:00
Merge branch 'release/1.6.16'
This commit is contained in:
61
.github/workflows/appimage.yml
vendored
61
.github/workflows/appimage.yml
vendored
@@ -10,43 +10,38 @@ on:
|
||||
required: false
|
||||
|
||||
env:
|
||||
JAVA_VERSION: 17
|
||||
JAVA_VERSION: 19
|
||||
|
||||
jobs:
|
||||
get-version:
|
||||
uses: ./.github/workflows/get-version.yml
|
||||
with:
|
||||
version: ${{ github.event.inputs.version }}
|
||||
|
||||
build:
|
||||
name: Build AppImage
|
||||
runs-on: ubuntu-latest
|
||||
needs: [get-version]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v2
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
distribution: 'zulu'
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
java-package: 'jdk+fx'
|
||||
cache: 'maven'
|
||||
- id: versions
|
||||
name: Apply version information
|
||||
- name: Ensure major jfx version in pom equals in jdk
|
||||
shell: pwsh
|
||||
run: |
|
||||
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
||||
SEM_VER_STR=${GITHUB_REF##*/}
|
||||
mvn versions:set -DnewVersion=${SEM_VER_STR}
|
||||
elif [[ "${{ github.event.inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
||||
SEM_VER_STR="${{ github.event.inputs.version }}"
|
||||
mvn versions:set -DnewVersion=${SEM_VER_STR}
|
||||
else
|
||||
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
|
||||
fi
|
||||
SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
|
||||
REVCOUNT=`git rev-list --count HEAD`
|
||||
echo "::set-output name=semVerStr::${SEM_VER_STR}"
|
||||
echo "::set-output name=semVerNum::${SEM_VER_NUM}"
|
||||
echo "::set-output name=revNum::${REVCOUNT}"
|
||||
- name: Validate Version
|
||||
uses: skymatic/semver-validation-action@v1
|
||||
with:
|
||||
version: ${{ steps.versions.outputs.semVerStr }}
|
||||
$jfxPomVersion = (&mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout) -split "\."
|
||||
$jfxJdkVersion = ((Get-Content -path "${env:JAVA_HOME}/lib/javafx.properties" | Where-Object {$_ -like 'javafx.version=*' }) -replace '.*=','') -split "\."
|
||||
if ($jfxPomVersion[0] -ne $jfxJdkVersion[0]) {
|
||||
Write-Error "Major part of JavaFX version in pom($($jfxPomVersion[0])) does not match the version in JDK($($jfxJdkVersion[0])) "
|
||||
exit 1
|
||||
}
|
||||
- name: Set version
|
||||
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
|
||||
- name: Run maven
|
||||
run: mvn -B clean package -Pdependency-check,linux -DskipTests
|
||||
- name: Patch target dir
|
||||
@@ -60,7 +55,7 @@ jobs:
|
||||
--verbose
|
||||
--output runtime
|
||||
--module-path "${JAVA_HOME}/jmods"
|
||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr
|
||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr
|
||||
--strip-native-commands
|
||||
--no-header-files
|
||||
--no-man-pages
|
||||
@@ -69,8 +64,8 @@ jobs:
|
||||
- name: Prepare additional launcher
|
||||
run: envsubst '${SEMVER_STR} ${REVISION_NUM}' < dist/linux/launcher-gtk2.properties > launcher-gtk2.properties
|
||||
env:
|
||||
SEMVER_STR: ${{ steps.versions.outputs.semVerStr }}
|
||||
REVISION_NUM: ${{ steps.versions.outputs.revNum }}
|
||||
SEMVER_STR: ${{ needs.get-version.outputs.semVerStr }}
|
||||
REVISION_NUM: ${{ needs.get-version.outputs.revNum }}
|
||||
- name: Run jpackage
|
||||
run: >
|
||||
${JAVA_HOME}/bin/jpackage
|
||||
@@ -84,10 +79,10 @@ jobs:
|
||||
--name Cryptomator
|
||||
--vendor "Skymatic GmbH"
|
||||
--copyright "(C) 2016 - 2022 Skymatic GmbH"
|
||||
--app-version "${{ steps.versions.outputs.semVerNum }}.${{ steps.versions.outputs.revNum }}"
|
||||
--app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}"
|
||||
--java-options "-Xss5m"
|
||||
--java-options "-Xmx256m"
|
||||
--java-options "-Dcryptomator.appVersion=\"${{ steps.versions.outputs.semVerStr }}\""
|
||||
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
|
||||
--java-options "-Dfile.encoding=\"utf-8\""
|
||||
--java-options "-Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\""
|
||||
--java-options "-Dcryptomator.pluginDir=\"~/.local/share/Cryptomator/plugins\""
|
||||
@@ -96,7 +91,7 @@ jobs:
|
||||
--java-options "-Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\""
|
||||
--java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\""
|
||||
--java-options "-Dcryptomator.showTrayIcon=false"
|
||||
--java-options "-Dcryptomator.buildNumber=\"appimage-${{ steps.versions.outputs.revNum }}\""
|
||||
--java-options "-Dcryptomator.buildNumber=\"appimage-${{ needs.get-version.outputs.revNum }}\""
|
||||
--add-launcher Cryptomator-gtk2=launcher-gtk2.properties
|
||||
--resource-dir dist/linux/resources
|
||||
- name: Patch Cryptomator.AppDir
|
||||
@@ -134,7 +129,7 @@ jobs:
|
||||
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
|
||||
- name: Build AppImage
|
||||
run: >
|
||||
./squashfs-root/AppRun Cryptomator.AppDir cryptomator-${{ steps.versions.outputs.semVerStr }}-x86_64.AppImage
|
||||
./squashfs-root/AppRun Cryptomator.AppDir cryptomator-${{ needs.get-version.outputs.semVerStr }}-x86_64.AppImage
|
||||
-u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-x86_64.AppImage.zsync'
|
||||
--sign --sign-key=615D449FE6E6A235 --sign-args="--batch --pinentry-mode loopback"
|
||||
- name: Create detached GPG signatures
|
||||
|
||||
13
.github/workflows/build.yml
vendored
13
.github/workflows/build.yml
vendored
@@ -6,26 +6,25 @@ on:
|
||||
types: [labeled]
|
||||
|
||||
env:
|
||||
JAVA_VERSION: 17
|
||||
JAVA_VERSION: 19
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
jobs:
|
||||
test:
|
||||
name: Compile and Test
|
||||
runs-on: ubuntu-latest
|
||||
if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-java@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
distribution: 'zulu'
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
cache: 'maven'
|
||||
- name: Cache SonarCloud packages
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.sonar/cache
|
||||
key: ${{ runner.os }}-sonar
|
||||
|
||||
87
.github/workflows/debian.yml
vendored
87
.github/workflows/debian.yml
vendored
@@ -15,55 +15,74 @@ on:
|
||||
required: false
|
||||
|
||||
env:
|
||||
JAVA_VERSION: 17
|
||||
JAVA_VERSION: 19
|
||||
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/19/openjfx-19_linux-x64_bin-jmods.zip'
|
||||
OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/19/openjfx-19_linux-aarch64_bin-jmods.zip'
|
||||
|
||||
jobs:
|
||||
get-version:
|
||||
uses: ./.github/workflows/get-version.yml
|
||||
with:
|
||||
version: ${{ github.event.inputs.version }}
|
||||
|
||||
build:
|
||||
name: Build Debian Package
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-20.04
|
||||
needs: [get-version]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install build tools
|
||||
run: |
|
||||
sudo add-apt-repository ppa:coffeelibs/openjdk
|
||||
sudo apt-get update
|
||||
sudo apt-get install debhelper devscripts dput
|
||||
sudo apt-get install debhelper devscripts dput coffeelibs-jdk-19 libgtk2.0-0
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v2
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
distribution: 'zulu'
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
cache: 'maven'
|
||||
- id: versions
|
||||
name: Apply version information
|
||||
run: |
|
||||
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
||||
SEM_VER_STR=${GITHUB_REF##*/}
|
||||
mvn versions:set -DnewVersion=${SEM_VER_STR}
|
||||
elif [[ "${{ github.event.inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
||||
SEM_VER_STR="${{ github.event.inputs.version }}"
|
||||
mvn versions:set -DnewVersion=${SEM_VER_STR}
|
||||
else
|
||||
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
|
||||
fi
|
||||
SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
|
||||
REVCOUNT=`git rev-list --count HEAD`
|
||||
echo "::set-output name=semVerStr::${SEM_VER_STR}"
|
||||
echo "::set-output name=semVerNum::${SEM_VER_NUM}"
|
||||
echo "::set-output name=revNum::${REVCOUNT}"
|
||||
echo "::set-output name=ppaVerStr::${SEM_VER_STR/-/\~}-${REVCOUNT}"
|
||||
- name: Validate Version
|
||||
uses: skymatic/semver-validation-action@v1
|
||||
with:
|
||||
version: ${{ steps.versions.outputs.semVerStr }}
|
||||
- id: versions
|
||||
name: Create PPA version string
|
||||
run: echo "ppaVerStr=${SEM_VER_STR/-/\~}-${REVCOUNT}" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
SEM_VER_STR: ${{ needs.get-version.outputs.semVerStr }}
|
||||
REVCOUNT: ${{ needs.get-version.outputs.revNum }}
|
||||
- name: Run maven
|
||||
run: mvn -B clean package -Pdependency-check,linux -DskipTests
|
||||
- name: Create orig.tar.gz with common/ libs/ mods/
|
||||
- name: Download OpenJFX jmods
|
||||
id: download-jmods
|
||||
run: |
|
||||
curl -L ${{ env.OPENJFX_JMODS_AMD64 }} -o openjfx-amd64.zip
|
||||
mkdir -p jmods/amd64
|
||||
unzip -j openjfx-amd64.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d jmods/amd64
|
||||
curl -L ${{ env.OPENJFX_JMODS_AARCH64 }} -o openjfx-aarch64.zip
|
||||
mkdir -p jmods/aarch64
|
||||
unzip -j openjfx-aarch64.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d jmods/aarch64
|
||||
- name: Ensure major jfx version in pom and in jmods is the same
|
||||
shell: pwsh
|
||||
run: |
|
||||
mkdir jfxBaseJmodAmd64
|
||||
jmod extract --dir jfxBaseJmodAmd64 jmods/amd64/javafx.base.jmod
|
||||
$jfxJmodVersionAmd64 = ((Get-Content -Path "jfxBaseJmodAmd64/lib/javafx.properties" | Where-Object {$_ -like 'javafx.version=*' }) -replace '.*=','') -split "\."
|
||||
mkdir jfxBaseJmodAarch64
|
||||
jmod extract --dir jfxBaseJmodAarch64 jmods/aarch64/javafx.base.jmod
|
||||
$jfxJmodVersionAarch64 = ((Get-Content -Path "jfxBaseJmodAarch64/lib/javafx.properties" | Where-Object {$_ -like 'javafx.version=*' }) -replace '.*=','') -split "\."
|
||||
if ($jfxJmodVersionAmd64[0] -ne $jfxJmodVersionAarch64[0] ) {
|
||||
Write-Error "JavaFX Jmods for aarch64 and amd64 are different major versions"
|
||||
exit 1
|
||||
}
|
||||
$jfxPomVersion = (&mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout) -split "\."
|
||||
if ($jfxPomVersion[0] -ne $jfxJmodVersionAmd64[0]) {
|
||||
Write-Error "Major part of JavaFX version in pom($($jfxPomVersion[0])) does not match the version of Jmods($($jfxJmodVersionAmd64[0])) "
|
||||
exit 1
|
||||
}
|
||||
- name: Create orig.tar.gz with common/ libs/ mods/ jmods/
|
||||
run: |
|
||||
mkdir pkgdir
|
||||
cp -r target/libs pkgdir
|
||||
cp -r target/mods pkgdir
|
||||
cp -r jmods pkgdir
|
||||
cp -r dist/linux/common/ pkgdir
|
||||
cp target/cryptomator-*.jar pkgdir/mods
|
||||
tar -cJf cryptomator_${{ steps.versions.outputs.ppaVerStr }}.orig.tar.xz -C pkgdir .
|
||||
@@ -76,9 +95,9 @@ jobs:
|
||||
find . -name "*.jar" >> pkgdir/debian/source/include-binaries
|
||||
mv pkgdir cryptomator_${{ steps.versions.outputs.ppaVerStr }}
|
||||
env:
|
||||
SEMVER_STR: ${{ steps.versions.outputs.semVerStr }}
|
||||
VERSION_NUM: ${{ steps.versions.outputs.semVerNum }}
|
||||
REVISION_NUM: ${{ steps.versions.outputs.revNum }}
|
||||
SEMVER_STR: ${{ needs.get-version.outputs.semVerStr }}
|
||||
VERSION_NUM: ${{ needs.get-version.outputs.semVerNum }}
|
||||
REVISION_NUM: ${{ needs.get-version.outputs.revNum }}
|
||||
PPA_VERSION: ${{ steps.versions.outputs.ppaVerStr }}-0ppa1
|
||||
- name: Prepare GPG-Agent for signing with key 615D449FE6E6A235
|
||||
run: |
|
||||
|
||||
2
.github/workflows/dl-stats.yml
vendored
2
.github/workflows/dl-stats.yml
vendored
@@ -48,7 +48,7 @@ jobs:
|
||||
jq -c 'select(.filename|endswith(".dmg")) | select(.filename|endswith("-arm64.dmg")|not) | {name: "github.releases.downloads", tags: ["file=dmg", "version=\(.release)", "arch=amd64"], value: .downloads, interval: .interval, time: .time}' input.json >> output.json
|
||||
|
||||
RESULT=$(jq -s -c "." output.json)
|
||||
echo "::set-output name=result::${RESULT}"
|
||||
echo "result=${RESULT}" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
INTERVAL: 900
|
||||
JSON_DATA: ${{ steps.get-stats.outputs.result }}
|
||||
|
||||
77
.github/workflows/get-version.yml
vendored
Normal file
77
.github/workflows/get-version.yml
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
name: Parse and Validate a version string or tag
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
version:
|
||||
description: "A specific version to use"
|
||||
required: false
|
||||
type: string
|
||||
outputs:
|
||||
semVerStr:
|
||||
description: "The full version string."
|
||||
value: ${{ jobs.determine-version.outputs.semVerStr}}
|
||||
semVerNum:
|
||||
description: "The numerical part of the version string"
|
||||
value: ${{ jobs.determine-version.outputs.semVerNum}}
|
||||
revNum:
|
||||
description: "The revision number"
|
||||
value: ${{ jobs.determine-version.outputs.revNum}}
|
||||
versionType:
|
||||
description: "Type of the version. Values are [stable, alpha, beta, rc, unknown]"
|
||||
value: ${{ jobs.determine-version.outputs.type }}
|
||||
|
||||
env:
|
||||
JAVA_VERSION: 19
|
||||
JAVA_DIST: 'temurin'
|
||||
JAVA_CACHE: 'maven'
|
||||
|
||||
jobs:
|
||||
determine-version:
|
||||
name: 'Determines the version following semver'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
semVerNum: ${{ steps.versions.outputs.semVerNum }}
|
||||
semVerStr: ${{ steps.versions.outputs.semVerStr }}
|
||||
revNum: ${{ steps.versions.outputs.revNum }}
|
||||
type: ${{ steps.versions.outputs.type}}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: ${{ env.JAVA_DIST }}
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
cache: ${{ env.JAVA_CACHE }}
|
||||
- id: versions
|
||||
name: Get version information
|
||||
run: |
|
||||
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
||||
SEM_VER_STR=${GITHUB_REF##*/}
|
||||
elif [[ "${{ inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
||||
SEM_VER_STR="${{ github.event.inputs.version }}"
|
||||
else
|
||||
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
|
||||
fi
|
||||
SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
|
||||
REVCOUNT=`git rev-list --count HEAD`
|
||||
TYPE="unknown"
|
||||
if [[ $SEM_VER_STR =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then
|
||||
TYPE="stable"
|
||||
elif [[ $SEM_VER_STR =~ [0-9]+\.[0-9]+\.[0-9]+-alpha[1-9] ]]; then
|
||||
TYPE="alpha"
|
||||
elif [[ $SEM_VER_STR =~ [0-9]+\.[0-9]+\.[0-9]+-beta[1-9] ]]; then
|
||||
TYPE="beta"
|
||||
elif [[ $SEM_VER_STR =~ [0-9]+\.[0-9]+\.[0-9]+-rc[1-9] ]]; then
|
||||
TYPE="rc"
|
||||
fi
|
||||
echo "semVerStr=${SEM_VER_STR}" >> $GITHUB_OUTPUT
|
||||
echo "semVerNum=${SEM_VER_NUM}" >> $GITHUB_OUTPUT
|
||||
echo "revNum=${REVCOUNT}" >> $GITHUB_OUTPUT
|
||||
echo "type=${TYPE}" >> $GITHUB_OUTPUT
|
||||
- name: Validate Version
|
||||
uses: skymatic/semver-validation-action@v2
|
||||
with:
|
||||
version: ${{ steps.versions.outputs.semVerStr }}
|
||||
71
.github/workflows/mac-dmg.yml
vendored
71
.github/workflows/mac-dmg.yml
vendored
@@ -10,54 +10,52 @@ on:
|
||||
required: false
|
||||
|
||||
env:
|
||||
JAVA_VERSION: 17
|
||||
JAVA_VERSION: 19
|
||||
|
||||
jobs:
|
||||
get-version:
|
||||
uses: ./.github/workflows/get-version.yml
|
||||
with:
|
||||
version: ${{ github.event.inputs.version }}
|
||||
|
||||
build:
|
||||
name: Build Cryptomator.app for ${{ matrix.output-suffix }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: [get-version]
|
||||
strategy:
|
||||
fail-fast: true
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: macos-11
|
||||
architecture: x64
|
||||
output-suffix: x64
|
||||
xcode-path: '/Applications/Xcode_13.2.1.app'
|
||||
- os: [self-hosted, macOS, ARM64]
|
||||
architecture: aarch64
|
||||
output-suffix: arm64
|
||||
xcode-path: '/Applications/Xcode_13.2.1.app'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
distribution: 'zulu'
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
java-package: 'jdk+fx'
|
||||
architecture: ${{ matrix.architecture }}
|
||||
cache: 'maven'
|
||||
- id: versions
|
||||
name: Apply version information
|
||||
- name: Ensure major jfx version in pom equals in jdk
|
||||
if: ${{ !contains(matrix.os, 'self-hosted') }}
|
||||
shell: pwsh
|
||||
run: |
|
||||
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
||||
SEM_VER_STR=${GITHUB_REF##*/}
|
||||
mvn versions:set -DnewVersion=${SEM_VER_STR}
|
||||
elif [[ "${{ github.event.inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
||||
SEM_VER_STR="${{ github.event.inputs.version }}"
|
||||
mvn versions:set -DnewVersion=${SEM_VER_STR}
|
||||
else
|
||||
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
|
||||
fi
|
||||
SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
|
||||
REVCOUNT=`git rev-list --count HEAD`
|
||||
echo "::set-output name=semVerStr::${SEM_VER_STR}"
|
||||
echo "::set-output name=semVerNum::${SEM_VER_NUM}"
|
||||
echo "::set-output name=revNum::${REVCOUNT}"
|
||||
- name: Validate Version
|
||||
uses: skymatic/semver-validation-action@v1
|
||||
with:
|
||||
version: ${{ steps.versions.outputs.semVerStr }}
|
||||
$jfxPomVersion = (&mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout) -split "\."
|
||||
$jfxJdkVersion = ((Get-Content -path "${env:JAVA_HOME}/lib/javafx.properties" | Where-Object {$_ -like 'javafx.version=*' }) -replace '.*=','') -split "\."
|
||||
if ($jfxPomVersion[0] -ne $jfxJdkVersion[0]) {
|
||||
Write-Error "Major part of JavaFX version in pom($($jfxPomVersion[0])) does not match the version in JDK($($jfxJdkVersion[0])) "
|
||||
exit 1
|
||||
}
|
||||
- name: Set version
|
||||
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
|
||||
- name: Run maven
|
||||
run: mvn -B clean package -Pdependency-check,mac -DskipTests
|
||||
- name: Patch target dir
|
||||
@@ -71,7 +69,7 @@ jobs:
|
||||
--verbose
|
||||
--output runtime
|
||||
--module-path "${JAVA_HOME}/jmods"
|
||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr
|
||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr
|
||||
--strip-native-commands
|
||||
--no-header-files
|
||||
--no-man-pages
|
||||
@@ -90,13 +88,13 @@ jobs:
|
||||
--name Cryptomator
|
||||
--vendor "Skymatic GmbH"
|
||||
--copyright "(C) 2016 - 2022 Skymatic GmbH"
|
||||
--app-version "${{ steps.versions.outputs.semVerNum }}"
|
||||
--app-version "${{ needs.get-version.outputs.semVerNum }}"
|
||||
--java-options "-Xss5m"
|
||||
--java-options "-Xmx256m"
|
||||
--java-options "-Dfile.encoding=\"utf-8\""
|
||||
--java-options "-Dapple.awt.enableTemplateImages=true"
|
||||
--java-options "-Dsun.java2d.metal=true"
|
||||
--java-options "-Dcryptomator.appVersion=\"${{ steps.versions.outputs.semVerStr }}\""
|
||||
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
|
||||
--java-options "-Dcryptomator.logDir=\"~/Library/Logs/Cryptomator\""
|
||||
--java-options "-Dcryptomator.pluginDir=\"~/Library/Application Support/Cryptomator/Plugins\""
|
||||
--java-options "-Dcryptomator.settingsPath=\"~/Library/Application Support/Cryptomator/settings.json\""
|
||||
@@ -104,7 +102,7 @@ jobs:
|
||||
--java-options "-Dcryptomator.ipcSocketPath=\"~/Library/Application Support/Cryptomator/ipc.socket\""
|
||||
--java-options "-Dcryptomator.integrationsMac.keychainServiceName=\"Cryptomator\""
|
||||
--java-options "-Dcryptomator.showTrayIcon=true"
|
||||
--java-options "-Dcryptomator.buildNumber=\"dmg-${{ steps.versions.outputs.revNum }}\""
|
||||
--java-options "-Dcryptomator.buildNumber=\"dmg-${{ needs.get-version.outputs.revNum }}\""
|
||||
--mac-package-identifier org.cryptomator
|
||||
--resource-dir dist/mac/resources
|
||||
- name: Patch Cryptomator.app
|
||||
@@ -114,8 +112,8 @@ jobs:
|
||||
sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NO}|g" Cryptomator.app/Contents/Info.plist
|
||||
sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NO}|g" Cryptomator.app/Contents/Info.plist
|
||||
env:
|
||||
VERSION_NO: ${{ steps.versions.outputs.semVerNum }}
|
||||
REVISION_NO: ${{ steps.versions.outputs.revNum }}
|
||||
VERSION_NO: ${{ needs.get-version.outputs.semVerNum }}
|
||||
REVISION_NO: ${{ needs.get-version.outputs.revNum }}
|
||||
- name: Generate license for dmg
|
||||
run: >
|
||||
mvn -B license:add-third-party
|
||||
@@ -149,6 +147,10 @@ jobs:
|
||||
CODESIGN_TMP_KEYCHAIN_PW: ${{ secrets.MACOS_CODESIGN_TMP_KEYCHAIN_PW }}
|
||||
- name: Codesign
|
||||
run: |
|
||||
echo "Codesigning jdk files..."
|
||||
find Cryptomator.app/Contents/runtime/Contents/Home/lib/ -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \;
|
||||
find Cryptomator.app/Contents/runtime/Contents/Home/lib/ -name 'jspawnhelper' -exec codesign --force -o runtime -s ${CODESIGN_IDENTITY} {} \;
|
||||
echo "Codesigning jar contents..."
|
||||
find Cryptomator.app/Contents/runtime/Contents/MacOS -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \;
|
||||
for JAR_PATH in `find Cryptomator.app -name "*.jar"`; do
|
||||
if [[ `unzip -l ${JAR_PATH} | grep '.dylib\|.jnilib'` ]]; then
|
||||
@@ -199,7 +201,7 @@ jobs:
|
||||
--icon ".VolumeIcon.icns" 512 758
|
||||
Cryptomator-${VERSION_NO}-${{ matrix.output-suffix }}.dmg dmg
|
||||
env:
|
||||
VERSION_NO: ${{ steps.versions.outputs.semVerNum }}
|
||||
VERSION_NO: ${{ needs.get-version.outputs.semVerNum }}
|
||||
- name: Notarize .dmg
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: cocoalibs/xcode-notarization-action@v1
|
||||
@@ -208,8 +210,9 @@ jobs:
|
||||
apple-id: ${{ secrets.MACOS_NOTARIZATION_APPLE_ID }}
|
||||
password: ${{ secrets.MACOS_NOTARIZATION_PW }}
|
||||
team-id: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }}
|
||||
xcode-path: ${{ matrix.xcode-path }}
|
||||
- name: Add possible alpha/beta tags to installer name
|
||||
run: mv Cryptomator-*.dmg Cryptomator-${{ steps.versions.outputs.semVerStr }}-${{ matrix.output-suffix }}.dmg
|
||||
run: mv Cryptomator-*.dmg Cryptomator-${{ needs.get-version.outputs.semVerStr }}-${{ matrix.output-suffix }}.dmg
|
||||
- name: Create detached GPG signature with key 615D449FE6E6A235
|
||||
run: |
|
||||
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
|
||||
|
||||
8
.github/workflows/pullrequest.yml
vendored
8
.github/workflows/pullrequest.yml
vendored
@@ -4,7 +4,7 @@ on:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
JAVA_VERSION: 17
|
||||
JAVA_VERSION: 19
|
||||
|
||||
defaults:
|
||||
run:
|
||||
@@ -16,10 +16,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-java@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
distribution: 'zulu'
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
cache: 'maven'
|
||||
- name: Build and Test
|
||||
|
||||
43
.github/workflows/release-check.yml
vendored
Normal file
43
.github/workflows/release-check.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
name: Release Check
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'release/**'
|
||||
- 'hotfix/**'
|
||||
|
||||
env:
|
||||
JAVA_VERSION: 19
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
release-check-precondition:
|
||||
name: Validate commits pushed to release/hotfix branch to fulfill release requirements
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- id: validate-pom-version
|
||||
name: Validate POM version
|
||||
run: |
|
||||
if [[ $GITHUB_REF =~ refs/heads/(hotfix|release)/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
||||
SEM_VER_STR=${GITHUB_REF##*/}
|
||||
else
|
||||
echo "Failed to parse version"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ${SEM_VER_STR} == `mvn help:evaluate -Dexpression=project.version -q -DforceStdout` ]]; then
|
||||
echo "semVerStr=${SEM_VER_STR}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "Version not set in POM"
|
||||
exit 1
|
||||
fi
|
||||
- name: Validate release in org.cryptomator.Cryptomator.metainfo.xml file
|
||||
run: |
|
||||
if ! grep -q "<release date=\".*\" version=\"${{ steps.validate-pom-version.outputs.semVerStr }}\"/>" dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml; then
|
||||
echo "Release not set in dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml"
|
||||
exit 1
|
||||
fi
|
||||
130
.github/workflows/win-exe.yml
vendored
130
.github/workflows/win-exe.yml
vendored
@@ -8,54 +8,48 @@ on:
|
||||
version:
|
||||
description: 'Version'
|
||||
required: false
|
||||
winget-release:
|
||||
description: 'Release artifacts to winget'
|
||||
required: true
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
env:
|
||||
JAVA_VERSION: 17
|
||||
JAVA_VERSION: 19
|
||||
JAVA_DIST: 'zulu'
|
||||
JAVA_CACHE: 'maven'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
get-version:
|
||||
uses: ./.github/workflows/get-version.yml
|
||||
with:
|
||||
version: ${{ github.event.inputs.version }}
|
||||
|
||||
build-msi:
|
||||
name: Build .msi Installer
|
||||
runs-on: windows-latest
|
||||
needs: [get-version]
|
||||
env:
|
||||
LOOPBACK_ALIAS: 'cryptomator-vault'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v2
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
distribution: ${{ env.JAVA_DIST }}
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
cache: 'maven'
|
||||
- id: versions
|
||||
name: Apply version information
|
||||
java-package: 'jdk+fx'
|
||||
cache: ${{ env.JAVA_CACHE }}
|
||||
- name: Ensure major jfx version in pom equals in jdk
|
||||
shell: pwsh
|
||||
run: |
|
||||
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
||||
SEM_VER_STR=${GITHUB_REF##*/}
|
||||
mvn versions:set -DnewVersion=${SEM_VER_STR}
|
||||
elif [[ "${{ github.event.inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
||||
SEM_VER_STR="${{ github.event.inputs.version }}"
|
||||
mvn versions:set -DnewVersion=${SEM_VER_STR}
|
||||
else
|
||||
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
|
||||
fi
|
||||
SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
|
||||
REVCOUNT=`git rev-list --count HEAD`
|
||||
echo "::set-output name=semVerStr::${SEM_VER_STR}"
|
||||
echo "::set-output name=semVerNum::${SEM_VER_NUM}"
|
||||
echo "::set-output name=revNum::${REVCOUNT}"
|
||||
- name: Validate Version
|
||||
uses: skymatic/semver-validation-action@v1
|
||||
with:
|
||||
version: ${{ steps.versions.outputs.semVerStr }}
|
||||
$jfxPomVersion = (&mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout) -split "\."
|
||||
$jfxJdkVersion = ((Get-Content -path "${env:JAVA_HOME}/lib/javafx.properties" | Where-Object {$_ -like 'javafx.version=*' }) -replace '.*=','') -split "\."
|
||||
if ($jfxPomVersion[0] -ne $jfxJdkVersion[0]) {
|
||||
Write-Error "Major part of JavaFX version in pom($($jfxPomVersion[0])) does not match the version in JDK($($jfxJdkVersion[0])) "
|
||||
exit 1
|
||||
}
|
||||
- name: Set version
|
||||
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
|
||||
- name: Run maven
|
||||
run: mvn -B clean package -Pdependency-check,win -DskipTests
|
||||
- name: Patch target dir
|
||||
@@ -69,7 +63,7 @@ jobs:
|
||||
--verbose
|
||||
--output runtime
|
||||
--module-path "${JAVA_HOME}/jmods"
|
||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr
|
||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr
|
||||
--strip-native-commands
|
||||
--no-header-files
|
||||
--no-man-pages
|
||||
@@ -88,10 +82,10 @@ jobs:
|
||||
--name Cryptomator
|
||||
--vendor "Skymatic GmbH"
|
||||
--copyright "(C) 2016 - 2022 Skymatic GmbH"
|
||||
--app-version "${{ steps.versions.outputs.semVerNum }}.${{ steps.versions.outputs.revNum }}"
|
||||
--app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}"
|
||||
--java-options "-Xss5m"
|
||||
--java-options "-Xmx256m"
|
||||
--java-options "-Dcryptomator.appVersion=\"${{ steps.versions.outputs.semVerStr }}\""
|
||||
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
|
||||
--java-options "-Dfile.encoding=\"utf-8\""
|
||||
--java-options "-Dcryptomator.logDir=\"~/AppData/Roaming/Cryptomator\""
|
||||
--java-options "-Dcryptomator.pluginDir=\"~/AppData/Roaming/Cryptomator/Plugins\""
|
||||
@@ -99,8 +93,9 @@ jobs:
|
||||
--java-options "-Dcryptomator.p12Path=\"~/AppData/Roaming/Cryptomator/key.p12\""
|
||||
--java-options "-Dcryptomator.ipcSocketPath=\"~/AppData/Roaming/Cryptomator/ipc.socket\""
|
||||
--java-options "-Dcryptomator.mountPointsDir=\"~/Cryptomator\""
|
||||
--java-options "-Dcryptomator.loopbackAlias=\"${{ env.LOOPBACK_ALIAS }}\""
|
||||
--java-options "-Dcryptomator.showTrayIcon=true"
|
||||
--java-options "-Dcryptomator.buildNumber=\"msi-${{ steps.versions.outputs.revNum }}\""
|
||||
--java-options "-Dcryptomator.buildNumber=\"msi-${{ needs.get-version.outputs.revNum }}\""
|
||||
--java-options "-Dcryptomator.integrationsWin.autoStartShellLinkName=\"Cryptomator\""
|
||||
--java-options "-Dcryptomator.integrationsWin.keychainPaths=\"~/AppData/Roaming/Cryptomator/keychain.json\""
|
||||
--resource-dir dist/win/resources
|
||||
@@ -108,11 +103,21 @@ jobs:
|
||||
- name: Patch Application Directory
|
||||
run: |
|
||||
cp dist/win/contrib/* appdir/Cryptomator
|
||||
- name: Set LOOPBACK_ALIAS in patchWebDAV.bat
|
||||
shell: pwsh
|
||||
run: |
|
||||
$patchScript = "appdir\Cryptomator\patchWebDAV.bat"
|
||||
try {
|
||||
(Get-Content $patchScript ) -replace '::REPLACE ME', "SET LOOPBACK_ALIAS=`"${{ env.LOOPBACK_ALIAS}}`"" | Set-Content $patchScript
|
||||
} catch {
|
||||
Write-Host "Failed to set LOOPBACK_ALIAS for patchWebDAV.bat"
|
||||
exit 1
|
||||
}
|
||||
- name: Fix permissions
|
||||
run: attrib -r appdir/Cryptomator/Cryptomator.exe
|
||||
shell: pwsh
|
||||
- name: Codesign
|
||||
uses: skymatic/code-sign-action@v1
|
||||
uses: skymatic/code-sign-action@v2
|
||||
with:
|
||||
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
|
||||
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
|
||||
@@ -143,7 +148,7 @@ jobs:
|
||||
--name Cryptomator
|
||||
--vendor "Skymatic GmbH"
|
||||
--copyright "(C) 2016 - 2022 Skymatic GmbH"
|
||||
--app-version "${{ steps.versions.outputs.semVerNum }}"
|
||||
--app-version "${{ needs.get-version.outputs.semVerNum }}"
|
||||
--win-menu
|
||||
--win-dir-chooser
|
||||
--win-shortcut-prompt
|
||||
@@ -155,7 +160,7 @@ jobs:
|
||||
env:
|
||||
JP_WIXWIZARD_RESOURCES: ${{ github.workspace }}/dist/win/resources # requires abs path, used in resources/main.wxs
|
||||
- name: Codesign MSI
|
||||
uses: skymatic/code-sign-action@v1
|
||||
uses: skymatic/code-sign-action@v2
|
||||
with:
|
||||
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
|
||||
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
|
||||
@@ -164,7 +169,7 @@ jobs:
|
||||
timestampUrl: 'http://timestamp.digicert.com'
|
||||
folder: installer
|
||||
- name: Add possible alpha/beta tags to installer name
|
||||
run: mv installer/Cryptomator-*.msi Cryptomator-${{ steps.versions.outputs.semVerStr }}-x64.msi
|
||||
run: mv installer/Cryptomator-*.msi Cryptomator-${{ needs.get-version.outputs.semVerStr }}-x64.msi
|
||||
- name: Create detached GPG signature with key 615D449FE6E6A235
|
||||
run: |
|
||||
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
|
||||
@@ -189,43 +194,34 @@ jobs:
|
||||
files: |
|
||||
*.msi
|
||||
*.asc
|
||||
outputs:
|
||||
semVerNum: ${{ steps.versions.outputs.semVerNum }}
|
||||
semVerStr: ${{ steps.versions.outputs.semVerStr }}
|
||||
revNum: ${{ steps.versions.outputs.revNum }}
|
||||
|
||||
publish-winget:
|
||||
name: Publish on winget repo
|
||||
runs-on: windows-latest
|
||||
needs: [build-msi]
|
||||
if: github.event.action == 'published' || inputs.winget-release
|
||||
steps:
|
||||
- name: Submit package to Windows Package Manager Community Repository
|
||||
run: |
|
||||
iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe
|
||||
$github = Get-Content '${{ github.event_path }}' | ConvertFrom-Json
|
||||
$installerUrl = $github.release.assets | Where-Object -Property name -match '^Cryptomator-.*\.msi' | Select -ExpandProperty browser_download_url -First 1
|
||||
.\wingetcreate.exe update Cryptomator.Cryptomator -s -v $github.release.tag_name -u $installerUrl -t ${{ secrets.CRYPTOBOT_WINGET_TOKEN }}
|
||||
shell: pwsh
|
||||
call-winget-flow:
|
||||
needs: [get-version, build-msi]
|
||||
if: github.event.action == 'published' && needs.get-version.outputs.type == 'stable'
|
||||
uses: ./.github/workflows/winget.yml
|
||||
with:
|
||||
releaseTag: ${{ github.event.release.tag_name }}
|
||||
secrets: inherit
|
||||
|
||||
|
||||
build-exe:
|
||||
name: Build .exe installer
|
||||
runs-on: windows-latest
|
||||
needs: [build-msi]
|
||||
needs: [get-version, build-msi]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Download .msi
|
||||
uses: actions/download-artifact@v2
|
||||
uses: actions/download-artifact@v3
|
||||
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@v2
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
distribution: ${{ env.JAVA_DIST }}
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
cache: 'maven'
|
||||
cache: ${{ env.JAVA_CACHE }}
|
||||
- name: Generate license for exe
|
||||
run: >
|
||||
mvn -B license:add-third-party
|
||||
@@ -247,7 +243,7 @@ jobs:
|
||||
"${WIX}/bin/candle.exe" dist/win/bundle/bundleWithWinfsp.wxs
|
||||
-ext WixBalExtension
|
||||
-out dist/win/bundle/
|
||||
-dBundleVersion="${{ needs.build-msi.outputs.semVerNum }}.${{ needs.build-msi.outputs.revNum }}"
|
||||
-dBundleVersion="${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}"
|
||||
-dBundleVendor="Skymatic GmbH"
|
||||
-dBundleCopyright="(C) 2016 - 2022 Skymatic GmbH"
|
||||
-dAboutUrl="https://cryptomator.org"
|
||||
@@ -264,7 +260,7 @@ jobs:
|
||||
-ib installer/unsigned/Cryptomator-Installer.exe
|
||||
-o tmp/engine.exe
|
||||
- name: Codesign burn engine
|
||||
uses: skymatic/code-sign-action@v1
|
||||
uses: skymatic/code-sign-action@v2
|
||||
with:
|
||||
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
|
||||
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
|
||||
@@ -278,7 +274,7 @@ jobs:
|
||||
-ab tmp/engine.exe installer/unsigned/Cryptomator-Installer.exe
|
||||
-o installer/Cryptomator-Installer.exe
|
||||
- name: Codesign EXE
|
||||
uses: skymatic/code-sign-action@v1
|
||||
uses: skymatic/code-sign-action@v2
|
||||
with:
|
||||
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
|
||||
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
|
||||
@@ -287,7 +283,7 @@ jobs:
|
||||
timestampUrl: 'http://timestamp.digicert.com'
|
||||
folder: installer
|
||||
- name: Add possible alpha/beta tags to installer name
|
||||
run: mv installer/Cryptomator-Installer.exe Cryptomator-${{ needs.build-msi.outputs.semVerStr }}-x64.exe
|
||||
run: mv installer/Cryptomator-Installer.exe Cryptomator-${{ needs.get-version.outputs.semVerStr }}-x64.exe
|
||||
- name: Create detached GPG signature with key 615D449FE6E6A235
|
||||
run: |
|
||||
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
|
||||
|
||||
49
.github/workflows/winget.yml
vendored
Normal file
49
.github/workflows/winget.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
name: Release to Winget
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
releaseTag:
|
||||
required: true
|
||||
type: string
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
releaseTag:
|
||||
description: 'Release tag name'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
publish-winget:
|
||||
name: Publish on winget repo
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Get download url for msi artifacts
|
||||
id: get-release-assets
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
const query =`query($tag:String!) {
|
||||
repository(owner:"cryptomator", name:"cryptomator"){
|
||||
release(tagName: $tag) {
|
||||
releaseAssets(first:20) {
|
||||
nodes {
|
||||
name
|
||||
downloadUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`;
|
||||
const variables = {
|
||||
tag: "${{ inputs.releaseTag }}"
|
||||
}
|
||||
return await github.graphql(query, variables)
|
||||
- name: Submit package to Windows Package Manager Community Repository
|
||||
id: submit-winget
|
||||
run: |
|
||||
iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe
|
||||
$releaseAssets = (ConvertFrom-Json '${{ steps.get-release-assets.outputs.result }}').repository.release.releaseAssets.nodes
|
||||
$installerUrl = $releaseAssets | Where-Object -Property name -match '^Cryptomator-.*\.msi$' | Select -ExpandProperty downloadUrl -First 1
|
||||
.\wingetcreate.exe update Cryptomator.Cryptomator -s -v "${{ inputs.releaseTag }}" -u "$installerUrl" -t ${{ secrets.CRYPTOBOT_WINGET_TOKEN }}
|
||||
shell: pwsh
|
||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -8,7 +8,7 @@
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="17" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_19" default="true" project-jdk-name="19" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -61,7 +61,7 @@ Download native binaries of Cryptomator on [cryptomator.org](https://cryptomator
|
||||
- File names get encrypted
|
||||
- Folder structure gets obfuscated
|
||||
- Use as many vaults in your Dropbox as you want, each having individual passwords
|
||||
- Three thousand commits for the security of your data!! :tada:
|
||||
- Four thousand commits for the security of your data!! :tada:
|
||||
|
||||
### Privacy
|
||||
|
||||
@@ -85,7 +85,7 @@ For more information on the security details visit [cryptomator.org](https://doc
|
||||
|
||||
### Dependencies
|
||||
|
||||
* JDK 17 (e.g. temurin)
|
||||
* JDK 19 (e.g. temurin)
|
||||
* Maven 3
|
||||
|
||||
### Run Maven
|
||||
@@ -99,10 +99,6 @@ mvn clean install
|
||||
|
||||
This will build all the jars and bundle them together with their OS-specific dependencies under `target`. This can now be used to build native packages.
|
||||
|
||||
### Start Cryptomator
|
||||
|
||||
If you unzip the buildkit for your OS, you will find a launcher script with some basic settings. You might want to adjust these to your needs. To start Cryptomator, simply execute the launcher script from a terminal, e.g. `launcher-linux.sh`, if you're on a Linux system.
|
||||
|
||||
## License
|
||||
|
||||
This project is dual-licensed under the GPLv3 for FOSS projects as well as a commercial license for independent software vendors and resellers. If you want to modify this application under different conditions, feel free to contact our support team.
|
||||
|
||||
@@ -66,6 +66,8 @@
|
||||
</content_rating>
|
||||
|
||||
<releases>
|
||||
<release date="2022-12-06" version="1.6.16"/>
|
||||
<release date="2022-10-06" version="1.6.15"/>
|
||||
<release date="2022-08-31" version="1.6.14"/>
|
||||
<release date="2022-07-27" version="1.6.12"/>
|
||||
<release date="2022-07-26" version="1.6.11"/>
|
||||
|
||||
2
dist/linux/debian/changelog
vendored
2
dist/linux/debian/changelog
vendored
@@ -1,4 +1,4 @@
|
||||
cryptomator (${PPA_VERSION}) bionic; urgency=low
|
||||
cryptomator (${PPA_VERSION}) focal; urgency=low
|
||||
|
||||
* Full changelog can be found on https://github.com/cryptomator/cryptomator/releases
|
||||
|
||||
|
||||
2
dist/linux/debian/control
vendored
2
dist/linux/debian/control
vendored
@@ -2,7 +2,7 @@ Source: cryptomator
|
||||
Maintainer: Cryptobot <releases@cryptomator.org>
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>=10), openjdk-17-jdk
|
||||
Build-Depends: debhelper (>=10), coffeelibs-jdk-19, libgtk2.0-0
|
||||
Standards-Version: 4.5.0
|
||||
Homepage: https://cryptomator.org
|
||||
Vcs-Git: https://github.com/cryptomator/cryptomator.git
|
||||
|
||||
15
dist/linux/debian/rules
vendored
15
dist/linux/debian/rules
vendored
@@ -4,6 +4,14 @@
|
||||
# Uncomment this to turn on verbose mode.
|
||||
#export DH_VERBOSE=1
|
||||
|
||||
JAVA_HOME = /usr/lib/jvm/java-19-coffeelibs
|
||||
DEB_BUILD_ARCH ?= $(shell dpkg-architecture -qDEB_BUILD_ARCH)
|
||||
ifeq ($(DEB_BUILD_ARCH),amd64)
|
||||
JMODS_PATH = jmods/amd64:${JAVA_HOME}/jmods
|
||||
else ifeq ($(DEB_BUILD_ARCH),arm64)
|
||||
JMODS_PATH = jmods/aarch64:${JAVA_HOME}/jmods
|
||||
endif
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
@@ -16,15 +24,16 @@ override_dh_auto_clean:
|
||||
override_dh_auto_build:
|
||||
mkdir resources
|
||||
ln -s ../common/org.cryptomator.Cryptomator512.png resources/cryptomator.png
|
||||
jlink \
|
||||
$(JAVA_HOME)/bin/jlink \
|
||||
--output runtime \
|
||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \
|
||||
--module-path "${JMODS_PATH}" \
|
||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \
|
||||
--strip-native-commands \
|
||||
--no-header-files \
|
||||
--no-man-pages \
|
||||
--strip-debug \
|
||||
--compress=2
|
||||
jpackage \
|
||||
$(JAVA_HOME)/bin/jpackage \
|
||||
--type app-image \
|
||||
--runtime-image runtime \
|
||||
--input libs \
|
||||
|
||||
4
dist/win/build.bat
vendored
4
dist/win/build.bat
vendored
@@ -9,8 +9,9 @@ SET ABOUT_URL="https://cryptomator.org"
|
||||
SET UPDATE_URL="https://cryptomator.org/downloads/"
|
||||
SET HELP_URL="https://cryptomator.org/contact/"
|
||||
SET MODULE_AND_MAIN_CLASS="org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator"
|
||||
SET LOOPBACK_ALIAS="cryptomator-vault"
|
||||
|
||||
powershell -NoLogo -NoExit -ExecutionPolicy Unrestricted -Command .\build.ps1^
|
||||
powershell -NoLogo -ExecutionPolicy Unrestricted -Command .\build.ps1^
|
||||
-AppName %APPNAME%^
|
||||
-MainJarGlob "%MAIN_JAR_GLOB%"^
|
||||
-ModuleAndMainClass "%MODULE_AND_MAIN_CLASS%"^
|
||||
@@ -20,4 +21,5 @@ powershell -NoLogo -NoExit -ExecutionPolicy Unrestricted -Command .\build.ps1^
|
||||
-AboutUrl "%ABOUT_URL%"^
|
||||
-HelpUrl "%HELP_URL%"^
|
||||
-UpdateUrl "%UPDATE_URL%"^
|
||||
-LoopbackAlias "%LOOPBACK_ALIAS%"^
|
||||
-Clean 1
|
||||
10
dist/win/build.ps1
vendored
10
dist/win/build.ps1
vendored
@@ -8,6 +8,7 @@ Param(
|
||||
[Parameter(Mandatory, HelpMessage="Please provide a help url")][string] $HelpUrl,
|
||||
[Parameter(Mandatory, HelpMessage="Please provide an update url")][string] $UpdateUrl,
|
||||
[Parameter(Mandatory, HelpMessage="Please provide an about url")][string] $AboutUrl,
|
||||
[Parameter(Mandatory, HelpMessage="Please provide an alias for localhost")][string] $LoopbackAlias,
|
||||
[bool] $clean
|
||||
)
|
||||
|
||||
@@ -85,6 +86,7 @@ if ($clean -and (Test-Path -Path $appPath)) {
|
||||
--java-options "-Dcryptomator.ipcSocketPath=`"~/AppData/Roaming/$AppName/ipc.socket`"" `
|
||||
--java-options "-Dcryptomator.p12Path=`"~/AppData/Roaming/$AppName/key.p12`"" `
|
||||
--java-options "-Dcryptomator.mountPointsDir=`"~/$AppName`"" `
|
||||
--java-options "-Dcryptomator.loopbackAlias=`"$LoopbackAlias`"" `
|
||||
--java-options "-Dcryptomator.integrationsWin.autoStartShellLinkName=`"$AppName`"" `
|
||||
--java-options "-Dcryptomator.integrationsWin.keychainPaths=`"~/AppData/Roaming/$AppName/keychain.json`"" `
|
||||
--java-options "-Dcryptomator.showTrayIcon=true" `
|
||||
@@ -105,6 +107,14 @@ if ($clean -and (Test-Path -Path $appPath)) {
|
||||
# patch app dir
|
||||
Copy-Item "contrib\*" -Destination "$AppName"
|
||||
attrib -r "$AppName\$AppName.exe"
|
||||
# patch batch script to set hostfile
|
||||
$webDAVPatcher = "$AppName\patchWebDAV.bat"
|
||||
try {
|
||||
(Get-Content $webDAVPatcher ) -replace '::REPLACE ME', "SET LOOPBACK_ALIAS=`"$LoopbackAlias`"" | Set-Content $webDAVPatcher
|
||||
} catch {
|
||||
Write-Host "Failed to set LOOPBACK_ALIAS for patchWebDAV.bat"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# create .msi
|
||||
$Env:JP_WIXWIZARD_RESOURCES = "$buildDir\resources"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[InternetShortcut]
|
||||
URL=https://github.com/winfsp/winfsp/releases/download/v1.11/winfsp-1.11.22176.msi
|
||||
URL=https://github.com/winfsp/winfsp/releases/download/v1.12/winfsp-1.12.22301.msi
|
||||
2
dist/win/contrib/patchWebDAV.bat
vendored
2
dist/win/contrib/patchWebDAV.bat
vendored
@@ -1,6 +1,6 @@
|
||||
@echo off
|
||||
:: Default values for Cryptomator builds
|
||||
SET LOOPBACK_ALIAS="cryptomator-vault"
|
||||
::REPLACE ME
|
||||
|
||||
cd %~dp0
|
||||
powershell -NoLogo -NonInteractive -ExecutionPolicy Unrestricted -Command .\patchWebDAV.ps1^
|
||||
|
||||
27
dist/win/resources/main.wxs
vendored
27
dist/win/resources/main.wxs
vendored
@@ -23,6 +23,11 @@
|
||||
<?define JpUpgradeVersionOnlyDetectDowngrade="yes"?>
|
||||
<?endif?>
|
||||
|
||||
<!-- Cryptomator defaults -->
|
||||
<?define IconFileEncryptedData= "Cryptomator-Vault.ico" ?>
|
||||
<?define ProgIdContentType= "application/vnd.cryptomator.encrypted" ?>
|
||||
<?define CloseApplicationTarget= "cryptomator.exe" ?>
|
||||
|
||||
<?include $(var.JpConfigDir)/overrides.wxi ?>
|
||||
|
||||
<Product
|
||||
@@ -65,16 +70,16 @@
|
||||
<CustomAction Id="JpDisallowDowngrade" Error="!(loc.DowngradeErrorMessage)" />
|
||||
<?endif?>
|
||||
|
||||
|
||||
<!-- Looking for legacy Cryptomator versions-->
|
||||
<Property Id="OLDEXEINSTALLER">
|
||||
<RegistrySearch Id="InnoSetupInstallation" Root="HKLM" Key="Software\Microsoft\Windows\CurrentVersion\Uninstall\Cryptomator_is1" Type="raw" Name="DisplayName" />
|
||||
</Property>
|
||||
<!-- Block installation if innosetup entry of Cryptomator is found -->
|
||||
<!-- TODO: localize -->
|
||||
<Condition Message="A lower version of [ProductName] is already installed. Uninstall it first and then start the setup again. Setup will now exit.">
|
||||
<![CDATA[Installed OR NOT OLDEXEINSTALLER]]>
|
||||
</Condition>
|
||||
<?ifndef SkipCryptomatorLegacyCheck ?>
|
||||
<!-- Block installation if innosetup entry of Cryptomator is found -->
|
||||
<Property Id="OLDEXEINSTALLER">
|
||||
<RegistrySearch Id="InnoSetupInstallation" Root="HKLM" Key="Software\Microsoft\Windows\CurrentVersion\Uninstall\Cryptomator_is1" Type="raw" Name="DisplayName" />
|
||||
</Property>
|
||||
<!-- TODO: localize -->
|
||||
<Condition Message="A lower version of [ProductName] is already installed. Uninstall it first and then start the setup again. Setup will now exit.">
|
||||
<![CDATA[Installed OR NOT OLDEXEINSTALLER]]>
|
||||
</Condition>
|
||||
<?endif?>
|
||||
<!-- Cryptomator uses UNIX Sockets, which are supported starting with Windows 10 v1803-->
|
||||
<Property Id="WINDOWSBUILDNUMBER" Secure="yes">
|
||||
<RegistrySearch Id="BuildNumberSearch" Root="HKLM" Key="SOFTWARE\Microsoft\Windows NT\CurrentVersion" Name="CurrentBuildNumber" Type="raw" />
|
||||
@@ -86,7 +91,7 @@
|
||||
<!-- Non-Opening ProgID -->
|
||||
<DirectoryRef Id="INSTALLDIR">
|
||||
<Component Win64="yes" Id="nonStartingProgID" >
|
||||
<File Id="IconFileForEncryptedData" KeyPath="yes" Source="$(env.JP_WIXWIZARD_RESOURCES)\$(var.IconFileC9rC9s)" Name="$(var.IconFileC9rC9s)"></File>
|
||||
<File Id="IconFileForEncryptedData" KeyPath="yes" Source="$(env.JP_WIXWIZARD_RESOURCES)\$(var.IconFileEncryptedData)" Name="$(var.IconFileEncryptedData)"></File>
|
||||
<ProgId Id="$(var.JpAppName).Encrypted.1" Description="$(var.JpAppName) Encrypted Data" Icon="IconFileForEncryptedData" IconIndex="0">
|
||||
<Extension Id="c9r" Advertise="no" ContentType="$(var.ProgIdContentType)">
|
||||
<MIME ContentType="$(var.ProgIdContentType)" Default="yes"></MIME>
|
||||
|
||||
23
dist/win/resources/overrides.wxi
vendored
23
dist/win/resources/overrides.wxi
vendored
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Stub by design -->
|
||||
|
||||
<!--
|
||||
<!-- jPackage Section
|
||||
overrides.wxi is a placeholder to set/alter WiX variables referenced from default
|
||||
main.wxs file.
|
||||
|
||||
@@ -30,10 +30,21 @@ Should be defined to enable upgrades and undefined to disable upgrades.
|
||||
Default value is `yes`.
|
||||
-->
|
||||
|
||||
<!-- Non-opening ProgID settings-->
|
||||
<?define IconFileC9rC9s= "Cryptomator-Vault.ico" ?>
|
||||
<?define ProgIdContentType= "application/vnd.cryptomator.encrypted" ?>
|
||||
<!-- Cryptomator Section
|
||||
|
||||
<!-- Close Application util -->
|
||||
<?define CloseApplicationTarget= "cryptomator.exe" ?>
|
||||
Non-opening ProgID settings:
|
||||
- IconFileEncryptedData
|
||||
Full file name of icon file used for encrypted data files. Default is "Cryptomator-Vault.ico"
|
||||
|
||||
- ProgIdContentType
|
||||
Media Type of the encrypted data files. Default is "application/vnd.cryptomator.encrypted"
|
||||
|
||||
Close Application settings:
|
||||
- CloseApplicationTarget
|
||||
Full name of executable to be checkd in the close application util. Default is "cryptomator.exe"
|
||||
|
||||
Legacy Installation settings:
|
||||
- SkipCryptomatorLegacyCheck
|
||||
Should be defined to disable checking for the inno setup installation of Cryptomator and undefined, to enable it.
|
||||
-->
|
||||
<Include/>
|
||||
|
||||
31
pom.xml
31
pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>cryptomator</artifactId>
|
||||
<version>1.6.15</version>
|
||||
<version>1.6.16</version>
|
||||
<name>Cryptomator Desktop App</name>
|
||||
|
||||
<organization>
|
||||
@@ -21,15 +21,14 @@
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.jdk.version>17</project.jdk.version>
|
||||
<project.jdk.version>19</project.jdk.version>
|
||||
|
||||
<!-- Group IDs of jars that need to stay on the class path for now -->
|
||||
<nonModularGroupIds>com.github.serceman,com.github.jnr,org.ow2.asm,net.java.dev.jna,org.apache.jackrabbit,org.apache.httpcomponents,de.swiesend,org.purejava,com.github.hypfvieh</nonModularGroupIds>
|
||||
|
||||
<!-- cryptomator dependencies -->
|
||||
<cryptomator.cryptolib.version>2.1.0-rc1</cryptomator.cryptolib.version>
|
||||
<cryptomator.cryptofs.version>2.4.4</cryptomator.cryptofs.version>
|
||||
<cryptomator.integrations.version>1.1.0</cryptomator.integrations.version>
|
||||
<cryptomator.cryptofs.version>2.5.3</cryptomator.cryptofs.version>
|
||||
<cryptomator.integrations.version>1.2.0-beta1</cryptomator.integrations.version>
|
||||
<cryptomator.integrations.win.version>1.1.2</cryptomator.integrations.win.version>
|
||||
<cryptomator.integrations.mac.version>1.1.2</cryptomator.integrations.mac.version>
|
||||
<cryptomator.integrations.linux.version>1.1.0</cryptomator.integrations.linux.version>
|
||||
@@ -43,10 +42,10 @@
|
||||
<easybind.version>2.2</easybind.version>
|
||||
<guava.version>31.1-jre</guava.version>
|
||||
<gson.version>2.9.1</gson.version>
|
||||
<javafx.version>18.0.2</javafx.version>
|
||||
<jwt.version>4.0.0</jwt.version>
|
||||
<javafx.version>19</javafx.version>
|
||||
<jwt.version>4.2.1</jwt.version>
|
||||
<nimbus-jose.version>9.25.4</nimbus-jose.version>
|
||||
<logback.version>1.4.3</logback.version>
|
||||
<logback.version>1.4.4</logback.version>
|
||||
<slf4j.version>2.0.3</slf4j.version>
|
||||
<tinyoauth2.version>0.5.1</tinyoauth2.version>
|
||||
<zxcvbn.version>1.7.0</zxcvbn.version>
|
||||
@@ -64,11 +63,6 @@
|
||||
|
||||
<dependencies>
|
||||
<!-- Cryptomator Libs -->
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>cryptolib</artifactId>
|
||||
<version>${cryptomator.cryptolib.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>cryptofs</artifactId>
|
||||
@@ -128,14 +122,6 @@
|
||||
<artifactId>logback-core</artifactId>
|
||||
<version>${logback.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!-- declared by logback-core but not provided -->
|
||||
<groupId>jakarta.mail</groupId>
|
||||
<artifactId>jakarta.mail-api</artifactId>
|
||||
<version>2.1.0</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
@@ -379,6 +365,7 @@
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<executions>
|
||||
<!-- sort jars into two buckets (classpath and modulepath). exclude openjfx, which gets jlinked separately -->
|
||||
<execution>
|
||||
<id>copy-mods</id>
|
||||
<phase>prepare-package</phase>
|
||||
@@ -388,7 +375,7 @@
|
||||
<configuration>
|
||||
<includeScope>runtime</includeScope>
|
||||
<outputDirectory>${project.build.directory}/mods</outputDirectory>
|
||||
<excludeGroupIds>${nonModularGroupIds}</excludeGroupIds>
|
||||
<excludeGroupIds>org.openjfx,${nonModularGroupIds}</excludeGroupIds>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
|
||||
@@ -23,6 +23,7 @@ import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import javafx.beans.binding.Binding;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
@@ -130,16 +131,16 @@ public abstract class CommonsModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static Binding<InetSocketAddress> provideServerSocketAddressBinding(Settings settings) {
|
||||
return Bindings.createObjectBinding(() -> {
|
||||
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());
|
||||
}, settings.port());
|
||||
});
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static WebDavServer provideWebDavServer(Binding<InetSocketAddress> serverSocketAddressBinding) {
|
||||
static WebDavServer provideWebDavServer(ObservableValue<InetSocketAddress> serverSocketAddressBinding) {
|
||||
WebDavServer server = WebDavServer.create();
|
||||
// no need to unsubscribe eventually, because server is a singleton
|
||||
EasyBind.subscribe(serverSocketAddressBinding, server::bind);
|
||||
|
||||
@@ -26,6 +26,7 @@ public class Environment {
|
||||
private static final String KEYCHAIN_PATHS_PROP_NAME = "cryptomator.integrationsWin.keychainPaths";
|
||||
private static final String P12_PATH_PROP_NAME = "cryptomator.p12Path";
|
||||
private static final String LOG_DIR_PROP_NAME = "cryptomator.logDir";
|
||||
private static final String LOOPBACK_ALIAS_PROP_NAME = "cryptomator.loopbackAlias";
|
||||
private static final String MOUNTPOINT_DIR_PROP_NAME = "cryptomator.mountPointsDir";
|
||||
private static final String MIN_PW_LENGTH_PROP_NAME = "cryptomator.minPwLength";
|
||||
private static final String APP_VERSION_PROP_NAME = "cryptomator.appVersion";
|
||||
@@ -45,6 +46,7 @@ public class Environment {
|
||||
logCryptomatorSystemProperty(IPC_SOCKET_PATH_PROP_NAME);
|
||||
logCryptomatorSystemProperty(KEYCHAIN_PATHS_PROP_NAME);
|
||||
logCryptomatorSystemProperty(LOG_DIR_PROP_NAME);
|
||||
logCryptomatorSystemProperty(LOOPBACK_ALIAS_PROP_NAME);
|
||||
logCryptomatorSystemProperty(PLUGIN_DIR_PROP_NAME);
|
||||
logCryptomatorSystemProperty(MOUNTPOINT_DIR_PROP_NAME);
|
||||
logCryptomatorSystemProperty(MIN_PW_LENGTH_PROP_NAME);
|
||||
@@ -90,6 +92,10 @@ public class Environment {
|
||||
return getPath(LOG_DIR_PROP_NAME).map(this::replaceHomeDir);
|
||||
}
|
||||
|
||||
public Optional<String> getLoopbackAlias() {
|
||||
return Optional.ofNullable(System.getProperty(LOOPBACK_ALIAS_PROP_NAME));
|
||||
}
|
||||
|
||||
public Optional<Path> getPluginDir() {
|
||||
return getPath(PLUGIN_DIR_PROP_NAME).map(this::replaceHomeDir);
|
||||
}
|
||||
@@ -112,22 +118,13 @@ public class Environment {
|
||||
}
|
||||
|
||||
public int getMinPwLength() {
|
||||
return getInt(MIN_PW_LENGTH_PROP_NAME, DEFAULT_MIN_PW_LENGTH);
|
||||
return Integer.getInteger(MIN_PW_LENGTH_PROP_NAME, DEFAULT_MIN_PW_LENGTH);
|
||||
}
|
||||
|
||||
public boolean showTrayIcon() {
|
||||
return Boolean.getBoolean(TRAY_ICON_PROP_NAME);
|
||||
}
|
||||
|
||||
private int getInt(String propertyName, int defaultValue) {
|
||||
String value = System.getProperty(propertyName);
|
||||
try {
|
||||
return Integer.parseInt(value);
|
||||
} catch (NumberFormatException e) { // includes "null" values
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<Path> getPath(String propertyName) {
|
||||
String value = System.getProperty(propertyName);
|
||||
return Optional.ofNullable(value).map(Paths::get);
|
||||
|
||||
@@ -10,6 +10,7 @@ import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import java.util.Optional;
|
||||
|
||||
@Singleton
|
||||
@@ -18,7 +19,7 @@ public class LicenseHolder {
|
||||
private final Settings settings;
|
||||
private final LicenseChecker licenseChecker;
|
||||
private final ObjectProperty<DecodedJWT> validJwtClaims;
|
||||
private final StringBinding licenseSubject;
|
||||
private final ObservableValue<String> licenseSubject;
|
||||
private final BooleanBinding validLicenseProperty;
|
||||
|
||||
@Inject
|
||||
@@ -26,7 +27,7 @@ public class LicenseHolder {
|
||||
this.settings = settings;
|
||||
this.licenseChecker = licenseChecker;
|
||||
this.validJwtClaims = new SimpleObjectProperty<>();
|
||||
this.licenseSubject = Bindings.createStringBinding(this::getLicenseSubject, validJwtClaims);
|
||||
this.licenseSubject = validJwtClaims.map(DecodedJWT::getSubject);
|
||||
this.validLicenseProperty = validJwtClaims.isNotNull();
|
||||
|
||||
Optional<DecodedJWT> claims = licenseChecker.check(settings.licenseKey().get());
|
||||
@@ -55,17 +56,12 @@ public class LicenseHolder {
|
||||
}
|
||||
}
|
||||
|
||||
public StringBinding licenseSubjectProperty() {
|
||||
public ObservableValue<String> licenseSubjectProperty() {
|
||||
return licenseSubject;
|
||||
}
|
||||
|
||||
public String getLicenseSubject() {
|
||||
DecodedJWT claims = validJwtClaims.get();
|
||||
if (claims != null) {
|
||||
return claims.getSubject();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return licenseSubject.getValue();
|
||||
}
|
||||
|
||||
public BooleanBinding validLicenseProperty() {
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.google.common.io.BaseEncoding;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.beans.binding.StringExpression;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
@@ -20,6 +21,7 @@ import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
@@ -56,11 +58,11 @@ public class VaultSettings {
|
||||
private final ObjectProperty<WhenUnlocked> actionAfterUnlock = new SimpleObjectProperty<>(DEFAULT_ACTION_AFTER_UNLOCK);
|
||||
private final BooleanProperty autoLockWhenIdle = new SimpleBooleanProperty(DEFAULT_AUTOLOCK_WHEN_IDLE);
|
||||
private final IntegerProperty autoLockIdleSeconds = new SimpleIntegerProperty(DEFAULT_AUTOLOCK_IDLE_SECONDS);
|
||||
private final StringBinding mountName;
|
||||
private final StringExpression mountName;
|
||||
|
||||
public VaultSettings(String id) {
|
||||
this.id = Objects.requireNonNull(id);
|
||||
this.mountName = Bindings.createStringBinding(this::normalizeDisplayName, displayName);
|
||||
this.mountName = StringExpression.stringExpression(displayName.map(VaultSettings::normalizeDisplayName).orElse(""));
|
||||
}
|
||||
|
||||
Observable[] observables() {
|
||||
@@ -78,8 +80,7 @@ public class VaultSettings {
|
||||
}
|
||||
|
||||
//visible for testing
|
||||
String normalizeDisplayName() {
|
||||
var original = displayName.getValueSafe();
|
||||
static String normalizeDisplayName(String original) {
|
||||
if (original.isBlank() || ".".equals(original) || "..".equals(original)) {
|
||||
return "_";
|
||||
}
|
||||
@@ -105,7 +106,7 @@ public class VaultSettings {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public StringBinding mountName() {
|
||||
public StringExpression mountName() {
|
||||
return mountName;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
@@ -60,7 +61,6 @@ public class Vault {
|
||||
private final ObjectProperty<Exception> lastKnownException;
|
||||
private final VaultConfigCache configCache;
|
||||
private final VaultStats stats;
|
||||
private final StringBinding displayName;
|
||||
private final StringBinding displayablePath;
|
||||
private final BooleanBinding locked;
|
||||
private final BooleanBinding processing;
|
||||
@@ -84,7 +84,6 @@ public class Vault {
|
||||
this.state = state;
|
||||
this.lastKnownException = lastKnownException;
|
||||
this.stats = stats;
|
||||
this.displayName = Bindings.createStringBinding(this::getDisplayName, vaultSettings.displayName());
|
||||
this.displayablePath = Bindings.createStringBinding(this::getDisplayablePath, vaultSettings.path());
|
||||
this.locked = Bindings.createBooleanBinding(this::isLocked, state);
|
||||
this.processing = Bindings.createBooleanBinding(this::isProcessing, state);
|
||||
@@ -266,8 +265,8 @@ public class Vault {
|
||||
return state.get() == VaultState.Value.ERROR;
|
||||
}
|
||||
|
||||
public StringBinding displayNameProperty() {
|
||||
return displayName;
|
||||
public ReadOnlyStringProperty displayNameProperty() {
|
||||
return vaultSettings.displayName();
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.slf4j.LoggerFactory;
|
||||
import javax.inject.Named;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.beans.binding.StringExpression;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
@@ -68,7 +69,7 @@ public class VaultModule {
|
||||
@DefaultMountFlags
|
||||
public StringBinding provideDefaultMountFlags(Settings settings, VaultSettings vaultSettings) {
|
||||
ObjectProperty<VolumeImpl> preferredVolumeImpl = settings.preferredVolumeImpl();
|
||||
StringBinding mountName = vaultSettings.mountName();
|
||||
StringExpression mountName = vaultSettings.mountName();
|
||||
BooleanProperty readOnly = vaultSettings.usesReadOnlyMode();
|
||||
|
||||
return Bindings.createStringBinding(() -> {
|
||||
@@ -88,7 +89,7 @@ public class VaultModule {
|
||||
}
|
||||
|
||||
// see: https://github.com/osxfuse/osxfuse/wiki/Mount-options
|
||||
private String getMacFuseDefaultMountFlags(StringBinding mountName, ReadOnlyBooleanProperty readOnly) {
|
||||
private String getMacFuseDefaultMountFlags(StringExpression mountName, ReadOnlyBooleanProperty readOnly) {
|
||||
assert SystemUtils.IS_OS_MAC_OSX;
|
||||
StringBuilder flags = new StringBuilder();
|
||||
if (readOnly.get()) {
|
||||
@@ -139,7 +140,7 @@ public class VaultModule {
|
||||
// see https://github.com/billziss-gh/winfsp/blob/5d0b10d0b643652c00ebb4704dc2bb28e7244973/src/dll/fuse/fuse_main.c#L53-L62 for syntax guide
|
||||
// see https://github.com/billziss-gh/winfsp/blob/5d0b10d0b643652c00ebb4704dc2bb28e7244973/src/dll/fuse/fuse.c#L295-L319 for options (-o <...>)
|
||||
// see https://github.com/billziss-gh/winfsp/wiki/Frequently-Asked-Questions/5ba00e4be4f5e938eaae6ef1500b331de12dee77 (FUSE 4.) on why the given defaults were chosen
|
||||
private String getWindowsFuseDefaultMountFlags(StringBinding mountName, ReadOnlyBooleanProperty readOnly) {
|
||||
private String getWindowsFuseDefaultMountFlags(StringExpression mountName, ReadOnlyBooleanProperty readOnly) {
|
||||
assert SystemUtils.IS_OS_WINDOWS;
|
||||
StringBuilder flags = new StringBuilder();
|
||||
|
||||
|
||||
@@ -41,6 +41,8 @@ public class VaultStats {
|
||||
private final LongProperty totalBytesDecrypted = new SimpleLongProperty();
|
||||
private final LongProperty filesRead = new SimpleLongProperty();
|
||||
private final LongProperty filesWritten = new SimpleLongProperty();
|
||||
private final LongProperty filesAccessed = new SimpleLongProperty();
|
||||
private final LongProperty totalFilesAccessed = new SimpleLongProperty();
|
||||
private final ObjectProperty<Instant> lastActivity = new SimpleObjectProperty<>();
|
||||
|
||||
@Inject
|
||||
@@ -82,6 +84,8 @@ public class VaultStats {
|
||||
var oldAccessCount = filesRead.get() + filesWritten.get();
|
||||
filesRead.set(stats.map(CryptoFileSystemStats::pollAmountOfAccessesRead).orElse(0L));
|
||||
filesWritten.set(stats.map(CryptoFileSystemStats::pollAmountOfAccessesWritten).orElse(0L));
|
||||
filesAccessed.set(stats.map(CryptoFileSystemStats::pollAmountOfAccesses).orElse(0L));
|
||||
totalFilesAccessed.set(stats.map(CryptoFileSystemStats::pollTotalAmountOfAccesses).orElse(0L));
|
||||
var newAccessCount = filesRead.get() + filesWritten.get();
|
||||
|
||||
// check for any I/O activity
|
||||
@@ -188,6 +192,19 @@ public class VaultStats {
|
||||
|
||||
public long getFilesWritten() {return filesWritten.get();}
|
||||
|
||||
public LongProperty filesAccessed() {
|
||||
return filesAccessed;}
|
||||
|
||||
public long getFilesAccessed() {return filesAccessed.get();}
|
||||
|
||||
public LongProperty totalFilesAccessed(){
|
||||
return totalFilesAccessed;
|
||||
}
|
||||
|
||||
public long getTotalFilesAccessed(){
|
||||
return totalFilesAccessed.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Instant> lastActivityProperty() {
|
||||
return lastActivity;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.cryptomator.common.vaults;
|
||||
|
||||
|
||||
import com.google.common.base.CharMatcher;
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
import org.cryptomator.common.settings.VolumeImpl;
|
||||
@@ -22,12 +23,11 @@ import java.util.function.Supplier;
|
||||
|
||||
public class WebDavVolume implements Volume {
|
||||
|
||||
private static final String LOCALHOST_ALIAS = "cryptomator-vault";
|
||||
|
||||
private final Provider<WebDavServer> serverProvider;
|
||||
private final VaultSettings vaultSettings;
|
||||
private final Settings settings;
|
||||
private final WindowsDriveLetters windowsDriveLetters;
|
||||
private final Environment environment;
|
||||
|
||||
private WebDavServer server;
|
||||
private WebDavServletController servlet;
|
||||
@@ -35,11 +35,12 @@ public class WebDavVolume implements Volume {
|
||||
private Consumer<Throwable> onExitAction;
|
||||
|
||||
@Inject
|
||||
public WebDavVolume(Provider<WebDavServer> serverProvider, VaultSettings vaultSettings, Settings settings, WindowsDriveLetters windowsDriveLetters) {
|
||||
public WebDavVolume(Provider<WebDavServer> serverProvider, VaultSettings vaultSettings, Settings settings, WindowsDriveLetters windowsDriveLetters, Environment environment) {
|
||||
this.serverProvider = serverProvider;
|
||||
this.vaultSettings = vaultSettings;
|
||||
this.settings = settings;
|
||||
this.windowsDriveLetters = windowsDriveLetters;
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -129,16 +130,17 @@ public class WebDavVolume implements Volume {
|
||||
}
|
||||
|
||||
private String getLocalhostAliasOrNull() {
|
||||
try {
|
||||
InetAddress alias = InetAddress.getByName(LOCALHOST_ALIAS);
|
||||
if (alias.getHostAddress().equals("127.0.0.1")) {
|
||||
return LOCALHOST_ALIAS;
|
||||
} else {
|
||||
return null;
|
||||
return environment.getLoopbackAlias().map(alias -> {
|
||||
try {
|
||||
var address = InetAddress.getByName(alias);
|
||||
if (address.getHostAddress().equals("127.0.0.1")) {
|
||||
return alias;
|
||||
}
|
||||
} catch (UnknownHostException e) {
|
||||
//no-op
|
||||
}
|
||||
} catch (UnknownHostException e) {
|
||||
return null;
|
||||
}
|
||||
}).orElse(null);
|
||||
}
|
||||
|
||||
private void cleanup() {
|
||||
|
||||
@@ -2,19 +2,20 @@ package org.cryptomator.ui.addvaultwizard;
|
||||
|
||||
import dagger.Lazy;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.UiTheme;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.integrations.uiappearance.Theme;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationStyle;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
@@ -23,6 +24,7 @@ import javafx.stage.Stage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_GLOB;
|
||||
@@ -40,12 +42,10 @@ public class ChooseExistingVaultController implements FxController {
|
||||
private final ObjectProperty<Vault> vault;
|
||||
private final VaultListManager vaultListManager;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final Settings settings;
|
||||
|
||||
private Image screenshot;
|
||||
private final ObservableValue<Image> screenshot;
|
||||
|
||||
@Inject
|
||||
ChooseExistingVaultController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy<Scene> welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, FxApplicationWindows appWindows, ObjectProperty<Path> vaultPath, @AddVaultWizardWindow ObjectProperty<Vault> vault, VaultListManager vaultListManager, ResourceBundle resourceBundle, Settings settings) {
|
||||
ChooseExistingVaultController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy<Scene> welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, FxApplicationWindows appWindows, ObjectProperty<Path> vaultPath, @AddVaultWizardWindow ObjectProperty<Vault> vault, VaultListManager vaultListManager, ResourceBundle resourceBundle, FxApplicationStyle applicationStyle) {
|
||||
this.window = window;
|
||||
this.welcomeScene = welcomeScene;
|
||||
this.successScene = successScene;
|
||||
@@ -54,16 +54,20 @@ public class ChooseExistingVaultController implements FxController {
|
||||
this.vault = vault;
|
||||
this.vaultListManager = vaultListManager;
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.settings = settings;
|
||||
this.screenshot = applicationStyle.appliedThemeProperty().map(this::selectScreenshot);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
private Image selectScreenshot(Theme theme) {
|
||||
String imageResourcePath;
|
||||
if (SystemUtils.IS_OS_MAC) {
|
||||
this.screenshot = new Image(getClass().getResource("/img/select-masterkey-mac"+(UiTheme.LIGHT == settings.theme().get()? "":"-dark")+".png").toString());
|
||||
imageResourcePath = switch (theme) {
|
||||
case LIGHT -> "/img/select-masterkey-mac.png";
|
||||
case DARK -> "/img/select-masterkey-mac-dark.png";
|
||||
};
|
||||
} else {
|
||||
this.screenshot = new Image(getClass().getResource("/img/select-masterkey-win.png").toString());
|
||||
imageResourcePath = "/img/select-masterkey-win.png";
|
||||
}
|
||||
return new Image((Objects.requireNonNull(getClass().getResource(imageResourcePath)).toString()));
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -92,8 +96,13 @@ public class ChooseExistingVaultController implements FxController {
|
||||
|
||||
/* Getter */
|
||||
|
||||
public Image getScreenshot() {
|
||||
public ObservableValue<Image> screenshotProperty() {
|
||||
return screenshot;
|
||||
}
|
||||
|
||||
public Image getScreenshot() {
|
||||
return screenshot.getValue();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ public class CreateNewVaultPasswordController implements FxController {
|
||||
this.masterkeyFileAccess = masterkeyFileAccess;
|
||||
this.processing = new SimpleBooleanProperty();
|
||||
this.readyToCreateVault = new SimpleBooleanProperty();
|
||||
this.createVaultButtonState = Bindings.createObjectBinding(this::getCreateVaultButtonState, processing);
|
||||
this.createVaultButtonState = Bindings.when(processing).then(ContentDisplay.LEFT).otherwise(ContentDisplay.TEXT_ONLY);
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -231,6 +231,6 @@ public class CreateNewVaultPasswordController implements FxController {
|
||||
}
|
||||
|
||||
public ContentDisplay getCreateVaultButtonState() {
|
||||
return processing.get() ? ContentDisplay.LEFT : ContentDisplay.TEXT_ONLY;
|
||||
return createVaultButtonState.get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.cryptomator.ui.common;
|
||||
|
||||
import com.tobiasdiez.easybind.EasyBind;
|
||||
import org.cryptomator.ui.controls.FontAwesome5IconView;
|
||||
import org.cryptomator.ui.controls.NiceSecurePasswordField;
|
||||
|
||||
@@ -42,7 +41,7 @@ public class NewPasswordController implements FxController {
|
||||
passwordStrength.bind(Bindings.createIntegerBinding(() -> strengthRater.computeRate(passwordField.getCharacters()), passwordField.textProperty()));
|
||||
|
||||
passwordStrengthLabel.graphicProperty().bind(Bindings.createObjectBinding(this::getIconViewForPasswordStrengthLabel, passwordField.textProperty(), passwordStrength));
|
||||
passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
|
||||
passwordStrengthLabel.textProperty().bind(passwordStrength.map(strengthRater::getStrengthDescription));
|
||||
|
||||
BooleanBinding passwordsMatch = Bindings.createBooleanBinding(this::passwordFieldsMatch, passwordField.textProperty(), reenterField.textProperty());
|
||||
BooleanBinding reenterFieldNotEmpty = reenterField.textProperty().isNotEmpty();
|
||||
|
||||
@@ -27,6 +27,7 @@ public enum FontAwesome5Icon {
|
||||
FILE("\uF15B"), //
|
||||
FILE_IMPORT("\uF56F"), //
|
||||
FOLDER_OPEN("\uF07C"), //
|
||||
FUNNEL("\uF0B0"), //
|
||||
HAND_HOLDING_HEART("\uF4BE"), //
|
||||
HEART("\uF004"), //
|
||||
HDD("\uF0A0"), //
|
||||
|
||||
@@ -46,7 +46,7 @@ public class NiceSecurePasswordField extends StackPane {
|
||||
nonPrintableCharsIcon.managedProperty().bind(passwordField.containingNonPrintableCharsProperty());
|
||||
|
||||
revealPasswordIcon.setGlyph(FontAwesome5Icon.EYE);
|
||||
revealPasswordIcon.glyphProperty().bind(Bindings.createObjectBinding(this::getRevealPasswordGlyph, revealPasswordButton.selectedProperty()));
|
||||
revealPasswordIcon.glyphProperty().bind(Bindings.when(revealPasswordButton.selectedProperty()).then(FontAwesome5Icon.EYE_SLASH).otherwise(FontAwesome5Icon.EYE));
|
||||
revealPasswordIcon.setGlyphSize(ICON_SIZE);
|
||||
|
||||
revealPasswordButton.setContentDisplay(ContentDisplay.LEFT);
|
||||
@@ -61,10 +61,6 @@ public class NiceSecurePasswordField extends StackPane {
|
||||
disabledProperty().addListener(this::disabledChanged);
|
||||
}
|
||||
|
||||
private FontAwesome5Icon getRevealPasswordGlyph() {
|
||||
return revealPasswordButton.isSelected() ? FontAwesome5Icon.EYE_SLASH : FontAwesome5Icon.EYE;
|
||||
}
|
||||
|
||||
private void disabledChanged(@SuppressWarnings("unused") Observable observable) {
|
||||
revealPasswordButton.setSelected(false);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.application.Application;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -24,9 +26,10 @@ public class FxApplicationStyle {
|
||||
private final Optional<UiAppearanceProvider> appearanceProvider;
|
||||
private final LicenseHolder licenseHolder;
|
||||
private final UiAppearanceListener systemInterfaceThemeListener = this::systemInterfaceThemeChanged;
|
||||
private final ObjectProperty<Theme> appliedTheme = new SimpleObjectProperty<>(Theme.LIGHT);
|
||||
|
||||
@Inject
|
||||
public FxApplicationStyle(Settings settings, Optional<UiAppearanceProvider> appearanceProvider, LicenseHolder licenseHolder){
|
||||
public FxApplicationStyle(Settings settings, Optional<UiAppearanceProvider> appearanceProvider, LicenseHolder licenseHolder) {
|
||||
this.settings = settings;
|
||||
this.appearanceProvider = appearanceProvider;
|
||||
this.licenseHolder = licenseHolder;
|
||||
@@ -91,6 +94,7 @@ public class FxApplicationStyle {
|
||||
} else {
|
||||
Application.setUserAgentStylesheet(stylesheet.toString());
|
||||
appearanceProvider.ifPresent(provider -> provider.adjustToTheme(Theme.LIGHT));
|
||||
appliedTheme.set(Theme.LIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +107,11 @@ public class FxApplicationStyle {
|
||||
} else {
|
||||
Application.setUserAgentStylesheet(stylesheet.toString());
|
||||
appearanceProvider.ifPresent(provider -> provider.adjustToTheme(Theme.DARK));
|
||||
appliedTheme.set(Theme.DARK);
|
||||
}
|
||||
}
|
||||
|
||||
public ObjectProperty<Theme> appliedThemeProperty() {
|
||||
return appliedTheme;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,62 +3,99 @@ package org.cryptomator.ui.health;
|
||||
import com.tobiasdiez.easybind.EasyBind;
|
||||
import com.tobiasdiez.easybind.EasyObservableList;
|
||||
import com.tobiasdiez.easybind.Subscription;
|
||||
import com.tobiasdiez.easybind.optional.OptionalBinding;
|
||||
import org.cryptomator.cryptofs.health.api.DiagnosticResult;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.binding.Binding;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.binding.BooleanExpression;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ChoiceBox;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.input.Clipboard;
|
||||
import javafx.scene.input.ClipboardContent;
|
||||
import javafx.util.StringConverter;
|
||||
import java.util.Arrays;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.cryptomator.cryptofs.health.api.DiagnosticResult.Severity;
|
||||
import static org.cryptomator.ui.health.Result.FixState.FIXABLE;
|
||||
import static org.cryptomator.ui.health.Result.FixState.FIXED;
|
||||
import static org.cryptomator.ui.health.Result.FixState.FIXING;
|
||||
import static org.cryptomator.ui.health.Result.FixState.FIX_FAILED;
|
||||
import static org.cryptomator.ui.health.Result.FixState.NOT_FIXABLE;
|
||||
|
||||
@HealthCheckScoped
|
||||
public class CheckDetailController implements FxController {
|
||||
|
||||
private final EasyObservableList<Result> results;
|
||||
private final ObjectProperty<Check> check;
|
||||
private final OptionalBinding<Check.CheckState> checkState;
|
||||
private final Binding<String> checkName;
|
||||
private final Binding<Boolean> checkRunning;
|
||||
private final Binding<Boolean> checkScheduled;
|
||||
private final Binding<Boolean> checkFinished;
|
||||
private final Binding<Boolean> checkSkipped;
|
||||
private final Binding<Boolean> checkSucceeded;
|
||||
private final Binding<Boolean> checkFailed;
|
||||
private final Binding<Boolean> checkCancelled;
|
||||
private final ObservableValue<Check.CheckState> checkState;
|
||||
private final ObservableValue<String> checkName;
|
||||
private final BooleanExpression checkRunning;
|
||||
private final BooleanExpression checkScheduled;
|
||||
private final BooleanExpression checkFinished;
|
||||
private final BooleanExpression checkSkipped;
|
||||
private final BooleanExpression checkSucceeded;
|
||||
private final BooleanExpression checkFailed;
|
||||
private final BooleanExpression checkCancelled;
|
||||
private final Binding<Number> countOfWarnSeverity;
|
||||
private final Binding<Number> countOfCritSeverity;
|
||||
private final Binding<Boolean> warnOrCritsExist;
|
||||
private final ResultListCellFactory resultListCellFactory;
|
||||
private final ResultFixApplier resultFixApplier;
|
||||
private final ResourceBundle resourceBundle;
|
||||
|
||||
private final BooleanProperty fixAllInfoResultsExecuted;
|
||||
private final BooleanBinding fixAllInfoResultsPossible;
|
||||
private final ObjectProperty<Predicate<Result>> resultsFilter;
|
||||
|
||||
public ListView<Result> resultsListView;
|
||||
public ChoiceBox<DiagnosticResult.Severity> severityChoiceBox;
|
||||
public ChoiceBox<Result.FixState> fixStateChoiceBox;
|
||||
private Subscription resultSubscription;
|
||||
|
||||
@Inject
|
||||
public CheckDetailController(ObjectProperty<Check> selectedTask, ResultListCellFactory resultListCellFactory) {
|
||||
public CheckDetailController(ObjectProperty<Check> selectedTask, ResultListCellFactory resultListCellFactory, ResultFixApplier resultFixApplier, ResourceBundle resourceBundle) {
|
||||
this.resultListCellFactory = resultListCellFactory;
|
||||
this.resultFixApplier = resultFixApplier;
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.results = EasyBind.wrapList(FXCollections.observableArrayList());
|
||||
this.check = selectedTask;
|
||||
this.checkState = EasyBind.wrapNullable(selectedTask).mapObservable(Check::stateProperty);
|
||||
this.checkName = EasyBind.wrapNullable(selectedTask).map(Check::getName).orElse("");
|
||||
this.checkRunning = checkState.map(Check.CheckState.RUNNING::equals).orElse(false);
|
||||
this.checkScheduled = checkState.map(Check.CheckState.SCHEDULED::equals).orElse(false);
|
||||
this.checkSkipped = checkState.map(Check.CheckState.SKIPPED::equals).orElse(false);
|
||||
this.checkSucceeded = checkState.map(Check.CheckState.SUCCEEDED::equals).orElse(false);
|
||||
this.checkFailed = checkState.map(Check.CheckState.ERROR::equals).orElse(false);
|
||||
this.checkCancelled = checkState.map(Check.CheckState.CANCELLED::equals).orElse(false);
|
||||
this.checkFinished = EasyBind.combine(checkSucceeded, checkFailed, checkCancelled, (a, b, c) -> a || b || c);
|
||||
this.countOfWarnSeverity = results.reduce(countSeverity(DiagnosticResult.Severity.WARN));
|
||||
this.countOfCritSeverity = results.reduce(countSeverity(DiagnosticResult.Severity.CRITICAL));
|
||||
this.warnOrCritsExist = EasyBind.combine(checkSucceeded, countOfWarnSeverity, countOfCritSeverity, (suceeded, warns, crits) -> suceeded && (warns.longValue() > 0 || crits.longValue() > 0) );
|
||||
this.checkState = selectedTask.flatMap(Check::stateProperty);
|
||||
this.checkName = selectedTask.map(Check::getName).orElse("");
|
||||
this.checkRunning = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.RUNNING::equals).orElse(false));
|
||||
this.checkScheduled = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.SCHEDULED::equals).orElse(false));
|
||||
this.checkSkipped = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.SKIPPED::equals).orElse(false));
|
||||
this.checkSucceeded = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.SUCCEEDED::equals).orElse(false));
|
||||
this.checkFailed = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.ERROR::equals).orElse(false));
|
||||
this.checkCancelled = BooleanExpression.booleanExpression(checkState.map(Check.CheckState.CANCELLED::equals).orElse(false));
|
||||
this.checkFinished = checkSucceeded.or(checkFailed).or(checkCancelled);
|
||||
this.countOfWarnSeverity = results.reduce(countSeverity(Severity.WARN));
|
||||
this.countOfCritSeverity = results.reduce(countSeverity(Severity.CRITICAL));
|
||||
this.warnOrCritsExist = EasyBind.combine(checkSucceeded, countOfWarnSeverity, countOfCritSeverity, (suceeded, warns, crits) -> suceeded && (warns.longValue() > 0 || crits.longValue() > 0));
|
||||
this.fixAllInfoResultsExecuted = new SimpleBooleanProperty(false);
|
||||
this.fixAllInfoResultsPossible = Bindings.createBooleanBinding(() -> results.stream().anyMatch(this::isFixableInfoResult), results) //
|
||||
.and(fixAllInfoResultsExecuted.not());
|
||||
this.resultsFilter = new SimpleObjectProperty<>(r -> true);
|
||||
selectedTask.addListener(this::selectedTaskChanged);
|
||||
}
|
||||
|
||||
private boolean isFixableInfoResult(Result r) {
|
||||
return r.diagnosis().getSeverity() == Severity.INFO && r.getState() == FIXABLE;
|
||||
}
|
||||
|
||||
private void selectedTaskChanged(ObservableValue<? extends Check> observable, Check oldValue, Check newValue) {
|
||||
if (resultSubscription != null) {
|
||||
resultSubscription.unsubscribe();
|
||||
@@ -66,6 +103,8 @@ public class CheckDetailController implements FxController {
|
||||
if (newValue != null) {
|
||||
resultSubscription = EasyBind.bindContent(results, newValue.getResults());
|
||||
}
|
||||
severityChoiceBox.setValue(null);
|
||||
fixStateChoiceBox.setValue(null);
|
||||
}
|
||||
|
||||
private Function<Stream<? extends Result>, Long> countSeverity(DiagnosticResult.Severity severity) {
|
||||
@@ -74,8 +113,110 @@ public class CheckDetailController implements FxController {
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
resultsListView.setItems(results);
|
||||
resultsListView.setItems(results.filtered(resultsFilter));
|
||||
resultsListView.setCellFactory(resultListCellFactory);
|
||||
|
||||
severityChoiceBox.getItems().add(null);
|
||||
severityChoiceBox.getItems().addAll(Arrays.stream(DiagnosticResult.Severity.values()).toList());
|
||||
severityChoiceBox.setConverter(new SeverityStringifier());
|
||||
severityChoiceBox.setValue(null);
|
||||
|
||||
fixStateChoiceBox.getItems().add(null);
|
||||
fixStateChoiceBox.getItems().addAll(Arrays.stream(Result.FixState.values()).toList());
|
||||
fixStateChoiceBox.setConverter(new FixStateStringifier());
|
||||
fixStateChoiceBox.setValue(null);
|
||||
|
||||
resultsFilter.bind(Bindings.createObjectBinding(() -> this::filterResults, severityChoiceBox.valueProperty(), fixStateChoiceBox.valueProperty()));
|
||||
}
|
||||
|
||||
private boolean filterResults(Result r) {
|
||||
var desiredFixState = fixStateChoiceBox.getValue();
|
||||
var desiredSeverity = severityChoiceBox.getValue();
|
||||
return (desiredFixState == null || r.getState() == desiredFixState) && (desiredSeverity == null || r.diagnosis().getSeverity() == desiredSeverity);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void fixAllInfoResults() {
|
||||
fixAllInfoResultsExecuted.setValue(true);
|
||||
results.stream().filter(this::isFixableInfoResult).forEach(resultFixApplier::fix);
|
||||
}
|
||||
|
||||
|
||||
@FXML
|
||||
public void copyResultDetails() {
|
||||
var result = resultsListView.getSelectionModel().getSelectedItem();
|
||||
if (result != null) {
|
||||
ClipboardContent clipboardContent = new ClipboardContent();
|
||||
clipboardContent.putString(result.diagnosis().toString());
|
||||
Clipboard.getSystemClipboard().setContent(clipboardContent);
|
||||
}
|
||||
}
|
||||
|
||||
/* -- Internal classes -- */
|
||||
|
||||
class SeverityStringifier extends StringConverter<Severity> {
|
||||
|
||||
@Override
|
||||
public String toString(Severity object) {
|
||||
if (object == null) {
|
||||
return resourceBundle.getString("health.result.severityFilter.all");
|
||||
}
|
||||
return switch (object) {
|
||||
case GOOD -> resourceBundle.getString("health.result.severityFilter.good");
|
||||
case INFO -> resourceBundle.getString("health.result.severityFilter.info");
|
||||
case WARN -> resourceBundle.getString("health.result.severityFilter.warn");
|
||||
case CRITICAL -> resourceBundle.getString("health.result.severityFilter.crit");
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Severity fromString(String string) {
|
||||
if (resourceBundle.getString("health.result.severityFilter.good").equals(string)) {
|
||||
return Severity.GOOD;
|
||||
} else if (resourceBundle.getString("health.result.severityFilter.info").equals(string)) {
|
||||
return Severity.INFO;
|
||||
} else if (resourceBundle.getString("health.result.severityFilter.warn").equals(string)) {
|
||||
return Severity.WARN;
|
||||
} else if (resourceBundle.getString("health.result.severityFilter.crit").equals(string)) {
|
||||
return Severity.CRITICAL;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FixStateStringifier extends StringConverter<Result.FixState> {
|
||||
|
||||
@Override
|
||||
public String toString(Result.FixState object) {
|
||||
if (object == null) {
|
||||
return resourceBundle.getString("health.result.fixStateFilter.all");
|
||||
}
|
||||
return switch (object) {
|
||||
case FIXABLE -> resourceBundle.getString("health.result.fixStateFilter.fixable");
|
||||
case NOT_FIXABLE -> resourceBundle.getString("health.result.fixStateFilter.notFixable");
|
||||
case FIXING -> resourceBundle.getString("health.result.fixStateFilter.fixing");
|
||||
case FIXED -> resourceBundle.getString("health.result.fixStateFilter.fixed");
|
||||
case FIX_FAILED -> resourceBundle.getString("health.result.fixStateFilter.fixFailed");
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result.FixState fromString(String string) {
|
||||
if (resourceBundle.getString("health.result.fixStateFilter.fixable").equals(string)) {
|
||||
return FIXABLE;
|
||||
} else if (resourceBundle.getString("health.result.fixStateFilter.notFixable").equals(string)) {
|
||||
return NOT_FIXABLE;
|
||||
} else if (resourceBundle.getString("health.result.fixStateFilter.fixing").equals(string)) {
|
||||
return FIXING;
|
||||
} else if (resourceBundle.getString("health.result.fixStateFilter.fixed").equals(string)) {
|
||||
return FIXED;
|
||||
} else if (resourceBundle.getString("health.result.fixStateFilter.fixFailed").equals(string)) {
|
||||
return FIX_FAILED;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
@@ -84,7 +225,7 @@ public class CheckDetailController implements FxController {
|
||||
return checkName.getValue();
|
||||
}
|
||||
|
||||
public Binding<String> checkNameProperty() {
|
||||
public ObservableValue<String> checkNameProperty() {
|
||||
return checkName;
|
||||
}
|
||||
|
||||
@@ -108,7 +249,7 @@ public class CheckDetailController implements FxController {
|
||||
return checkRunning.getValue();
|
||||
}
|
||||
|
||||
public Binding<Boolean> checkRunningProperty() {
|
||||
public BooleanExpression checkRunningProperty() {
|
||||
return checkRunning;
|
||||
}
|
||||
|
||||
@@ -116,7 +257,7 @@ public class CheckDetailController implements FxController {
|
||||
return checkFinished.getValue();
|
||||
}
|
||||
|
||||
public Binding<Boolean> checkFinishedProperty() {
|
||||
public BooleanExpression checkFinishedProperty() {
|
||||
return checkFinished;
|
||||
}
|
||||
|
||||
@@ -124,7 +265,7 @@ public class CheckDetailController implements FxController {
|
||||
return checkScheduled.getValue();
|
||||
}
|
||||
|
||||
public Binding<Boolean> checkScheduledProperty() {
|
||||
public BooleanExpression checkScheduledProperty() {
|
||||
return checkScheduled;
|
||||
}
|
||||
|
||||
@@ -132,7 +273,7 @@ public class CheckDetailController implements FxController {
|
||||
return checkSkipped.getValue();
|
||||
}
|
||||
|
||||
public Binding<Boolean> checkSkippedProperty() {
|
||||
public BooleanExpression checkSkippedProperty() {
|
||||
return checkSkipped;
|
||||
}
|
||||
|
||||
@@ -140,7 +281,7 @@ public class CheckDetailController implements FxController {
|
||||
return checkSucceeded.getValue();
|
||||
}
|
||||
|
||||
public Binding<Boolean> checkSucceededProperty() {
|
||||
public BooleanExpression checkSucceededProperty() {
|
||||
return checkSucceeded;
|
||||
}
|
||||
|
||||
@@ -148,7 +289,7 @@ public class CheckDetailController implements FxController {
|
||||
return checkFailed.getValue();
|
||||
}
|
||||
|
||||
public Binding<Boolean> checkFailedProperty() {
|
||||
public BooleanExpression checkFailedProperty() {
|
||||
return checkFailed;
|
||||
}
|
||||
|
||||
@@ -164,7 +305,7 @@ public class CheckDetailController implements FxController {
|
||||
return warnOrCritsExist.getValue();
|
||||
}
|
||||
|
||||
public Binding<Boolean> checkCancelledProperty() {
|
||||
public BooleanExpression checkCancelledProperty() {
|
||||
return checkCancelled;
|
||||
}
|
||||
|
||||
@@ -175,4 +316,12 @@ public class CheckDetailController implements FxController {
|
||||
public Check getCheck() {
|
||||
return check.get();
|
||||
}
|
||||
|
||||
public ObservableValue<Boolean> fixAllInfoResultsPossibleProperty() {
|
||||
return fixAllInfoResultsPossible;
|
||||
}
|
||||
|
||||
public boolean getFixAllInfoResultsPossible() {
|
||||
return fixAllInfoResultsPossible.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ public class CheckExecutor {
|
||||
try (var masterkeyClone = masterkey.copy(); //
|
||||
var cryptor = CryptorProvider.forScheme(vaultConfig.getCipherCombo()).provide(masterkeyClone, csprng)) {
|
||||
c.getHealthCheck().check(vaultPath, vaultConfig, masterkeyClone, cryptor, diagnosis -> {
|
||||
Platform.runLater(() -> c.getResults().add(Result.create(diagnosis)));
|
||||
Platform.runLater(() -> c.getResults().add(Result.create(diagnosis, vaultPath, vaultConfig, masterkeyClone, cryptor)));
|
||||
highestResultSeverity = Comparators.max(highestResultSeverity, diagnosis.getSeverity());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
package org.cryptomator.ui.health;
|
||||
|
||||
import com.tobiasdiez.easybind.EasyBind;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.binding.Binding;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.scene.control.CheckBox;
|
||||
|
||||
public class CheckListCellController implements FxController {
|
||||
|
||||
|
||||
private final ObjectProperty<Check> check;
|
||||
private final Binding<String> checkName;
|
||||
private final Binding<Boolean> checkRunnable;
|
||||
private final ObservableValue<Boolean> checkRunnable;
|
||||
private final ObservableValue<String> checkName;
|
||||
|
||||
/* FXML */
|
||||
public CheckBox checkbox;
|
||||
@@ -23,8 +22,8 @@ public class CheckListCellController implements FxController {
|
||||
@Inject
|
||||
public CheckListCellController() {
|
||||
check = new SimpleObjectProperty<>();
|
||||
checkRunnable = EasyBind.wrapNullable(check).mapObservable(Check::stateProperty).map(Check.CheckState.RUNNABLE::equals).orElse(false);
|
||||
checkName = EasyBind.wrapNullable(check).map(Check::getName).orElse("");
|
||||
checkRunnable = check.flatMap(Check::stateProperty).map(Check.CheckState.RUNNABLE::equals).orElse(false);
|
||||
checkName = check.map(Check::getName).orElse("");
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
@@ -50,7 +49,7 @@ public class CheckListCellController implements FxController {
|
||||
check.set(c);
|
||||
}
|
||||
|
||||
public Binding<String> checkNameProperty() {
|
||||
public ObservableValue<String> checkNameProperty() {
|
||||
return checkName;
|
||||
}
|
||||
|
||||
@@ -58,7 +57,7 @@ public class CheckListCellController implements FxController {
|
||||
return checkName.getValue();
|
||||
}
|
||||
|
||||
public Binding<Boolean> checkRunnableProperty() {
|
||||
public ObservableValue<Boolean> checkRunnableProperty() {
|
||||
return checkRunnable;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ public class CheckStateIconView extends FontAwesome5IconView {
|
||||
this.severity = EasyBind.wrapNullable(check).mapObservable(Check::highestResultSeverityProperty).asOrdinary();
|
||||
this.glyph.bind(Bindings.createObjectBinding(this::glyphForState, state, severity));
|
||||
this.subscriptions = List.of( //
|
||||
EasyBind.includeWhen(getStyleClass(), "glyph-icon-muted", Bindings.equal(state, Check.CheckState.SKIPPED).or(Bindings.equal(state, Check.CheckState.CANCELLED))), //
|
||||
EasyBind.includeWhen(getStyleClass(), "glyph-icon-muted", Bindings.equal(state, Check.CheckState.SKIPPED).or(Bindings.equal(state, Check.CheckState.CANCELLED)).or(Bindings.equal(severity, DiagnosticResult.Severity.INFO))), //
|
||||
EasyBind.includeWhen(getStyleClass(), "glyph-icon-primary", Bindings.equal(severity, DiagnosticResult.Severity.GOOD)), //
|
||||
EasyBind.includeWhen(getStyleClass(), "glyph-icon-orange", Bindings.equal(severity, DiagnosticResult.Severity.WARN).or(Bindings.equal(severity, DiagnosticResult.Severity.CRITICAL))), //
|
||||
EasyBind.includeWhen(getStyleClass(), "glyph-icon-red", Bindings.equal(state, Check.CheckState.ERROR)) //
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package org.cryptomator.ui.health;
|
||||
|
||||
import org.cryptomator.cryptofs.VaultConfig;
|
||||
import org.cryptomator.cryptofs.health.api.DiagnosticResult;
|
||||
import org.cryptomator.cryptolib.api.Cryptor;
|
||||
import org.cryptomator.cryptolib.api.Masterkey;
|
||||
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import java.nio.file.Path;
|
||||
|
||||
record Result(DiagnosticResult diagnosis, ObjectProperty<FixState> fixState) {
|
||||
|
||||
@@ -16,8 +20,8 @@ record Result(DiagnosticResult diagnosis, ObjectProperty<FixState> fixState) {
|
||||
FIX_FAILED
|
||||
}
|
||||
|
||||
public static Result create(DiagnosticResult diagnosis) {
|
||||
FixState initialState = diagnosis.getSeverity() == DiagnosticResult.Severity.WARN ? FixState.FIXABLE : FixState.NOT_FIXABLE;
|
||||
public static Result create(DiagnosticResult diagnosis, Path vaultPath, VaultConfig config, Masterkey masterkey, Cryptor cryptor) {
|
||||
FixState initialState = diagnosis.getFix(vaultPath, config, masterkey, cryptor).map( _f -> FixState.FIXABLE).orElse(FixState.NOT_FIXABLE);
|
||||
return new Result(diagnosis, new SimpleObjectProperty<>(initialState));
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
@HealthCheckScoped
|
||||
class ResultFixApplier {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ResultFixApplier.class);
|
||||
|
||||
private final Path vaultPath;
|
||||
private final SecureRandom csprng;
|
||||
private final Masterkey masterkey;
|
||||
@@ -40,25 +42,34 @@ class ResultFixApplier {
|
||||
|
||||
public CompletionStage<Void> fix(Result result) {
|
||||
Preconditions.checkArgument(result.getState() == Result.FixState.FIXABLE);
|
||||
result.setState(Result.FixState.FIXING);
|
||||
return CompletableFuture.runAsync(() -> fix(result.diagnosis()), sequentialExecutor)
|
||||
return CompletableFuture.runAsync(() -> result.setState(Result.FixState.FIXING), Platform::runLater) //
|
||||
.thenRunAsync(() -> fix(result.diagnosis()), sequentialExecutor) //
|
||||
.whenCompleteAsync((unused, throwable) -> {
|
||||
var fixed = throwable == null ? Result.FixState.FIXED : Result.FixState.FIX_FAILED;
|
||||
result.setState(fixed);
|
||||
final Result.FixState s;
|
||||
if (throwable == null) {
|
||||
LOG.debug("Fix for {} applied successful.", result.diagnosis().getClass().getName());
|
||||
s = Result.FixState.FIXED;
|
||||
} else {
|
||||
LOG.error("Failed to apply fix for {}", result.diagnosis().getClass().getName(), throwable);
|
||||
s = Result.FixState.FIX_FAILED;
|
||||
}
|
||||
result.setState(s);
|
||||
}, Platform::runLater);
|
||||
}
|
||||
|
||||
public void fix(DiagnosticResult diagnosis) {
|
||||
Preconditions.checkArgument(diagnosis.getSeverity() == DiagnosticResult.Severity.WARN, "Unfixable result");
|
||||
private void fix(DiagnosticResult diagnosis) {
|
||||
try (var masterkeyClone = masterkey.copy(); //
|
||||
var cryptor = CryptorProvider.forScheme(vaultConfig.getCipherCombo()).provide(masterkeyClone, csprng)) {
|
||||
diagnosis.fix(vaultPath, vaultConfig, masterkeyClone, cryptor);
|
||||
diagnosis.getFix(vaultPath, vaultConfig, masterkeyClone, cryptor) //
|
||||
.orElseThrow(() -> new IllegalStateException("No fix for diagnosis " + diagnosis.getClass().getName() + " implemented.")) //
|
||||
.apply();
|
||||
} catch (Exception e) {
|
||||
throw new FixFailedException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static class FixFailedException extends CompletionException {
|
||||
|
||||
private FixFailedException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
@@ -12,14 +12,12 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Binding;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableObjectValue;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.util.Duration;
|
||||
@@ -35,25 +33,24 @@ public class ResultListCellController implements FxController {
|
||||
private static final FontAwesome5Icon WARN_ICON = FontAwesome5Icon.EXCLAMATION_TRIANGLE;
|
||||
private static final FontAwesome5Icon CRIT_ICON = FontAwesome5Icon.TIMES;
|
||||
|
||||
private final Logger LOG = LoggerFactory.getLogger(ResultListCellController.class);
|
||||
|
||||
private final ObjectProperty<Result> result;
|
||||
private final ObservableObjectValue<DiagnosticResult.Severity> severity;
|
||||
private final Binding<String> description;
|
||||
private final ObservableValue<DiagnosticResult.Severity> severity;
|
||||
private final ObservableValue<String> description;
|
||||
private final ResultFixApplier fixApplier;
|
||||
private final ObservableObjectValue<Result.FixState> fixState;
|
||||
private final ObservableValue<Result.FixState> fixState;
|
||||
private final ObjectBinding<FontAwesome5Icon> severityGlyph;
|
||||
private final ObjectBinding<FontAwesome5Icon> fixGlyph;
|
||||
private final BooleanBinding fixable;
|
||||
private final BooleanBinding fixing;
|
||||
private final BooleanBinding fixed;
|
||||
private final BooleanBinding fixFailed;
|
||||
private final BooleanBinding fixRunningOrDone;
|
||||
private final ObservableValue<FontAwesome5Icon> fixGlyph;
|
||||
private final List<Subscription> subscriptions;
|
||||
private final Tooltip fixSuccess;
|
||||
private final Tooltip fixFail;
|
||||
|
||||
private final Tooltip fixStateTip;
|
||||
private final Tooltip severityTip;
|
||||
private AutoAnimator fixRunningRotator;
|
||||
private final ResourceBundle resourceBundle;
|
||||
|
||||
/* FXML */
|
||||
public FontAwesome5IconView severityView;
|
||||
@@ -61,54 +58,83 @@ public class ResultListCellController implements FxController {
|
||||
|
||||
@Inject
|
||||
public ResultListCellController(ResultFixApplier fixApplier, ResourceBundle resourceBundle) {
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.result = new SimpleObjectProperty<>(null);
|
||||
this.severity = EasyBind.wrapNullable(result).map(r -> r.diagnosis().getSeverity()).asOrdinary();
|
||||
this.description = EasyBind.wrapNullable(result).map(Result::getDescription).orElse("");
|
||||
this.severity = result.map(Result::diagnosis).map(DiagnosticResult::getSeverity);
|
||||
this.description = result.map(Result::getDescription).orElse("");
|
||||
this.fixApplier = fixApplier;
|
||||
this.fixState = EasyBind.wrapNullable(result).mapObservable(Result::fixState).asOrdinary();
|
||||
this.fixState = result.flatMap(Result::fixState);
|
||||
this.severityGlyph = Bindings.createObjectBinding(this::getSeverityGlyph, result);
|
||||
this.fixGlyph = Bindings.createObjectBinding(this::getFixGlyph, fixState);
|
||||
this.fixable = Bindings.createBooleanBinding(this::isFixable, fixState);
|
||||
this.fixing = Bindings.createBooleanBinding(this::isFixing, fixState);
|
||||
this.fixed = Bindings.createBooleanBinding(this::isFixed, fixState);
|
||||
this.fixFailed = Bindings.createBooleanBinding(this::isFixFailed, fixState);
|
||||
this.fixRunningOrDone = fixing.or(fixed).or(fixFailed);
|
||||
this.fixGlyph = fixState.map(this::getFixGlyph);
|
||||
this.subscriptions = new ArrayList<>();
|
||||
this.fixSuccess = new Tooltip(resourceBundle.getString("health.fix.successTip"));
|
||||
this.fixFail = new Tooltip(resourceBundle.getString("health.fix.failTip"));
|
||||
fixSuccess.setShowDelay(Duration.millis(100));
|
||||
fixFail.setShowDelay(Duration.millis(100));
|
||||
|
||||
this.fixStateTip = new Tooltip();
|
||||
fixStateTip.textProperty().bind(fixState.map(this::getFixStateDescription));
|
||||
fixStateTip.setShowDelay(Duration.millis(100));
|
||||
|
||||
this.severityTip = new Tooltip();
|
||||
severityTip.textProperty().bind(severity.map(this::getSeverityDescription));
|
||||
severityTip.setShowDelay(Duration.millis(150));
|
||||
}
|
||||
|
||||
public FontAwesome5Icon getFixGlyph(Result.FixState state) {
|
||||
return switch (state) {
|
||||
case FIXING -> FontAwesome5Icon.SPINNER;
|
||||
case FIXED -> FontAwesome5Icon.CHECK;
|
||||
case FIX_FAILED -> FontAwesome5Icon.TIMES;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
private String getFixStateDescription(Result.FixState fixState) {
|
||||
return switch (fixState) {
|
||||
case FIXED -> resourceBundle.getString("health.fix.successTip");
|
||||
case FIX_FAILED -> resourceBundle.getString("health.fix.failTip");
|
||||
default -> "";
|
||||
};
|
||||
}
|
||||
|
||||
private String getSeverityDescription(DiagnosticResult.Severity severity) {
|
||||
return resourceBundle.getString(switch (severity) {
|
||||
case GOOD -> "health.result.severityTip.good";
|
||||
case INFO -> "health.result.severityTip.info";
|
||||
case WARN -> "health.result.severityTip.warn";
|
||||
case CRITICAL -> "health.result.severityTip.crit";
|
||||
});
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
// see getGlyph() for relevant glyphs:
|
||||
subscriptions.addAll(List.of(EasyBind.includeWhen(severityView.getStyleClass(), "glyph-icon-muted", Bindings.equal(severity, DiagnosticResult.Severity.INFO)), //
|
||||
EasyBind.includeWhen(severityView.getStyleClass(), "glyph-icon-primary", Bindings.equal(severity, DiagnosticResult.Severity.GOOD)), //
|
||||
EasyBind.includeWhen(severityView.getStyleClass(), "glyph-icon-orange", Bindings.equal(severity, DiagnosticResult.Severity.WARN)), //
|
||||
EasyBind.includeWhen(severityView.getStyleClass(), "glyph-icon-red", Bindings.equal(severity, DiagnosticResult.Severity.CRITICAL)) //
|
||||
subscriptions.addAll(List.of(EasyBind.includeWhen(severityView.getStyleClass(), "glyph-icon-muted", severity.map(DiagnosticResult.Severity.INFO::equals).orElse(false)), //
|
||||
EasyBind.includeWhen(severityView.getStyleClass(), "glyph-icon-primary", severity.map(DiagnosticResult.Severity.GOOD::equals).orElse(false)), //
|
||||
EasyBind.includeWhen(severityView.getStyleClass(), "glyph-icon-orange", severity.map(DiagnosticResult.Severity.WARN::equals).orElse(false)), //
|
||||
EasyBind.includeWhen(severityView.getStyleClass(), "glyph-icon-red", severity.map(DiagnosticResult.Severity.CRITICAL::equals).orElse(false)) //
|
||||
));
|
||||
|
||||
var animation = Animations.createDiscrete360Rotation(fixView);
|
||||
this.fixRunningRotator = AutoAnimator.animate(animation) //
|
||||
.onCondition(Bindings.equal(fixState, Result.FixState.FIXING)) //
|
||||
.onCondition(fixing) //
|
||||
.afterStop(() -> fixView.setRotate(0)) //
|
||||
.build();
|
||||
fixState.addListener(((observable, oldValue, newValue) -> {
|
||||
if (newValue == Result.FixState.FIXED || newValue == Result.FixState.FIX_FAILED) {
|
||||
Tooltip.install(fixView, fixStateTip);
|
||||
}
|
||||
}));
|
||||
Tooltip.install(severityView, severityTip);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void fix() {
|
||||
Result r = result.get();
|
||||
if (r != null) {
|
||||
fixApplier.fix(r).whenCompleteAsync(this::fixFinished, Platform::runLater);
|
||||
}
|
||||
}
|
||||
|
||||
private void fixFinished(Void unused, Throwable exception) {
|
||||
if (exception != null) {
|
||||
LOG.error("Failed to apply fix", exception);
|
||||
Tooltip.install(fixView, fixFail);
|
||||
} else {
|
||||
Tooltip.install(fixView, fixSuccess);
|
||||
fixApplier.fix(r);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +153,7 @@ public class ResultListCellController implements FxController {
|
||||
return result;
|
||||
}
|
||||
|
||||
public Binding<String> descriptionProperty() {
|
||||
public ObservableValue<String> descriptionProperty() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@@ -152,20 +178,12 @@ public class ResultListCellController implements FxController {
|
||||
};
|
||||
}
|
||||
|
||||
public ObjectBinding<FontAwesome5Icon> fixGlyphProperty() {
|
||||
public ObservableValue<FontAwesome5Icon> fixGlyphProperty() {
|
||||
return fixGlyph;
|
||||
}
|
||||
|
||||
public FontAwesome5Icon getFixGlyph() {
|
||||
if (fixState.getValue() == null) {
|
||||
return null;
|
||||
}
|
||||
return switch (fixState.getValue()) {
|
||||
case NOT_FIXABLE, FIXABLE -> null;
|
||||
case FIXING -> FontAwesome5Icon.SPINNER;
|
||||
case FIXED -> FontAwesome5Icon.CHECK;
|
||||
case FIX_FAILED -> FontAwesome5Icon.TIMES;
|
||||
};
|
||||
return fixGlyph.getValue();
|
||||
}
|
||||
|
||||
public BooleanBinding fixableProperty() {
|
||||
@@ -173,7 +191,7 @@ public class ResultListCellController implements FxController {
|
||||
}
|
||||
|
||||
public boolean isFixable() {
|
||||
return Result.FixState.FIXABLE.equals(fixState.get());
|
||||
return Result.FixState.FIXABLE.equals(fixState.getValue());
|
||||
}
|
||||
|
||||
public BooleanBinding fixingProperty() {
|
||||
@@ -181,7 +199,7 @@ public class ResultListCellController implements FxController {
|
||||
}
|
||||
|
||||
public boolean isFixing() {
|
||||
return Result.FixState.FIXING.equals(fixState.get());
|
||||
return Result.FixState.FIXING.equals(fixState.getValue());
|
||||
}
|
||||
|
||||
public BooleanBinding fixedProperty() {
|
||||
@@ -189,7 +207,7 @@ public class ResultListCellController implements FxController {
|
||||
}
|
||||
|
||||
public boolean isFixed() {
|
||||
return Result.FixState.FIXED.equals(fixState.get());
|
||||
return Result.FixState.FIXED.equals(fixState.getValue());
|
||||
}
|
||||
|
||||
public BooleanBinding fixFailedProperty() {
|
||||
@@ -197,7 +215,7 @@ public class ResultListCellController implements FxController {
|
||||
}
|
||||
|
||||
public Boolean isFixFailed() {
|
||||
return Result.FixState.FIX_FAILED.equals(fixState.get());
|
||||
return Result.FixState.FIX_FAILED.equals(fixState.getValue());
|
||||
}
|
||||
|
||||
public BooleanBinding fixRunningOrDoneProperty() {
|
||||
|
||||
@@ -56,8 +56,10 @@ public abstract class HubKeyLoadingModule {
|
||||
@Named("deviceId")
|
||||
static String provideDeviceId(DeviceKey deviceKey) {
|
||||
var publicKey = Objects.requireNonNull(deviceKey.get()).getPublic().getEncoded();
|
||||
var hashedKey = MessageDigestSupplier.SHA256.get().digest(publicKey);
|
||||
return BaseEncoding.base16().encode(hashedKey);
|
||||
try (var instance = MessageDigestSupplier.SHA256.instance()) {
|
||||
var hashedKey = instance.get().digest(publicKey);
|
||||
return BaseEncoding.base16().encode(hashedKey);
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -50,7 +50,7 @@ public class PassphraseEntryController implements FxController {
|
||||
private final KeychainManager keychain;
|
||||
private final StringBinding vaultName;
|
||||
private final BooleanProperty unlockInProgress = new SimpleBooleanProperty();
|
||||
private final ObjectBinding<ContentDisplay> unlockButtonContentDisplay = Bindings.createObjectBinding(this::getUnlockButtonContentDisplay, unlockInProgress);
|
||||
private final ObjectBinding<ContentDisplay> unlockButtonContentDisplay = Bindings.when(unlockInProgress).then(ContentDisplay.LEFT).otherwise(ContentDisplay.TEXT_ONLY);
|
||||
private final BooleanProperty unlockButtonDisabled = new SimpleBooleanProperty();
|
||||
|
||||
/* FXML */
|
||||
@@ -186,7 +186,7 @@ public class PassphraseEntryController implements FxController {
|
||||
}
|
||||
|
||||
public ContentDisplay getUnlockButtonContentDisplay() {
|
||||
return unlockInProgress.get() ? ContentDisplay.LEFT : ContentDisplay.TEXT_ONLY;
|
||||
return unlockButtonContentDisplay.get();
|
||||
}
|
||||
|
||||
public ReadOnlyBooleanProperty userInteractionDisabledProperty() {
|
||||
|
||||
@@ -46,7 +46,7 @@ public class ResizeController implements FxController {
|
||||
ResizeController(@MainWindow Stage window, Settings settings) {
|
||||
this.window = window;
|
||||
this.settings = settings;
|
||||
this.showResizingArrows = Bindings.createBooleanBinding(this::isShowResizingArrows, window.fullScreenProperty());
|
||||
this.showResizingArrows = window.fullScreenProperty().not();
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -181,8 +181,7 @@ public class ResizeController implements FxController {
|
||||
}
|
||||
|
||||
public boolean isShowResizingArrows() {
|
||||
//If in fullscreen resizing is not be possible;
|
||||
return !window.isFullScreen();
|
||||
return showResizingArrows.get();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.cryptomator.ui.mainwindow;
|
||||
|
||||
import com.tobiasdiez.easybind.EasyBind;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultState;
|
||||
import org.cryptomator.ui.common.Animations;
|
||||
@@ -11,10 +10,10 @@ import org.cryptomator.ui.controls.FontAwesome5IconView;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.application.Application;
|
||||
import javafx.beans.binding.Binding;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
|
||||
@MainWindowScoped
|
||||
@@ -22,7 +21,7 @@ public class VaultDetailController implements FxController {
|
||||
|
||||
private final ReadOnlyObjectProperty<Vault> vault;
|
||||
private final Application application;
|
||||
private final Binding<FontAwesome5Icon> glyph;
|
||||
private final ObservableValue<FontAwesome5Icon> glyph;
|
||||
private final BooleanBinding anyVaultSelected;
|
||||
|
||||
private AutoAnimator spinAnimation;
|
||||
@@ -35,15 +34,13 @@ public class VaultDetailController implements FxController {
|
||||
VaultDetailController(ObjectProperty<Vault> vault, Application application) {
|
||||
this.vault = vault;
|
||||
this.application = application;
|
||||
this.glyph = EasyBind.select(vault) //
|
||||
.selectObject(Vault::stateProperty) //
|
||||
.map(this::getGlyphForVaultState);
|
||||
this.glyph = vault.flatMap(Vault::stateProperty).map(this::getGlyphForVaultState);
|
||||
this.anyVaultSelected = vault.isNotNull();
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
this.spinAnimation = AutoAnimator.animate(Animations.createDiscrete360Rotation(vaultStateView)) //
|
||||
.onCondition(EasyBind.select(vault).selectObject(Vault::stateProperty).map(VaultState.Value.PROCESSING::equals)) //
|
||||
.onCondition(vault.flatMap(Vault::stateProperty).map(VaultState.Value.PROCESSING::equals).orElse(false)) //
|
||||
.afterStop(() -> vaultStateView.setRotate(0)) //
|
||||
.build();
|
||||
}
|
||||
@@ -77,7 +74,7 @@ public class VaultDetailController implements FxController {
|
||||
return vault.get();
|
||||
}
|
||||
|
||||
public Binding<FontAwesome5Icon> glyphProperty() {
|
||||
public ObservableValue<FontAwesome5Icon> glyphProperty() {
|
||||
return glyph;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.cryptomator.ui.mainwindow;
|
||||
|
||||
import com.tobiasdiez.easybind.EasyBind;
|
||||
import org.cryptomator.common.keychain.KeychainManager;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
@@ -9,10 +8,10 @@ import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab;
|
||||
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.binding.BooleanExpression;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
@@ -24,7 +23,7 @@ public class VaultDetailLockedController implements FxController {
|
||||
private final VaultOptionsComponent.Factory vaultOptionsWindow;
|
||||
private final KeychainManager keychain;
|
||||
private final Stage mainWindow;
|
||||
private final BooleanExpression passwordSaved;
|
||||
private final ObservableValue<Boolean> passwordSaved;
|
||||
|
||||
@Inject
|
||||
VaultDetailLockedController(ObjectProperty<Vault> vault, FxApplicationWindows appWindows, VaultOptionsComponent.Factory vaultOptionsWindow, KeychainManager keychain, @MainWindow Stage mainWindow) {
|
||||
@@ -34,7 +33,7 @@ public class VaultDetailLockedController implements FxController {
|
||||
this.keychain = keychain;
|
||||
this.mainWindow = mainWindow;
|
||||
if (keychain.isSupported() && !keychain.isLocked()) {
|
||||
this.passwordSaved = BooleanExpression.booleanExpression(EasyBind.select(vault).selectObject(v -> keychain.getPassphraseStoredProperty(v.getId())));
|
||||
this.passwordSaved = vault.flatMap(v -> keychain.getPassphraseStoredProperty(v.getId())).orElse(false);
|
||||
} else {
|
||||
this.passwordSaved = new SimpleBooleanProperty(false);
|
||||
}
|
||||
@@ -65,13 +64,11 @@ public class VaultDetailLockedController implements FxController {
|
||||
return vault.get();
|
||||
}
|
||||
|
||||
public BooleanExpression passwordSavedProperty() {
|
||||
public ObservableValue<Boolean> passwordSavedProperty() {
|
||||
return passwordSaved;
|
||||
}
|
||||
|
||||
public boolean isPasswordSaved() {
|
||||
if (keychain.isSupported() && vault.get() != null) {
|
||||
return keychain.getPassphraseStoredProperty(vault.get().getId()).get();
|
||||
} else return false;
|
||||
return passwordSaved.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.cryptomator.ui.mainwindow;
|
||||
|
||||
import com.tobiasdiez.easybind.EasyBind;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultState;
|
||||
import org.cryptomator.ui.common.Animations;
|
||||
@@ -10,15 +9,15 @@ import org.cryptomator.ui.controls.FontAwesome5Icon;
|
||||
import org.cryptomator.ui.controls.FontAwesome5IconView;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.binding.Binding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
||||
// unscoped because each cell needs its own controller
|
||||
public class VaultListCellController implements FxController {
|
||||
|
||||
private final ObjectProperty<Vault> vault = new SimpleObjectProperty<>();
|
||||
private final Binding<FontAwesome5Icon> glyph;
|
||||
private final ObservableValue<FontAwesome5Icon> glyph;
|
||||
|
||||
private AutoAnimator spinAnimation;
|
||||
|
||||
@@ -27,14 +26,12 @@ public class VaultListCellController implements FxController {
|
||||
|
||||
@Inject
|
||||
VaultListCellController() {
|
||||
this.glyph = EasyBind.select(vault) //
|
||||
.selectObject(Vault::stateProperty) //
|
||||
.map(this::getGlyphForVaultState);
|
||||
this.glyph = vault.flatMap(Vault::stateProperty).map(this::getGlyphForVaultState);
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
this.spinAnimation = AutoAnimator.animate(Animations.createDiscrete360Rotation(vaultStateView)) //
|
||||
.onCondition(EasyBind.select(vault).selectObject(Vault::stateProperty).map(VaultState.Value.PROCESSING::equals)) //
|
||||
.onCondition(vault.flatMap(Vault::stateProperty).map(VaultState.Value.PROCESSING::equals).orElse(false)) //
|
||||
.afterStop(() -> vaultStateView.setRotate(0)) //
|
||||
.build();
|
||||
}
|
||||
@@ -55,7 +52,7 @@ public class VaultListCellController implements FxController {
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public Binding<FontAwesome5Icon> glyphProperty() {
|
||||
public ObservableValue<FontAwesome5Icon> glyphProperty() {
|
||||
return glyph;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package org.cryptomator.ui.mainwindow;
|
||||
|
||||
import com.tobiasdiez.easybind.EasyBind;
|
||||
import com.tobiasdiez.easybind.optional.ObservableOptionalValue;
|
||||
import com.tobiasdiez.easybind.optional.OptionalBinding;
|
||||
import org.cryptomator.common.keychain.KeychainManager;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultState;
|
||||
@@ -14,33 +11,39 @@ import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab;
|
||||
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.binding.Binding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.stage.Stage;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.*;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.ERROR;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.LOCKED;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.MISSING;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.NEEDS_MIGRATION;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.UNLOCKED;
|
||||
|
||||
@MainWindowScoped
|
||||
public class VaultListContextMenuController implements FxController {
|
||||
|
||||
private final ObservableOptionalValue<Vault> selectedVault;
|
||||
private final ReadOnlyObjectProperty<Vault> selectedVault;
|
||||
private final Stage mainWindow;
|
||||
private final FxApplicationWindows appWindows;
|
||||
private final VaultService vaultService;
|
||||
private final KeychainManager keychain;
|
||||
private final RemoveVaultComponent.Builder removeVault;
|
||||
private final VaultOptionsComponent.Factory vaultOptionsWindow;
|
||||
private final OptionalBinding<VaultState.Value> selectedVaultState;
|
||||
private final Binding<Boolean> selectedVaultPassphraseStored;
|
||||
private final Binding<Boolean> selectedVaultRemovable;
|
||||
private final Binding<Boolean> selectedVaultUnlockable;
|
||||
private final Binding<Boolean> selectedVaultLockable;
|
||||
private final ObservableValue<VaultState.Value> selectedVaultState;
|
||||
private final ObservableValue<Boolean> selectedVaultPassphraseStored;
|
||||
private final ObservableValue<Boolean> selectedVaultRemovable;
|
||||
private final ObservableValue<Boolean> selectedVaultUnlockable;
|
||||
private final ObservableValue<Boolean> selectedVaultLockable;
|
||||
|
||||
@Inject
|
||||
VaultListContextMenuController(ObjectProperty<Vault> selectedVault, @MainWindow Stage mainWindow, FxApplicationWindows appWindows, VaultService vaultService, KeychainManager keychain, RemoveVaultComponent.Builder removeVault, VaultOptionsComponent.Factory vaultOptionsWindow) {
|
||||
this.selectedVault = EasyBind.wrapNullable(selectedVault);
|
||||
this.selectedVault = selectedVault;
|
||||
this.mainWindow = mainWindow;
|
||||
this.appWindows = appWindows;
|
||||
this.vaultService = vaultService;
|
||||
@@ -48,8 +51,8 @@ public class VaultListContextMenuController implements FxController {
|
||||
this.removeVault = removeVault;
|
||||
this.vaultOptionsWindow = vaultOptionsWindow;
|
||||
|
||||
this.selectedVaultState = this.selectedVault.mapObservable(Vault::stateProperty);
|
||||
this.selectedVaultPassphraseStored = this.selectedVault.map(this::isPasswordStored).orElse(false);
|
||||
this.selectedVaultState = selectedVault.flatMap(Vault::stateProperty).orElse(null);
|
||||
this.selectedVaultPassphraseStored = selectedVault.map(this::isPasswordStored).orElse(false);
|
||||
this.selectedVaultRemovable = selectedVaultState.map(EnumSet.of(LOCKED, MISSING, ERROR, NEEDS_MIGRATION)::contains).orElse(false);
|
||||
this.selectedVaultUnlockable = selectedVaultState.map(LOCKED::equals).orElse(false);
|
||||
this.selectedVaultLockable = selectedVaultState.map(UNLOCKED::equals).orElse(false);
|
||||
@@ -61,40 +64,37 @@ public class VaultListContextMenuController implements FxController {
|
||||
|
||||
@FXML
|
||||
public void didClickRemoveVault() {
|
||||
selectedVault.ifValuePresent(v -> {
|
||||
removeVault.vault(v).build().showRemoveVault();
|
||||
});
|
||||
var vault = Objects.requireNonNull(selectedVault.get());
|
||||
removeVault.vault(vault).build().showRemoveVault();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void didClickShowVaultOptions() {
|
||||
selectedVault.ifValuePresent(v -> {
|
||||
vaultOptionsWindow.create(v).showVaultOptionsWindow(SelectedVaultOptionsTab.ANY);
|
||||
});
|
||||
var vault = Objects.requireNonNull(selectedVault.get());
|
||||
vaultOptionsWindow.create(vault).showVaultOptionsWindow(SelectedVaultOptionsTab.ANY);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void didClickUnlockVault() {
|
||||
selectedVault.ifValuePresent(v -> {
|
||||
appWindows.startUnlockWorkflow(v, mainWindow);
|
||||
});
|
||||
var vault = Objects.requireNonNull(selectedVault.get());
|
||||
appWindows.startUnlockWorkflow(vault, mainWindow);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void didClickLockVault() {
|
||||
selectedVault.ifValuePresent(v -> {
|
||||
appWindows.startLockWorkflow(v, mainWindow);
|
||||
});
|
||||
var vault = Objects.requireNonNull(selectedVault.get());
|
||||
appWindows.startLockWorkflow(vault, mainWindow);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void didClickRevealVault() {
|
||||
selectedVault.ifValuePresent(vaultService::reveal);
|
||||
var vault = Objects.requireNonNull(selectedVault.get());
|
||||
vaultService.reveal(vault);
|
||||
}
|
||||
|
||||
// Getter and Setter
|
||||
|
||||
public Binding<Boolean> selectedVaultUnlockableProperty() {
|
||||
public ObservableValue<Boolean> selectedVaultUnlockableProperty() {
|
||||
return selectedVaultUnlockable;
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ public class VaultListContextMenuController implements FxController {
|
||||
return selectedVaultUnlockable.getValue();
|
||||
}
|
||||
|
||||
public Binding<Boolean> selectedVaultLockableProperty() {
|
||||
public ObservableValue<Boolean> selectedVaultLockableProperty() {
|
||||
return selectedVaultLockable;
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ public class VaultListContextMenuController implements FxController {
|
||||
return selectedVaultLockable.getValue();
|
||||
}
|
||||
|
||||
public Binding<Boolean> selectedVaultRemovableProperty() {
|
||||
public ObservableValue<Boolean> selectedVaultRemovableProperty() {
|
||||
return selectedVaultRemovable;
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ public class VaultListContextMenuController implements FxController {
|
||||
return selectedVaultRemovable.getValue();
|
||||
}
|
||||
|
||||
public Binding<Boolean> selectedVaultPassphraseStoredProperty() {
|
||||
public ObservableValue<Boolean> selectedVaultPassphraseStoredProperty() {
|
||||
return selectedVaultPassphraseStored;
|
||||
}
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ public class MigrationRunController implements FxController {
|
||||
this.appWindows = appWindows;
|
||||
this.startScene = startScene;
|
||||
this.successScene = successScene;
|
||||
this.migrateButtonContentDisplay = Bindings.createObjectBinding(this::getMigrateButtonContentDisplay, vault.stateProperty());
|
||||
this.migrateButtonContentDisplay = Bindings.when(vault.processingProperty()).then(ContentDisplay.LEFT).otherwise(ContentDisplay.TEXT_ONLY);
|
||||
this.capabilityErrorScene = capabilityErrorScene;
|
||||
this.migrationButtonDisabled = new SimpleBooleanProperty();
|
||||
this.migrationProgress = new SimpleDoubleProperty(volatileMigrationProgress);
|
||||
@@ -211,10 +211,7 @@ public class MigrationRunController implements FxController {
|
||||
}
|
||||
|
||||
public ContentDisplay getMigrateButtonContentDisplay() {
|
||||
return switch (vault.getState()) {
|
||||
case PROCESSING -> ContentDisplay.LEFT;
|
||||
default -> ContentDisplay.TEXT_ONLY;
|
||||
};
|
||||
return migrateButtonContentDisplay.get();
|
||||
}
|
||||
|
||||
public ReadOnlyDoubleProperty migrationProgressProperty() {
|
||||
|
||||
@@ -9,8 +9,10 @@ import javax.inject.Inject;
|
||||
import javafx.animation.Animation;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.DoubleBinding;
|
||||
import javafx.beans.binding.LongBinding;
|
||||
import javafx.beans.property.LongProperty;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
@@ -21,6 +23,7 @@ import javafx.scene.chart.XYChart.Series;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
@VaultStatisticsScoped
|
||||
public class VaultStatisticsController implements FxController {
|
||||
@@ -32,6 +35,7 @@ public class VaultStatisticsController implements FxController {
|
||||
private final VaultStats stats;
|
||||
private final Series<Number, Number> readData;
|
||||
private final Series<Number, Number> writeData;
|
||||
private final Series<Number, Number> accessData;
|
||||
private final Timeline ioAnimation;
|
||||
private final LongBinding bpsRead;
|
||||
private final LongBinding bpsWritten;
|
||||
@@ -44,15 +48,20 @@ public class VaultStatisticsController implements FxController {
|
||||
private final LongBinding totalBytesDecrypted;
|
||||
private final LongBinding filesRead;
|
||||
private final LongBinding filesWritten;
|
||||
private final LongBinding filesAccessed;
|
||||
private final LongBinding totalFilesAccessed;
|
||||
private final LongBinding bpsEncrypted;
|
||||
private final LongBinding bpsDecrypted;
|
||||
|
||||
public AreaChart<Number, Number> readChart;
|
||||
public AreaChart<Number, Number> writeChart;
|
||||
public AreaChart<Number, Number> accessChart;
|
||||
public NumberAxis readChartXAxis;
|
||||
public NumberAxis readChartYAxis;
|
||||
public NumberAxis writeChartXAxis;
|
||||
public NumberAxis writeChartYAxis;
|
||||
public NumberAxis accessChartXAxis;
|
||||
public NumberAxis accessChartYAxis;
|
||||
|
||||
@Inject
|
||||
public VaultStatisticsController(VaultStatisticsComponent component, @VaultStatisticsWindow Stage window, @VaultStatisticsWindow Vault vault) {
|
||||
@@ -60,6 +69,7 @@ public class VaultStatisticsController implements FxController {
|
||||
this.stats = vault.getStats();
|
||||
this.readData = new Series<>();
|
||||
this.writeData = new Series<>();
|
||||
this.accessData = new Series<>();
|
||||
this.bpsRead = WeakBindings.bindLong(stats.bytesPerSecondReadProperty());
|
||||
this.bpsWritten = WeakBindings.bindLong(stats.bytesPerSecondWrittenProperty());
|
||||
this.cacheHitRate = WeakBindings.bindDouble(stats.cacheHitRateProperty());
|
||||
@@ -71,11 +81,13 @@ public class VaultStatisticsController implements FxController {
|
||||
this.totalBytesEncrypted = WeakBindings.bindLong(stats.totalBytesEncryptedProperty());
|
||||
this.filesRead = WeakBindings.bindLong(stats.filesRead());
|
||||
this.filesWritten = WeakBindings.bindLong(stats.filesWritten());
|
||||
this.filesAccessed = WeakBindings.bindLong(stats.filesAccessed());
|
||||
this.totalFilesAccessed = WeakBindings.bindLong(stats.totalFilesAccessed());
|
||||
this.bpsEncrypted = WeakBindings.bindLong(stats.bytesPerSecondEncryptedProperty());
|
||||
this.bpsDecrypted = WeakBindings.bindLong(stats.bytesPerSecondDecryptedProperty());
|
||||
|
||||
this.ioAnimation = new Timeline(); //TODO Research better timer
|
||||
ioAnimation.getKeyFrames().add(new KeyFrame(Duration.seconds(IO_SAMPLING_INTERVAL), new IoSamplingAnimationHandler(readData, writeData)));
|
||||
ioAnimation.getKeyFrames().add(new KeyFrame(Duration.seconds(IO_SAMPLING_INTERVAL), new IoSamplingAnimationHandler(readData, writeData, accessData)));
|
||||
ioAnimation.setCycleCount(Animation.INDEFINITE);
|
||||
ioAnimation.play();
|
||||
|
||||
@@ -89,6 +101,7 @@ public class VaultStatisticsController implements FxController {
|
||||
public void initialize() {
|
||||
readChart.getData().addAll(readData);
|
||||
writeChart.getData().addAll(writeData);
|
||||
accessChart.getData().addAll(accessData);
|
||||
}
|
||||
|
||||
private class IoSamplingAnimationHandler implements EventHandler<ActionEvent> {
|
||||
@@ -96,16 +109,21 @@ public class VaultStatisticsController implements FxController {
|
||||
private long step = IO_SAMPLING_STEPS;
|
||||
private final Series<Number, Number> decryptedBytesRead;
|
||||
private final Series<Number, Number> encryptedBytesWrite;
|
||||
private final Series<Number, Number> accessedFiles;
|
||||
private final long[] maxBuf = new long[IO_SAMPLING_STEPS];
|
||||
private final long[] maxAccessBuf = new long[IO_SAMPLING_STEPS];
|
||||
|
||||
public IoSamplingAnimationHandler(Series<Number, Number> readData, Series<Number, Number> writeData) {
|
||||
|
||||
public IoSamplingAnimationHandler(Series<Number, Number> readData, Series<Number, Number> writeData, Series<Number, Number> accessData) {
|
||||
this.decryptedBytesRead = readData;
|
||||
this.encryptedBytesWrite = writeData;
|
||||
this.accessedFiles = accessData;
|
||||
|
||||
// initialize data once and change value of data points later:
|
||||
for (int i = 0; i < IO_SAMPLING_STEPS; i++) {
|
||||
decryptedBytesRead.getData().add(new Data<>(i, 0));
|
||||
encryptedBytesWrite.getData().add(new Data<>(i, 0));
|
||||
accessedFiles.getData().add(new Data<>(i, 0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,17 +132,22 @@ public class VaultStatisticsController implements FxController {
|
||||
final long currentStep = step++;
|
||||
final long decBytes = stats.bytesPerSecondReadProperty().get();
|
||||
final long encBytes = stats.bytesPerSecondWrittenProperty().get();
|
||||
final long accFiles = stats.filesAccessed().get();
|
||||
|
||||
maxBuf[(int) currentStep % IO_SAMPLING_STEPS] = Math.max(decBytes, encBytes);
|
||||
long allTimeMax = Arrays.stream(maxBuf).max().orElse(0l);
|
||||
long allTimeMax = Arrays.stream(maxBuf).max().orElse(0L);
|
||||
maxAccessBuf[(int) currentStep % IO_SAMPLING_STEPS] = accFiles;
|
||||
long allTimeMaxAccessedFiles = Arrays.stream(maxAccessBuf).max().orElse(0L);
|
||||
|
||||
// remove oldest value:
|
||||
decryptedBytesRead.getData().remove(0);
|
||||
encryptedBytesWrite.getData().remove(0);
|
||||
accessedFiles.getData().remove(0);
|
||||
|
||||
// add latest value:
|
||||
decryptedBytesRead.getData().add(new Data<>(currentStep, decBytes));
|
||||
encryptedBytesWrite.getData().add(new Data<>(currentStep, encBytes));
|
||||
accessedFiles.getData().add(new Data<>(currentStep, accFiles));
|
||||
|
||||
// adjust ranges:
|
||||
readChartXAxis.setLowerBound(currentStep - IO_SAMPLING_STEPS * 1.0);
|
||||
@@ -133,6 +156,9 @@ public class VaultStatisticsController implements FxController {
|
||||
writeChartXAxis.setLowerBound(currentStep - IO_SAMPLING_STEPS * 1.0);
|
||||
writeChartXAxis.setUpperBound(currentStep);
|
||||
writeChartYAxis.setUpperBound(allTimeMax);
|
||||
accessChartXAxis.setLowerBound(currentStep - IO_SAMPLING_STEPS * 1.0);
|
||||
accessChartXAxis.setUpperBound(currentStep);
|
||||
accessChartYAxis.setUpperBound(allTimeMaxAccessedFiles);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,4 +233,12 @@ public class VaultStatisticsController implements FxController {
|
||||
public LongBinding filesWrittenProperty() {return filesWritten;}
|
||||
|
||||
public long getFilesWritten() {return filesWritten.get();}
|
||||
|
||||
public LongBinding filesAccessedProperty() {return filesAccessed;}
|
||||
|
||||
public long getFilesAccessed() {return filesAccessed.get();}
|
||||
|
||||
public LongBinding totalFilesAccessedProperty() {return totalFilesAccessed;}
|
||||
|
||||
public long getTotalFilesAccessed() {return totalFilesAccessed.get();}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.cryptomator.ui.traymenu;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.integrations.common.CheckAvailability;
|
||||
import org.cryptomator.integrations.common.Priority;
|
||||
@@ -19,6 +20,8 @@ import java.awt.PopupMenu;
|
||||
import java.awt.SystemTray;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.TrayIcon;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.List;
|
||||
|
||||
@CheckAvailability
|
||||
@@ -28,6 +31,7 @@ public class AwtTrayMenuController implements TrayMenuController {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AwtTrayMenuController.class);
|
||||
|
||||
private final PopupMenu menu = new PopupMenu();
|
||||
private TrayIcon trayIcon;
|
||||
|
||||
@CheckAvailability
|
||||
public static boolean isAvailable() {
|
||||
@@ -37,7 +41,7 @@ public class AwtTrayMenuController implements TrayMenuController {
|
||||
@Override
|
||||
public void showTrayIcon(byte[] rawImageData, Runnable defaultAction, String tooltip) throws TrayMenuException {
|
||||
var image = Toolkit.getDefaultToolkit().createImage(rawImageData);
|
||||
var trayIcon = new TrayIcon(image, tooltip, menu);
|
||||
trayIcon = new TrayIcon(image, tooltip, menu);
|
||||
|
||||
trayIcon.setImageAutoSize(true);
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
@@ -58,6 +62,18 @@ public class AwtTrayMenuController implements TrayMenuController {
|
||||
addChildren(menu, items);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onBeforeOpenMenu(Runnable listener) {
|
||||
Preconditions.checkNotNull(this.trayIcon);
|
||||
this.trayIcon.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
listener.run();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addChildren(Menu menu, List<TrayMenuItem> items) {
|
||||
for (var item : items) {
|
||||
// TODO: use Pattern Matching for switch, once available
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.cryptomator.ui.traymenu;
|
||||
import com.google.common.base.Preconditions;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.integrations.tray.ActionItem;
|
||||
import org.cryptomator.integrations.tray.SeparatorItem;
|
||||
import org.cryptomator.integrations.tray.SubMenuItem;
|
||||
@@ -63,6 +64,11 @@ public class TrayMenuBuilder {
|
||||
|
||||
try (var image = getClass().getResourceAsStream(SystemUtils.IS_OS_MAC_OSX ? TRAY_ICON_MAC : TRAY_ICON)) {
|
||||
trayMenu.showTrayIcon(image.readAllBytes(), this::showMainWindow, "Cryptomator");
|
||||
trayMenu.onBeforeOpenMenu(() -> {
|
||||
for (Vault vault : vaults) {
|
||||
VaultListManager.redetermineVaultState(vault);
|
||||
}
|
||||
});
|
||||
rebuildMenu();
|
||||
initialized = true;
|
||||
} catch (IOException e) {
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.cryptomator.ui.vaultoptions;
|
||||
|
||||
import org.cryptomator.common.keychain.KeychainManager;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.integrations.keychain.KeychainAccessException;
|
||||
import org.cryptomator.ui.changepassword.ChangePasswordComponent;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent;
|
||||
@@ -11,9 +10,8 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanExpression;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
@@ -28,7 +26,7 @@ public class MasterkeyOptionsController implements FxController {
|
||||
private final RecoveryKeyComponent.Builder recoveryKeyWindow;
|
||||
private final ForgetPasswordComponent.Builder forgetPasswordWindow;
|
||||
private final KeychainManager keychain;
|
||||
private final BooleanExpression passwordSaved;
|
||||
private final ObservableValue<Boolean> passwordSaved;
|
||||
|
||||
|
||||
@Inject
|
||||
@@ -40,7 +38,7 @@ public class MasterkeyOptionsController implements FxController {
|
||||
this.forgetPasswordWindow = forgetPasswordWindow;
|
||||
this.keychain = keychain;
|
||||
if (keychain.isSupported() && !keychain.isLocked()) {
|
||||
this.passwordSaved = Bindings.createBooleanBinding(this::isPasswordSaved, keychain.getPassphraseStoredProperty(vault.getId()));
|
||||
this.passwordSaved = keychain.getPassphraseStoredProperty(vault.getId()).orElse(false);
|
||||
} else {
|
||||
this.passwordSaved = new SimpleBooleanProperty(false);
|
||||
}
|
||||
@@ -67,13 +65,11 @@ public class MasterkeyOptionsController implements FxController {
|
||||
forgetPasswordWindow.vault(vault).owner(window).build().showForgetPassword();
|
||||
}
|
||||
|
||||
public BooleanExpression passwordSavedProperty() {
|
||||
public ObservableValue<Boolean> passwordSavedProperty() {
|
||||
return passwordSaved;
|
||||
}
|
||||
|
||||
public boolean isPasswordSaved() {
|
||||
if (keychain.isSupported() && !keychain.isLocked() && vault != null) {
|
||||
return keychain.getPassphraseStoredProperty(vault.getId()).get();
|
||||
} else return false;
|
||||
return passwordSaved.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,25 +5,59 @@
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import org.cryptomator.ui.health.CheckStateIconView?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.control.ChoiceBox?>
|
||||
<?import javafx.scene.control.ContextMenu?>
|
||||
<?import javafx.scene.control.MenuItem?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.health.CheckDetailController"
|
||||
spacing="6">
|
||||
<Label fx:id="detailHeader" styleClass="label-extra-large" text="${controller.checkName}" contentDisplay="LEFT">
|
||||
<graphic>
|
||||
<HBox alignment="CENTER" minWidth="25" maxWidth="25">
|
||||
<CheckStateIconView fx:id="checkStateIconView" check="${controller.check}" glyphSize="20"/>
|
||||
</HBox>
|
||||
</graphic>
|
||||
</Label>
|
||||
spacing="12">
|
||||
<HBox alignment="CENTER" >
|
||||
<VBox spacing="12">
|
||||
<Label fx:id="detailHeader" styleClass="label-extra-large" text="${controller.checkName}" contentDisplay="RIGHT">
|
||||
<graphic>
|
||||
<HBox alignment="CENTER" minWidth="25" maxWidth="25">
|
||||
<CheckStateIconView check="${controller.check}" glyphSize="20"/>
|
||||
</HBox>
|
||||
</graphic>
|
||||
</Label>
|
||||
<Label text="%health.check.detail.checkRunning" visible="${controller.checkRunning}" managed="${controller.checkRunning}"/>
|
||||
<Label text="%health.check.detail.checkScheduled" visible="${controller.checkScheduled}" managed="${controller.checkScheduled}"/>
|
||||
<Label text="%health.check.detail.checkSkipped" visible="${controller.checkSkipped}" managed="${controller.checkSkipped}"/>
|
||||
<Label text="%health.check.detail.checkCancelled" visible="${controller.checkCancelled}" managed="${controller.checkCancelled}"/>
|
||||
<Label text="%health.check.detail.checkFailed" visible="${controller.checkFailed}" managed="${controller.checkFailed}"/>
|
||||
<Label text="%health.check.detail.checkFinished" visible="${controller.checkSucceeded && !controller.warnOrCritsExist}" managed="${controller.checkSucceeded && !controller.warnOrCritsExist}"/>
|
||||
<Label text="%health.check.detail.checkFinishedAndFound" visible="${controller.checkSucceeded && controller.warnOrCritsExist}" managed="${controller.checkSucceeded && controller.warnOrCritsExist}"/>
|
||||
</VBox>
|
||||
<Region HBox.hgrow="ALWAYS"/>
|
||||
<Button text="%health.check.detail.fixAllSpecificBtn" contentDisplay="RIGHT" graphicTextGap="3" visible="${controller.checkFinished}" managed="${controller.checkFinished}" disable="${! controller.fixAllInfoResultsPossible}" onAction="#fixAllInfoResults">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="INFO_CIRCLE" glyphSize="12" styleClass="glyph-icon-muted"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
|
||||
<Label text="%health.check.detail.checkRunning" visible="${controller.checkRunning}" managed="${controller.checkRunning}"/>
|
||||
<Label text="%health.check.detail.checkScheduled" visible="${controller.checkScheduled}" managed="${controller.checkScheduled}"/>
|
||||
<Label text="%health.check.detail.checkSkipped" visible="${controller.checkSkipped}" managed="${controller.checkSkipped}"/>
|
||||
<Label text="%health.check.detail.checkCancelled" visible="${controller.checkCancelled}" managed="${controller.checkCancelled}"/>
|
||||
<Label text="%health.check.detail.checkFailed" visible="${controller.checkFailed}" managed="${controller.checkFailed}"/>
|
||||
<Label text="%health.check.detail.checkFinished" visible="${controller.checkSucceeded && !controller.warnOrCritsExist}" managed="${controller.checkSucceeded && !controller.warnOrCritsExist}"/>
|
||||
<Label text="%health.check.detail.checkFinishedAndFound" visible="${controller.checkSucceeded && controller.warnOrCritsExist}" managed="${controller.checkSucceeded && controller.warnOrCritsExist}"/>
|
||||
|
||||
<ListView fx:id="resultsListView" VBox.vgrow="ALWAYS" visible="${!controller.checkSkipped}" fixedCellSize="25"/>
|
||||
</HBox>
|
||||
<VBox spacing="6" VBox.vgrow="ALWAYS">
|
||||
<HBox alignment="CENTER_LEFT" spacing="6">
|
||||
<Label fx:id="filterLbl" text="%health.check.detail.listFilters.label">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="FUNNEL" glyphSize="${filterLbl.height}" styleClass="glyph-icon-muted"/>
|
||||
</graphic>
|
||||
</Label>
|
||||
<ChoiceBox fx:id="severityChoiceBox" />
|
||||
<ChoiceBox fx:id="fixStateChoiceBox" />
|
||||
</HBox>
|
||||
<ListView fx:id="resultsListView" VBox.vgrow="ALWAYS" visible="${!controller.checkSkipped}" fixedCellSize="25">
|
||||
<contextMenu>
|
||||
<ContextMenu>
|
||||
<items>
|
||||
<MenuItem text="%generic.button.copy" onAction="#copyResultDetails"/>
|
||||
</items>
|
||||
</ContextMenu>
|
||||
</contextMenu>
|
||||
</ListView>
|
||||
</VBox>
|
||||
</VBox>
|
||||
@@ -12,7 +12,7 @@
|
||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.health.CheckListController"
|
||||
prefWidth="600"
|
||||
prefWidth="650"
|
||||
prefHeight="400"
|
||||
spacing="12">
|
||||
<padding>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.health.StartController"
|
||||
prefWidth="600"
|
||||
prefWidth="650"
|
||||
prefHeight="400"
|
||||
spacing="12">
|
||||
<padding>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.shape.Arc?>
|
||||
<?import javafx.scene.shape.Circle?>
|
||||
<HBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="org.cryptomator.ui.stats.VaultStatisticsController"
|
||||
@@ -22,18 +23,17 @@
|
||||
</padding>
|
||||
|
||||
<!-- Caching -->
|
||||
<VBox prefWidth="200" prefHeight="200">
|
||||
<StackPane>
|
||||
<Group>
|
||||
<Arc styleClass="cache-arc-background" centerX="100" centerY="100" radiusX="100" radiusY="100" startAngle="225" length="-270"/>
|
||||
<Arc styleClass="cache-arc-foreground" centerX="100" centerY="100" radiusX="100" radiusY="100" startAngle="225" length="${controller.cacheHitDegrees}"/>
|
||||
</Group>
|
||||
<VBox StackPane.alignment="CENTER" alignment="CENTER">
|
||||
<FormattedLabel styleClass="label-extra-large" format="\%1.0f %%" arg1="${controller.cacheHitPercentage}"/>
|
||||
<Label text="%stats.cacheHitRate"/>
|
||||
</VBox>
|
||||
</StackPane>
|
||||
</VBox>
|
||||
<StackPane prefWidth="200" prefHeight="200">
|
||||
<Group>
|
||||
<Circle centerX="100" centerY="100" radius="100" opacity="0"/> <!-- for alignment. visible=false does not yield the desired result -->
|
||||
<Arc styleClass="cache-arc-background" centerX="100" centerY="100" radiusX="100" radiusY="100" startAngle="225" length="-270"/>
|
||||
<Arc styleClass="cache-arc-foreground" centerX="100" centerY="100" radiusX="100" radiusY="100" startAngle="225" length="${controller.cacheHitDegrees}"/>
|
||||
</Group>
|
||||
<VBox StackPane.alignment="CENTER" alignment="CENTER">
|
||||
<FormattedLabel styleClass="label-extra-large" format="\%1.0f %%" arg1="${controller.cacheHitPercentage}"/>
|
||||
<Label text="%stats.cacheHitRate"/>
|
||||
</VBox>
|
||||
</StackPane>
|
||||
|
||||
<!-- Read -->
|
||||
<VBox prefWidth="300" prefHeight="300" spacing="6" alignment="CENTER">
|
||||
@@ -72,4 +72,22 @@
|
||||
<DataLabel byteFormat="%stats.encr.total.data.none" kibFormat="%stats.encr.total.data.kib" mibFormat="%stats.encr.total.data.mib" gibFormat="%stats.encr.total.data.gib" dataInBytes="${controller.totalBytesEncrypted}"/>
|
||||
<FormattedLabel format="%stats.write.accessCount" arg1="${controller.filesWritten}"/>
|
||||
</VBox>
|
||||
<!-- Access -->
|
||||
<VBox prefWidth="300" prefHeight="300" spacing="6" alignment="CENTER">
|
||||
<FormattedLabel styleClass="label-large" format="%stats.access.current" arg1="${controller.filesAccessed}"/>
|
||||
<AreaChart fx:id="accessChart" styleClass="io-stats" createSymbols="false" animated="false">
|
||||
<xAxis>
|
||||
<NumberAxis fx:id="accessChartXAxis" styleClass="io-stats" autoRanging="false" forceZeroInRange="false" side="BOTTOM"/>
|
||||
</xAxis>
|
||||
<yAxis>
|
||||
<NumberAxis fx:id="accessChartYAxis" styleClass="io-stats" autoRanging="true" forceZeroInRange="true" side="LEFT" tickUnit="Infinity"/>
|
||||
</yAxis>
|
||||
<cursor>
|
||||
<Cursor fx:constant="DEFAULT"/>
|
||||
</cursor>
|
||||
</AreaChart>
|
||||
<FormattedLabel format="%stats.access.total" arg1="${controller.totalFilesAccessed}"/>
|
||||
<Label/> <!-- to align with other graphs -->
|
||||
<Label/> <!-- to align with other graphs -->
|
||||
</VBox>
|
||||
</HBox>
|
||||
|
||||
@@ -219,7 +219,27 @@ health.check.detail.checkFinished=The check finished successfully.
|
||||
health.check.detail.checkFinishedAndFound=The check finished running. Please review the results.
|
||||
health.check.detail.checkFailed=The check exited due to an error.
|
||||
health.check.detail.checkCancelled=The check was cancelled.
|
||||
health.check.detail.listFilters.label=Filter
|
||||
health.check.detail.listFilters.severity=Severity
|
||||
health.check.detail.listFilters.fixState=Fix state
|
||||
health.check.detail.fixAllSpecificBtn=Fix all of type
|
||||
health.check.exportBtn=Export Report
|
||||
## Result view
|
||||
health.result.severityFilter.all=Severity - All
|
||||
health.result.severityFilter.good=Good
|
||||
health.result.severityFilter.info=Info
|
||||
health.result.severityFilter.warn=Warning
|
||||
health.result.severityFilter.crit=Critical
|
||||
health.result.severityTip.good=Severity: Good\nNormal vault structure.
|
||||
health.result.severityTip.info=Severity: Info\nVault structure intact, fix suggested.
|
||||
health.result.severityTip.warn=Severity: Warning\nVault structure corrupted, fix highly advised.
|
||||
health.result.severityTip.crit=Severity: Critical\nVault structure corrupted, data loss determined.
|
||||
health.result.fixStateFilter.all=Fix state - All
|
||||
health.result.fixStateFilter.fixable=Fixable
|
||||
health.result.fixStateFilter.notFixable=Not fixable
|
||||
health.result.fixStateFilter.fixing=Fixing…
|
||||
health.result.fixStateFilter.fixed=Fixed
|
||||
health.result.fixStateFilter.fixFailed=Fix failed
|
||||
## Fix Application
|
||||
health.fix.fixBtn=Fix
|
||||
health.fix.successTip=Fix successful
|
||||
@@ -301,6 +321,11 @@ stats.encr.total.data.mib=Data encrypted: %.1f MiB
|
||||
stats.encr.total.data.gib=Data encrypted: %.1f GiB
|
||||
stats.write.accessCount=Total writes: %d
|
||||
|
||||
## Accesses
|
||||
stats.access.current=Access: %d
|
||||
stats.access.total=Total accesses: %d
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Close
|
||||
main.minimizeBtn.tooltip=Minimize
|
||||
|
||||
@@ -155,6 +155,7 @@ migration.impossible.moreInfo=لا يزال ممكناً فتح المخزن ب
|
||||
## Start Failure
|
||||
## Check Selection
|
||||
## Detail view
|
||||
## Result view
|
||||
## Fix Application
|
||||
|
||||
# Preferences
|
||||
@@ -200,6 +201,9 @@ stats.decr.total.data.none=تم فك تشفير البيانات:-
|
||||
## Write
|
||||
stats.encr.total.data.none=البيانات المشفرة: -
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=إغلاق
|
||||
main.minimizeBtn.tooltip=تصغير
|
||||
|
||||
@@ -53,6 +53,12 @@ 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, ж або 수
|
||||
addvaultwizard.new.validCharacters.numbers=Лічбы
|
||||
addvaultwizard.new.validCharacters.dashes=Злучок (%s) або падкрэслінік (%s)
|
||||
### Password
|
||||
addvaultwizard.new.createVaultBtn=Стварыць скарбніцу
|
||||
addvaultwizard.new.generateRecoveryKeyChoice=Бяз гэтага пароля ты ня зможаш атрымаць доступ да сваіх даных. Ці хочаш ты мець ключ аднаўлення на выпадак, калі ты згубіш свой пароль?
|
||||
@@ -76,26 +82,30 @@ addvault.new.readme.accessLocation.2=Гэта месца дуступу да т
|
||||
addvault.new.readme.accessLocation.3=Любы дададзены сюды файл будзе зашыфраваны праз Cryptomator. Ты можаш працаваць тут як са звычайнаю тэчкаю альбо дыскам. Гэта толькі расшыфраваны агляд кантэнту, самі файлы заўжды захоўваюцца зашыфраванымі на тваім цвёрдым дыску.
|
||||
addvault.new.readme.accessLocation.4=Ты можаш выдаліць гэты файл.
|
||||
## Existing
|
||||
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=Я разумею, што калі забудуся на пароль, то згублю доступ да сваех даных
|
||||
changepassword.finalConfirmation=Я разумею, што калі забудуся на пароль, то згублю доступ да маіх даных
|
||||
|
||||
# Forget Password
|
||||
forgetPassword.title=Забыўся на пароль
|
||||
forgetPassword.message=Забыўся на захаваны пароль?
|
||||
forgetPassword.confirmBtn=Забыўся на пароль
|
||||
forgetPassword.title=Забыцца на пароль
|
||||
forgetPassword.message=Ці забыцца на захаваны пароль?
|
||||
forgetPassword.description=Гэта выдаліць захаваны пароль ад гэтай скарбніцы з твайго мэнэджара ключоў.
|
||||
forgetPassword.confirmBtn=Забыцца на пароль
|
||||
|
||||
# Unlock
|
||||
unlock.title=Разамкнуць "%s"
|
||||
@@ -104,45 +114,72 @@ unlock.savePassword=Захоўваць пароль
|
||||
unlock.unlockBtn=Адамкнуць
|
||||
## Select
|
||||
unlock.chooseMasterkey.message=Файл майстра ключоў ня знойдзены
|
||||
unlock.chooseMasterkey.description=Cryptomator ня змог знайсці файл masterkey у тваёй скарбніцы "%s". Калі ласка, абяры самастойна файл з ключом.
|
||||
unlock.chooseMasterkey.filePickerTitle=Абяры файл masterkey
|
||||
unlock.chooseMasterkey.filePickerMimeDesc=Майстар ключоў Cryptomator
|
||||
## Success
|
||||
unlock.success.message=Паспяховае размыканне
|
||||
unlock.success.description=Змест скарбніцы "%s" цяпер дасяжны праз ёйны мантажны пункт.
|
||||
unlock.success.rememberChoice=Запомні мой выбар і больш не пытай
|
||||
unlock.success.revealBtn=Паказаць дыск
|
||||
## Failure
|
||||
unlock.error.message=Немагчыма разамкнуць скарбніцу
|
||||
### Invalid Mount Point
|
||||
unlock.error.invalidMountPoint.notExisting=Пункт мантажу "%s" не з'яўляецца пустой тэчкаю альбо ўвогуле не існуе.
|
||||
unlock.error.invalidMountPoint.existing=Пункт мантажу "%s" ужо існуе альбо адсутнічае бацькоўская тэчка.
|
||||
unlock.error.invalidMountPoint.driveLetterOccupied=Дыскавая літара "%s" ужо выкарыстоўваецца.
|
||||
## Hub
|
||||
### Waiting
|
||||
hub.auth.message=Чаканне спраўджання…
|
||||
hub.auth.description=Ты мусіш аўтаматычна перанакіравацца на старонку ўваходу.
|
||||
hub.auth.loginLink=Не перанакіраваўся? Пстрыкні сюды, каб адчыніць старонку.
|
||||
### Receive Key
|
||||
hub.receive.message=Апрацоўка адказу…
|
||||
hub.receive.description=Cryptomator атрымлівае ды апрацоўвае адказ ад Hub. Калі ласка, пачакай.
|
||||
### Register Device
|
||||
hub.register.message=Патрабуецца назва прылады
|
||||
hub.register.description=Здаецца, што ты ў першы раз увайшла/-оў у Hub з гэтай прылады. Каб ідэнтыфікаваць яе для спраўджання доступу, табе трэба назваць гэтую прыладу.
|
||||
hub.register.nameLabel=Назва прылады
|
||||
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=Тваёй прыладзе ў дадзены момант не дазволена мець доступ да гэтай скрабніцы. Запытайся ўладальніка скрабніцы за дазволам.
|
||||
### License Exceeded
|
||||
hub.licenseExceeded.message=Ліцэнзія пратэрмінаваная
|
||||
|
||||
|
||||
# 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.prompt=Тваю скарбніцу "%s" неабходна абнавіць у новы фармат. Перад тым, як пачаць, запэўніся, што не адбываецца працэсу сінхранавання, які можа ўздзейнічаць на скарбніцу.
|
||||
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=Файлавая сістэма не дазваляе чытаць у ёй.
|
||||
@@ -152,6 +189,8 @@ migration.impossible.heading=Не мажліва перанесці скарбн
|
||||
|
||||
# Health Check
|
||||
## Start
|
||||
health.title=Тэст на цэласнасць для "%s"
|
||||
health.intro.header=Тэст на цэласнасць
|
||||
health.intro.remarkBackup=Калі даныя пашкоджаныя, дапаможа толькі рэзервовая копія.
|
||||
## Start Failure
|
||||
health.fail.moreInfo=Падрабязней
|
||||
@@ -160,18 +199,43 @@ health.checkList.selectAllButton=Вылучыць усе элементы
|
||||
health.checkList.deselectAllButton=Адрабіць вылучэнне ўсіх элементаў
|
||||
health.check.runBatchBtn=Выканаць вылучаныя элементы
|
||||
## Detail view
|
||||
health.check.detail.checkCancelled=Праверка была скасавана.
|
||||
health.check.exportBtn=Экспартаваць справаздачу
|
||||
## Result view
|
||||
## 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=Iнтэрфэйс
|
||||
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.webdav.port=Порт WebDAV
|
||||
preferences.volume.webdav.scheme=Схема WebDAV
|
||||
## Updates
|
||||
preferences.updates=Абнаўленні
|
||||
preferences.updates.currentVersion=Бягучая версія: %s
|
||||
@@ -180,20 +244,55 @@ preferences.updates.checkNowBtn=Праверыць зараз
|
||||
preferences.updates.updateAvailable=Даступна абнаўленне да версіі %s
|
||||
## Contribution
|
||||
preferences.contribute=Падтрымай нас
|
||||
preferences.contribute.getCertificate=Яшчэ ня маеш такога? Даведайся, як атрымаць.
|
||||
#<-- Add entries for donations and code/translation/documentation contribution -->
|
||||
|
||||
## About
|
||||
preferences.about=Пра нас
|
||||
|
||||
# Vault Statistics
|
||||
stats.title=Статыстыкі для %s
|
||||
## 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
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Зачыніць
|
||||
main.minimizeBtn.tooltip=Згарнуць
|
||||
main.preferencesBtn.tooltip=Налады
|
||||
main.debugModeEnabled.tooltip=Функццыя дыягназавання выключана
|
||||
main.supporterCertificateMissing.tooltip=Калі ласка, падумай пра ахвяраванне
|
||||
## Drag 'n' Drop
|
||||
main.dropZone.dropVault=Дадаць гэтую скарбніцу
|
||||
main.dropZone.unknownDragboardContent=Калі ты хочаш дадаць скарбніцу, перацягні яе ў гэтае акно
|
||||
## Vault List
|
||||
main.vaultlist.emptyList.onboardingInstruction=Пстрыкні тут, каб дадаць скарбніцу
|
||||
main.vaultlist.contextMenu.remove=Выдаліць…
|
||||
main.vaultlist.contextMenu.lock=Замкнуць
|
||||
main.vaultlist.contextMenu.unlock=Адамкнуць…
|
||||
@@ -207,37 +306,98 @@ main.vaultlist.addVaultBtn=Дадаць скарбніцу
|
||||
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.lockBtn=Замкнуць
|
||||
main.vaultDetail.bytesPerSecondRead=Чытанне:
|
||||
main.vaultDetail.bytesPerSecondWritten=Пісанне:
|
||||
main.vaultDetail.throughput.idle=бяздзейны
|
||||
main.vaultDetail.throughput.kbps=%.1f КіБ/с
|
||||
main.vaultDetail.throughput.mbps=%.1f МіБ/с
|
||||
main.vaultDetail.stats=Статыстыка скарбніцы
|
||||
### 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.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=Запытацца
|
||||
|
||||
## Mount
|
||||
vaultOptions.mount=Мантажаванне
|
||||
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=Абяры парожнюю тэчку
|
||||
## Master Key
|
||||
vaultOptions.masterkey=Пароль
|
||||
vaultOptions.masterkey.changePasswordBtn=Змяніць пароль
|
||||
vaultOptions.masterkey.forgetSavedPasswordBtn=Забыцца на захаваны пароль
|
||||
vaultOptions.masterkey.showRecoveryKeyBtn=Паказаць ключ аднаўлення
|
||||
vaultOptions.masterkey.recoverPasswordBtn=Скінуць пароль
|
||||
|
||||
|
||||
# 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.validKey=Гэта валідны ключ аднаўлення
|
||||
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=Ты можаш разамкнуць сваю скарбніцу з дапамогаю новага паролю.
|
||||
|
||||
# New Password
|
||||
newPassword.promptText=Увядзі новы пароль
|
||||
newPassword.reenterPassword=Пацвердзі новы пароль
|
||||
newPassword.passwordsMatch=Паролі супадаюць!
|
||||
newPassword.passwordsDoNotMatch=Паролі не супадаюць
|
||||
passwordStrength.messageLabel.tooShort=Выкарыстай ня менш за %d сімвалаў
|
||||
passwordStrength.messageLabel.0=Вельмі слабы
|
||||
passwordStrength.messageLabel.1=Слабы
|
||||
passwordStrength.messageLabel.2=Нармалёвы
|
||||
passwordStrength.messageLabel.3=Моцны
|
||||
@@ -246,5 +406,9 @@ passwordStrength.messageLabel.4=Вельмі моцны
|
||||
# Quit
|
||||
quit.title=Пакінуць праграму
|
||||
quit.message=Існуюць разамкнёныя скарбніцы
|
||||
quit.description=Калі ласка, пацвердзі, што ты збіраешся выйсці. Cryptomator замкне ўсе разомкнутыя скарбніцы, каб прадухіліць страту даных.
|
||||
quit.lockAndQuitBtn=Замкнуць ды вайсці
|
||||
|
||||
# Forced Quit
|
||||
# Forced Quit
|
||||
quit.forced.message=Некаторыя скарбніцы не магчыма замкнуць
|
||||
quit.forced.forceAndQuitBtn=Прымусіць і выйсці
|
||||
@@ -108,6 +108,7 @@ lock.forced.retryBtn=পুনরায় চেষ্টা করুন
|
||||
## Start Failure
|
||||
## Check Selection
|
||||
## Detail view
|
||||
## Result view
|
||||
## Fix Application
|
||||
|
||||
# Preferences
|
||||
@@ -124,6 +125,9 @@ lock.forced.retryBtn=পুনরায় চেষ্টা করুন
|
||||
## Read
|
||||
## Write
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=বন্ধ করুন
|
||||
## Drag 'n' Drop
|
||||
|
||||
@@ -154,6 +154,7 @@ migration.impossible.moreInfo=Sef se i dalje može otvoriti sa starijom verzijom
|
||||
## Start Failure
|
||||
## Check Selection
|
||||
## Detail view
|
||||
## Result view
|
||||
## Fix Application
|
||||
|
||||
# Preferences
|
||||
@@ -215,6 +216,9 @@ stats.encr.total.data.mib=Otključano podataka: %.1f MiB
|
||||
stats.encr.total.data.gib=Otključano podataka: %.1f GiB
|
||||
stats.write.accessCount=Ukupno upisano: %d
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Zatvori
|
||||
main.minimizeBtn.tooltip=Minimiziraj
|
||||
|
||||
@@ -210,7 +210,23 @@ health.check.detail.checkFinished=La prova ha finalitzat amb èxit.
|
||||
health.check.detail.checkFinishedAndFound=La comprovació ha finalitzat. Si us plau, comproveu-ne es resultat.
|
||||
health.check.detail.checkFailed=La comprovació ha acabat a causa d'un error.
|
||||
health.check.detail.checkCancelled=S'ha cancel·lat la prova.
|
||||
health.check.detail.listFilters.label=Filtre
|
||||
health.check.detail.listFilters.severity=Gravetat
|
||||
health.check.detail.listFilters.fixState=Estat de la reparació
|
||||
health.check.detail.fixAllSpecificBtn=Qualsevol reparació
|
||||
health.check.exportBtn=Exporta informe
|
||||
## Result view
|
||||
health.result.severityFilter.all=Gravetat - Totes
|
||||
health.result.severityFilter.good=Bé
|
||||
health.result.severityFilter.info=Info
|
||||
health.result.severityFilter.warn=Avís
|
||||
health.result.severityFilter.crit=Crític
|
||||
health.result.fixStateFilter.all=Estat de reparació - Tot
|
||||
health.result.fixStateFilter.fixable=Es pot arreglar
|
||||
health.result.fixStateFilter.notFixable=No es pot arreglar
|
||||
health.result.fixStateFilter.fixing=Reparant…
|
||||
health.result.fixStateFilter.fixed=Solucionat
|
||||
health.result.fixStateFilter.fixFailed=Reparació fallida
|
||||
## Fix Application
|
||||
health.fix.fixBtn=Corregeix
|
||||
health.fix.successTip=S'ha corregit amb èxit
|
||||
@@ -292,6 +308,11 @@ stats.encr.total.data.mib=Dades xifrades: %.1f MiB
|
||||
stats.encr.total.data.gib=Dades xifrades: %.1f GiB
|
||||
stats.write.accessCount=Total escrits: %d
|
||||
|
||||
## Accesses
|
||||
stats.access.current=Accés: %d
|
||||
stats.access.total=Total d'accessos: %d
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Tanca
|
||||
main.minimizeBtn.tooltip=Minimitza
|
||||
|
||||
@@ -192,6 +192,7 @@ health.check.detail.checkFinishedAndFound=Kontrola byla dokončena. Zkontrolujte
|
||||
health.check.detail.checkFailed=Kontrola byla ukončena z důvodu chyby.
|
||||
health.check.detail.checkCancelled=Kontrola byla zrušena.
|
||||
health.check.exportBtn=Exportovat sestavu
|
||||
## Result view
|
||||
## Fix Application
|
||||
health.fix.fixBtn=Opravit
|
||||
health.fix.successTip=Oprava byla úspěšná
|
||||
@@ -272,6 +273,9 @@ stats.encr.total.data.mib=Zašifrováno: %.1f MiB
|
||||
stats.encr.total.data.gib=Zašifrováno: %.1f GiB
|
||||
stats.write.accessCount=Celkem zapsáno: %d
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Zavřít
|
||||
main.minimizeBtn.tooltip=Minimalizovat
|
||||
|
||||
@@ -219,6 +219,7 @@ health.check.detail.checkFinishedAndFound=Kontrolproceduren er kørt færdig. Ge
|
||||
health.check.detail.checkFailed=Kontrolproceduren blev afbrudt af en fejl.
|
||||
health.check.detail.checkCancelled=Kontrolproceduren blev annulleret.
|
||||
health.check.exportBtn=Eksportér rapport
|
||||
## Result view
|
||||
## Fix Application
|
||||
health.fix.fixBtn=Reparér
|
||||
health.fix.successTip=Repareret
|
||||
@@ -300,6 +301,9 @@ stats.encr.total.data.mib=Data krypteret: %.1f MiB
|
||||
stats.encr.total.data.gib=Data krypteret: %.1f GiB
|
||||
stats.write.accessCount=Totalt antal skrivninger: %d
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Luk
|
||||
main.minimizeBtn.tooltip=Minimér
|
||||
|
||||
@@ -218,7 +218,26 @@ health.check.detail.checkFinished=Die Prüfung wurde erfolgreich abgeschlossen.
|
||||
health.check.detail.checkFinishedAndFound=Die Prüfung wurde abgeschlossen. Bitte überprüfe die Ergebnisse.
|
||||
health.check.detail.checkFailed=Die Prüfung wurde wegen eines Fehlers abgebrochen.
|
||||
health.check.detail.checkCancelled=Die Prüfung wurde abgebrochen.
|
||||
health.check.detail.listFilters.label=Filter
|
||||
health.check.detail.listFilters.severity=Schweregrad
|
||||
health.check.detail.listFilters.fixState=Fix Status
|
||||
health.check.detail.fixAllSpecificBtn=Behebe alle mit Status
|
||||
health.check.exportBtn=Bericht exportieren
|
||||
## Result view
|
||||
health.result.severityFilter.all=Schweregrad - Alle
|
||||
health.result.severityFilter.good=Gut
|
||||
health.result.severityFilter.info=Info
|
||||
health.result.severityFilter.warn=Warnung
|
||||
health.result.severityFilter.crit=Kritisch
|
||||
health.result.severityTip.good=Schweregrad: Gut\nNormale Tresorstruktur.
|
||||
health.result.severityTip.info=Schweregrad: Info\nTresorstruktur intakt, Beheben empfohlen.
|
||||
health.result.severityTip.warn=Schweregrad: Warnung\nTresorstruktur beschädigt, Beheben dringend empfohlen.
|
||||
health.result.severityTip.crit=Schweregrad: Kritisch\nTresorstruktur beschädigt, Datenverlust möglich.
|
||||
health.result.fixStateFilter.all=Fix-Status - Alle
|
||||
health.result.fixStateFilter.fixable=Fixierbar
|
||||
health.result.fixStateFilter.notFixable=Nicht behebbar
|
||||
health.result.fixStateFilter.fixed=Behoben
|
||||
health.result.fixStateFilter.fixFailed=Fix fehlgeschlagen
|
||||
## Fix Application
|
||||
health.fix.fixBtn=Beheben
|
||||
health.fix.successTip=Fehlerbehebung erfolgreich
|
||||
@@ -300,6 +319,11 @@ stats.encr.total.data.mib=Verschlüsselt: %.1f MiB
|
||||
stats.encr.total.data.gib=Verschlüsselt: %.1f GiB
|
||||
stats.write.accessCount=Schreibzugriffe: %d
|
||||
|
||||
## Accesses
|
||||
stats.access.current=Zugriffe: %d
|
||||
stats.access.total=Gesamte Zugriffe: %d
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Schließen
|
||||
main.minimizeBtn.tooltip=Minimieren
|
||||
|
||||
@@ -219,6 +219,7 @@ health.check.detail.checkFinishedAndFound=Ο έλεγχος σταμάτησε
|
||||
health.check.detail.checkFailed=Ο έλεγχος τερματίστηκε λόγω σφάλματος.
|
||||
health.check.detail.checkCancelled=Ο έλεγχος ακυρώθηκε.
|
||||
health.check.exportBtn=Εξαγωγή Αναφοράς
|
||||
## Result view
|
||||
## Fix Application
|
||||
health.fix.fixBtn=Επιδιόρθωση
|
||||
health.fix.successTip=Επιτυχής επιδιόρθωση
|
||||
@@ -300,6 +301,9 @@ stats.encr.total.data.mib=Δεδομένα που κρυπτογραφήθηκα
|
||||
stats.encr.total.data.gib=Δεδομένα που κρυπτογραφήθηκαν: %.1f GiB
|
||||
stats.write.accessCount=Συνολικές εγγραφές: %d
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Κλείσιμο
|
||||
main.minimizeBtn.tooltip=Ελαχιστοποίηση
|
||||
|
||||
@@ -219,6 +219,7 @@ health.check.detail.checkFinishedAndFound=La comprobación terminó de ejecutars
|
||||
health.check.detail.checkFailed=La comprobación terminó debido a un error.
|
||||
health.check.detail.checkCancelled=La comprobación se canceló.
|
||||
health.check.exportBtn=Exportar informe
|
||||
## Result view
|
||||
## Fix Application
|
||||
health.fix.fixBtn=Reparar
|
||||
health.fix.successTip=Reparación exitosa
|
||||
@@ -300,6 +301,9 @@ stats.encr.total.data.mib=Datos cifrados: %.1f MiB
|
||||
stats.encr.total.data.gib=Datos cifrados: %.1f GiB
|
||||
stats.write.accessCount=Total de escrituras: %d
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Cerrar
|
||||
main.minimizeBtn.tooltip=Minimizar
|
||||
|
||||
@@ -69,6 +69,7 @@ unlock.unlockBtn=بازکردن قفل
|
||||
## Start Failure
|
||||
## Check Selection
|
||||
## Detail view
|
||||
## Result view
|
||||
## Fix Application
|
||||
|
||||
# Preferences
|
||||
@@ -85,6 +86,9 @@ unlock.unlockBtn=بازکردن قفل
|
||||
## Read
|
||||
## Write
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
main.supporterCertificateMissing.tooltip=لطفا کمک مالی در نظر بگیرند
|
||||
## Drag 'n' Drop
|
||||
|
||||
@@ -94,6 +94,7 @@ lock.forced.retryBtn=Subukan muli
|
||||
## Start Failure
|
||||
## Check Selection
|
||||
## Detail view
|
||||
## Result view
|
||||
## Fix Application
|
||||
|
||||
# Preferences
|
||||
@@ -113,6 +114,9 @@ preferences.interface.theme.light=Light
|
||||
## Read
|
||||
## Write
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Isara
|
||||
main.preferencesBtn.tooltip=Mga Kagustuhan
|
||||
|
||||
@@ -218,7 +218,27 @@ health.check.detail.checkFinished=La vérification s'est terminée avec succès.
|
||||
health.check.detail.checkFinishedAndFound=Le test est terminé. Veuillez vérifier les résultats.
|
||||
health.check.detail.checkFailed=La vérification s'est arrêtée en raison d'une erreur.
|
||||
health.check.detail.checkCancelled=Vérification annulée.
|
||||
health.check.detail.listFilters.label=Filtre
|
||||
health.check.detail.listFilters.severity=Gravité
|
||||
health.check.detail.listFilters.fixState=Corriger l'état
|
||||
health.check.detail.fixAllSpecificBtn=Corriger tout le type
|
||||
health.check.exportBtn=Exporter le rapport
|
||||
## Result view
|
||||
health.result.severityFilter.all=Gravité - Tous
|
||||
health.result.severityFilter.good=Satisfaisant
|
||||
health.result.severityFilter.info=Information
|
||||
health.result.severityFilter.warn=Avertissement
|
||||
health.result.severityFilter.crit=Critique
|
||||
health.result.severityTip.good=Gravité : structure de coffre normale.
|
||||
health.result.severityTip.info=Gravité : Info\nStructure du coffre intacte, correction suggérée.
|
||||
health.result.severityTip.warn=Gravité : Avertissement\nLa structure du coffre est corrompue, correction fortement recommandée.
|
||||
health.result.severityTip.crit=Gravité : Structure du coffre critique,\ncorruption et perte de données.
|
||||
health.result.fixStateFilter.all=Réparer l'état - Tous
|
||||
health.result.fixStateFilter.fixable=Réparable
|
||||
health.result.fixStateFilter.notFixable=Pas réparable
|
||||
health.result.fixStateFilter.fixing=Réparation en cours…
|
||||
health.result.fixStateFilter.fixed=Réparé
|
||||
health.result.fixStateFilter.fixFailed=Réparation a échouée
|
||||
## Fix Application
|
||||
health.fix.fixBtn=Réparer
|
||||
health.fix.successTip=Réparation réussie
|
||||
@@ -300,6 +320,11 @@ stats.encr.total.data.mib=Données chiffrées: %.1f MiO
|
||||
stats.encr.total.data.gib=Données chiffrées: %.1f GiO
|
||||
stats.write.accessCount=Total des écritures: %d
|
||||
|
||||
## Accesses
|
||||
stats.access.current=Accès : %d
|
||||
stats.access.total=Total des accès: %d
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Fermer
|
||||
main.minimizeBtn.tooltip=Réduire
|
||||
|
||||
@@ -69,6 +69,7 @@ lock.forced.retryBtn=Tentar de novo
|
||||
## Start Failure
|
||||
## Check Selection
|
||||
## Detail view
|
||||
## Result view
|
||||
## Fix Application
|
||||
|
||||
# Preferences
|
||||
@@ -85,6 +86,9 @@ lock.forced.retryBtn=Tentar de novo
|
||||
## Read
|
||||
## Write
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Pechar
|
||||
## Drag 'n' Drop
|
||||
|
||||
@@ -176,6 +176,7 @@ health.check.detail.checkFinishedAndFound=הבדיקה הסתיימה. ניתן
|
||||
health.check.detail.checkFailed=הבדיקה נכשלה בשל שגיאה.
|
||||
health.check.detail.checkCancelled=הבדיקה בוטלה.
|
||||
health.check.exportBtn=יצוא דוחות
|
||||
## Result view
|
||||
## Fix Application
|
||||
health.fix.fixBtn=תיקון
|
||||
|
||||
@@ -227,6 +228,9 @@ stats.read.throughput.mibs=קריאה: %.2f MiB/s
|
||||
stats.read.total.data.none=מידע שנקרא: -
|
||||
## Write
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=סגור
|
||||
main.preferencesBtn.tooltip=העדפות
|
||||
|
||||
@@ -50,6 +50,13 @@ 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.numbers=नंबर
|
||||
### Password
|
||||
addvaultwizard.new.createVaultBtn=वॉल्ट बनाएं
|
||||
addvaultwizard.new.generateRecoveryKeyChoice=आप अपने पासवर्ड के बिना अपने डेटा तक नहीं पहुंच पाएंगे। क्या आप उस वक़्त के लिए एक पुनर्प्राप्ति कुंजी चाहते हैं जब आप अपना पासवर्ड खो देते हैं?
|
||||
@@ -86,8 +93,12 @@ removeVault.confirmBtn=वॉल्ट हटाए
|
||||
# Change Password
|
||||
changepassword.title=पासवर्ड बदलें
|
||||
changepassword.enterOldPassword="%s" का वर्तमान पासवर्ड दर्ज करें
|
||||
changepassword.finalConfirmation=मैं समझता/समझती हूं कि अगर मैं अपना पासवर्ड भूल जाता हूं, तो मैं अपना डेटा एक्सेस नहीं कर पाऊंगा
|
||||
|
||||
# Forget Password
|
||||
forgetPassword.title=पासवर्ड भूल गए
|
||||
forgetPassword.message=पासवर्ड भूल गए?
|
||||
forgetPassword.confirmBtn=पासवर्ड भूल गए
|
||||
|
||||
# Unlock
|
||||
unlock.title=अनलॉक "%s"
|
||||
@@ -99,13 +110,17 @@ unlock.chooseMasterkey.description=क्रिप्टोमेटर "%s" क
|
||||
## Success
|
||||
unlock.success.message=अनलॉक सफल हुआ
|
||||
unlock.success.rememberChoice=विकल्प याद रखें, दोबारा ना दिखाएं
|
||||
unlock.success.revealBtn=फोल्डर खोलें
|
||||
## Failure
|
||||
unlock.error.message=वॉल्ट को अनलॉक करना असफल हुआ
|
||||
### Invalid Mount Point
|
||||
## Hub
|
||||
### Waiting
|
||||
### Receive Key
|
||||
hub.receive.message=अभी संसाधित किया जा रहा है
|
||||
### Register Device
|
||||
hub.register.nameLabel=डिवाइस का नाम
|
||||
hub.register.occupiedMsg=नाम पहले से प्रयोग में है
|
||||
hub.register.registerBtn=पुष्टि करें
|
||||
### Registration Success
|
||||
### Registration Failed
|
||||
@@ -118,6 +133,7 @@ hub.register.registerBtn=पुष्टि करें
|
||||
lock.forced.message=लॉक करना विफल हुआ
|
||||
lock.forced.retryBtn=पुन: प्रयास करें
|
||||
## Failure
|
||||
lock.fail.message=लॉक करना विफल
|
||||
|
||||
# Migration
|
||||
migration.title=वाउल्ट को अपग्रेड करें
|
||||
@@ -135,6 +151,7 @@ migration.success.unlockNow=अब अनलॉक करें
|
||||
## Start Failure
|
||||
## Check Selection
|
||||
## Detail view
|
||||
## Result view
|
||||
## Fix Application
|
||||
|
||||
# Preferences
|
||||
@@ -145,6 +162,8 @@ preferences.general.autoCloseVaults=एप्लीकेशन बंद कर
|
||||
preferences.general.autoStart=क्रिप्टोमेटर को सिस्टम स्टार्ट पे खोले
|
||||
## Interface
|
||||
preferences.interface.theme.automatic=ऑटोमैटिक
|
||||
preferences.interface.theme.dark=डार्क
|
||||
preferences.interface.theme.light=लाइट
|
||||
preferences.interface.interfaceOrientation.ltr=बाएं से दाएं
|
||||
preferences.interface.interfaceOrientation.rtl=दाएं से बाएं
|
||||
## Volume
|
||||
@@ -159,6 +178,9 @@ preferences.contribute=हमें सपोर्ट करें
|
||||
## Read
|
||||
## Write
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=बंद करें
|
||||
main.preferencesBtn.tooltip=प्राथमिकताएं
|
||||
@@ -171,6 +193,7 @@ main.vaultlist.contextMenu.lock=लॉक करें
|
||||
main.vaultlist.contextMenu.unlock=अनलॉक करें...
|
||||
main.vaultlist.contextMenu.unlockNow=अब अनलॉक करें
|
||||
main.vaultlist.contextMenu.vaultoptions=वॉल्ट के विकल्प दिखाए
|
||||
main.vaultlist.contextMenu.reveal=फोल्डर खोलें
|
||||
main.vaultlist.addVaultBtn=वाउल्ट डालें
|
||||
## Vault Detail
|
||||
### Welcome
|
||||
@@ -181,6 +204,7 @@ main.vaultDetail.unlockNowBtn=अब अनलॉक करें
|
||||
main.vaultDetail.optionsBtn=वॉल्ट के विकल्प
|
||||
### Unlocked
|
||||
main.vaultDetail.accessLocation=आपके वॉल्ट की चीजें यहाँ एक्सेस कर सकतें हैं:
|
||||
main.vaultDetail.revealBtn=फोल्डर खोलें
|
||||
main.vaultDetail.lockBtn=लॉक करें
|
||||
main.vaultDetail.stats=वॉल्ट के आंकड़े
|
||||
### Missing
|
||||
@@ -199,6 +223,7 @@ wrongFileAlert.link=और मदद के लिए, यह जाएं
|
||||
vaultOptions.general=सामान्य
|
||||
vaultOptions.general.vaultName=वॉल्ट का नाम
|
||||
vaultOptions.general.actionAfterUnlock.ignore=कुछ न करें
|
||||
vaultOptions.general.actionAfterUnlock.reveal=फोल्डर खोलें
|
||||
|
||||
## Mount
|
||||
vaultOptions.mount=माउंट हो रहा है
|
||||
|
||||
@@ -192,6 +192,7 @@ health.check.detail.checkFinishedAndFound=Provjera je završena. Molimo pregleda
|
||||
health.check.detail.checkFailed=Provjera je otkazana zbog pogreške.
|
||||
health.check.detail.checkCancelled=Provjera je otkazana.
|
||||
health.check.exportBtn=Izvoz izvješća
|
||||
## Result view
|
||||
## Fix Application
|
||||
health.fix.fixBtn=Popravi
|
||||
health.fix.successTip=Popravak uspješan
|
||||
@@ -272,6 +273,9 @@ stats.encr.total.data.mib=Podataka šifrirano: %.1f MiB
|
||||
stats.encr.total.data.gib=Podataka šifrirano: %.1f GiB
|
||||
stats.write.accessCount=Ukupno pisanja: %d
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Zatvori
|
||||
main.minimizeBtn.tooltip=Smanji
|
||||
|
||||
@@ -189,6 +189,7 @@ health.check.detail.checkFinishedAndFound=Az ellenőrzés véget ért. Kérem el
|
||||
health.check.detail.checkFailed=Az ellenőrzés egy hiba miatt megszakadt.
|
||||
health.check.detail.checkCancelled=Az ellenőrzés meg lett szakítva.
|
||||
health.check.exportBtn=Jelentés exportálása
|
||||
## Result view
|
||||
## Fix Application
|
||||
health.fix.fixBtn=Javítás
|
||||
health.fix.successTip=Javítás sikeres
|
||||
@@ -269,6 +270,9 @@ stats.encr.total.data.mib=Titkosított adat: %.1f MiB
|
||||
stats.encr.total.data.gib=Titkosított adat: %.1f GiB
|
||||
stats.write.accessCount=Összes írás: %d
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Bezárás
|
||||
main.minimizeBtn.tooltip=Minimalizálás
|
||||
|
||||
@@ -192,6 +192,7 @@ health.check.detail.checkFinishedAndFound=Pemeriksaan selesai. Silahkan tinjau h
|
||||
health.check.detail.checkFailed=Pemeriksaan terhenti karena terjadi kesalahan.
|
||||
health.check.detail.checkCancelled=Pemeriksaan dibatalkan.
|
||||
health.check.exportBtn=Ekspor Laporan
|
||||
## Result view
|
||||
## Fix Application
|
||||
health.fix.fixBtn=Perbaiki
|
||||
health.fix.successTip=Perbaikan berhasil
|
||||
@@ -272,6 +273,9 @@ stats.encr.total.data.mib=Data terenkripsi: %.1f MiB
|
||||
stats.encr.total.data.gib=Data terenkripsi: %.1f GiB
|
||||
stats.write.accessCount=Total ditulis: %d
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Tutup
|
||||
main.minimizeBtn.tooltip=Perkecil
|
||||
|
||||
@@ -219,6 +219,7 @@ health.check.detail.checkFinishedAndFound=Il controllo è terminato. Sei pregato
|
||||
health.check.detail.checkFailed=Il controllo è terminato a causa di un errore.
|
||||
health.check.detail.checkCancelled=Il controllo è stato annullato.
|
||||
health.check.exportBtn=Esporta il Rapporto
|
||||
## Result view
|
||||
## Fix Application
|
||||
health.fix.fixBtn=Correggi
|
||||
health.fix.successTip=Correzione riuscita
|
||||
@@ -300,6 +301,9 @@ stats.encr.total.data.mib=Dati crittografati: %.1f MiB
|
||||
stats.encr.total.data.gib=Dati crittografati: %.1f GiB
|
||||
stats.write.accessCount=Scritture totali: %d
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Chiudi
|
||||
main.minimizeBtn.tooltip=Minimizza
|
||||
|
||||
@@ -219,6 +219,7 @@ health.check.detail.checkFinishedAndFound=実行中のチェックが終了し
|
||||
health.check.detail.checkFailed=エラーが発生したためチェックを終了しました。
|
||||
health.check.detail.checkCancelled=チェックがキャンセルされました。
|
||||
health.check.exportBtn=結果をエクスポート
|
||||
## Result view
|
||||
## Fix Application
|
||||
health.fix.fixBtn=修正
|
||||
health.fix.successTip=正常に修正されました
|
||||
@@ -300,6 +301,9 @@ stats.encr.total.data.mib=暗号化済みデータ: %.1f MiB
|
||||
stats.encr.total.data.gib=暗号化済みデータ: %.1f GiB
|
||||
stats.write.accessCount=合計書き込み: %d
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=閉じる
|
||||
main.minimizeBtn.tooltip=最小化
|
||||
|
||||
@@ -182,6 +182,7 @@ health.check.detail.checkRunning=검사가 현재 실행중입니다...
|
||||
health.check.detail.checkSkipped=선택된 검사항목이 없습니다.
|
||||
health.check.detail.checkFinished=검사가 성공적으로 완료되었습니다.
|
||||
health.check.exportBtn=보고서 내보내기
|
||||
## Result view
|
||||
## Fix Application
|
||||
health.fix.fixBtn=문제해결
|
||||
health.fix.successTip=문제 해결이 성공적으로 완료되었습니다
|
||||
@@ -262,6 +263,9 @@ stats.encr.total.data.mib=데이터 암호화: %.1f MiB
|
||||
stats.encr.total.data.gib=데이터 암호화: %.1f GiB
|
||||
stats.write.accessCount=총 쓰기 횟수: %d
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=닫기
|
||||
main.minimizeBtn.tooltip=최소화
|
||||
|
||||
@@ -148,6 +148,7 @@ migration.error.missingFileSystemCapabilities.reason.WRITE_ACCESS=Nav atļaujas
|
||||
## Start Failure
|
||||
## Check Selection
|
||||
## Detail view
|
||||
## Result view
|
||||
## Fix Application
|
||||
|
||||
# Preferences
|
||||
@@ -181,6 +182,9 @@ preferences.about=Par lietotni
|
||||
## Read
|
||||
## Write
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Aizvērt
|
||||
main.minimizeBtn.tooltip=Minimizēt
|
||||
|
||||
@@ -57,6 +57,7 @@ hub.register.registerBtn=Потврди
|
||||
## Start Failure
|
||||
## Check Selection
|
||||
## Detail view
|
||||
## Result view
|
||||
## Fix Application
|
||||
|
||||
# Preferences
|
||||
@@ -73,6 +74,9 @@ hub.register.registerBtn=Потврди
|
||||
## Read
|
||||
## Write
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
## Drag 'n' Drop
|
||||
## Vault List
|
||||
|
||||
@@ -219,6 +219,7 @@ health.check.detail.checkFinishedAndFound=Helsesjekken er ferdig å kjøre. Venn
|
||||
health.check.detail.checkFailed=Helsesjekken avsluttet på grunn av en feil.
|
||||
health.check.detail.checkCancelled=Helsesjekken ble avbrutt.
|
||||
health.check.exportBtn=Eksporter rapport
|
||||
## Result view
|
||||
## Fix Application
|
||||
health.fix.fixBtn=Reparer
|
||||
health.fix.successTip=Vellykket reparering
|
||||
@@ -229,7 +230,7 @@ preferences.title=Innstillinger
|
||||
## General
|
||||
preferences.general=Generelt
|
||||
preferences.general.startHidden=Skjul vinduet når du starter Cryptomator
|
||||
preferences.general.autoCloseVaults=Låse åpne hvelv automatisk ved avslutning av programmet
|
||||
preferences.general.autoCloseVaults=Låsen åpner hvelv automatisk ved avslutning av programmet
|
||||
preferences.general.debugLogging=Aktiver loggføring av feilsøk
|
||||
preferences.general.debugDirectory=Vis loggfiler
|
||||
preferences.general.autoStart=Start Cryptomator ved systemstart
|
||||
@@ -300,6 +301,9 @@ stats.encr.total.data.mib=Data kryptert: %.1f MiB
|
||||
stats.encr.total.data.gib=Data kryptert: %.1f GiB
|
||||
stats.write.accessCount=Skrivninger totalt: %d
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Lukk
|
||||
main.minimizeBtn.tooltip=Minimer
|
||||
|
||||
@@ -218,7 +218,27 @@ health.check.detail.checkFinished=De controle is succesvol beëindigd.
|
||||
health.check.detail.checkFinishedAndFound=De controle is beëindigd. Bekijk alstublieft de resultaten.
|
||||
health.check.detail.checkFailed=De controle is afgesloten door een fout.
|
||||
health.check.detail.checkCancelled=De controle werd geannuleerd.
|
||||
health.check.detail.listFilters.label=Filter
|
||||
health.check.detail.listFilters.severity=Ernst
|
||||
health.check.detail.listFilters.fixState=Herstel staat
|
||||
health.check.detail.fixAllSpecificBtn=Repareer alle soorten
|
||||
health.check.exportBtn=Exporteer rapport
|
||||
## Result view
|
||||
health.result.severityFilter.all=Ernst - Alles
|
||||
health.result.severityFilter.good=Goed
|
||||
health.result.severityFilter.info=Info
|
||||
health.result.severityFilter.warn=Waarschuwing
|
||||
health.result.severityFilter.crit=Kritiek
|
||||
health.result.severityTip.good=Ernst: Goed\nNormale kluis structuur.
|
||||
health.result.severityTip.info=Ernst: Info\nKluis structuur intact, oplossing voorgesteld,.
|
||||
health.result.severityTip.warn=Ernst: Waarschuwing\nKluis structuur beschadigd, reparatie hoogst aanbevolen.
|
||||
health.result.severityTip.crit=Ernst: Kritiek\nKluis structuur corrupt, data verlies bepaald.
|
||||
health.result.fixStateFilter.all=Herstel status - Alles
|
||||
health.result.fixStateFilter.fixable=Repareerbaar
|
||||
health.result.fixStateFilter.notFixable=Niet repareerbaar
|
||||
health.result.fixStateFilter.fixing=Repareren…
|
||||
health.result.fixStateFilter.fixed=Gerepareerd
|
||||
health.result.fixStateFilter.fixFailed=Reparatie mislukt
|
||||
## Fix Application
|
||||
health.fix.fixBtn=Herstel
|
||||
health.fix.successTip=Hersteld
|
||||
@@ -300,6 +320,11 @@ stats.encr.total.data.mib=Gegevens versleuteld: %.1f MiB
|
||||
stats.encr.total.data.gib=Gegevens versleuteld: %.1f GiB
|
||||
stats.write.accessCount=Totaal geschreven: %d
|
||||
|
||||
## Accesses
|
||||
stats.access.current=Toegang: %d
|
||||
stats.access.total=Totaal aantal toegangen: %d
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Sluiten
|
||||
main.minimizeBtn.tooltip=Minimaliseer
|
||||
|
||||
@@ -139,6 +139,7 @@ migration.impossible.moreInfo=Kvelven kan framleis opnast viss du bruker ein eld
|
||||
## Start Failure
|
||||
## Check Selection
|
||||
## Detail view
|
||||
## Result view
|
||||
## Fix Application
|
||||
|
||||
# Preferences
|
||||
@@ -173,6 +174,9 @@ preferences.about=Om
|
||||
## Read
|
||||
## Write
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Lukk
|
||||
main.minimizeBtn.tooltip=Minimer
|
||||
|
||||
@@ -147,6 +147,7 @@ migration.impossible.moreInfo=ਵਾਲਟ ਨੂੰ ਅਜੇ ਵੀ ਪੁਰ
|
||||
## Start Failure
|
||||
## Check Selection
|
||||
## Detail view
|
||||
## Result view
|
||||
## Fix Application
|
||||
|
||||
# Preferences
|
||||
@@ -208,6 +209,9 @@ stats.encr.total.data.mib=ਡਾਟਾ ਇੰਕ੍ਰਿਪਟ ਕੀਤਾ: %.
|
||||
stats.encr.total.data.gib=ਡਾਟਾ ਇੰਕ੍ਰਿਪਟ ਕੀਤਾ: %.1f GiB
|
||||
stats.write.accessCount=ਕੁੱਲ ਲਿਖੇ: %d
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=ਬੰਦ ਕਰੋ
|
||||
main.minimizeBtn.tooltip=ਘੱਟੋ-ਘੱਟ
|
||||
|
||||
@@ -219,6 +219,7 @@ health.check.detail.checkFinishedAndFound=Test zakończony. Proszę sprawdzić w
|
||||
health.check.detail.checkFailed=Wystąpił błąd, test zakończony.
|
||||
health.check.detail.checkCancelled=Test został anulowany.
|
||||
health.check.exportBtn=Eksportuj raport
|
||||
## Result view
|
||||
## Fix Application
|
||||
health.fix.fixBtn=Napraw
|
||||
health.fix.successTip=Naprawa udana
|
||||
@@ -300,6 +301,9 @@ stats.encr.total.data.mib=Dane odszyfrowane: %.1f kiB
|
||||
stats.encr.total.data.gib=Dane odszyfrowane: %.1f kiB
|
||||
stats.write.accessCount=Całkowity zapis: %d
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Zamknij
|
||||
main.minimizeBtn.tooltip=Minimalizuj
|
||||
|
||||
@@ -209,6 +209,7 @@ health.check.detail.checkRunning=A verificação está atualmente a decorrer…
|
||||
health.check.detail.checkSkipped=A verificação não foi selecionada para ser executada.
|
||||
health.check.detail.checkFinished=A verificação foi concluída com sucesso.
|
||||
health.check.detail.checkFinishedAndFound=A verificação concluiu. Por favor, reveja os resultados.
|
||||
## Result view
|
||||
## Fix Application
|
||||
health.fix.fixBtn=Corrigir
|
||||
|
||||
@@ -247,6 +248,9 @@ stats.title=Estatísticas de %s
|
||||
## Read
|
||||
## Write
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Fechar
|
||||
main.minimizeBtn.tooltip=Minimizar
|
||||
|
||||
@@ -218,7 +218,10 @@ health.check.detail.checkFinished=A verificação foi concluída com sucesso.
|
||||
health.check.detail.checkFinishedAndFound=A verificação terminou em execução. Por favor, reveja os resultados.
|
||||
health.check.detail.checkFailed=A verificação foi encerrada devido a um erro.
|
||||
health.check.detail.checkCancelled=A verificação foi cancelada.
|
||||
health.check.detail.listFilters.label=Filtro
|
||||
health.check.detail.listFilters.severity=Severidade
|
||||
health.check.exportBtn=Exportar Relatório
|
||||
## Result view
|
||||
## Fix Application
|
||||
health.fix.fixBtn=Corrigir
|
||||
health.fix.successTip=Consertado com sucesso
|
||||
@@ -300,6 +303,9 @@ stats.encr.total.data.mib=Dados criptografados: %.1f MiB
|
||||
stats.encr.total.data.gib=Dados criptografados: %.1f GiB
|
||||
stats.write.accessCount=Total gravado: %d
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Fechar
|
||||
main.minimizeBtn.tooltip=Minimizar
|
||||
|
||||
@@ -16,6 +16,10 @@ generic.button.print=Tipărește
|
||||
|
||||
# Error
|
||||
error.message=A apărut o eroare
|
||||
error.description=Cryptomatorul nu se aștepta să se întâmple asta. Puteți căuta soluții existente pentru această eroare. În cazul in care nu s-a raportat încă, nu ezitați să faceți acest lucru.
|
||||
error.hyperlink.lookup=Caută soluții pentru această eroare
|
||||
error.hyperlink.report=Raportează această eroare
|
||||
error.technicalDetails=Detalii:
|
||||
|
||||
# Defaults
|
||||
defaults.vault.vaultName=Seif
|
||||
@@ -49,6 +53,12 @@ addvaultwizard.new.fileAlreadyExists=Există deja un fișier sau un dosar cu num
|
||||
addvaultwizard.new.locationDoesNotExist=Dosarul în calea specificată nu există sau nu poate fi accesat
|
||||
addvaultwizard.new.locationIsNotWritable=Nu există acces la scriere la calea specificată
|
||||
addvaultwizard.new.locationIsOk=Locația potrivită pentru seiful dumneavoastră
|
||||
addvaultwizard.new.invalidName=Nume de seif invalid
|
||||
addvaultwizard.new.validName=Nume de seif valid
|
||||
addvaultwizard.new.validCharacters.message=Numele seifului poate conține următoarele caractere:
|
||||
addvaultwizard.new.validCharacters.chars=Caractere (e.x. a, ж or 수)
|
||||
addvaultwizard.new.validCharacters.numbers=Numere
|
||||
addvaultwizard.new.validCharacters.dashes=Linii(%s) sau sub-linii(%s)
|
||||
### Password
|
||||
addvaultwizard.new.createVaultBtn=Crează seif
|
||||
addvaultwizard.new.generateRecoveryKeyChoice=Nu veți putea accesa datele dvs. fără parolă. Doriți o cheie de recuperare pentru cazul în care vă pierdeți parola?
|
||||
@@ -67,18 +77,22 @@ addvault.new.readme.storageLocation.8=2. Deblocați seiful în Cryptomator.
|
||||
addvault.new.readme.storageLocation.9=3. Deschideți locația de acces făcând clic pe butonul "Reveal".
|
||||
addvault.new.readme.storageLocation.10=Dacă aveți nevoie de ajutor, vizitați documentația: %s
|
||||
addvault.new.readme.accessLocation.fileName=BINEATIVENIT.rtf
|
||||
addvault.new.readme.accessLocation.1=🔐️ UNITATE CRIPTATĂ 🔐️
|
||||
addvault.new.readme.accessLocation.1=🔐️ VOLUM CRIPTAT 🔐️
|
||||
addvault.new.readme.accessLocation.2=Aceasta este locația de acces a seifului dvs.
|
||||
addvault.new.readme.accessLocation.3=Orice fișier adăugat la acest volum va fi criptat de către Cryptomator. Puteți lucra la el ca pe orice altă unitate/folder. Aceasta este doar o vizualizare decriptată a conținutului său, fișierele sunt criptate tot timpul pe hard disk-ul tău.
|
||||
addvault.new.readme.accessLocation.4=Puteți să ștergeți acest fișier.
|
||||
## Existing
|
||||
addvaultwizard.existing.instruction=Alegeți fișierul "vault.cryptomator" al seifului dvs. existent. Dacă există doar un fișier numit "masterkey.cryptomator", alegeți-l pe acesta.
|
||||
addvaultwizard.existing.chooseBtn=Alege…
|
||||
addvaultwizard.existing.filePickerTitle=Selectați fișierul seif
|
||||
addvaultwizard.existing.filePickerMimeDesc=Seif Cryptomator
|
||||
## Success
|
||||
addvaultwizard.success.nextStepsInstructions=Seiful "%s" a fost adăugat.\nTrebuie să deblocați acest seif pentru a accesa sau adăuga conținut. Alternativ, îl puteți debloca în orice moment ulterior.
|
||||
addvaultwizard.success.unlockNow=Deblochează acum
|
||||
|
||||
# Remove Vault
|
||||
removeVault.title=Eliminați seiful
|
||||
removeVault.message=Ștergeți seiful?
|
||||
removeVault.description=Acest lucru va face Cryptomator să uite de acest seif. Îl puteţi adăuga din nou mai târziu. Nici un fişier criptat nu va fi şters din hard disk-ul dvs.
|
||||
removeVault.confirmBtn=Eliminați seiful
|
||||
|
||||
@@ -89,6 +103,7 @@ changepassword.finalConfirmation=Înțeleg că nu voi putea accesa datele mele d
|
||||
|
||||
# Forget Password
|
||||
forgetPassword.title=Parolă uitată
|
||||
forgetPassword.message=Ați uitat parola?
|
||||
forgetPassword.description=Această acțiune va șterge parola salvată a acestui seif din keychain-ul sistemului de operare.
|
||||
forgetPassword.confirmBtn=Parolă uitată
|
||||
|
||||
@@ -98,32 +113,55 @@ unlock.passwordPrompt=Introduceți parola pentru "%s":
|
||||
unlock.savePassword=Memorează parola
|
||||
unlock.unlockBtn=Deblocați
|
||||
## Select
|
||||
unlock.chooseMasterkey.message=Fișierul cheii principale nu a fost găsit
|
||||
unlock.chooseMasterkey.description=Nu s-a putut găsi fișierul masterkey pentru acest seif la locația așteptată. Vă rugăm să alegeți manual fișierul cheie.
|
||||
unlock.chooseMasterkey.filePickerTitle=Selectaţi fişierul Masterkey
|
||||
unlock.chooseMasterkey.filePickerMimeDesc=Cheia principală a Cryptomator-ului
|
||||
## Success
|
||||
unlock.success.message=Seiful a fost deblocat cu succes
|
||||
unlock.success.description=Deblocat "%s" cu succes! Seiful dvs. este acum accesibil prin unitatea sa virtuală.
|
||||
unlock.success.rememberChoice=Ține minte alegerea, nu mai arăta asta din nou
|
||||
unlock.success.revealBtn=Dezvăluie unitatea
|
||||
unlock.success.revealBtn=Dezvăluie partiția
|
||||
## Failure
|
||||
unlock.error.message=Imposibil de deblocat seiful
|
||||
unlock.error.message=Seiful nu a fost putut deschis
|
||||
### Invalid Mount Point
|
||||
unlock.error.invalidMountPoint.notExisting=Punctul de montare "%s" nu este un dosar, nu este gol sau nu există.
|
||||
unlock.error.invalidMountPoint.existing=Punctul de montare "%s" există deja sau dosarul părinte lipsește.
|
||||
unlock.error.invalidMountPoint.driveLetterOccupied=Partiția cu litera "%s" este deja in folosința.
|
||||
## Hub
|
||||
### Waiting
|
||||
hub.auth.message=Se așteaptă autentificarea…
|
||||
hub.auth.description=Ar trebui să fiți redirecționat automat către pagina de autentificare.
|
||||
hub.auth.loginLink=Nu ați fost redirecționat? Apăsați aici pentru a deschide pagina.
|
||||
### Receive Key
|
||||
hub.receive.message=Se procesează răspunsul…
|
||||
hub.receive.description=In acest moment Criptomatorul primește și procesează răspunsul de la Hub. Vă rugăm să așteptați.
|
||||
### Register Device
|
||||
hub.register.message=Numele dispozitivului este necesar
|
||||
hub.register.description=Se pare că este prima data când accesați Hub-ul de pe acest dispozitiv. Trebuie sa denumiți acest dispozitiv pentru autorizarea accesului.
|
||||
hub.register.nameLabel=Numele dispozitivului
|
||||
hub.register.occupiedMsg=Acest nume este deja utilizat
|
||||
hub.register.registerBtn=Confirmați
|
||||
### Registration Success
|
||||
hub.registerSuccess.message=Dispozitiv numit
|
||||
hub.registerSuccess.description=Pentru a accesa acest seif, dispozitivul dvs. trebuie să fie autorizat de proprietarul seifului.
|
||||
### Registration Failed
|
||||
hub.registerFailed.message=Numirea dispozitivului a eșuat
|
||||
hub.registerFailed.description=O eroare a fost întâmpinata în procesul de denumire. Pentru mai multe detalii, verificați jurnalul aplicației.
|
||||
### Unauthorized
|
||||
hub.unauthorized.message=Acces respins
|
||||
hub.unauthorized.description=Dispozitivul dvs. nu a fost autorizat să acceseze acest seif. Solicitați proprietarului seifului să va autorizeze accesul.
|
||||
### License Exceeded
|
||||
hub.licenseExceeded.message=Numărul de licențe a fost depășit
|
||||
hub.licenseExceeded.description=Cryptomator Hub a permis accesul la mai mulți utilizatori decât licența permite. Vă rugăm să contactați administratorul Hub-ului dumneavoastră pentru a actualiza licența sau un administrator de seif pentru a elimina utilizatorii din seifuri.
|
||||
|
||||
|
||||
# Lock
|
||||
## Force
|
||||
lock.forced.message=Blocarea a eșuat
|
||||
lock.forced.description=Blocarea "%s" a fost blocată de operațiile în așteptare sau de fișierele deschise. Puteți forța blocarea acestui seif, dar întreruperea I/O poate duce la pierderea datelor nesalvate.
|
||||
lock.forced.retryBtn=Încercați din nou
|
||||
lock.forced.forceBtn=Blocare forțată
|
||||
## Failure
|
||||
lock.fail.message=Blocarea seifului a eșuat.
|
||||
lock.fail.description=Seiful "%s" nu a putut fi blocat. Asigurați-vă că lucrările nesalvate sunt salvate altundeva și că operațiunile importante de citire/scriere sunt terminate. Pentru a închide seiful omoară procesul Cryptomator.
|
||||
@@ -131,7 +169,7 @@ lock.fail.description=Seiful "%s" nu a putut fi blocat. Asigurați-vă că lucr
|
||||
# Migration
|
||||
migration.title=Îmbunătățește seiful
|
||||
## Start
|
||||
migration.start.prompt=Seiful dvs. "%s" trebuie să fie actualizat la un format mai nou. Înainte de a continua, asigurați-vă că nu există sincronizare în așteptare care să afecteze acest seif.
|
||||
migration.start.prompt=Seiful dvs. "%s" trebuie să fie actualizat la un format mai nou. Înainte de a continua, asigurați-vă că nu există sincronizări în așteptare care să afecteze acest seif.
|
||||
migration.start.confirm=Da, seiful meu este complet sincronizat
|
||||
## Run
|
||||
migration.run.enterPassword=Introduceți parola pentru "%s"
|
||||
@@ -141,16 +179,16 @@ migration.run.progressHint=Acest lucru poate dura ceva timp…
|
||||
migration.success.nextStepsInstructions=Seiful "%s" a fost migrat cu succes.\nAcum puteți debloca seiful dvs.
|
||||
migration.success.unlockNow=Deblochează acum
|
||||
## Missing file system capabilities
|
||||
migration.error.missingFileSystemCapabilities.title=Tip de fișier nesuportat
|
||||
migration.error.missingFileSystemCapabilities.title=Sistem de fișiere nesuportat
|
||||
migration.error.missingFileSystemCapabilities.description=Migrarea nu a fost pornită, deoarece seiful dvs. este localizat pe un sistem de fișiere necorespunzător.
|
||||
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=Sistemul de fişiere nu acceptă nume de fişiere lungi.
|
||||
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Sistemul de fișiere nu suportă căi lungi.
|
||||
migration.error.missingFileSystemCapabilities.reason.READ_ACCESS=Sistemul de fișiere nu permite citirea.
|
||||
migration.error.missingFileSystemCapabilities.reason.WRITE_ACCESS=Sistemul de fişiere nu permite scrierea.
|
||||
## Impossible
|
||||
migration.impossible.heading=Imposibil de migrat seiful
|
||||
migration.impossible.heading=Seiful nu a putut fi migrat
|
||||
migration.impossible.reason=Seiful nu poate fi migrat automat deoarece locația sa de stocare sau punctul de acces nu este compatibilă.
|
||||
migration.impossible.moreInfo=Seiful poate fi deschis în continuare cu o versiune mai veche. Pentru instrucţiuni despre cum să migraţi manual un seif, vizitaţi
|
||||
migration.impossible.moreInfo=Seiful poate fi deschis cu o versiune mai veche. Pentru instrucţiuni despre cum să migraţi manual un seif, vizitaţi
|
||||
|
||||
# Health Check
|
||||
## Start
|
||||
@@ -180,7 +218,27 @@ health.check.detail.checkFinished=Verificarea s-a terminat cu succes.
|
||||
health.check.detail.checkFinishedAndFound=Verificarea s-a terminat. Vă rugăm să examinați rezultatele.
|
||||
health.check.detail.checkFailed=Verificarea a ieșit din cauza unei erori.
|
||||
health.check.detail.checkCancelled=Verificarea a fost anulată.
|
||||
health.check.detail.listFilters.label=Filtru
|
||||
health.check.detail.listFilters.severity=Severitate
|
||||
health.check.detail.listFilters.fixState=Repară starea
|
||||
health.check.detail.fixAllSpecificBtn=Repară tot de tipul
|
||||
health.check.exportBtn=Exportare raport
|
||||
## Result view
|
||||
health.result.severityFilter.all=Severitate - Toate
|
||||
health.result.severityFilter.good=Bună
|
||||
health.result.severityFilter.info=Informații
|
||||
health.result.severityFilter.warn=Atenție
|
||||
health.result.severityFilter.crit=Critică
|
||||
health.result.severityTip.good=Severitate: Bună\nStructura seifului este normală.
|
||||
health.result.severityTip.info=Severitate: Info\nStructura de seif este intactă, reparația este sugerată.
|
||||
health.result.severityTip.warn=Severitate: Avertisment\nStructura seifului este coruptă, repararea este recomandată.
|
||||
health.result.severityTip.crit=Severitate: Critică\nStructura de seif este coruptă, pierderea de date a fost determinată.
|
||||
health.result.fixStateFilter.all=Repară starea - Toate
|
||||
health.result.fixStateFilter.fixable=Reparabil
|
||||
health.result.fixStateFilter.notFixable=Nu este reparabil
|
||||
health.result.fixStateFilter.fixing=In curs de reparare…
|
||||
health.result.fixStateFilter.fixed=Reparat
|
||||
health.result.fixStateFilter.fixFailed=Reparația a eșuat
|
||||
## Fix Application
|
||||
health.fix.fixBtn=Repară
|
||||
health.fix.successTip=Remediere reușită
|
||||
@@ -191,13 +249,25 @@ preferences.title=Preferințe
|
||||
## General
|
||||
preferences.general=Setări Generale
|
||||
preferences.general.startHidden=Ascunde fereastra la pornirea Cryptomator
|
||||
preferences.general.autoCloseVaults=Încuie seifurile deschise la ieșirea din aplicație automat
|
||||
preferences.general.debugLogging=Activează jurnalul de depanare
|
||||
preferences.general.debugDirectory=Dezvăluie fişierele jurnal
|
||||
preferences.general.autoStart=Lansați Cryptomator la pornirea sistemului
|
||||
preferences.general.keychainBackend=Salvează parolele cu
|
||||
## Interface
|
||||
preferences.interface=Interfață
|
||||
preferences.interface.theme=Aspect
|
||||
preferences.interface.theme.automatic=Automat
|
||||
preferences.interface.theme.dark=Întunecat
|
||||
preferences.interface.theme.light=Luminos
|
||||
preferences.interface.unlockThemes=Deblochează tema întunecată
|
||||
preferences.interface.language=Limba (necesită repornirea programului)
|
||||
preferences.interface.language.auto=Prestabilit din sistem
|
||||
preferences.interface.interfaceOrientation=Orientarea interfeței
|
||||
preferences.interface.interfaceOrientation.ltr=De la stânga la dreapta
|
||||
preferences.interface.interfaceOrientation.rtl=De la dreapta la stânga
|
||||
preferences.interface.showMinimizeButton=Arată butonul de minimizare
|
||||
preferences.interface.showTrayIcon=Arată tray icon (necesită repornire)
|
||||
## Volume
|
||||
preferences.volume=Unitate virtuală
|
||||
preferences.volume.type=Tip volum
|
||||
@@ -250,6 +320,11 @@ stats.encr.total.data.mib=Date criptate: %.1f MiB
|
||||
stats.encr.total.data.gib=Date criptate: %.1f GiB
|
||||
stats.write.accessCount=Total scrieri: %d
|
||||
|
||||
## Accesses
|
||||
stats.access.current=Acces: %d
|
||||
stats.access.total=Accesuri totale: %d
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Închide
|
||||
main.minimizeBtn.tooltip=Minimizează
|
||||
@@ -297,6 +372,9 @@ main.vaultDetail.missing.changeLocation=Schimbați locația seifului…
|
||||
main.vaultDetail.migrateButton=Îmbunătățește seiful
|
||||
main.vaultDetail.migratePrompt=Înainte de a-l putea accesa, seiful dumneavoastră trebuie actualizat la format nou
|
||||
### Error
|
||||
main.vaultDetail.error.info=A apărut o eroare la încărcarea seifului de pe disc.
|
||||
main.vaultDetail.error.reload=Reîncărcare
|
||||
main.vaultDetail.error.windowTitle=S-a produs o eroare la încărcarea datelor
|
||||
|
||||
# Wrong File Alert
|
||||
wrongFileAlert.title=Cum să criptați fișierele
|
||||
@@ -338,20 +416,27 @@ vaultOptions.masterkey.changePasswordBtn=Schimbați parola
|
||||
vaultOptions.masterkey.forgetSavedPasswordBtn=Parolă salvată uitată
|
||||
vaultOptions.masterkey.recoveryKeyExplanation=O cheie de recuperare este singurul mijloc de a restabili accesul la un seif în caz că vă pierdeți parola.
|
||||
vaultOptions.masterkey.showRecoveryKeyBtn=Afișează cheia de recuperare
|
||||
vaultOptions.masterkey.recoverPasswordBtn=Resetează Parola
|
||||
|
||||
|
||||
# Recovery Key
|
||||
## Display Recovery Key
|
||||
recoveryKey.display.title=Arată cheia de recuperare
|
||||
recoveryKey.create.message=Trebuie să introduceți o parolă
|
||||
recoveryKey.create.description=Introduceți parola pentru a afișa cheia de recuperare pentru "%s:
|
||||
recoveryKey.display.description=Următoarea cheie de recuperare poate fi folosită pentru a restabili accesul la "%s":
|
||||
recoveryKey.display.StorageHints=Păstrați cheia de recuperare undeva foarte sigur, de ex.\n • Păstrați-o folosind un manager de parole\n • Salvați-o pe un flash USB stick\n • Imprimați-o pe hârtie
|
||||
## Reset Password
|
||||
### Enter Recovery Key
|
||||
recoveryKey.recover.title=Resetează Parola
|
||||
recoveryKey.recover.prompt=Introduceți cheia de recuperare pentru "%s:
|
||||
recoveryKey.recover.validKey=Aceasta este o cheie de recuperare validă
|
||||
recoveryKey.printout.heading=Cheia de recuperare Cryptomator\n"%s"\n
|
||||
### Reset Password
|
||||
recoveryKey.recover.resetBtn=Resetează
|
||||
### Recovery Key Password Reset Success
|
||||
recoveryKey.recover.resetSuccess.message=Parola a fost resetată cu succes
|
||||
recoveryKey.recover.resetSuccess.description=Puteți debloca seiful cu parola noua.
|
||||
|
||||
# New Password
|
||||
newPassword.promptText=Introduceți o parolă nouă
|
||||
@@ -366,6 +451,12 @@ passwordStrength.messageLabel.3=Puternică
|
||||
passwordStrength.messageLabel.4=Foarte puternică
|
||||
|
||||
# Quit
|
||||
quit.title=Închide aplicația
|
||||
quit.message=Există seifuri deblocate
|
||||
quit.description=Vă rugăm să confirmați că doriți să ieșiți. Cryptomatorul va încuia toate seifurile deblocate pentru a preveni pierderea datelor.
|
||||
quit.lockAndQuitBtn=Blocați și ieșiți
|
||||
|
||||
# Forced Quit
|
||||
# Forced Quit
|
||||
quit.forced.message=Unele seifuri nu au putut fi blocate
|
||||
quit.forced.description=Încuierea seifurilor a fost blocata de operațiuni in curs sau de fișiere deschise. Puteți încuia forțat aceste seifuri dar prin întreruperea operațiunilor I/O poate rezulta în pierderea datelor nesalvate.
|
||||
quit.forced.forceAndQuitBtn=Încuiați forțat și ieșiți
|
||||
@@ -219,6 +219,7 @@ health.check.detail.checkFinishedAndFound=Проверка завершена.
|
||||
health.check.detail.checkFailed=Проверка прервана из-за ошибки.
|
||||
health.check.detail.checkCancelled=Проверка была отменена.
|
||||
health.check.exportBtn=Экспорт отчёта
|
||||
## Result view
|
||||
## Fix Application
|
||||
health.fix.fixBtn=Исправить
|
||||
health.fix.successTip=Исправлено
|
||||
@@ -300,6 +301,9 @@ stats.encr.total.data.mib=Зашифровано: %.1f МиБ
|
||||
stats.encr.total.data.gib=Зашифровано: %.1f ГиБ
|
||||
stats.write.accessCount=Всего записей: %d
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Закрыть
|
||||
main.minimizeBtn.tooltip=Свернуть
|
||||
|
||||
@@ -72,6 +72,7 @@ unlock.unlockBtn=අගුළුහරින්න
|
||||
## Start Failure
|
||||
## Check Selection
|
||||
## Detail view
|
||||
## Result view
|
||||
## Fix Application
|
||||
|
||||
# Preferences
|
||||
@@ -88,6 +89,9 @@ unlock.unlockBtn=අගුළුහරින්න
|
||||
## Read
|
||||
## Write
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=වසන්න
|
||||
## Drag 'n' Drop
|
||||
|
||||
@@ -218,7 +218,20 @@ health.check.detail.checkFinished=Kontrola skončila úspešne.
|
||||
health.check.detail.checkFinishedAndFound=Kontrola skončila. Prosím pozrite si jej výsledky.
|
||||
health.check.detail.checkFailed=Kontrola skončila kôli chybe.
|
||||
health.check.detail.checkCancelled=Kontrola bola zrušená.
|
||||
health.check.detail.listFilters.label=Filter
|
||||
health.check.detail.listFilters.severity=Závažnosť
|
||||
health.check.detail.listFilters.fixState=Stav opravy
|
||||
health.check.exportBtn=Exportovať správu
|
||||
## Result view
|
||||
health.result.severityFilter.good=Dobrý
|
||||
health.result.severityFilter.info=Informácie
|
||||
health.result.severityFilter.warn=Výstraha
|
||||
health.result.severityFilter.crit=Kritický
|
||||
health.result.fixStateFilter.fixable=Opraviteľné
|
||||
health.result.fixStateFilter.notFixable=Neopraviteľné
|
||||
health.result.fixStateFilter.fixing=Opravovanie…
|
||||
health.result.fixStateFilter.fixed=Opravené
|
||||
health.result.fixStateFilter.fixFailed=Oprava zlyhala
|
||||
## Fix Application
|
||||
health.fix.fixBtn=Opraviť
|
||||
health.fix.successTip=Oprava úspešná
|
||||
@@ -300,6 +313,11 @@ stats.encr.total.data.mib=Odkódovaných dát: %.1f MiB
|
||||
stats.encr.total.data.gib=Odkódovaných dát: %.1f GiB
|
||||
stats.write.accessCount=Suma zápisov: %d
|
||||
|
||||
## Accesses
|
||||
stats.access.current=Sprístupnené: %d
|
||||
stats.access.total=Celkovo sprístupnené: %d
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Zavrieť
|
||||
main.minimizeBtn.tooltip=Minimalizovať
|
||||
|
||||
@@ -152,6 +152,7 @@ migration.impossible.moreInfo=Sef se i dalje može otvoriti sa starijom verzijom
|
||||
## Start Failure
|
||||
## Check Selection
|
||||
## Detail view
|
||||
## Result view
|
||||
## Fix Application
|
||||
|
||||
# Preferences
|
||||
@@ -218,6 +219,9 @@ stats.encr.total.data.mib=Шифровано података: %.1f MiB
|
||||
stats.encr.total.data.gib=Шифровано података: %.1f GiB
|
||||
stats.write.accessCount=Укупно уписано: %d
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Zatvori
|
||||
main.minimizeBtn.tooltip=Умањи
|
||||
|
||||
@@ -141,6 +141,7 @@ migration.impossible.moreInfo=Sef se i dalje može otvoriti sa starijom verzijom
|
||||
## Start Failure
|
||||
## Check Selection
|
||||
## Detail view
|
||||
## Result view
|
||||
## Fix Application
|
||||
|
||||
# Preferences
|
||||
@@ -194,6 +195,9 @@ stats.decr.total.data.mib=Dešifrovano podataka: %.1f MiB
|
||||
stats.decr.total.data.gib=Dešifrovano podataka: %.1f GiB
|
||||
## Write
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Zatvori
|
||||
main.preferencesBtn.tooltip=Podešavanja
|
||||
|
||||
@@ -219,6 +219,7 @@ health.check.detail.checkFinishedAndFound=Kontrollen är slutförd. Vänligen gr
|
||||
health.check.detail.checkFailed=Kontrollen avslutades på grund av ett fel.
|
||||
health.check.detail.checkCancelled=Kontrollen avbröts.
|
||||
health.check.exportBtn=Exportera rapport
|
||||
## Result view
|
||||
## Fix Application
|
||||
health.fix.fixBtn=Åtgärda
|
||||
health.fix.successTip=Åtgärden lyckades
|
||||
@@ -300,6 +301,9 @@ stats.encr.total.data.mib=Data krypterad: %.1f MiB
|
||||
stats.encr.total.data.gib=Data krypterad: %.1f GiB
|
||||
stats.write.accessCount=Totalt skrivet: %d
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Stäng
|
||||
main.minimizeBtn.tooltip=Minimera
|
||||
|
||||
@@ -219,6 +219,7 @@ health.check.detail.checkFinishedAndFound=Ukaguzi ulimaliza kukimbia. Tafadhali
|
||||
health.check.detail.checkFailed=Ukaguzi ulitoka kwa sababu ya kosa.
|
||||
health.check.detail.checkCancelled=Ukaguzi ulikatishwa.
|
||||
health.check.exportBtn=Hamisha Ripoti
|
||||
## Result view
|
||||
## Fix Application
|
||||
health.fix.fixBtn=Kurekebisha
|
||||
health.fix.successTip=Rekebisha imefanikiwa
|
||||
@@ -300,6 +301,9 @@ stats.encr.total.data.mib=Data iliyosimbwa kwa njia fiche: %.1f MiB
|
||||
stats.encr.total.data.gib=Data iliyosimbwa kwa njia fiche: %.1f GiB
|
||||
stats.write.accessCount=Jumla ya maandishi anaandika: %d
|
||||
|
||||
## Accesses
|
||||
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Futa
|
||||
main.minimizeBtn.tooltip=Kupunguza
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user