mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-09-25 09:24:14 +00:00
Compare commits
169
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6225d8d1f0 | ||
|
|
ed975b459e | ||
|
|
800b2440b3 | ||
|
|
f50a49e7a3 | ||
|
|
92ece4dfb6 | ||
|
|
174225c60e | ||
|
|
3071410a22 | ||
|
|
5c5777ffc5 | ||
|
|
8dd8b93656 | ||
|
|
3b2d455b4a | ||
|
|
b0dfa22903 | ||
|
|
88b1c28d88 | ||
|
|
5b85d7859a | ||
|
|
468eed58d5 | ||
|
|
34c17be474 | ||
|
|
5dedd6b1c1 | ||
|
|
25e8e81686 | ||
|
|
1f7ab03bbb | ||
|
|
86f3cb7288 | ||
|
|
a1b8bf23b4 | ||
|
|
1f21d5ea4c | ||
|
|
f8ff7201d7 | ||
|
|
3567c036c3 | ||
|
|
0cf1f087ad | ||
|
|
209f60727e | ||
|
|
560a979e0e | ||
|
|
1debe4c7c8 | ||
|
|
2e2aa77727 | ||
|
|
162ebf6545 | ||
|
|
2d503a0edb | ||
|
|
7f7f0a099a | ||
|
|
109f5d1faa | ||
|
|
9b55f6fc56 | ||
|
|
83879f5cfe | ||
|
|
083ac5a7e0 | ||
|
|
a7eba377ba | ||
|
|
4ee1e6d9f1 | ||
|
|
24a63c10d0 | ||
|
|
0d805b2d43 | ||
|
|
7bc47fe6d7 | ||
|
|
ae50846257 | ||
|
|
ce466e7715 | ||
|
|
15885545b5 | ||
|
|
29fedcd390 | ||
|
|
fadd6b761f | ||
|
|
d857335901 | ||
|
|
93b4cbfb2c | ||
|
|
625334c6c8 | ||
|
|
943374856e | ||
|
|
e1d7fd92ad | ||
|
|
c10dc74818 | ||
|
|
92d9f2c18d | ||
|
|
d7488b6984 | ||
|
|
88ad3cd724 | ||
|
|
ba9e0b0c12 | ||
|
|
bbe7255901 | ||
|
|
51e55d3e3b | ||
|
|
f704dc0de9 | ||
|
|
e858280f30 | ||
|
|
f9b170204a | ||
|
|
0ed7415a0d | ||
|
|
7a0d50ecca | ||
|
|
19e035aa33 | ||
|
|
830970cb75 | ||
|
|
9b2987d0a2 | ||
|
|
76a4062f8b | ||
|
|
113717f955 | ||
|
|
c40ad58028 | ||
|
|
d6a7efcb7f | ||
|
|
910b89d6ce | ||
|
|
5554dfdd89 | ||
|
|
1657cd50fc | ||
|
|
57bf5e11b5 | ||
|
|
594ca47c85 | ||
|
|
6c5ee14c73 | ||
|
|
8e9d54b44b | ||
|
|
70805665bf | ||
|
|
ac243a706e | ||
|
|
aa382ce80d | ||
|
|
b7da508ccc | ||
|
|
47bd0ca647 | ||
|
|
c5d6c0ce98 | ||
|
|
6017d6b7a9 | ||
|
|
e31e06b288 | ||
|
|
84ac803a7d | ||
|
|
bcb970afb1 | ||
|
|
9d640b57ce | ||
|
|
4c83617847 | ||
|
|
5d7906972b | ||
|
|
666cd4a4f0 | ||
|
|
eb4d39e8b4 | ||
|
|
a1ad89a2e2 | ||
|
|
38108299fa | ||
|
|
189e2679f1 | ||
|
|
22ceb57052 | ||
|
|
0130c7cce6 | ||
|
|
18b6484731 | ||
|
|
f19ff59005 | ||
|
|
8786b5ee2a | ||
|
|
c7e23f9646 | ||
|
|
4043e3f71f | ||
|
|
08887f1147 | ||
|
|
13a0578ab7 | ||
|
|
888bf04ec2 | ||
|
|
d0822041ec | ||
|
|
e65a57bba9 | ||
|
|
2bb1b09789 | ||
|
|
b83dad4e24 | ||
|
|
4bb28f4286 | ||
|
|
721cdf20e7 | ||
|
|
cf248c5cbc | ||
|
|
151ca75e5f | ||
|
|
0e52b7aff4 | ||
|
|
3a0199a1e5 | ||
|
|
50e96095ba | ||
|
|
2f5bce6229 | ||
|
|
62f50ce366 | ||
|
|
864c1c9c0d | ||
|
|
cfd433b328 | ||
|
|
ad97338f9b | ||
|
|
5d596f4fd0 | ||
|
|
aa14d79642 | ||
|
|
745d435a57 | ||
|
|
094a7c6a20 | ||
|
|
aedbefc38a | ||
|
|
0cd3cf9e1c | ||
|
|
ef24141713 | ||
|
|
820d4ff573 | ||
|
|
f024f0ee5d | ||
|
|
a038b34e29 | ||
|
|
4cc516def6 | ||
|
|
bb1095eb78 | ||
|
|
4c11190be9 | ||
|
|
8a7a5eeea1 | ||
|
|
34328c10bf | ||
|
|
c4d503c8d5 | ||
|
|
5fdf5141a5 | ||
|
|
9be00a2693 | ||
|
|
17778f32b9 | ||
|
|
bcbdad99bc | ||
|
|
1e6d2bb6fb | ||
|
|
57fee3e428 | ||
|
|
4ea45e937a | ||
|
|
dc80d79da0 | ||
|
|
57f68376fe | ||
|
|
f47cfa60c5 | ||
|
|
de78cd014d | ||
|
|
9db53f73e2 | ||
|
|
e5f0aca477 | ||
|
|
261f2da105 | ||
|
|
ef4738f920 | ||
|
|
f23348eda2 | ||
|
|
6edfaf3eb6 | ||
|
|
f48963a6d0 | ||
|
|
6ce34efb52 | ||
|
|
cd10d38990 | ||
|
|
8cb21c97e3 | ||
|
|
0ad8ce77ef | ||
|
|
e358ffd666 | ||
|
|
496f9a9cd5 | ||
|
|
61025de0d6 | ||
|
|
f08049b960 | ||
|
|
746c8f54eb | ||
|
|
9fc1efa005 | ||
|
|
448eac8ff5 | ||
|
|
5a18d086e0 | ||
|
|
918ace2eb6 | ||
|
|
fe733967dc | ||
|
|
59f5c0cb12 |
@@ -43,6 +43,7 @@ body:
|
|||||||
- WinFsp (Local Drive)
|
- WinFsp (Local Drive)
|
||||||
- FUSE-T
|
- FUSE-T
|
||||||
- macFUSE
|
- macFUSE
|
||||||
|
- FUSE
|
||||||
- WebDAV (Windows Explorer)
|
- WebDAV (Windows Explorer)
|
||||||
- WebDAV (AppleScript)
|
- WebDAV (AppleScript)
|
||||||
- WebDAV (gio)
|
- WebDAV (gio)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ updates:
|
|||||||
time: "06:00"
|
time: "06:00"
|
||||||
timezone: "UTC"
|
timezone: "UTC"
|
||||||
groups:
|
groups:
|
||||||
all: # one PR for all dependencies
|
maven-dependencies:
|
||||||
patterns:
|
patterns:
|
||||||
- "*"
|
- "*"
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ updates:
|
|||||||
schedule:
|
schedule:
|
||||||
interval: "monthly"
|
interval: "monthly"
|
||||||
groups:
|
groups:
|
||||||
all: # one PR for all actions
|
github-actions:
|
||||||
patterns:
|
patterns:
|
||||||
- "*"
|
- "*"
|
||||||
labels:
|
labels:
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_VERSION: 20
|
JAVA_DIST: 'zulu'
|
||||||
|
JAVA_VERSION: 21
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
get-version:
|
get-version:
|
||||||
@@ -20,26 +21,50 @@ jobs:
|
|||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build AppImage
|
name: Build AppImage
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ matrix.os }}
|
||||||
needs: [get-version]
|
needs: [get-version]
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: ubuntu-latest
|
||||||
|
appimage-suffix: x86_64
|
||||||
|
openjfx-url: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-x64_bin-jmods.zip'
|
||||||
|
openjfx-sha: 'f522ac2ae4bdd61f0219b7b8d2058ff72a22f36a44378453bcfdcd82f8f5e08c'
|
||||||
|
- os: [self-hosted, Linux, ARM64]
|
||||||
|
appimage-suffix: aarch64
|
||||||
|
openjfx-url: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-aarch64_bin-jmods.zip'
|
||||||
|
openjfx-sha: 'c0d80ebbe0aab404ef9ad8b46c05bf533a1e40b39b2720eebd9238d81f6326ca'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: 'zulu'
|
distribution: ${{ env.JAVA_DIST }}
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
java-package: 'jdk+fx'
|
check-latest: true
|
||||||
cache: 'maven'
|
cache: 'maven'
|
||||||
- name: Ensure major jfx version in pom equals in jdk
|
|
||||||
shell: pwsh
|
- name: Download OpenJFX jmods
|
||||||
|
id: download-jmods
|
||||||
run: |
|
run: |
|
||||||
$jfxPomVersion = (&mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout) -split "\."
|
curl -L ${{ matrix.openjfx-url }} -o openjfx-jmods.zip
|
||||||
$jfxJdkVersion = ((Get-Content -path "${env:JAVA_HOME}/lib/javafx.properties" | Where-Object {$_ -like 'javafx.version=*' }) -replace '.*=','') -split "\."
|
echo "${{ matrix.openjfx-sha }} openjfx-jmods.zip" | shasum -a256 --check
|
||||||
if ($jfxPomVersion[0] -ne $jfxJdkVersion[0]) {
|
mkdir -p openjfx-jmods
|
||||||
Write-Error "Major part of JavaFX version in pom($($jfxPomVersion[0])) does not match the version in JDK($($jfxJdkVersion[0])) "
|
unzip -j openjfx-jmods.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d openjfx-jmods
|
||||||
|
- name: Ensure major jfx version in pom and in jmods is the same
|
||||||
|
run: |
|
||||||
|
JMOD_VERSION=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1)
|
||||||
|
JMOD_VERSION=${JMOD_VERSION#*@}
|
||||||
|
JMOD_VERSION=${JMOD_VERSION%%.*}
|
||||||
|
POM_JFX_VERSION=$(mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout)
|
||||||
|
POM_JFX_VERSION=${POM_JFX_VERSION#*@}
|
||||||
|
POM_JFX_VERSION=${POM_JFX_VERSION%%.*}
|
||||||
|
|
||||||
|
if [ $POM_JFX_VERSION -ne $JMOD_VERSION_AMD64 ]; then
|
||||||
|
>&2 echo "Major JavaFX version in pom.xml (${POM_JFX_VERSION}) != amd64 jmod version (${JMOD_VERSION})"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
fi
|
||||||
- name: Set version
|
- name: Set version
|
||||||
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
|
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
|
||||||
- name: Run maven
|
- name: Run maven
|
||||||
@@ -53,7 +78,7 @@ jobs:
|
|||||||
${JAVA_HOME}/bin/jlink
|
${JAVA_HOME}/bin/jlink
|
||||||
--verbose
|
--verbose
|
||||||
--output runtime
|
--output runtime
|
||||||
--module-path "${JAVA_HOME}/jmods"
|
--module-path "${JAVA_HOME}/jmods:openjfx-jmods"
|
||||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net
|
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net
|
||||||
--strip-native-commands
|
--strip-native-commands
|
||||||
--no-header-files
|
--no-header-files
|
||||||
@@ -118,7 +143,7 @@ jobs:
|
|||||||
ln -s bin/cryptomator.sh Cryptomator.AppDir/AppRun
|
ln -s bin/cryptomator.sh Cryptomator.AppDir/AppRun
|
||||||
- name: Download AppImageKit
|
- name: Download AppImageKit
|
||||||
run: |
|
run: |
|
||||||
curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-x86_64.AppImage -o appimagetool.AppImage
|
curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-${{ matrix.appimage-suffix }}.AppImage -o appimagetool.AppImage
|
||||||
chmod +x appimagetool.AppImage
|
chmod +x appimagetool.AppImage
|
||||||
./appimagetool.AppImage --appimage-extract
|
./appimagetool.AppImage --appimage-extract
|
||||||
- name: Prepare GPG-Agent for signing with key 615D449FE6E6A235
|
- name: Prepare GPG-Agent for signing with key 615D449FE6E6A235
|
||||||
@@ -130,8 +155,8 @@ jobs:
|
|||||||
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
|
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
|
||||||
- name: Build AppImage
|
- name: Build AppImage
|
||||||
run: >
|
run: >
|
||||||
./squashfs-root/AppRun Cryptomator.AppDir cryptomator-${{ needs.get-version.outputs.semVerStr }}-x86_64.AppImage
|
./squashfs-root/AppRun Cryptomator.AppDir cryptomator-${{ needs.get-version.outputs.semVerStr }}-${{ matrix.appimage-suffix }}.AppImage
|
||||||
-u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-x86_64.AppImage.zsync'
|
-u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-${{ matrix.appimage-suffix }}.AppImage.zsync'
|
||||||
--sign --sign-key=615D449FE6E6A235 --sign-args="--batch --pinentry-mode loopback"
|
--sign --sign-key=615D449FE6E6A235 --sign-args="--batch --pinentry-mode loopback"
|
||||||
- name: Create detached GPG signatures
|
- name: Create detached GPG signatures
|
||||||
run: |
|
run: |
|
||||||
@@ -140,7 +165,7 @@ jobs:
|
|||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: appimage
|
name: appimage-${{ matrix.appimage-suffix }}
|
||||||
path: |
|
path: |
|
||||||
cryptomator-*.AppImage
|
cryptomator-*.AppImage
|
||||||
cryptomator-*.AppImage.zsync
|
cryptomator-*.AppImage.zsync
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ on:
|
|||||||
types: [labeled]
|
types: [labeled]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_VERSION: 20
|
JAVA_DIST: 'zulu'
|
||||||
|
JAVA_VERSION: 21
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
@@ -17,10 +18,10 @@ jobs:
|
|||||||
name: Compile and Test
|
name: Compile and Test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-java@v3
|
- uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: 'zulu'
|
distribution: ${{ env.JAVA_DIST }}
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
cache: 'maven'
|
cache: 'maven'
|
||||||
- name: Cache SonarCloud packages
|
- name: Cache SonarCloud packages
|
||||||
|
|||||||
@@ -16,16 +16,20 @@ on:
|
|||||||
type: boolean
|
type: boolean
|
||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_VERSION: 20
|
JAVA_DIST: 'zulu'
|
||||||
|
JAVA_VERSION: 21
|
||||||
|
COFFEELIBS_JDK_VERSION: 21
|
||||||
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-x64_bin-jmods.zip'
|
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-x64_bin-jmods.zip'
|
||||||
|
OPENJFX_JMODS_AMD64_HASH: 'f522ac2ae4bdd61f0219b7b8d2058ff72a22f36a44378453bcfdcd82f8f5e08c'
|
||||||
OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-aarch64_bin-jmods.zip'
|
OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-aarch64_bin-jmods.zip'
|
||||||
|
OPENJFX_JMODS_AARCH64_HASH: 'c0d80ebbe0aab404ef9ad8b46c05bf533a1e40b39b2720eebd9238d81f6326ca'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build Debian Package
|
name: Build Debian Package
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- id: versions
|
- id: versions
|
||||||
name: Get version information
|
name: Get version information
|
||||||
run: |
|
run: |
|
||||||
@@ -39,12 +43,13 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
sudo add-apt-repository ppa:coffeelibs/openjdk
|
sudo add-apt-repository ppa:coffeelibs/openjdk
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install debhelper devscripts dput coffeelibs-jdk-${{ env.JAVA_VERSION }} libgtk2.0-0
|
sudo apt-get install debhelper devscripts dput coffeelibs-jdk-${{ env.COFFEELIBS_JDK_VERSION }} libgtk2.0-0
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: 'zulu'
|
distribution: ${{ env.JAVA_DIST }}
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
|
check-latest: true
|
||||||
cache: 'maven'
|
cache: 'maven'
|
||||||
- name: Run maven
|
- name: Run maven
|
||||||
run: mvn -B clean package -Pdependency-check,linux -DskipTests
|
run: mvn -B clean package -Pdependency-check,linux -DskipTests
|
||||||
@@ -52,9 +57,11 @@ jobs:
|
|||||||
id: download-jmods
|
id: download-jmods
|
||||||
run: |
|
run: |
|
||||||
curl -L ${{ env.OPENJFX_JMODS_AMD64 }} -o openjfx-amd64.zip
|
curl -L ${{ env.OPENJFX_JMODS_AMD64 }} -o openjfx-amd64.zip
|
||||||
|
echo "${{ env.OPENJFX_JMODS_AMD64_HASH }} openjfx-amd64.zip" | shasum -a256 --check
|
||||||
mkdir -p jmods/amd64
|
mkdir -p jmods/amd64
|
||||||
unzip -j openjfx-amd64.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d 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
|
curl -L ${{ env.OPENJFX_JMODS_AARCH64 }} -o openjfx-aarch64.zip
|
||||||
|
echo "${{ env.OPENJFX_JMODS_AARCH64_HASH }} openjfx-aarch64.zip" | shasum -a256 --check
|
||||||
mkdir -p jmods/aarch64
|
mkdir -p jmods/aarch64
|
||||||
unzip -j openjfx-aarch64.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d 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
|
- name: Ensure major jfx version in pom and in jmods is the same
|
||||||
@@ -91,7 +98,8 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cp -r dist/linux/debian/ pkgdir
|
cp -r dist/linux/debian/ pkgdir
|
||||||
export RFC2822_TIMESTAMP=`date --rfc-2822`
|
export RFC2822_TIMESTAMP=`date --rfc-2822`
|
||||||
envsubst '${SEMVER_STR} ${VERSION_NUM} ${REVISION_NUM}' < dist/linux/debian/rules > pkgdir/debian/rules
|
export DISABLE_UPDATE_CHECK=${{ inputs.dput }}
|
||||||
|
envsubst '${SEMVER_STR} ${VERSION_NUM} ${REVISION_NUM} ${DISABLE_UPDATE_CHECK}' < dist/linux/debian/rules > pkgdir/debian/rules
|
||||||
envsubst '${PPA_VERSION} ${RFC2822_TIMESTAMP}' < dist/linux/debian/changelog > pkgdir/debian/changelog
|
envsubst '${PPA_VERSION} ${RFC2822_TIMESTAMP}' < dist/linux/debian/changelog > pkgdir/debian/changelog
|
||||||
find . -name "*.jar" >> pkgdir/debian/source/include-binaries
|
find . -name "*.jar" >> pkgdir/debian/source/include-binaries
|
||||||
mv pkgdir cryptomator_${{ inputs.ppaver }}
|
mv pkgdir cryptomator_${{ inputs.ppaver }}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ name: Update Error Database
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
discussion:
|
discussion:
|
||||||
types: [created, edited, category_changed, answered, unanswered]
|
types: [created, edited, deleted, category_changed, answered, unanswered]
|
||||||
discussion_comment:
|
discussion_comment:
|
||||||
types: [created, edited, deleted]
|
types: [created, edited, deleted]
|
||||||
|
|
||||||
@@ -12,6 +12,7 @@ jobs:
|
|||||||
if: github.event.discussion.category.name == 'Errors'
|
if: github.event.discussion.category.name == 'Errors'
|
||||||
steps:
|
steps:
|
||||||
- name: Query Discussion Data
|
- name: Query Discussion Data
|
||||||
|
if: github.event_name == 'discussion_comment' || github.event_name == 'discussion' && github.event.action != 'deleted'
|
||||||
id: query-data
|
id: query-data
|
||||||
uses: actions/github-script@v6
|
uses: actions/github-script@v6
|
||||||
with:
|
with:
|
||||||
@@ -47,8 +48,13 @@ jobs:
|
|||||||
- name: Merge Error Code Data
|
- name: Merge Error Code Data
|
||||||
run: |
|
run: |
|
||||||
jq -c '.' ${{ steps.get-gist.outputs.file }} > original.json
|
jq -c '.' ${{ steps.get-gist.outputs.file }} > original.json
|
||||||
echo $DISCUSSION | jq -c '.repository.discussion | .comments = .comments.totalCount | {(.id|tostring) : .}' > new.json
|
if [ ! -z "$DISCUSSION" ]
|
||||||
jq -s '.[0] * .[1]' original.json new.json > merged.json
|
then
|
||||||
|
echo $DISCUSSION | jq -c '.repository.discussion | .comments = .comments.totalCount | {(.id|tostring) : .}' > new.json
|
||||||
|
jq -s '.[0] * .[1]' original.json new.json > merged.json
|
||||||
|
else
|
||||||
|
cat original.json | jq 'del(.[] | select(.url=="https://github.com/cryptomator/cryptomator/discussions/${{ github.event.discussion.number }}"))' > merged.json
|
||||||
|
fi
|
||||||
env:
|
env:
|
||||||
DISCUSSION: ${{ steps.query-data.outputs.result }}
|
DISCUSSION: ${{ steps.query-data.outputs.result }}
|
||||||
- name: Patch Gist
|
- name: Patch Gist
|
||||||
|
|||||||
@@ -22,9 +22,8 @@ on:
|
|||||||
value: ${{ jobs.determine-version.outputs.type }}
|
value: ${{ jobs.determine-version.outputs.type }}
|
||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_VERSION: 20
|
JAVA_DIST: 'zulu'
|
||||||
JAVA_DIST: 'temurin'
|
JAVA_VERSION: 21
|
||||||
JAVA_CACHE: 'maven'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
determine-version:
|
determine-version:
|
||||||
@@ -36,7 +35,7 @@ jobs:
|
|||||||
revNum: ${{ steps.versions.outputs.revNum }}
|
revNum: ${{ steps.versions.outputs.revNum }}
|
||||||
type: ${{ steps.versions.outputs.type}}
|
type: ${{ steps.versions.outputs.type}}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
@@ -44,7 +43,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
distribution: ${{ env.JAVA_DIST }}
|
distribution: ${{ env.JAVA_DIST }}
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
cache: ${{ env.JAVA_CACHE }}
|
cache: 'maven'
|
||||||
- id: versions
|
- id: versions
|
||||||
name: Get version information
|
name: Get version information
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ on:
|
|||||||
type: boolean
|
type: boolean
|
||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_VERSION: 20
|
JAVA_DIST: 'zulu'
|
||||||
|
JAVA_VERSION: 21
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
get-version:
|
get-version:
|
||||||
@@ -36,24 +37,24 @@ jobs:
|
|||||||
output-suffix: x64
|
output-suffix: x64
|
||||||
xcode-path: '/Applications/Xcode_13.2.1.app'
|
xcode-path: '/Applications/Xcode_13.2.1.app'
|
||||||
fuse-lib: macFUSE
|
fuse-lib: macFUSE
|
||||||
openjfx-url: https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_osx-x64_bin-jmods.zip
|
openjfx-url: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_osx-x64_bin-jmods.zip'
|
||||||
openjfx-sha: 55b8ff7453d59c89ae129f6c9c5ad7b09a5d359568811b376ac1766c14d6a17c
|
openjfx-sha: '55b8ff7453d59c89ae129f6c9c5ad7b09a5d359568811b376ac1766c14d6a17c'
|
||||||
- os: [self-hosted, macOS, ARM64]
|
- os: [self-hosted, macOS, ARM64]
|
||||||
architecture: aarch64
|
architecture: aarch64
|
||||||
output-suffix: arm64
|
output-suffix: arm64
|
||||||
xcode-path: '/Applications/Xcode_13.2.1.app'
|
xcode-path: '/Applications/Xcode_13.2.1.app'
|
||||||
fuse-lib: FUSE-T
|
fuse-lib: FUSE-T
|
||||||
openjfx-url: https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_osx-aarch64_bin-jmods.zip
|
openjfx-url: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_osx-aarch64_bin-jmods.zip'
|
||||||
openjfx-sha: c60f5f19aa847e0e620e0b011e5de68f2c6755641c2141cec27a0b89f612beaf
|
openjfx-sha: 'c60f5f19aa847e0e620e0b011e5de68f2c6755641c2141cec27a0b89f612beaf'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin'
|
distribution: ${{ env.JAVA_DIST }}
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
java-package: 'jdk'
|
|
||||||
architecture: ${{ matrix.architecture }}
|
architecture: ${{ matrix.architecture }}
|
||||||
|
check-latest: true
|
||||||
cache: 'maven'
|
cache: 'maven'
|
||||||
- name: Download OpenJFX jmods
|
- name: Download OpenJFX jmods
|
||||||
id: download-jmods
|
id: download-jmods
|
||||||
@@ -61,7 +62,7 @@ jobs:
|
|||||||
curl -L ${{ matrix.openjfx-url }} -o openjfx-jmods.zip
|
curl -L ${{ matrix.openjfx-url }} -o openjfx-jmods.zip
|
||||||
echo "${{ matrix.openjfx-sha }} *openjfx-jmods.zip" | shasum -a256 --check
|
echo "${{ matrix.openjfx-sha }} *openjfx-jmods.zip" | shasum -a256 --check
|
||||||
mkdir -p openjfx-jmods/
|
mkdir -p openjfx-jmods/
|
||||||
unzip -j openjfx-jmods.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d openjfx-jmods
|
unzip -jo openjfx-jmods.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d openjfx-jmods
|
||||||
- name: Ensure major jfx version in pom and in jmods is the same
|
- name: Ensure major jfx version in pom and in jmods is the same
|
||||||
run: |
|
run: |
|
||||||
JMOD_VERSION=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1)
|
JMOD_VERSION=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1)
|
||||||
@@ -71,8 +72,8 @@ jobs:
|
|||||||
POM_JFX_VERSION=${POM_JFX_VERSION#*@}
|
POM_JFX_VERSION=${POM_JFX_VERSION#*@}
|
||||||
POM_JFX_VERSION=${POM_JFX_VERSION%%.*}
|
POM_JFX_VERSION=${POM_JFX_VERSION%%.*}
|
||||||
|
|
||||||
if [ $POM_JFX_VERSION -ne $JMOD_VERSION ]; then
|
if [ "${POM_JFX_VERSION}" -ne "${JMOD_VERSION}" ]; then
|
||||||
>&2 echo "Major JavaFX version in pom.xml (${POM_JFX_VERSION}) != jmod version (${JMOD_VERSION_AMD64})"
|
>&2 echo "Major JavaFX version in pom.xml (${POM_JFX_VERSION}) != jmod version (${JMOD_VERSION})"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
- name: Set version
|
- name: Set version
|
||||||
@@ -221,7 +222,6 @@ jobs:
|
|||||||
--app-drop-link 512 245
|
--app-drop-link 512 245
|
||||||
--eula "dist/mac/dmg/resources/license.rtf"
|
--eula "dist/mac/dmg/resources/license.rtf"
|
||||||
--icon ".background" 128 758
|
--icon ".background" 128 758
|
||||||
--icon ".fseventsd" 320 758
|
|
||||||
--icon ".VolumeIcon.icns" 512 758
|
--icon ".VolumeIcon.icns" 512 758
|
||||||
Cryptomator-${VERSION_NO}-${{ matrix.output-suffix }}.dmg dmg
|
Cryptomator-${VERSION_NO}-${{ matrix.output-suffix }}.dmg dmg
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_VERSION: 20
|
JAVA_DIST: 'zulu'
|
||||||
|
JAVA_VERSION: 21
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
@@ -16,10 +17,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')"
|
if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-java@v3
|
- uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: 'zulu'
|
distribution: ${{ env.JAVA_DIST }}
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
cache: 'maven'
|
cache: 'maven'
|
||||||
- name: Build and Test
|
- name: Build and Test
|
||||||
|
|||||||
@@ -6,9 +6,6 @@ on:
|
|||||||
- 'release/**'
|
- 'release/**'
|
||||||
- 'hotfix/**'
|
- 'hotfix/**'
|
||||||
|
|
||||||
env:
|
|
||||||
JAVA_VERSION: 20
|
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -18,7 +15,7 @@ jobs:
|
|||||||
name: Validate commits pushed to release/hotfix branch to fulfill release requirements
|
name: Validate commits pushed to release/hotfix branch to fulfill release requirements
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- id: validate-pom-version
|
- id: validate-pom-version
|
||||||
name: Validate POM version
|
name: Validate POM version
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -14,11 +14,10 @@ on:
|
|||||||
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_VERSION: 20
|
JAVA_DIST: 'zulu'
|
||||||
JAVA_DIST: 'temurin'
|
JAVA_VERSION: 21
|
||||||
JAVA_CACHE: 'maven'
|
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_windows-x64_bin-jmods.zip'
|
||||||
JFX_JMODS_URL: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_windows-x64_bin-jmods.zip'
|
OPENJFX_JMODS_AMD64_HASH: '18625bbc13c57dbf802486564247a8d8cab72ec558c240a401bf6440384ebd77'
|
||||||
JFX_JMODS_HASH: '18625bbc13c57dbf802486564247a8d8cab72ec558c240a401bf6440384ebd77'
|
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
@@ -38,20 +37,20 @@ jobs:
|
|||||||
LOOPBACK_ALIAS: 'cryptomator-vault'
|
LOOPBACK_ALIAS: 'cryptomator-vault'
|
||||||
WIN_CONSOLE_FLAG: ''
|
WIN_CONSOLE_FLAG: ''
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: ${{ env.JAVA_DIST }}
|
distribution: ${{ env.JAVA_DIST }}
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
java-package: 'jdk'
|
check-latest: true
|
||||||
cache: ${{ env.JAVA_CACHE }}
|
cache: 'maven'
|
||||||
- name: Download and extract JavaFX jmods from Gluon
|
- name: Download and extract JavaFX jmods from Gluon
|
||||||
#In the last step we move all jmods files a dir level up because jmods are placed inside a directory in the zip
|
#In the last step we move all jmods files a dir level up because jmods are placed inside a directory in the zip
|
||||||
run: |
|
run: |
|
||||||
curl --output jfxjmods.zip -L "${{ env.JFX_JMODS_URL }}"
|
curl --output jfxjmods.zip -L "${{ env.OPENJFX_JMODS_AMD64 }}"
|
||||||
if(!(Get-FileHash -Path jfxjmods.zip -Algorithm SHA256).Hash.equals("${{ env.JFX_JMODS_HASH }}")) {
|
if(!(Get-FileHash -Path jfxjmods.zip -Algorithm SHA256).Hash.ToLower().equals("${{ env.OPENJFX_JMODS_AMD64_HASH }}")) {
|
||||||
throw "Wrong checksum of JMOD archive downloaded from ${{ env.JFX_JMODS_URL }}.";
|
throw "Wrong checksum of JMOD archive downloaded from ${{ env.OPENJFX_JMODS_AMD64 }}.";
|
||||||
}
|
}
|
||||||
Expand-Archive -Path jfxjmods.zip -DestinationPath jfxjmods
|
Expand-Archive -Path jfxjmods.zip -DestinationPath jfxjmods
|
||||||
Get-ChildItem -Path jfxjmods -Recurse -Filter "*.jmod" | ForEach-Object { Move-Item -Path $_ -Destination $_.Directory.Parent}
|
Get-ChildItem -Path jfxjmods -Recurse -Filter "*.jmod" | ForEach-Object { Move-Item -Path $_ -Destination $_.Directory.Parent}
|
||||||
@@ -144,9 +143,29 @@ jobs:
|
|||||||
- name: Fix permissions
|
- name: Fix permissions
|
||||||
run: attrib -r appdir/Cryptomator/Cryptomator.exe
|
run: attrib -r appdir/Cryptomator/Cryptomator.exe
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
- name: Extract integrations DLL for code signing
|
- name: Extract jars with DLLs for Codesigning
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
run: gci ./appdir/Cryptomator/app/mods/ -File integrations-win-*.jar | ForEach-Object {Set-Location -Path $_.Directory; jar --file=$($_.FullName) --extract integrations.dll }
|
run: |
|
||||||
|
Add-Type -AssemblyName "System.io.compression.filesystem"
|
||||||
|
$jarFolder = Resolve-Path ".\appdir\Cryptomator\app\mods"
|
||||||
|
$jarExtractDir = New-Item -Path ".\appdir\jar-extract" -ItemType Directory
|
||||||
|
|
||||||
|
#for all jars inspect
|
||||||
|
Get-ChildItem -Path $jarFolder -Filter "*.jar" | ForEach-Object {
|
||||||
|
$jar = [Io.compression.zipfile]::OpenRead($_.FullName)
|
||||||
|
if (@($jar.Entries | Where-Object {$_.Name.ToString().EndsWith(".dll")} | Select-Object -First 1).Count -gt 0) {
|
||||||
|
#jars containing dlls extract
|
||||||
|
Set-Location $jarExtractDir
|
||||||
|
Expand-Archive -Path $_.FullName
|
||||||
|
}
|
||||||
|
$jar.Dispose()
|
||||||
|
}
|
||||||
|
- name: Extract wixhelper.dll for Codesigning #see https://github.com/cryptomator/cryptomator/issues/3130
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
New-Item -Path appdir/jpackage-jmod -ItemType Directory
|
||||||
|
& $env:JAVA_HOME\bin\jmod.exe extract --dir jpackage-jmod "${env:JAVA_HOME}\jmods\jdk.jpackage.jmod"
|
||||||
|
Get-ChildItem -Recurse -Path "jpackage-jmod" -File wixhelper.dll | Select-Object -Last 1 | Copy-Item -Destination "appdir"
|
||||||
- name: Codesign
|
- name: Codesign
|
||||||
uses: skymatic/code-sign-action@v2
|
uses: skymatic/code-sign-action@v2
|
||||||
with:
|
with:
|
||||||
@@ -155,12 +174,22 @@ jobs:
|
|||||||
certificatesha1: 5FC94CE149E5B511E621F53A060AC67CBD446B3A
|
certificatesha1: 5FC94CE149E5B511E621F53A060AC67CBD446B3A
|
||||||
description: Cryptomator
|
description: Cryptomator
|
||||||
timestampUrl: 'http://timestamp.digicert.com'
|
timestampUrl: 'http://timestamp.digicert.com'
|
||||||
folder: appdir/Cryptomator
|
folder: appdir
|
||||||
recursive: true
|
recursive: true
|
||||||
- name: Repack signed DLL into jar
|
- name: Replace DLLs inside jars with signed ones
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
run: |
|
run: |
|
||||||
gci ./appdir/Cryptomator/app/mods/ -File integrations-win-*.jar | ForEach-Object {Set-Location -Path $_.Directory; jar --file=$($_.FullName) --update integrations.dll; Remove-Item integrations.dll}
|
$jarExtractDir = Resolve-Path ".\appdir\jar-extract"
|
||||||
|
$jarFolder = Resolve-Path ".\appdir\Cryptomator\app\mods"
|
||||||
|
Get-ChildItem -Path $jarExtractDir | ForEach-Object {
|
||||||
|
$jarName = $_.Name
|
||||||
|
$jarFile = "${jarFolder}\${jarName}.jar"
|
||||||
|
Set-Location $_
|
||||||
|
Get-ChildItem -Path $_ -Recurse -File "*.dll" | ForEach-Object {
|
||||||
|
# update jar with signed dll
|
||||||
|
jar --file="$jarFile" --update $(Resolve-Path -Relative -Path $_)
|
||||||
|
}
|
||||||
|
}
|
||||||
- name: Generate license for MSI
|
- name: Generate license for MSI
|
||||||
run: >
|
run: >
|
||||||
mvn -B license:add-third-party
|
mvn -B license:add-third-party
|
||||||
@@ -194,6 +223,7 @@ jobs:
|
|||||||
--file-associations dist/win/resources/FAvaultFile.properties
|
--file-associations dist/win/resources/FAvaultFile.properties
|
||||||
env:
|
env:
|
||||||
JP_WIXWIZARD_RESOURCES: ${{ github.workspace }}/dist/win/resources # requires abs path, used in resources/main.wxs
|
JP_WIXWIZARD_RESOURCES: ${{ github.workspace }}/dist/win/resources # requires abs path, used in resources/main.wxs
|
||||||
|
JP_WIXHELPER_DIR: ${{ github.workspace }}\appdir
|
||||||
- name: Codesign MSI
|
- name: Codesign MSI
|
||||||
uses: skymatic/code-sign-action@v2
|
uses: skymatic/code-sign-action@v2
|
||||||
with:
|
with:
|
||||||
@@ -235,7 +265,7 @@ jobs:
|
|||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
needs: [get-version, build-msi]
|
needs: [get-version, build-msi]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Download .msi
|
- name: Download .msi
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
@@ -247,7 +277,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
distribution: ${{ env.JAVA_DIST }}
|
distribution: ${{ env.JAVA_DIST }}
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
cache: ${{ env.JAVA_CACHE }}
|
check-latest: true
|
||||||
|
cache: 'maven'
|
||||||
- name: Generate license for exe
|
- name: Generate license for exe
|
||||||
run: >
|
run: >
|
||||||
mvn -B license:add-third-party
|
mvn -B license:add-third-party
|
||||||
|
|||||||
Generated
+11
-12
@@ -14,10 +14,10 @@
|
|||||||
<option name="dagger.fastInit" value="enabled" />
|
<option name="dagger.fastInit" value="enabled" />
|
||||||
<option name="dagger.formatGeneratedSource" value="enabled" />
|
<option name="dagger.formatGeneratedSource" value="enabled" />
|
||||||
<processorPath useClasspath="false">
|
<processorPath useClasspath="false">
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.45/dagger-compiler-2.45.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.48.1/dagger-compiler-2.48.1.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.45/dagger-2.45.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.48.1/dagger-2.48.1.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar" />
|
<entry name="$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-producers/2.45/dagger-producers-2.45.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-producers/2.48.1/dagger-producers-2.48.1.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/guava/guava/31.0.1-jre/guava-31.0.1-jre.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/guava/guava/31.0.1-jre/guava-31.0.1-jre.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" />
|
||||||
@@ -26,20 +26,19 @@
|
|||||||
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.7.1/error_prone_annotations-2.7.1.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.7.1/error_prone_annotations-2.7.1.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-compat-qual/2.5.5/checker-compat-qual-2.5.5.jar" />
|
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-compat-qual/2.5.5/checker-compat-qual-2.5.5.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.45/dagger-spi-2.45.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.48.1/dagger-spi-2.48.1.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/devtools/ksp/symbol-processing-api/1.7.0-1.0.6/symbol-processing-api-1.7.0-1.0.6.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/devtools/ksp/symbol-processing-api/1.9.0-1.0.12/symbol-processing-api-1.9.0-1.0.12.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.7.0/kotlin-stdlib-1.7.0.jar" />
|
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.0/kotlin-stdlib-1.9.0.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.7.0/kotlin-stdlib-common-1.7.0.jar" />
|
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.9.0/kotlin-stdlib-common-1.9.0.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" />
|
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/squareup/javapoet/1.13.0/javapoet-1.13.0.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/squareup/javapoet/1.13.0/javapoet-1.13.0.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/squareup/kotlinpoet/1.11.0/kotlinpoet-1.11.0.jar" />
|
|
||||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.7.0/kotlin-stdlib-jdk8-1.7.0.jar" />
|
|
||||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.7.0/kotlin-stdlib-jdk7-1.7.0.jar" />
|
|
||||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.10/kotlin-reflect-1.6.10.jar" />
|
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/googlejavaformat/google-java-format/1.5/google-java-format-1.5.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/googlejavaformat/google-java-format/1.5/google-java-format-1.5.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/javac-shaded/9-dev-r4023-3/javac-shaded-9-dev-r4023-3.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/javac-shaded/9-dev-r4023-3/javac-shaded-9-dev-r4023-3.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/com/squareup/kotlinpoet/1.11.0/kotlinpoet-1.11.0.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.10/kotlin-stdlib-jdk8-1.6.10.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.6.10/kotlin-stdlib-jdk7-1.6.10.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.10/kotlin-reflect-1.6.10.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/net/ltgt/gradle/incap/incap/0.2/incap-0.2.jar" />
|
<entry name="$MAVEN_REPOSITORY$/net/ltgt/gradle/incap/incap/0.2/incap-0.2.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-metadata-jvm/0.5.0/kotlinx-metadata-jvm-0.5.0.jar" />
|
|
||||||
</processorPath>
|
</processorPath>
|
||||||
<module name="cryptomator" />
|
<module name="cryptomator" />
|
||||||
</profile>
|
</profile>
|
||||||
|
|||||||
Generated
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
</list>
|
</list>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_20_PREVIEW" project-jdk-name="20" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_21_PREVIEW" project-jdk-name="21" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
Vendored
+6
-4
@@ -1,4 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
cd $(dirname $0)
|
cd $(dirname $0)
|
||||||
REVISION_NO=`git rev-list --count HEAD`
|
REVISION_NO=`git rev-list --count HEAD`
|
||||||
@@ -10,6 +11,7 @@ command -v curl >/dev/null 2>&1 || { echo >&2 "curl not found."; exit 1; }
|
|||||||
|
|
||||||
VERSION=$(mvn -f ../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout)
|
VERSION=$(mvn -f ../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout)
|
||||||
SEMVER_STR=${VERSION}
|
SEMVER_STR=${VERSION}
|
||||||
|
MACHINE_TYPE=$(uname -m)
|
||||||
|
|
||||||
mvn -f ../../../pom.xml versions:set -DnewVersion=${SEMVER_STR}
|
mvn -f ../../../pom.xml versions:set -DnewVersion=${SEMVER_STR}
|
||||||
|
|
||||||
@@ -83,17 +85,17 @@ ln -s usr/share/applications/org.cryptomator.Cryptomator.desktop Cryptomator.App
|
|||||||
ln -s bin/cryptomator.sh Cryptomator.AppDir/AppRun
|
ln -s bin/cryptomator.sh Cryptomator.AppDir/AppRun
|
||||||
|
|
||||||
# load AppImageTool
|
# load AppImageTool
|
||||||
curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-x86_64.AppImage -o /tmp/appimagetool.AppImage
|
curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-${MACHINE_TYPE}.AppImage -o /tmp/appimagetool.AppImage
|
||||||
chmod +x /tmp/appimagetool.AppImage
|
chmod +x /tmp/appimagetool.AppImage
|
||||||
|
|
||||||
# create AppImage
|
# create AppImage
|
||||||
/tmp/appimagetool.AppImage \
|
/tmp/appimagetool.AppImage \
|
||||||
Cryptomator.AppDir \
|
Cryptomator.AppDir \
|
||||||
cryptomator-${SEMVER_STR}-x86_64.AppImage \
|
cryptomator-${SEMVER_STR}-${MACHINE_TYPE}.AppImage \
|
||||||
-u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-x86_64.AppImage.zsync'
|
-u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-${MACHINE_TYPE}.AppImage.zsync'
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Done. AppImage successfully created: cryptomator-${SEMVER_STR}-x86_64.AppImage"
|
echo "Done. AppImage successfully created: cryptomator-${SEMVER_STR}-${MACHINE_TYPE}.AppImage"
|
||||||
echo ""
|
echo ""
|
||||||
echo >&2 "To clean up, run: rm -rf Cryptomator.AppDir appdir jni runtime squashfs-root; rm launcher-gtk2.properties /tmp/appimagetool.AppImage"
|
echo >&2 "To clean up, run: rm -rf Cryptomator.AppDir appdir jni runtime squashfs-root; rm launcher-gtk2.properties /tmp/appimagetool.AppImage"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -66,6 +66,9 @@
|
|||||||
</content_rating>
|
</content_rating>
|
||||||
|
|
||||||
<releases>
|
<releases>
|
||||||
|
<release date="2023-09-20" version="1.10.1"/>
|
||||||
|
<release date="2023-09-11" version="1.10.0"/>
|
||||||
|
<release date="2023-08-11" version="1.9.4"/>
|
||||||
<release date="2023-08-07" version="1.9.3"/>
|
<release date="2023-08-07" version="1.9.3"/>
|
||||||
<release date="2023-07-24" version="1.9.2"/>
|
<release date="2023-07-24" version="1.9.2"/>
|
||||||
<release date="2023-06-07" version="1.9.1"/>
|
<release date="2023-06-07" version="1.9.1"/>
|
||||||
|
|||||||
Vendored
+1
-1
@@ -2,7 +2,7 @@ Source: cryptomator
|
|||||||
Maintainer: Cryptobot <releases@cryptomator.org>
|
Maintainer: Cryptobot <releases@cryptomator.org>
|
||||||
Section: utils
|
Section: utils
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Build-Depends: debhelper (>=10), coffeelibs-jdk-20, libgtk2.0-0, libgtk-3-0, libxxf86vm1, libgl1
|
Build-Depends: debhelper (>=10), coffeelibs-jdk-21, libgtk2.0-0, libgtk-3-0, libxxf86vm1, libgl1
|
||||||
Standards-Version: 4.5.0
|
Standards-Version: 4.5.0
|
||||||
Homepage: https://cryptomator.org
|
Homepage: https://cryptomator.org
|
||||||
Vcs-Git: https://github.com/cryptomator/cryptomator.git
|
Vcs-Git: https://github.com/cryptomator/cryptomator.git
|
||||||
|
|||||||
Vendored
+2
-1
@@ -4,7 +4,7 @@
|
|||||||
# Uncomment this to turn on verbose mode.
|
# Uncomment this to turn on verbose mode.
|
||||||
#export DH_VERBOSE=1
|
#export DH_VERBOSE=1
|
||||||
|
|
||||||
JAVA_HOME = /usr/lib/jvm/java-20-coffeelibs
|
JAVA_HOME = /usr/lib/jvm/java-21-coffeelibs
|
||||||
DEB_BUILD_ARCH ?= $(shell dpkg-architecture -qDEB_BUILD_ARCH)
|
DEB_BUILD_ARCH ?= $(shell dpkg-architecture -qDEB_BUILD_ARCH)
|
||||||
ifeq ($(DEB_BUILD_ARCH),amd64)
|
ifeq ($(DEB_BUILD_ARCH),amd64)
|
||||||
JMODS_PATH = jmods/amd64:${JAVA_HOME}/jmods
|
JMODS_PATH = jmods/amd64:${JAVA_HOME}/jmods
|
||||||
@@ -59,6 +59,7 @@ override_dh_auto_build:
|
|||||||
--java-options "-Dcryptomator.integrationsLinux.trayIconsDir=\"/usr/share/icons/hicolor/symbolic/apps\"" \
|
--java-options "-Dcryptomator.integrationsLinux.trayIconsDir=\"/usr/share/icons/hicolor/symbolic/apps\"" \
|
||||||
--java-options "-Dcryptomator.buildNumber=\"deb-${REVISION_NUM}\"" \
|
--java-options "-Dcryptomator.buildNumber=\"deb-${REVISION_NUM}\"" \
|
||||||
--java-options "-Dcryptomator.appVersion=\"${SEMVER_STR}\"" \
|
--java-options "-Dcryptomator.appVersion=\"${SEMVER_STR}\"" \
|
||||||
|
--java-options "-Dcryptomator.disableUpdateCheck=\"${DISABLE_UPDATE_CHECK}\"" \
|
||||||
--app-version "${VERSION_NUM}.${REVISION_NUM}" \
|
--app-version "${VERSION_NUM}.${REVISION_NUM}" \
|
||||||
--resource-dir resources \
|
--resource-dir resources \
|
||||||
--verbose
|
--verbose
|
||||||
|
|||||||
Vendored
+6
-6
@@ -49,21 +49,22 @@ fi
|
|||||||
# download and check jmods
|
# download and check jmods
|
||||||
curl -L ${OPENJFX_JMODS} -o openjfx-jmods.zip
|
curl -L ${OPENJFX_JMODS} -o openjfx-jmods.zip
|
||||||
mkdir -p openjfx-jmods/
|
mkdir -p openjfx-jmods/
|
||||||
unzip -j openjfx-jmods.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d openjfx-jmods/
|
unzip -jo openjfx-jmods.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d openjfx-jmods
|
||||||
JMOD_VERSION=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1)
|
JMOD_VERSION=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1)
|
||||||
JMOD_VERSION=${JMOD_VERSION#*@}
|
JMOD_VERSION=${JMOD_VERSION#*@}
|
||||||
JMOD_VERSION=${JMOD_VERSION%%.*}
|
JMOD_VERSION=${JMOD_VERSION%%.*}
|
||||||
POM_JFX_VERSION=$(mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout)
|
POM_JFX_VERSION=$(mvn -f../../../pom.xml help:evaluate "-Dexpression=javafx.version" -q -DforceStdout)
|
||||||
POM_JFX_VERSION=${POM_JFX_VERSION#*@}
|
POM_JFX_VERSION=${POM_JFX_VERSION#*@}
|
||||||
POM_JFX_VERSION=${POM_JFX_VERSION%%.*}
|
POM_JFX_VERSION=${POM_JFX_VERSION%%.*}
|
||||||
|
|
||||||
if [ $POM_JFX_VERSION -ne $JMOD_VERSION ]; then
|
if [ "${POM_JFX_VERSION}" -ne "${JMOD_VERSION}" ]; then
|
||||||
>&2 echo "Major JavaFX version in pom.xml (${POM_JFX_VERSION}) != jmod version (${JMOD_VERSION})"
|
>&2 echo "Major JavaFX version in pom.xml (${POM_JFX_VERSION}) != jmod version (${JMOD_VERSION})"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# compile
|
# compile
|
||||||
mvn -B -f../../../pom.xml clean package -DskipTests -Pmac
|
mvn -B -f../../../pom.xml clean package -DskipTests -Pmac
|
||||||
|
cp ../../../LICENSE.txt ../../../target
|
||||||
cp ../../../target/${MAIN_JAR_GLOB} ../../../target/mods
|
cp ../../../target/${MAIN_JAR_GLOB} ../../../target/mods
|
||||||
|
|
||||||
# add runtime
|
# add runtime
|
||||||
@@ -168,6 +169,5 @@ create-dmg \
|
|||||||
--app-drop-link 512 245 \
|
--app-drop-link 512 245 \
|
||||||
--eula "resources/license.rtf" \
|
--eula "resources/license.rtf" \
|
||||||
--icon ".background" 128 758 \
|
--icon ".background" 128 758 \
|
||||||
--icon ".fseventsd" 320 758 \
|
|
||||||
--icon ".VolumeIcon.icns" 512 758 \
|
--icon ".VolumeIcon.icns" 512 758 \
|
||||||
${APP_NAME}-${VERSION_NO}.dmg dmg
|
${APP_NAME}-${VERSION_NO}.dmg dmg
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
Vendored
+4
-2
@@ -65,8 +65,9 @@ if( $jmodsChecksumActual -ne $jfxJmodsChecksum ) {
|
|||||||
Write-Error "Checksum mismatch for jfxJmods.zip. Expected: $jfxJmodsChecksum, actual: $jmodsChecksumActual"
|
Write-Error "Checksum mismatch for jfxJmods.zip. Expected: $jfxJmodsChecksum, actual: $jmodsChecksumActual"
|
||||||
exit 1;
|
exit 1;
|
||||||
}
|
}
|
||||||
Expand-Archive -Force -Path $jfxJmodsZip -DestinationPath ".\resources\"
|
Expand-Archive -Path $jfxJmodsZip -Force -DestinationPath ".\resources\"
|
||||||
Move-Item -Path ".\resources\javafx-jmods-*" -Destination ".\resources\javafx-jmods" -ErrorAction Stop
|
Remove-Item -Recurse -Force -Path ".\resources\javafx-jmods"
|
||||||
|
Move-Item -Force -Path ".\resources\javafx-jmods-*" -Destination ".\resources\javafx-jmods" -ErrorAction Stop
|
||||||
|
|
||||||
|
|
||||||
& "$Env:JAVA_HOME\bin\jlink" `
|
& "$Env:JAVA_HOME\bin\jlink" `
|
||||||
@@ -143,6 +144,7 @@ try {
|
|||||||
|
|
||||||
# create .msi
|
# create .msi
|
||||||
$Env:JP_WIXWIZARD_RESOURCES = "$buildDir\resources"
|
$Env:JP_WIXWIZARD_RESOURCES = "$buildDir\resources"
|
||||||
|
$Env:JP_WIXHELPER_DIR = "."
|
||||||
& "$Env:JAVA_HOME\bin\jpackage" `
|
& "$Env:JAVA_HOME\bin\jpackage" `
|
||||||
--verbose `
|
--verbose `
|
||||||
--type msi `
|
--type msi `
|
||||||
|
|||||||
Vendored
+1
-1
@@ -3,5 +3,5 @@
|
|||||||
::REPLACE ME
|
::REPLACE ME
|
||||||
|
|
||||||
cd %~dp0
|
cd %~dp0
|
||||||
powershell -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command .\patchWebDAV.ps1^
|
powershell -NoLogo -NoProfile -NonInteractive -ExecutionPolicy RemoteSigned -Command .\patchWebDAV.ps1^
|
||||||
-LoopbackAlias %LOOPBACK_ALIAS%
|
-LoopbackAlias %LOOPBACK_ALIAS%
|
||||||
+1
-1
@@ -2,4 +2,4 @@
|
|||||||
:: see comments in file ./version170-migrate-settings.ps1
|
:: see comments in file ./version170-migrate-settings.ps1
|
||||||
|
|
||||||
cd %~dp0
|
cd %~dp0
|
||||||
powershell -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command .\version170-migrate-settings.ps1
|
powershell -NoLogo -NoProfile -NonInteractive -ExecutionPolicy RemoteSigned -Command .\version170-migrate-settings.ps1
|
||||||
|
|||||||
Vendored
+1
-1
@@ -70,7 +70,7 @@
|
|||||||
<CustomAction Id="JpDisallowDowngrade" Error="!(loc.DowngradeErrorMessage)" />
|
<CustomAction Id="JpDisallowDowngrade" Error="!(loc.DowngradeErrorMessage)" />
|
||||||
<?endif?>
|
<?endif?>
|
||||||
|
|
||||||
<Binary Id="JpCaDll" SourceFile="wixhelper.dll"/>
|
<Binary Id="JpCaDll" SourceFile="$(env.JP_WIXHELPER_DIR)\wixhelper.dll"/>
|
||||||
<CustomAction Id="JpFindRelatedProducts" BinaryKey="JpCaDll" DllEntry="FindRelatedProductsEx" />
|
<CustomAction Id="JpFindRelatedProducts" BinaryKey="JpCaDll" DllEntry="FindRelatedProductsEx" />
|
||||||
|
|
||||||
<?ifndef SkipCryptomatorLegacyCheck ?>
|
<?ifndef SkipCryptomatorLegacyCheck ?>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>cryptomator</artifactId>
|
<artifactId>cryptomator</artifactId>
|
||||||
<version>1.10.0</version>
|
<version>1.11.0</version>
|
||||||
<name>Cryptomator Desktop App</name>
|
<name>Cryptomator Desktop App</name>
|
||||||
|
|
||||||
<organization>
|
<organization>
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<project.jdk.version>20</project.jdk.version>
|
<project.jdk.version>21</project.jdk.version>
|
||||||
|
|
||||||
<!-- Group IDs of jars that need to stay on the class path for now -->
|
<!-- Group IDs of jars that need to stay on the class path for now -->
|
||||||
<!-- remove them, as soon they got modularized or support is dropped (i.e., WebDAV) -->
|
<!-- remove them, as soon they got modularized or support is dropped (i.e., WebDAV) -->
|
||||||
@@ -35,36 +35,43 @@
|
|||||||
<!-- cryptomator dependencies -->
|
<!-- cryptomator dependencies -->
|
||||||
<cryptomator.cryptofs.version>2.6.7</cryptomator.cryptofs.version>
|
<cryptomator.cryptofs.version>2.6.7</cryptomator.cryptofs.version>
|
||||||
<cryptomator.integrations.version>1.3.0</cryptomator.integrations.version>
|
<cryptomator.integrations.version>1.3.0</cryptomator.integrations.version>
|
||||||
<cryptomator.integrations.win.version>1.2.2</cryptomator.integrations.win.version>
|
<cryptomator.integrations.win.version>1.2.4</cryptomator.integrations.win.version>
|
||||||
<cryptomator.integrations.mac.version>1.2.0</cryptomator.integrations.mac.version>
|
<cryptomator.integrations.mac.version>1.2.2</cryptomator.integrations.mac.version>
|
||||||
<cryptomator.integrations.linux.version>1.3.0-beta6</cryptomator.integrations.linux.version>
|
<cryptomator.integrations.linux.version>1.4.0-beta2</cryptomator.integrations.linux.version>
|
||||||
<cryptomator.fuse.version>3.0.0</cryptomator.fuse.version>
|
<cryptomator.fuse.version>4.0.0-beta4</cryptomator.fuse.version>
|
||||||
<cryptomator.dokany.version>2.0.0</cryptomator.dokany.version>
|
<cryptomator.dokany.version>2.0.0</cryptomator.dokany.version>
|
||||||
<cryptomator.webdav.version>2.0.3</cryptomator.webdav.version>
|
<cryptomator.webdav.version>2.0.5</cryptomator.webdav.version>
|
||||||
|
|
||||||
<!-- 3rd party dependencies -->
|
<!-- 3rd party dependencies -->
|
||||||
<commons-lang3.version>3.12.0</commons-lang3.version>
|
<commons-lang3.version>3.13.0</commons-lang3.version>
|
||||||
<dagger.version>2.45</dagger.version>
|
<dagger.version>2.48.1</dagger.version>
|
||||||
<easybind.version>2.2</easybind.version>
|
<easybind.version>2.2</easybind.version>
|
||||||
<guava.version>32.0.1-jre</guava.version>
|
<guava.version>32.1.3-jre</guava.version>
|
||||||
<jackson.version>2.15.2</jackson.version>
|
<jackson.version>2.15.3</jackson.version>
|
||||||
<javafx.version>20.0.2</javafx.version>
|
<javafx.version>20.0.2</javafx.version>
|
||||||
<jwt.version>4.4.0</jwt.version>
|
<jwt.version>4.4.0</jwt.version>
|
||||||
<nimbus-jose.version>9.31</nimbus-jose.version>
|
<nimbus-jose.version>9.37</nimbus-jose.version>
|
||||||
<logback.version>1.4.7</logback.version>
|
<logback.version>1.4.11</logback.version>
|
||||||
<slf4j.version>2.0.7</slf4j.version>
|
<slf4j.version>2.0.9</slf4j.version>
|
||||||
<tinyoauth2.version>0.5.1</tinyoauth2.version>
|
<tinyoauth2.version>0.7.0</tinyoauth2.version>
|
||||||
<zxcvbn.version>1.7.0</zxcvbn.version>
|
<zxcvbn.version>1.8.2</zxcvbn.version>
|
||||||
|
|
||||||
<!-- test dependencies -->
|
<!-- test dependencies -->
|
||||||
<junit.jupiter.version>5.9.3</junit.jupiter.version>
|
<junit.jupiter.version>5.10.0</junit.jupiter.version>
|
||||||
<mockito.version>5.3.1</mockito.version>
|
<mockito.version>5.6.0</mockito.version>
|
||||||
<hamcrest.version>2.2</hamcrest.version>
|
<hamcrest.version>2.2</hamcrest.version>
|
||||||
|
|
||||||
<!-- build-time dependencies -->
|
<!-- build-time dependencies -->
|
||||||
<jetbrains.annotations.version>23.0.0</jetbrains.annotations.version>
|
<jetbrains.annotations.version>24.0.1</jetbrains.annotations.version>
|
||||||
<dependency-check.version>8.1.2</dependency-check.version>
|
<dependency-check.version>8.4.0</dependency-check.version>
|
||||||
<jacoco.version>0.8.9</jacoco.version>
|
<jacoco.version>0.8.11</jacoco.version>
|
||||||
|
<license-generator.version>2.2.0</license-generator.version>
|
||||||
|
<junit-tree-reporter.version>1.2.1</junit-tree-reporter.version>
|
||||||
|
<mvn-compiler.version>3.11.0</mvn-compiler.version>
|
||||||
|
<mvn-resources.version>3.3.1</mvn-resources.version>
|
||||||
|
<mvn-dependency.version>3.6.0</mvn-dependency.version>
|
||||||
|
<mvn-surefire.version>3.1.2</mvn-surefire.version>
|
||||||
|
<mvn-jar.version>3.3.0</mvn-jar.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -240,7 +247,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.jimfs</groupId>
|
<groupId>com.google.jimfs</groupId>
|
||||||
<artifactId>jimfs</artifactId>
|
<artifactId>jimfs</artifactId>
|
||||||
<version>1.2</version>
|
<version>1.3.0</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@@ -258,32 +265,32 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<version>3.10.1</version>
|
<version>${mvn-compiler.version}</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-resources-plugin</artifactId>
|
<artifactId>maven-resources-plugin</artifactId>
|
||||||
<version>3.3.0</version>
|
<version>${mvn-resources.version}</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-dependency-plugin</artifactId>
|
<artifactId>maven-dependency-plugin</artifactId>
|
||||||
<version>3.3.0</version>
|
<version>${mvn-dependency.version}</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<version>3.0.0-M7</version>
|
<version>${mvn-surefire.version}</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
<artifactId>license-maven-plugin</artifactId>
|
<artifactId>license-maven-plugin</artifactId>
|
||||||
<version>2.0.0</version>
|
<version>${license-generator.version}</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-jar-plugin</artifactId>
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
<version>3.3.0</version>
|
<version>${mvn-jar.version}</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.jacoco</groupId>
|
<groupId>org.jacoco</groupId>
|
||||||
@@ -332,8 +339,22 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>me.fabriciorby</groupId>
|
||||||
|
<artifactId>maven-surefire-junit5-tree-reporter</artifactId>
|
||||||
|
<version>${junit-tree-reporter.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
<configuration>
|
<configuration>
|
||||||
<argLine>--enable-preview</argLine>
|
<argLine>--enable-preview</argLine>
|
||||||
|
<reportFormat>plain</reportFormat>
|
||||||
|
<consoleOutputReporter>
|
||||||
|
<disable>true</disable>
|
||||||
|
</consoleOutputReporter>
|
||||||
|
<statelessTestsetInfoReporter
|
||||||
|
implementation="org.apache.maven.plugin.surefire.extensions.junit5.JUnit5StatelessTestsetInfoTreeReporter">
|
||||||
|
</statelessTestsetInfoReporter>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.cryptomator.common;
|
|||||||
|
|
||||||
import com.google.common.base.Splitter;
|
import com.google.common.base.Splitter;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
|
import org.jetbrains.annotations.VisibleForTesting;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -31,6 +32,7 @@ public class Environment {
|
|||||||
private static final String BUILD_NUMBER_PROP_NAME = "cryptomator.buildNumber";
|
private static final String BUILD_NUMBER_PROP_NAME = "cryptomator.buildNumber";
|
||||||
private static final String PLUGIN_DIR_PROP_NAME = "cryptomator.pluginDir";
|
private static final String PLUGIN_DIR_PROP_NAME = "cryptomator.pluginDir";
|
||||||
private static final String TRAY_ICON_PROP_NAME = "cryptomator.showTrayIcon";
|
private static final String TRAY_ICON_PROP_NAME = "cryptomator.showTrayIcon";
|
||||||
|
private static final String DISABLE_UPDATE_CHECK_PROP_NAME = "cryptomator.disableUpdateCheck";
|
||||||
|
|
||||||
private Environment() {}
|
private Environment() {}
|
||||||
|
|
||||||
@@ -43,15 +45,16 @@ public class Environment {
|
|||||||
logCryptomatorSystemProperty(SETTINGS_PATH_PROP_NAME);
|
logCryptomatorSystemProperty(SETTINGS_PATH_PROP_NAME);
|
||||||
logCryptomatorSystemProperty(IPC_SOCKET_PATH_PROP_NAME);
|
logCryptomatorSystemProperty(IPC_SOCKET_PATH_PROP_NAME);
|
||||||
logCryptomatorSystemProperty(KEYCHAIN_PATHS_PROP_NAME);
|
logCryptomatorSystemProperty(KEYCHAIN_PATHS_PROP_NAME);
|
||||||
|
logCryptomatorSystemProperty(P12_PATH_PROP_NAME);
|
||||||
logCryptomatorSystemProperty(LOG_DIR_PROP_NAME);
|
logCryptomatorSystemProperty(LOG_DIR_PROP_NAME);
|
||||||
logCryptomatorSystemProperty(LOOPBACK_ALIAS_PROP_NAME);
|
logCryptomatorSystemProperty(LOOPBACK_ALIAS_PROP_NAME);
|
||||||
logCryptomatorSystemProperty(PLUGIN_DIR_PROP_NAME);
|
|
||||||
logCryptomatorSystemProperty(MOUNTPOINT_DIR_PROP_NAME);
|
logCryptomatorSystemProperty(MOUNTPOINT_DIR_PROP_NAME);
|
||||||
logCryptomatorSystemProperty(MIN_PW_LENGTH_PROP_NAME);
|
logCryptomatorSystemProperty(MIN_PW_LENGTH_PROP_NAME);
|
||||||
logCryptomatorSystemProperty(APP_VERSION_PROP_NAME);
|
logCryptomatorSystemProperty(APP_VERSION_PROP_NAME);
|
||||||
logCryptomatorSystemProperty(BUILD_NUMBER_PROP_NAME);
|
logCryptomatorSystemProperty(BUILD_NUMBER_PROP_NAME);
|
||||||
|
logCryptomatorSystemProperty(PLUGIN_DIR_PROP_NAME);
|
||||||
logCryptomatorSystemProperty(TRAY_ICON_PROP_NAME);
|
logCryptomatorSystemProperty(TRAY_ICON_PROP_NAME);
|
||||||
logCryptomatorSystemProperty(P12_PATH_PROP_NAME);
|
logCryptomatorSystemProperty(DISABLE_UPDATE_CHECK_PROP_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Environment getInstance() {
|
public static Environment getInstance() {
|
||||||
@@ -74,10 +77,6 @@ public class Environment {
|
|||||||
return getPaths(SETTINGS_PATH_PROP_NAME);
|
return getPaths(SETTINGS_PATH_PROP_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Stream<Path> getP12Path() {
|
|
||||||
return getPaths(P12_PATH_PROP_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Stream<Path> getIpcSocketPath() {
|
public Stream<Path> getIpcSocketPath() {
|
||||||
return getPaths(IPC_SOCKET_PATH_PROP_NAME);
|
return getPaths(IPC_SOCKET_PATH_PROP_NAME);
|
||||||
}
|
}
|
||||||
@@ -86,6 +85,10 @@ public class Environment {
|
|||||||
return getPaths(KEYCHAIN_PATHS_PROP_NAME);
|
return getPaths(KEYCHAIN_PATHS_PROP_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Stream<Path> getP12Path() {
|
||||||
|
return getPaths(P12_PATH_PROP_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
public Optional<Path> getLogDir() {
|
public Optional<Path> getLogDir() {
|
||||||
return getPath(LOG_DIR_PROP_NAME);
|
return getPath(LOG_DIR_PROP_NAME);
|
||||||
}
|
}
|
||||||
@@ -94,14 +97,14 @@ public class Environment {
|
|||||||
return Optional.ofNullable(System.getProperty(LOOPBACK_ALIAS_PROP_NAME));
|
return Optional.ofNullable(System.getProperty(LOOPBACK_ALIAS_PROP_NAME));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<Path> getPluginDir() {
|
|
||||||
return getPath(PLUGIN_DIR_PROP_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<Path> getMountPointsDir() {
|
public Optional<Path> getMountPointsDir() {
|
||||||
return getPath(MOUNTPOINT_DIR_PROP_NAME);
|
return getPath(MOUNTPOINT_DIR_PROP_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getMinPwLength() {
|
||||||
|
return Integer.getInteger(MIN_PW_LENGTH_PROP_NAME, DEFAULT_MIN_PW_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the app version defined in the {@value APP_VERSION_PROP_NAME} property or returns "SNAPSHOT".
|
* Returns the app version defined in the {@value APP_VERSION_PROP_NAME} property or returns "SNAPSHOT".
|
||||||
*
|
*
|
||||||
@@ -115,20 +118,24 @@ public class Environment {
|
|||||||
return Optional.ofNullable(System.getProperty(BUILD_NUMBER_PROP_NAME));
|
return Optional.ofNullable(System.getProperty(BUILD_NUMBER_PROP_NAME));
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getMinPwLength() {
|
public Optional<Path> getPluginDir() {
|
||||||
return Integer.getInteger(MIN_PW_LENGTH_PROP_NAME, DEFAULT_MIN_PW_LENGTH);
|
return getPath(PLUGIN_DIR_PROP_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean showTrayIcon() {
|
public boolean showTrayIcon() {
|
||||||
return Boolean.getBoolean(TRAY_ICON_PROP_NAME);
|
return Boolean.getBoolean(TRAY_ICON_PROP_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean disableUpdateCheck() {
|
||||||
|
return Boolean.getBoolean(DISABLE_UPDATE_CHECK_PROP_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
private Optional<Path> getPath(String propertyName) {
|
private Optional<Path> getPath(String propertyName) {
|
||||||
String value = System.getProperty(propertyName);
|
String value = System.getProperty(propertyName);
|
||||||
return Optional.ofNullable(value).map(Paths::get);
|
return Optional.ofNullable(value).map(Paths::get);
|
||||||
}
|
}
|
||||||
|
|
||||||
// visible for testing
|
@VisibleForTesting
|
||||||
Stream<Path> getPaths(String propertyName) {
|
Stream<Path> getPaths(String propertyName) {
|
||||||
Stream<String> rawSettingsPaths = getRawList(propertyName, System.getProperty("path.separator").charAt(0));
|
Stream<String> rawSettingsPaths = getRawList(propertyName, System.getProperty("path.separator").charAt(0));
|
||||||
return rawSettingsPaths.filter(Predicate.not(Strings::isNullOrEmpty)).map(Path::of);
|
return rawSettingsPaths.filter(Predicate.not(Strings::isNullOrEmpty)).map(Path::of);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.cryptomator.common;
|
|||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.base.Throwables;
|
import com.google.common.base.Throwables;
|
||||||
|
import org.jetbrains.annotations.VisibleForTesting;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@@ -114,7 +115,7 @@ public class ErrorCode {
|
|||||||
* @param bottomFrames Other stack frames, potentially forming the bottom of the stack of <code>allFrames</code>
|
* @param bottomFrames Other stack frames, potentially forming the bottom of the stack of <code>allFrames</code>
|
||||||
* @return The number of additional frames in <code>allFrames</code>. In most cases this should be equal to the difference in size.
|
* @return The number of additional frames in <code>allFrames</code>. In most cases this should be equal to the difference in size.
|
||||||
*/
|
*/
|
||||||
// visible for testing
|
@VisibleForTesting
|
||||||
static int countTopmostFrames(StackTraceElement[] allFrames, StackTraceElement[] bottomFrames) {
|
static int countTopmostFrames(StackTraceElement[] allFrames, StackTraceElement[] bottomFrames) {
|
||||||
if (allFrames.length < bottomFrames.length) {
|
if (allFrames.length < bottomFrames.length) {
|
||||||
// if frames had been stacked on top of bottomFrames, allFrames would be larger
|
// if frames had been stacked on top of bottomFrames, allFrames would be larger
|
||||||
@@ -124,7 +125,7 @@ public class ErrorCode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// visible for testing
|
@VisibleForTesting
|
||||||
static <T> int commonSuffixLength(T[] set, T[] subset) {
|
static <T> int commonSuffixLength(T[] set, T[] subset) {
|
||||||
Preconditions.checkArgument(set.length >= subset.length);
|
Preconditions.checkArgument(set.length >= subset.length);
|
||||||
// iterate items backwards as long as they are identical
|
// iterate items backwards as long as they are identical
|
||||||
|
|||||||
+1
-1
@@ -62,7 +62,7 @@ public final class OneDriveWindowsLocationPresetsProvider implements LocationPre
|
|||||||
ProcessBuilder command = new ProcessBuilder(args);
|
ProcessBuilder command = new ProcessBuilder(args);
|
||||||
Process p = command.start();
|
Process p = command.start();
|
||||||
waitForSuccess(p, 3, "`reg query`");
|
waitForSuccess(p, 3, "`reg query`");
|
||||||
return p.inputReader(StandardCharsets.UTF_8).lines().filter(outputFilter);
|
return p.inputReader(StandardCharsets.ISO_8859_1).lines().filter(outputFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.cryptomator.common.mount;
|
package org.cryptomator.common.mount;
|
||||||
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
import org.jetbrains.annotations.VisibleForTesting;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -66,7 +67,7 @@ public final class MountWithinParentUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//visible for testing
|
@VisibleForTesting
|
||||||
static MountPointState getMountPointState(Path path) throws IOException, IllegalMountPointException {
|
static MountPointState getMountPointState(Path path) throws IOException, IllegalMountPointException {
|
||||||
if (Files.notExists(path, LinkOption.NOFOLLOW_LINKS)) {
|
if (Files.notExists(path, LinkOption.NOFOLLOW_LINKS)) {
|
||||||
return MountPointState.NOT_EXISTING;
|
return MountPointState.NOT_EXISTING;
|
||||||
@@ -82,7 +83,7 @@ public final class MountWithinParentUtil {
|
|||||||
return MountPointState.BROKEN_JUNCTION;
|
return MountPointState.BROKEN_JUNCTION;
|
||||||
}
|
}
|
||||||
|
|
||||||
//visible for testing
|
@VisibleForTesting
|
||||||
enum MountPointState {
|
enum MountPointState {
|
||||||
|
|
||||||
NOT_EXISTING,
|
NOT_EXISTING,
|
||||||
@@ -93,7 +94,7 @@ public final class MountWithinParentUtil {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//visible for testing
|
@VisibleForTesting
|
||||||
static void removeResidualHideaway(Path mountPoint, Path hideaway) throws IOException {
|
static void removeResidualHideaway(Path mountPoint, Path hideaway) throws IOException {
|
||||||
checkIsHideawayDirectory(mountPoint, hideaway);
|
checkIsHideawayDirectory(mountPoint, hideaway);
|
||||||
Files.delete(hideaway); //Fails if not empty
|
Files.delete(hideaway); //Fails if not empty
|
||||||
@@ -155,7 +156,7 @@ public final class MountWithinParentUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//visible for testing
|
@VisibleForTesting
|
||||||
static Path getHideaway(Path mountPoint) {
|
static Path getHideaway(Path mountPoint) {
|
||||||
return mountPoint.resolveSibling(HIDEAWAY_PREFIX + mountPoint.getFileName().toString() + HIDEAWAY_SUFFIX);
|
return mountPoint.resolveSibling(HIDEAWAY_PREFIX + mountPoint.getFileName().toString() + HIDEAWAY_SUFFIX);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ public class Settings {
|
|||||||
public final IntegerProperty windowYPosition;
|
public final IntegerProperty windowYPosition;
|
||||||
public final IntegerProperty windowWidth;
|
public final IntegerProperty windowWidth;
|
||||||
public final IntegerProperty windowHeight;
|
public final IntegerProperty windowHeight;
|
||||||
public final StringProperty displayConfiguration;
|
|
||||||
public final StringProperty language;
|
public final StringProperty language;
|
||||||
public final StringProperty mountService;
|
public final StringProperty mountService;
|
||||||
public final StringProperty lastUpdateCheck;
|
public final StringProperty lastUpdateCheck;
|
||||||
@@ -103,7 +102,6 @@ public class Settings {
|
|||||||
this.windowYPosition = new SimpleIntegerProperty(this, "windowYPosition", json.windowYPosition);
|
this.windowYPosition = new SimpleIntegerProperty(this, "windowYPosition", json.windowYPosition);
|
||||||
this.windowWidth = new SimpleIntegerProperty(this, "windowWidth", json.windowWidth);
|
this.windowWidth = new SimpleIntegerProperty(this, "windowWidth", json.windowWidth);
|
||||||
this.windowHeight = new SimpleIntegerProperty(this, "windowHeight", json.windowHeight);
|
this.windowHeight = new SimpleIntegerProperty(this, "windowHeight", json.windowHeight);
|
||||||
this.displayConfiguration = new SimpleStringProperty(this, "displayConfiguration", json.displayConfiguration);
|
|
||||||
this.language = new SimpleStringProperty(this, "language", json.language);
|
this.language = new SimpleStringProperty(this, "language", json.language);
|
||||||
this.mountService = new SimpleStringProperty(this, "mountService", json.mountService);
|
this.mountService = new SimpleStringProperty(this, "mountService", json.mountService);
|
||||||
this.lastUpdateCheck = new SimpleStringProperty(this, "lastUpdateCheck", json.lastUpdateCheck);
|
this.lastUpdateCheck = new SimpleStringProperty(this, "lastUpdateCheck", json.lastUpdateCheck);
|
||||||
@@ -131,7 +129,6 @@ public class Settings {
|
|||||||
windowYPosition.addListener(this::somethingChanged);
|
windowYPosition.addListener(this::somethingChanged);
|
||||||
windowWidth.addListener(this::somethingChanged);
|
windowWidth.addListener(this::somethingChanged);
|
||||||
windowHeight.addListener(this::somethingChanged);
|
windowHeight.addListener(this::somethingChanged);
|
||||||
displayConfiguration.addListener(this::somethingChanged);
|
|
||||||
language.addListener(this::somethingChanged);
|
language.addListener(this::somethingChanged);
|
||||||
mountService.addListener(this::somethingChanged);
|
mountService.addListener(this::somethingChanged);
|
||||||
lastUpdateCheck.addListener(this::somethingChanged);
|
lastUpdateCheck.addListener(this::somethingChanged);
|
||||||
@@ -186,7 +183,6 @@ public class Settings {
|
|||||||
json.windowYPosition = windowYPosition.get();
|
json.windowYPosition = windowYPosition.get();
|
||||||
json.windowWidth = windowWidth.get();
|
json.windowWidth = windowWidth.get();
|
||||||
json.windowHeight = windowHeight.get();
|
json.windowHeight = windowHeight.get();
|
||||||
json.displayConfiguration = displayConfiguration.get();
|
|
||||||
json.language = language.get();
|
json.language = language.get();
|
||||||
json.mountService = mountService.get();
|
json.mountService = mountService.get();
|
||||||
json.lastUpdateCheck = lastUpdateCheck.get();
|
json.lastUpdateCheck = lastUpdateCheck.get();
|
||||||
|
|||||||
@@ -31,9 +31,6 @@ class SettingsJson {
|
|||||||
@JsonProperty("theme")
|
@JsonProperty("theme")
|
||||||
UiTheme theme = Settings.DEFAULT_THEME;
|
UiTheme theme = Settings.DEFAULT_THEME;
|
||||||
|
|
||||||
@JsonProperty("displayConfiguration")
|
|
||||||
String displayConfiguration;
|
|
||||||
|
|
||||||
@JsonProperty("keychainProvider")
|
@JsonProperty("keychainProvider")
|
||||||
String keychainProvider = Settings.DEFAULT_KEYCHAIN_PROVIDER;
|
String keychainProvider = Settings.DEFAULT_KEYCHAIN_PROVIDER;
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import com.google.common.base.CharMatcher;
|
|||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.io.BaseEncoding;
|
import com.google.common.io.BaseEncoding;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
import org.jetbrains.annotations.VisibleForTesting;
|
||||||
|
|
||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
@@ -126,7 +127,7 @@ public class VaultSettings {
|
|||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
//visible for testing
|
@VisibleForTesting
|
||||||
static String normalizeDisplayName(String original) {
|
static String normalizeDisplayName(String original) {
|
||||||
if (original.isBlank() || ".".equals(original) || "..".equals(original)) {
|
if (original.isBlank() || ".".equals(original) || "..".equals(original)) {
|
||||||
return "_";
|
return "_";
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.launcher;
|
package org.cryptomator.launcher;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.VisibleForTesting;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -48,7 +49,7 @@ class FileOpenRequestHandler {
|
|||||||
handleLaunchArgs(FileSystems.getDefault(), args);
|
handleLaunchArgs(FileSystems.getDefault(), args);
|
||||||
}
|
}
|
||||||
|
|
||||||
// visible for testing
|
@VisibleForTesting
|
||||||
void handleLaunchArgs(FileSystem fs, List<String> args) {
|
void handleLaunchArgs(FileSystem fs, List<String> args) {
|
||||||
Collection<Path> pathsToOpen = args.stream().map(str -> {
|
Collection<Path> pathsToOpen = args.stream().map(str -> {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import ch.qos.logback.classic.Logger;
|
|||||||
import ch.qos.logback.classic.LoggerContext;
|
import ch.qos.logback.classic.LoggerContext;
|
||||||
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
|
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
|
||||||
import ch.qos.logback.classic.spi.Configurator;
|
import ch.qos.logback.classic.spi.Configurator;
|
||||||
|
import ch.qos.logback.classic.spi.ConfiguratorRank;
|
||||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||||
import ch.qos.logback.core.Appender;
|
import ch.qos.logback.core.Appender;
|
||||||
import ch.qos.logback.core.ConsoleAppender;
|
import ch.qos.logback.core.ConsoleAppender;
|
||||||
@@ -19,6 +20,7 @@ import org.cryptomator.common.Environment;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ConfiguratorRank(ConfiguratorRank.CUSTOM_NORMAL_PRIORITY)
|
||||||
public class LogbackConfigurator extends ContextAwareBase implements Configurator {
|
public class LogbackConfigurator extends ContextAwareBase implements Configurator {
|
||||||
|
|
||||||
private static final String LOG_PATTERN = "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n";
|
private static final String LOG_PATTERN = "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n";
|
||||||
|
|||||||
@@ -45,9 +45,8 @@ public abstract class AddVaultModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@AddVaultWizardWindow
|
@AddVaultWizardWindow
|
||||||
@AddVaultWizardScoped
|
@AddVaultWizardScoped
|
||||||
static Stage provideStage(StageFactory factory, @PrimaryStage Stage primaryStage, ResourceBundle resourceBundle) {
|
static Stage provideStage(StageFactory factory, @PrimaryStage Stage primaryStage) {
|
||||||
Stage stage = factory.create();
|
Stage stage = factory.create();
|
||||||
stage.setTitle(resourceBundle.getString("addvaultwizard.title"));
|
|
||||||
stage.setResizable(false);
|
stage.setResizable(false);
|
||||||
stage.initModality(Modality.WINDOW_MODAL);
|
stage.initModality(Modality.WINDOW_MODAL);
|
||||||
stage.initOwner(primaryStage);
|
stage.initOwner(primaryStage);
|
||||||
@@ -90,13 +89,6 @@ public abstract class AddVaultModule {
|
|||||||
|
|
||||||
// ------------------
|
// ------------------
|
||||||
|
|
||||||
@Provides
|
|
||||||
@FxmlScene(FxmlFile.ADDVAULT_WELCOME)
|
|
||||||
@AddVaultWizardScoped
|
|
||||||
static Scene provideWelcomeScene(@AddVaultWizardWindow FxmlLoaderFactory fxmlLoaders) {
|
|
||||||
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_WELCOME);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.ADDVAULT_EXISTING)
|
@FxmlScene(FxmlFile.ADDVAULT_EXISTING)
|
||||||
@AddVaultWizardScoped
|
@AddVaultWizardScoped
|
||||||
@@ -148,11 +140,6 @@ public abstract class AddVaultModule {
|
|||||||
|
|
||||||
// ------------------
|
// ------------------
|
||||||
|
|
||||||
@Binds
|
|
||||||
@IntoMap
|
|
||||||
@FxControllerKey(AddVaultWelcomeController.class)
|
|
||||||
abstract FxController bindWelcomeController(AddVaultWelcomeController controller);
|
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FxControllerKey(ChooseExistingVaultController.class)
|
@FxControllerKey(ChooseExistingVaultController.class)
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
package org.cryptomator.ui.addvaultwizard;
|
|
||||||
|
|
||||||
import dagger.Lazy;
|
|
||||||
import org.cryptomator.ui.common.FxController;
|
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
|
||||||
import org.cryptomator.ui.common.FxmlScene;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javafx.scene.Scene;
|
|
||||||
import javafx.stage.Stage;
|
|
||||||
|
|
||||||
@AddVaultWizardScoped
|
|
||||||
public class AddVaultWelcomeController implements FxController {
|
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(AddVaultWelcomeController.class);
|
|
||||||
private final Stage window;
|
|
||||||
private final Lazy<Scene> chooseExistingVaultScene;
|
|
||||||
private final Lazy<Scene> createNewVaultScene;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
AddVaultWelcomeController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_EXISTING) Lazy<Scene> chooseExistingVaultScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_NAME) Lazy<Scene> createNewVaultScene) {
|
|
||||||
this.window = window;
|
|
||||||
this.chooseExistingVaultScene = chooseExistingVaultScene;
|
|
||||||
this.createNewVaultScene = createNewVaultScene;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void createNewVault() {
|
|
||||||
LOG.debug("AddVaultWelcomeController.createNewVault()");
|
|
||||||
window.setScene(createNewVaultScene.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void chooseExistingVault() {
|
|
||||||
LOG.debug("AddVaultWelcomeController.chooseExistingVault()");
|
|
||||||
window.setScene(chooseExistingVaultScene.get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,6 +12,7 @@ import org.cryptomator.ui.common.FxmlScene;
|
|||||||
|
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
@AddVaultWizardScoped
|
@AddVaultWizardScoped
|
||||||
@Subcomponent(modules = {AddVaultModule.class})
|
@Subcomponent(modules = {AddVaultModule.class})
|
||||||
@@ -20,12 +21,23 @@ public interface AddVaultWizardComponent {
|
|||||||
@AddVaultWizardWindow
|
@AddVaultWizardWindow
|
||||||
Stage window();
|
Stage window();
|
||||||
|
|
||||||
@FxmlScene(FxmlFile.ADDVAULT_WELCOME)
|
@FxmlScene(FxmlFile.ADDVAULT_NEW_NAME)
|
||||||
Lazy<Scene> scene();
|
Lazy<Scene> sceneNew();
|
||||||
|
@FxmlScene(FxmlFile.ADDVAULT_EXISTING)
|
||||||
|
Lazy<Scene> sceneExisting();
|
||||||
|
|
||||||
default void showAddVaultWizard() {
|
default void showAddNewVaultWizard(ResourceBundle resourceBundle) {
|
||||||
Stage stage = window();
|
Stage stage = window();
|
||||||
stage.setScene(scene().get());
|
stage.setScene(sceneNew().get());
|
||||||
|
stage.setTitle(resourceBundle.getString("addvaultwizard.new.title"));
|
||||||
|
stage.sizeToScene();
|
||||||
|
stage.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
default void showAddExistingVaultWizard(ResourceBundle resourceBundle) {
|
||||||
|
Stage stage = window();
|
||||||
|
stage.setScene(sceneExisting().get());
|
||||||
|
stage.setTitle(resourceBundle.getString("addvaultwizard.existing.title"));
|
||||||
stage.sizeToScene();
|
stage.sizeToScene();
|
||||||
stage.show();
|
stage.show();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ public class ChooseExistingVaultController implements FxController {
|
|||||||
private static final Logger LOG = LoggerFactory.getLogger(ChooseExistingVaultController.class);
|
private static final Logger LOG = LoggerFactory.getLogger(ChooseExistingVaultController.class);
|
||||||
|
|
||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final Lazy<Scene> welcomeScene;
|
|
||||||
private final Lazy<Scene> successScene;
|
private final Lazy<Scene> successScene;
|
||||||
private final FxApplicationWindows appWindows;
|
private final FxApplicationWindows appWindows;
|
||||||
private final ObjectProperty<Path> vaultPath;
|
private final ObjectProperty<Path> vaultPath;
|
||||||
@@ -45,9 +44,15 @@ public class ChooseExistingVaultController implements FxController {
|
|||||||
private final ObservableValue<Image> screenshot;
|
private final ObservableValue<Image> screenshot;
|
||||||
|
|
||||||
@Inject
|
@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, FxApplicationStyle applicationStyle) {
|
ChooseExistingVaultController(@AddVaultWizardWindow Stage window, //
|
||||||
|
@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.window = window;
|
||||||
this.welcomeScene = welcomeScene;
|
|
||||||
this.successScene = successScene;
|
this.successScene = successScene;
|
||||||
this.appWindows = appWindows;
|
this.appWindows = appWindows;
|
||||||
this.vaultPath = vaultPath;
|
this.vaultPath = vaultPath;
|
||||||
@@ -70,11 +75,6 @@ public class ChooseExistingVaultController implements FxController {
|
|||||||
return new Image((Objects.requireNonNull(getClass().getResource(imageResourcePath)).toString()));
|
return new Image((Objects.requireNonNull(getClass().getResource(imageResourcePath)).toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
|
||||||
public void back() {
|
|
||||||
window.setScene(welcomeScene.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void chooseFileAndNext() {
|
public void chooseFileAndNext() {
|
||||||
FileChooser fileChooser = new FileChooser();
|
FileChooser fileChooser = new FileChooser();
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import javafx.scene.Scene;
|
|||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ResourceBundle;
|
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@AddVaultWizardScoped
|
@AddVaultWizardScoped
|
||||||
@@ -27,16 +26,17 @@ public class CreateNewVaultNameController implements FxController {
|
|||||||
|
|
||||||
public TextField textField;
|
public TextField textField;
|
||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final Lazy<Scene> welcomeScene;
|
|
||||||
private final Lazy<Scene> chooseLocationScene;
|
private final Lazy<Scene> chooseLocationScene;
|
||||||
private final ObjectProperty<Path> vaultPath;
|
private final ObjectProperty<Path> vaultPath;
|
||||||
private final StringProperty vaultName;
|
private final StringProperty vaultName;
|
||||||
private final BooleanBinding validVaultName;
|
private final BooleanBinding validVaultName;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
CreateNewVaultNameController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy<Scene> welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy<Scene> chooseLocationScene, ObjectProperty<Path> vaultPath, @Named("vaultName") StringProperty vaultName, ResourceBundle resourceBundle) {
|
CreateNewVaultNameController(@AddVaultWizardWindow Stage window, //
|
||||||
|
@FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy<Scene> chooseLocationScene, //
|
||||||
|
ObjectProperty<Path> vaultPath, //
|
||||||
|
@Named("vaultName") StringProperty vaultName) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.welcomeScene = welcomeScene;
|
|
||||||
this.chooseLocationScene = chooseLocationScene;
|
this.chooseLocationScene = chooseLocationScene;
|
||||||
this.vaultPath = vaultPath;
|
this.vaultPath = vaultPath;
|
||||||
this.vaultName = vaultName;
|
this.vaultName = vaultName;
|
||||||
@@ -58,11 +58,6 @@ public class CreateNewVaultNameController implements FxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
|
||||||
public void back() {
|
|
||||||
window.setScene(welcomeScene.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void next() {
|
public void next() {
|
||||||
window.setScene(chooseLocationScene.get());
|
window.setScene(chooseLocationScene.get());
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package org.cryptomator.ui.addvaultwizard;
|
package org.cryptomator.ui.addvaultwizard;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.VisibleForTesting;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
@@ -51,7 +53,7 @@ public class ReadmeGenerator {
|
|||||||
resourceBundle.getString("addvault.new.readme.accessLocation.4")));
|
resourceBundle.getString("addvault.new.readme.accessLocation.4")));
|
||||||
}
|
}
|
||||||
|
|
||||||
// visible for testing
|
@VisibleForTesting
|
||||||
String createDocument(Iterable<String> paragraphs) {
|
String createDocument(Iterable<String> paragraphs) {
|
||||||
StringBuilder sb = new StringBuilder(RTF_HEADER);
|
StringBuilder sb = new StringBuilder(RTF_HEADER);
|
||||||
for (String p : paragraphs) {
|
for (String p : paragraphs) {
|
||||||
@@ -63,7 +65,7 @@ public class ReadmeGenerator {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// visible for testing
|
@VisibleForTesting
|
||||||
String escapeNonAsciiChars(CharSequence input) {
|
String escapeNonAsciiChars(CharSequence input) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
appendEscaped(sb, input);
|
appendEscaped(sb, input);
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ public enum FxmlFile {
|
|||||||
ADDVAULT_NEW_PASSWORD("/fxml/addvault_new_password.fxml"), //
|
ADDVAULT_NEW_PASSWORD("/fxml/addvault_new_password.fxml"), //
|
||||||
ADDVAULT_NEW_RECOVERYKEY("/fxml/addvault_new_recoverykey.fxml"), //
|
ADDVAULT_NEW_RECOVERYKEY("/fxml/addvault_new_recoverykey.fxml"), //
|
||||||
ADDVAULT_SUCCESS("/fxml/addvault_success.fxml"), //
|
ADDVAULT_SUCCESS("/fxml/addvault_success.fxml"), //
|
||||||
ADDVAULT_WELCOME("/fxml/addvault_welcome.fxml"), //
|
|
||||||
CHANGEPASSWORD("/fxml/changepassword.fxml"), //
|
CHANGEPASSWORD("/fxml/changepassword.fxml"), //
|
||||||
CONVERTVAULT_HUBTOPASSWORD_START("/fxml/convertvault_hubtopassword_start.fxml"), //
|
CONVERTVAULT_HUBTOPASSWORD_START("/fxml/convertvault_hubtopassword_start.fxml"), //
|
||||||
CONVERTVAULT_HUBTOPASSWORD_CONVERT("/fxml/convertvault_hubtopassword_convert.fxml"), //
|
CONVERTVAULT_HUBTOPASSWORD_CONVERT("/fxml/convertvault_hubtopassword_convert.fxml"), //
|
||||||
@@ -21,9 +20,10 @@ public enum FxmlFile {
|
|||||||
HUB_AUTH_FLOW("/fxml/hub_auth_flow.fxml"), //
|
HUB_AUTH_FLOW("/fxml/hub_auth_flow.fxml"), //
|
||||||
HUB_INVALID_LICENSE("/fxml/hub_invalid_license.fxml"), //
|
HUB_INVALID_LICENSE("/fxml/hub_invalid_license.fxml"), //
|
||||||
HUB_RECEIVE_KEY("/fxml/hub_receive_key.fxml"), //
|
HUB_RECEIVE_KEY("/fxml/hub_receive_key.fxml"), //
|
||||||
HUB_REGISTER_DEVICE("/fxml/hub_register_device.fxml"), //
|
HUB_LEGACY_REGISTER_DEVICE("/fxml/hub_legacy_register_device.fxml"), //
|
||||||
HUB_REGISTER_SUCCESS("/fxml/hub_register_success.fxml"), //
|
HUB_REGISTER_SUCCESS("/fxml/hub_register_success.fxml"), //
|
||||||
HUB_REGISTER_FAILED("/fxml/hub_register_failed.fxml"),
|
HUB_REGISTER_FAILED("/fxml/hub_register_failed.fxml"), //
|
||||||
|
HUB_SETUP_DEVICE("/fxml/hub_setup_device.fxml"), //
|
||||||
HUB_UNAUTHORIZED_DEVICE("/fxml/hub_unauthorized_device.fxml"), //
|
HUB_UNAUTHORIZED_DEVICE("/fxml/hub_unauthorized_device.fxml"), //
|
||||||
LOCK_FORCED("/fxml/lock_forced.fxml"), //
|
LOCK_FORCED("/fxml/lock_forced.fxml"), //
|
||||||
LOCK_FAILED("/fxml/lock_failed.fxml"), //
|
LOCK_FAILED("/fxml/lock_failed.fxml"), //
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import org.cryptomator.ui.common.FxmlFile;
|
|||||||
import org.cryptomator.ui.common.FxmlScene;
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||||
import org.cryptomator.ui.recoverykey.RecoveryKeyFactory;
|
import org.cryptomator.ui.recoverykey.RecoveryKeyFactory;
|
||||||
|
import org.jetbrains.annotations.VisibleForTesting;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -116,7 +117,7 @@ public class HubToPasswordConvertController implements FxController {
|
|||||||
}, Platform::runLater); //
|
}, Platform::runLater); //
|
||||||
}
|
}
|
||||||
|
|
||||||
//visible for testing
|
@VisibleForTesting
|
||||||
void convertInternal() throws CompletionException, IllegalArgumentException {
|
void convertInternal() throws CompletionException, IllegalArgumentException {
|
||||||
var passphrase = newPasswordController.getNewPassword();
|
var passphrase = newPasswordController.getNewPassword();
|
||||||
var vaultPath = vault.getPath();
|
var vaultPath = vault.getPath();
|
||||||
@@ -141,7 +142,7 @@ public class HubToPasswordConvertController implements FxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//visible for testing
|
@VisibleForTesting
|
||||||
void backupHubConfig(Path hubConfigPath) throws IOException {
|
void backupHubConfig(Path hubConfigPath) throws IOException {
|
||||||
byte[] hubConfigBytes = Files.readAllBytes(hubConfigPath);
|
byte[] hubConfigBytes = Files.readAllBytes(hubConfigPath);
|
||||||
Path backupPath = hubConfigPath.resolveSibling(VAULTCONFIG_FILENAME + BackupHelper.generateFileIdSuffix(hubConfigBytes) + MASTERKEY_BACKUP_SUFFIX);
|
Path backupPath = hubConfigPath.resolveSibling(VAULTCONFIG_FILENAME + BackupHelper.generateFileIdSuffix(hubConfigBytes) + MASTERKEY_BACKUP_SUFFIX);
|
||||||
@@ -149,7 +150,7 @@ public class HubToPasswordConvertController implements FxController {
|
|||||||
LOG.debug("Successfully created hub config backup {}", backupPath.getFileName());
|
LOG.debug("Successfully created hub config backup {}", backupPath.getFileName());
|
||||||
}
|
}
|
||||||
|
|
||||||
//visible for testing
|
@VisibleForTesting
|
||||||
Path createPasswordConfig(Path passwordConfigPath, Path masterkeyFile, Passphrase passphrase) throws IOException, MasterkeyLoadingFailedException {
|
Path createPasswordConfig(Path passwordConfigPath, Path masterkeyFile, Passphrase passphrase) throws IOException, MasterkeyLoadingFailedException {
|
||||||
var unverifiedVaultConfig = vault.getVaultConfigCache().get();
|
var unverifiedVaultConfig = vault.getVaultConfigCache().get();
|
||||||
try (var masterkey = masterkeyFileAccess.load(masterkeyFile, passphrase)) {
|
try (var masterkey = masterkeyFileAccess.load(masterkeyFile, passphrase)) {
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import java.net.http.HttpClient;
|
|||||||
import java.net.http.HttpRequest;
|
import java.net.http.HttpRequest;
|
||||||
import java.net.http.HttpResponse;
|
import java.net.http.HttpResponse;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.time.Duration;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -42,19 +43,21 @@ public class ErrorController implements FxController {
|
|||||||
|
|
||||||
private static final ObjectMapper JSON = new ObjectMapper();
|
private static final ObjectMapper JSON = new ObjectMapper();
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ErrorController.class);
|
private static final Logger LOG = LoggerFactory.getLogger(ErrorController.class);
|
||||||
private static final String ERROR_CODES_URL = "https://api.cryptomator.org/desktop/error-codes.json";
|
private static final String USER_AGENT_FORMAT = "Cryptomator/%s (Build %s) (%s %s %s)";
|
||||||
|
private static final String ERROR_CODES_URL_FORMAT = "https://api.cryptomator.org/desktop/error-codes.json?error-code=%s";
|
||||||
private static final String SEARCH_URL_FORMAT = "https://github.com/cryptomator/cryptomator/discussions/categories/errors?discussions_q=category:Errors+%s";
|
private static final String SEARCH_URL_FORMAT = "https://github.com/cryptomator/cryptomator/discussions/categories/errors?discussions_q=category:Errors+%s";
|
||||||
private static final String REPORT_URL_FORMAT = "https://github.com/cryptomator/cryptomator/discussions/new?category=Errors&title=Error+%s&body=%s";
|
private static final String REPORT_URL_FORMAT = "https://github.com/cryptomator/cryptomator/discussions/new?category=Errors&title=Error+%s&body=%s";
|
||||||
private static final String SEARCH_ERRORCODE_DELIM = " OR ";
|
private static final String SEARCH_ERRORCODE_DELIM = " OR ";
|
||||||
private static final String REPORT_BODY_TEMPLATE = """
|
private static final String REPORT_BODY_TEMPLATE = """
|
||||||
|
<!-- 💚 Thank you for reporting this error. -->
|
||||||
OS: %s / %s
|
OS: %s / %s
|
||||||
App: %s / %s
|
App: %s / %s
|
||||||
|
|
||||||
<!-- ✏ Please describe what happened as accurately as possible. -->
|
<!-- ✏ Please describe what happened as accurately as possible. -->
|
||||||
|
Description:
|
||||||
|
|
||||||
<!-- 📋 Please also copy and paste the detail text from the error window. -->
|
<!-- 📋 Please also copy and paste the details from the error window. -->
|
||||||
|
Details:
|
||||||
<!-- ℹ Text enclosed like this (chevrons, exclamation mark, two dashes) is not visible to others! -->
|
|
||||||
|
|
||||||
<!-- ❗ If the description or the detail text is missing, the discussion will be deleted. -->
|
<!-- ❗ If the description or the detail text is missing, the discussion will be deleted. -->
|
||||||
""";
|
""";
|
||||||
@@ -141,11 +144,19 @@ public class ErrorController implements FxController {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void lookUpSolution() {
|
public void lookUpSolution() {
|
||||||
|
String userAgent = USER_AGENT_FORMAT.formatted( //
|
||||||
|
environment.getAppVersion(), //
|
||||||
|
environment.getBuildNumber().orElse("undefined"), //
|
||||||
|
System.getProperty("os.name"), //
|
||||||
|
System.getProperty("os.version"), //
|
||||||
|
System.getProperty("os.arch"));
|
||||||
isLoadingHttpResponse.set(true);
|
isLoadingHttpResponse.set(true);
|
||||||
askedForLookupDatabasePermission.set(true);
|
askedForLookupDatabasePermission.set(true);
|
||||||
HttpClient httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
|
HttpClient httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
|
||||||
HttpRequest httpRequest = HttpRequest.newBuilder()//
|
HttpRequest httpRequest = HttpRequest.newBuilder()//
|
||||||
.uri(URI.create(ERROR_CODES_URL))//
|
.header("User-Agent", userAgent)
|
||||||
|
.timeout(Duration.ofSeconds(10))
|
||||||
|
.uri(URI.create(ERROR_CODES_URL_FORMAT.formatted(URLEncoder.encode(errorCode.toString(),StandardCharsets.UTF_8))))//
|
||||||
.build();
|
.build();
|
||||||
httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofInputStream())//
|
httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofInputStream())//
|
||||||
.thenAcceptAsync(this::loadHttpResponse, executorService)//
|
.thenAcceptAsync(this::loadHttpResponse, executorService)//
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.cryptomator.ui.fxapp;
|
package org.cryptomator.ui.fxapp;
|
||||||
|
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
|
import org.cryptomator.common.Environment;
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.ui.traymenu.TrayMenuComponent;
|
import org.cryptomator.ui.traymenu.TrayMenuComponent;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -17,6 +18,7 @@ public class FxApplication {
|
|||||||
private static final Logger LOG = LoggerFactory.getLogger(FxApplication.class);
|
private static final Logger LOG = LoggerFactory.getLogger(FxApplication.class);
|
||||||
|
|
||||||
private final long startupTime;
|
private final long startupTime;
|
||||||
|
private final Environment environment;
|
||||||
private final Settings settings;
|
private final Settings settings;
|
||||||
private final AppLaunchEventHandler launchEventHandler;
|
private final AppLaunchEventHandler launchEventHandler;
|
||||||
private final Lazy<TrayMenuComponent> trayMenu;
|
private final Lazy<TrayMenuComponent> trayMenu;
|
||||||
@@ -26,8 +28,9 @@ public class FxApplication {
|
|||||||
private final AutoUnlocker autoUnlocker;
|
private final AutoUnlocker autoUnlocker;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
FxApplication(@Named("startupTime") long startupTime, Settings settings, AppLaunchEventHandler launchEventHandler, Lazy<TrayMenuComponent> trayMenu, FxApplicationWindows appWindows, FxApplicationStyle applicationStyle, FxApplicationTerminator applicationTerminator, AutoUnlocker autoUnlocker) {
|
FxApplication(@Named("startupTime") long startupTime, Environment environment, Settings settings, AppLaunchEventHandler launchEventHandler, Lazy<TrayMenuComponent> trayMenu, FxApplicationWindows appWindows, FxApplicationStyle applicationStyle, FxApplicationTerminator applicationTerminator, AutoUnlocker autoUnlocker) {
|
||||||
this.startupTime = startupTime;
|
this.startupTime = startupTime;
|
||||||
|
this.environment = environment;
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.launchEventHandler = launchEventHandler;
|
this.launchEventHandler = launchEventHandler;
|
||||||
this.trayMenu = trayMenu;
|
this.trayMenu = trayMenu;
|
||||||
@@ -68,7 +71,9 @@ public class FxApplication {
|
|||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
appWindows.checkAndShowUpdateReminderWindow();
|
if (!environment.disableUpdateCheck()) {
|
||||||
|
appWindows.checkAndShowUpdateReminderWindow();
|
||||||
|
}
|
||||||
|
|
||||||
launchEventHandler.startHandlingLaunchEvents();
|
launchEventHandler.startHandlingLaunchEvents();
|
||||||
autoUnlocker.tryUnlockForTimespan(2, TimeUnit.MINUTES);
|
autoUnlocker.tryUnlockForTimespan(2, TimeUnit.MINUTES);
|
||||||
|
|||||||
@@ -22,23 +22,23 @@ public class UpdateChecker {
|
|||||||
private static final Logger LOG = LoggerFactory.getLogger(UpdateChecker.class);
|
private static final Logger LOG = LoggerFactory.getLogger(UpdateChecker.class);
|
||||||
private static final Duration AUTOCHECK_DELAY = Duration.seconds(5);
|
private static final Duration AUTOCHECK_DELAY = Duration.seconds(5);
|
||||||
|
|
||||||
|
private final Environment env;
|
||||||
private final Settings settings;
|
private final Settings settings;
|
||||||
private final String currentVersion;
|
|
||||||
private final StringProperty latestVersionProperty;
|
private final StringProperty latestVersionProperty;
|
||||||
private final Comparator<String> semVerComparator;
|
private final Comparator<String> semVerComparator;
|
||||||
private final ScheduledService<String> updateCheckerService;
|
private final ScheduledService<String> updateCheckerService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
UpdateChecker(Settings settings, Environment env, @Named("latestVersion") StringProperty latestVersionProperty, @Named("SemVer") Comparator<String> semVerComparator, ScheduledService<String> updateCheckerService) {
|
UpdateChecker(Settings settings, Environment env, @Named("latestVersion") StringProperty latestVersionProperty, @Named("SemVer") Comparator<String> semVerComparator, ScheduledService<String> updateCheckerService) {
|
||||||
|
this.env = env;
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.latestVersionProperty = latestVersionProperty;
|
this.latestVersionProperty = latestVersionProperty;
|
||||||
this.semVerComparator = semVerComparator;
|
this.semVerComparator = semVerComparator;
|
||||||
this.updateCheckerService = updateCheckerService;
|
this.updateCheckerService = updateCheckerService;
|
||||||
this.currentVersion = env.getAppVersion();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void automaticallyCheckForUpdatesIfEnabled() {
|
public void automaticallyCheckForUpdatesIfEnabled() {
|
||||||
if (settings.checkForUpdates.get()) {
|
if (!env.disableUpdateCheck() && settings.checkForUpdates.get()) {
|
||||||
startCheckingForUpdates(AUTOCHECK_DELAY);
|
startCheckingForUpdates(AUTOCHECK_DELAY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,9 +63,9 @@ public class UpdateChecker {
|
|||||||
|
|
||||||
private void checkSucceeded(WorkerStateEvent event) {
|
private void checkSucceeded(WorkerStateEvent event) {
|
||||||
String latestVersion = updateCheckerService.getValue();
|
String latestVersion = updateCheckerService.getValue();
|
||||||
LOG.info("Current version: {}, lastest version: {}", currentVersion, latestVersion);
|
LOG.info("Current version: {}, lastest version: {}", getCurrentVersion(), latestVersion);
|
||||||
|
|
||||||
if (semVerComparator.compare(currentVersion, latestVersion) < 0) {
|
if (semVerComparator.compare(getCurrentVersion(), latestVersion) < 0) {
|
||||||
// update is available
|
// update is available
|
||||||
latestVersionProperty.set(latestVersion);
|
latestVersionProperty.set(latestVersion);
|
||||||
} else {
|
} else {
|
||||||
@@ -88,7 +88,7 @@ public class UpdateChecker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getCurrentVersion() {
|
public String getCurrentVersion() {
|
||||||
return currentVersion;
|
return env.getAppVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ public abstract class UpdateCheckerModule {
|
|||||||
return HttpRequest.newBuilder() //
|
return HttpRequest.newBuilder() //
|
||||||
.uri(LATEST_VERSION_URI) //
|
.uri(LATEST_VERSION_URI) //
|
||||||
.header("User-Agent", userAgent) //
|
.header("User-Agent", userAgent) //
|
||||||
|
.timeout(java.time.Duration.ofSeconds(10))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,16 +101,16 @@ public class StartController implements FxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadingKeyFailed(Throwable e) {
|
private void loadingKeyFailed(Throwable t) {
|
||||||
switch (e) {
|
switch (t) {
|
||||||
case UnlockCancelledException uce -> {} //ok
|
case UnlockCancelledException e -> {} // ok // TODO: rename to _ with JEP 443
|
||||||
case VaultKeyInvalidException vkie -> {
|
case VaultKeyInvalidException e -> { // TODO: rename to _ with JEP 443
|
||||||
LOG.error("Invalid key"); //TODO: specific error screen
|
LOG.error("Invalid key"); // TODO: specific error screen
|
||||||
appWindows.showErrorWindow(e, window, null);
|
appWindows.showErrorWindow(e, window, null);
|
||||||
}
|
}
|
||||||
default -> {
|
default -> {
|
||||||
LOG.error("Failed to load key.", e);
|
LOG.error("Failed to load key.", t);
|
||||||
appWindows.showErrorWindow(e, window, null);
|
appWindows.showErrorWindow(t, window, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,13 +35,13 @@ public class AuthFlowController implements FxController {
|
|||||||
private final String deviceId;
|
private final String deviceId;
|
||||||
private final HubConfig hubConfig;
|
private final HubConfig hubConfig;
|
||||||
private final AtomicReference<String> tokenRef;
|
private final AtomicReference<String> tokenRef;
|
||||||
private final CompletableFuture<JWEObject> result;
|
private final CompletableFuture<ReceivedKey> result;
|
||||||
private final Lazy<Scene> receiveKeyScene;
|
private final Lazy<Scene> receiveKeyScene;
|
||||||
private final ObjectProperty<URI> authUri;
|
private final ObjectProperty<URI> authUri;
|
||||||
private AuthFlowTask task;
|
private AuthFlowTask task;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public AuthFlowController(Application application, @KeyLoading Stage window, ExecutorService executor, @Named("deviceId") String deviceId, HubConfig hubConfig, @Named("bearerToken") AtomicReference<String> tokenRef, CompletableFuture<JWEObject> result, @FxmlScene(FxmlFile.HUB_RECEIVE_KEY) Lazy<Scene> receiveKeyScene) {
|
public AuthFlowController(Application application, @KeyLoading Stage window, ExecutorService executor, @Named("deviceId") String deviceId, HubConfig hubConfig, @Named("bearerToken") AtomicReference<String> tokenRef, CompletableFuture<ReceivedKey> result, @FxmlScene(FxmlFile.HUB_RECEIVE_KEY) Lazy<Scene> receiveKeyScene) {
|
||||||
this.application = application;
|
this.application = application;
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.executor = executor;
|
this.executor = executor;
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import io.github.coffeelibs.tinyoauth2client.http.response.Response;
|
|||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.time.Duration;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
class AuthFlowTask extends Task<String> {
|
class AuthFlowTask extends Task<String> {
|
||||||
@@ -34,6 +36,7 @@ class AuthFlowTask extends Task<String> {
|
|||||||
protected String call() throws IOException, InterruptedException {
|
protected String call() throws IOException, InterruptedException {
|
||||||
var response = TinyOAuth2.client(hubConfig.clientId) //
|
var response = TinyOAuth2.client(hubConfig.clientId) //
|
||||||
.withTokenEndpoint(URI.create(hubConfig.tokenEndpoint)) //
|
.withTokenEndpoint(URI.create(hubConfig.tokenEndpoint)) //
|
||||||
|
.withRequestTimeout(Duration.ofSeconds(10)) //
|
||||||
.authFlow(URI.create(hubConfig.authEndpoint)) //
|
.authFlow(URI.create(hubConfig.authEndpoint)) //
|
||||||
.setSuccessResponse(Response.redirect(URI.create(hubConfig.authSuccessUrl + "&device=" + authFlowContext.deviceId()))) //
|
.setSuccessResponse(Response.redirect(URI.create(hubConfig.authSuccessUrl + "&device=" + authFlowContext.deviceId()))) //
|
||||||
.setErrorResponse(Response.redirect(URI.create(hubConfig.authErrorUrl + "&device=" + authFlowContext.deviceId()))) //
|
.setErrorResponse(Response.redirect(URI.create(hubConfig.authErrorUrl + "&device=" + authFlowContext.deviceId()))) //
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
package org.cryptomator.ui.keyloading.hub;
|
|
||||||
|
|
||||||
record CreateDeviceDto(String id, String name, String publicKey) {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package org.cryptomator.ui.keyloading.hub;
|
|
||||||
|
|
||||||
import com.google.common.io.CharStreams;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.net.http.HttpResponse;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
class HttpHelper {
|
|
||||||
|
|
||||||
public static String readBody(HttpResponse<InputStream> response) throws IOException {
|
|
||||||
try (var in = response.body(); var reader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
|
|
||||||
return CharStreams.toString(reader);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
package org.cryptomator.ui.keyloading.hub;
|
package org.cryptomator.ui.keyloading.hub;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
// needs to be accessible by JSON decoder
|
// needs to be accessible by JSON decoder
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
@@ -9,8 +13,19 @@ public class HubConfig {
|
|||||||
public String clientId;
|
public String clientId;
|
||||||
public String authEndpoint;
|
public String authEndpoint;
|
||||||
public String tokenEndpoint;
|
public String tokenEndpoint;
|
||||||
public String devicesResourceUrl;
|
|
||||||
public String authSuccessUrl;
|
public String authSuccessUrl;
|
||||||
public String authErrorUrl;
|
public String authErrorUrl;
|
||||||
|
public @Nullable String apiBaseUrl;
|
||||||
|
@Deprecated // use apiBaseUrl + "/devices/"
|
||||||
|
public String devicesResourceUrl;
|
||||||
|
|
||||||
|
public URI getApiBaseUrl() {
|
||||||
|
if (apiBaseUrl != null) {
|
||||||
|
return URI.create(apiBaseUrl);
|
||||||
|
} else {
|
||||||
|
// legacy approach
|
||||||
|
assert devicesResourceUrl != null;
|
||||||
|
return URI.create(devicesResourceUrl + "/..").normalize();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.cryptomator.ui.keyloading.hub;
|
package org.cryptomator.ui.keyloading.hub;
|
||||||
|
|
||||||
import com.google.common.io.BaseEncoding;
|
import com.google.common.io.BaseEncoding;
|
||||||
import com.nimbusds.jose.JWEObject;
|
|
||||||
import dagger.Binds;
|
import dagger.Binds;
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
@@ -69,7 +68,7 @@ public abstract class HubKeyLoadingModule {
|
|||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@KeyLoadingScoped
|
@KeyLoadingScoped
|
||||||
static CompletableFuture<JWEObject> provideResult() {
|
static CompletableFuture<ReceivedKey> provideResult() {
|
||||||
return new CompletableFuture<>();
|
return new CompletableFuture<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,10 +113,10 @@ public abstract class HubKeyLoadingModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.HUB_REGISTER_DEVICE)
|
@FxmlScene(FxmlFile.HUB_LEGACY_REGISTER_DEVICE)
|
||||||
@KeyLoadingScoped
|
@KeyLoadingScoped
|
||||||
static Scene provideHubRegisterDeviceScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
|
static Scene provideHubLegacyRegisterDeviceScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene(FxmlFile.HUB_REGISTER_DEVICE);
|
return fxmlLoaders.createScene(FxmlFile.HUB_LEGACY_REGISTER_DEVICE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -134,6 +133,13 @@ public abstract class HubKeyLoadingModule {
|
|||||||
return fxmlLoaders.createScene(FxmlFile.HUB_REGISTER_FAILED);
|
return fxmlLoaders.createScene(FxmlFile.HUB_REGISTER_FAILED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@FxmlScene(FxmlFile.HUB_SETUP_DEVICE)
|
||||||
|
@KeyLoadingScoped
|
||||||
|
static Scene provideHubRegisterDeviceScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
|
||||||
|
return fxmlLoaders.createScene(FxmlFile.HUB_SETUP_DEVICE);
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE)
|
@FxmlScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE)
|
||||||
@KeyLoadingScoped
|
@KeyLoadingScoped
|
||||||
@@ -166,6 +172,11 @@ public abstract class HubKeyLoadingModule {
|
|||||||
@FxControllerKey(RegisterDeviceController.class)
|
@FxControllerKey(RegisterDeviceController.class)
|
||||||
abstract FxController bindRegisterDeviceController(RegisterDeviceController controller);
|
abstract FxController bindRegisterDeviceController(RegisterDeviceController controller);
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FxControllerKey(LegacyRegisterDeviceController.class)
|
||||||
|
abstract FxController bindLegacyRegisterDeviceController(LegacyRegisterDeviceController controller);
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FxControllerKey(RegisterSuccessController.class)
|
@FxControllerKey(RegisterSuccessController.class)
|
||||||
|
|||||||
@@ -36,11 +36,11 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
|
|||||||
private final KeychainManager keychainManager;
|
private final KeychainManager keychainManager;
|
||||||
private final Lazy<Scene> authFlowScene;
|
private final Lazy<Scene> authFlowScene;
|
||||||
private final Lazy<Scene> noKeychainScene;
|
private final Lazy<Scene> noKeychainScene;
|
||||||
private final CompletableFuture<JWEObject> result;
|
private final CompletableFuture<ReceivedKey> result;
|
||||||
private final DeviceKey deviceKey;
|
private final DeviceKey deviceKey;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public HubKeyLoadingStrategy(@KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_AUTH_FLOW) Lazy<Scene> authFlowScene, @FxmlScene(FxmlFile.HUB_NO_KEYCHAIN) Lazy<Scene> noKeychainScene, CompletableFuture<JWEObject> result, DeviceKey deviceKey, KeychainManager keychainManager, @Named("windowTitle") String windowTitle) {
|
public HubKeyLoadingStrategy(@KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_AUTH_FLOW) Lazy<Scene> authFlowScene, @FxmlScene(FxmlFile.HUB_NO_KEYCHAIN) Lazy<Scene> noKeychainScene, CompletableFuture<ReceivedKey> result, DeviceKey deviceKey, KeychainManager keychainManager, @Named("windowTitle") String windowTitle) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.keychainManager = keychainManager;
|
this.keychainManager = keychainManager;
|
||||||
window.setTitle(windowTitle);
|
window.setTitle(windowTitle);
|
||||||
@@ -60,7 +60,7 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
|
|||||||
var keypair = deviceKey.get();
|
var keypair = deviceKey.get();
|
||||||
showWindow(authFlowScene);
|
showWindow(authFlowScene);
|
||||||
var jwe = result.get();
|
var jwe = result.get();
|
||||||
return JWEHelper.decrypt(jwe, keypair.getPrivate());
|
return jwe.decryptMasterkey(keypair.getPrivate());
|
||||||
} catch (NoKeychainAccessProviderException e) {
|
} catch (NoKeychainAccessProviderException e) {
|
||||||
showWindow(noKeychainScene);
|
showWindow(noKeychainScene);
|
||||||
throw new UnlockCancelledException("Unlock canceled due to missing prerequisites", e);
|
throw new UnlockCancelledException("Unlock canceled due to missing prerequisites", e);
|
||||||
|
|||||||
@@ -2,35 +2,103 @@ package org.cryptomator.ui.keyloading.hub;
|
|||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.io.BaseEncoding;
|
import com.google.common.io.BaseEncoding;
|
||||||
|
import com.nimbusds.jose.EncryptionMethod;
|
||||||
import com.nimbusds.jose.JOSEException;
|
import com.nimbusds.jose.JOSEException;
|
||||||
|
import com.nimbusds.jose.JWEAlgorithm;
|
||||||
|
import com.nimbusds.jose.JWEHeader;
|
||||||
import com.nimbusds.jose.JWEObject;
|
import com.nimbusds.jose.JWEObject;
|
||||||
|
import com.nimbusds.jose.Payload;
|
||||||
import com.nimbusds.jose.crypto.ECDHDecrypter;
|
import com.nimbusds.jose.crypto.ECDHDecrypter;
|
||||||
|
import com.nimbusds.jose.crypto.ECDHEncrypter;
|
||||||
|
import com.nimbusds.jose.crypto.PasswordBasedDecrypter;
|
||||||
|
import com.nimbusds.jose.jwk.Curve;
|
||||||
|
import com.nimbusds.jose.jwk.gen.ECKeyGenerator;
|
||||||
|
import com.nimbusds.jose.jwk.gen.JWKGenerator;
|
||||||
import org.cryptomator.cryptolib.api.Masterkey;
|
import org.cryptomator.cryptolib.api.Masterkey;
|
||||||
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
|
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.KeyPairGenerator;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.interfaces.ECPrivateKey;
|
import java.security.interfaces.ECPrivateKey;
|
||||||
|
import java.security.interfaces.ECPublicKey;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
class JWEHelper {
|
class JWEHelper {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(JWEHelper.class);
|
private static final Logger LOG = LoggerFactory.getLogger(JWEHelper.class);
|
||||||
private static final String JWE_PAYLOAD_MASTERKEY_FIELD = "key";
|
private static final String JWE_PAYLOAD_KEY_FIELD = "key";
|
||||||
|
private static final String EC_ALG = "EC";
|
||||||
|
|
||||||
private JWEHelper(){}
|
private JWEHelper(){}
|
||||||
|
public static JWEObject encryptUserKey(ECPrivateKey userKey, ECPublicKey deviceKey) {
|
||||||
public static Masterkey decrypt(JWEObject jwe, ECPrivateKey privateKey) throws MasterkeyLoadingFailedException {
|
|
||||||
try {
|
try {
|
||||||
jwe.decrypt(new ECDHDecrypter(privateKey));
|
var encodedUserKey = Base64.getEncoder().encodeToString(userKey.getEncoded());
|
||||||
return readKey(jwe);
|
var keyGen = new ECKeyGenerator(Curve.P_384);
|
||||||
|
var ephemeralKeyPair = keyGen.generate();
|
||||||
|
var header = new JWEHeader.Builder(JWEAlgorithm.ECDH_ES, EncryptionMethod.A256GCM).ephemeralPublicKey(ephemeralKeyPair.toPublicJWK()).build();
|
||||||
|
var payload = new Payload(Map.of(JWE_PAYLOAD_KEY_FIELD, encodedUserKey));
|
||||||
|
var jwe = new JWEObject(header, payload);
|
||||||
|
jwe.encrypt(new ECDHEncrypter(deviceKey));
|
||||||
|
return jwe;
|
||||||
} catch (JOSEException e) {
|
} catch (JOSEException e) {
|
||||||
LOG.warn("Failed to decrypt JWE: {}", jwe);
|
throw new RuntimeException(e);
|
||||||
throw new MasterkeyLoadingFailedException("Failed to decrypt JWE", e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Masterkey readKey(JWEObject jwe) throws MasterkeyLoadingFailedException {
|
public static ECPrivateKey decryptUserKey(JWEObject jwe, String setupCode) throws InvalidJweKeyException {
|
||||||
|
try {
|
||||||
|
jwe.decrypt(new PasswordBasedDecrypter(setupCode));
|
||||||
|
return decodeUserKey(jwe);
|
||||||
|
} catch (JOSEException e) {
|
||||||
|
throw new InvalidJweKeyException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ECPrivateKey decryptUserKey(JWEObject jwe, ECPrivateKey deviceKey) throws InvalidJweKeyException {
|
||||||
|
try {
|
||||||
|
jwe.decrypt(new ECDHDecrypter(deviceKey));
|
||||||
|
return decodeUserKey(jwe);
|
||||||
|
} catch (JOSEException e) {
|
||||||
|
throw new InvalidJweKeyException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ECPrivateKey decodeUserKey(JWEObject decryptedJwe) {
|
||||||
|
try {
|
||||||
|
var keySpec = readKey(decryptedJwe, JWE_PAYLOAD_KEY_FIELD, PKCS8EncodedKeySpec::new);
|
||||||
|
var factory = KeyFactory.getInstance(EC_ALG);
|
||||||
|
var privateKey = factory.generatePrivate(keySpec);
|
||||||
|
if (privateKey instanceof ECPrivateKey ecPrivateKey) {
|
||||||
|
return ecPrivateKey;
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException(EC_ALG + " key factory not generating ECPrivateKeys");
|
||||||
|
}
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new IllegalStateException(EC_ALG + " not supported");
|
||||||
|
} catch (InvalidKeySpecException e) {
|
||||||
|
LOG.warn("Unexpected JWE payload: {}", decryptedJwe.getPayload());
|
||||||
|
throw new MasterkeyLoadingFailedException("Unexpected JWE payload", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Masterkey decryptVaultKey(JWEObject jwe, ECPrivateKey privateKey) throws InvalidJweKeyException {
|
||||||
|
try {
|
||||||
|
jwe.decrypt(new ECDHDecrypter(privateKey));
|
||||||
|
return readKey(jwe, JWE_PAYLOAD_KEY_FIELD, Masterkey::new);
|
||||||
|
} catch (JOSEException e) {
|
||||||
|
throw new InvalidJweKeyException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> T readKey(JWEObject jwe, String keyField, Function<byte[], T> rawKeyFactory) throws MasterkeyLoadingFailedException {
|
||||||
Preconditions.checkArgument(jwe.getState() == JWEObject.State.DECRYPTED);
|
Preconditions.checkArgument(jwe.getState() == JWEObject.State.DECRYPTED);
|
||||||
var fields = jwe.getPayload().toJSONObject();
|
var fields = jwe.getPayload().toJSONObject();
|
||||||
if (fields == null) {
|
if (fields == null) {
|
||||||
@@ -39,11 +107,11 @@ class JWEHelper {
|
|||||||
}
|
}
|
||||||
var keyBytes = new byte[0];
|
var keyBytes = new byte[0];
|
||||||
try {
|
try {
|
||||||
if (fields.get(JWE_PAYLOAD_MASTERKEY_FIELD) instanceof String key) {
|
if (fields.get(keyField) instanceof String key) {
|
||||||
keyBytes = BaseEncoding.base64().decode(key);
|
keyBytes = BaseEncoding.base64().decode(key);
|
||||||
return new Masterkey(keyBytes);
|
return rawKeyFactory.apply(keyBytes);
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("JWE payload doesn't contain field " + JWE_PAYLOAD_MASTERKEY_FIELD);
|
throw new IllegalArgumentException("JWE payload doesn't contain field " + keyField);
|
||||||
}
|
}
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
LOG.error("Unexpected JWE payload: {}", jwe.getPayload());
|
LOG.error("Unexpected JWE payload: {}", jwe.getPayload());
|
||||||
@@ -52,4 +120,11 @@ class JWEHelper {
|
|||||||
Arrays.fill(keyBytes, (byte) 0x00);
|
Arrays.fill(keyBytes, (byte) 0x00);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class InvalidJweKeyException extends MasterkeyLoadingFailedException {
|
||||||
|
|
||||||
|
public InvalidJweKeyException(Throwable cause) {
|
||||||
|
super("Invalid key", cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,191 @@
|
|||||||
|
package org.cryptomator.ui.keyloading.hub;
|
||||||
|
|
||||||
|
import com.auth0.jwt.JWT;
|
||||||
|
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||||
|
import com.fasterxml.jackson.core.JacksonException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import dagger.Lazy;
|
||||||
|
import org.cryptomator.common.settings.DeviceKey;
|
||||||
|
import org.cryptomator.cryptolib.common.P384KeyPair;
|
||||||
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
|
import org.cryptomator.ui.keyloading.KeyLoading;
|
||||||
|
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.ContentDisplay;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import javafx.stage.WindowEvent;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
@KeyLoadingScoped
|
||||||
|
public class LegacyRegisterDeviceController implements FxController {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(LegacyRegisterDeviceController.class);
|
||||||
|
private static final ObjectMapper JSON = new ObjectMapper().setDefaultLeniency(true);
|
||||||
|
private static final List<Integer> EXPECTED_RESPONSE_CODES = List.of(201, 409);
|
||||||
|
|
||||||
|
private final Stage window;
|
||||||
|
private final HubConfig hubConfig;
|
||||||
|
private final String bearerToken;
|
||||||
|
private final Lazy<Scene> registerSuccessScene;
|
||||||
|
private final Lazy<Scene> registerFailedScene;
|
||||||
|
private final String deviceId;
|
||||||
|
private final P384KeyPair keyPair;
|
||||||
|
private final CompletableFuture<ReceivedKey> result;
|
||||||
|
private final DecodedJWT jwt;
|
||||||
|
private final HttpClient httpClient;
|
||||||
|
private final BooleanProperty deviceNameAlreadyExists = new SimpleBooleanProperty(false);
|
||||||
|
|
||||||
|
public TextField deviceNameField;
|
||||||
|
public Button registerBtn;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public LegacyRegisterDeviceController(@KeyLoading Stage window, ExecutorService executor, HubConfig hubConfig, @Named("deviceId") String deviceId, DeviceKey deviceKey, CompletableFuture<ReceivedKey> result, @Named("bearerToken") AtomicReference<String> bearerToken, @FxmlScene(FxmlFile.HUB_REGISTER_SUCCESS) Lazy<Scene> registerSuccessScene, @FxmlScene(FxmlFile.HUB_REGISTER_FAILED) Lazy<Scene> registerFailedScene) {
|
||||||
|
this.window = window;
|
||||||
|
this.hubConfig = hubConfig;
|
||||||
|
this.deviceId = deviceId;
|
||||||
|
this.keyPair = Objects.requireNonNull(deviceKey.get());
|
||||||
|
this.result = result;
|
||||||
|
this.bearerToken = Objects.requireNonNull(bearerToken.get());
|
||||||
|
this.registerSuccessScene = registerSuccessScene;
|
||||||
|
this.registerFailedScene = registerFailedScene;
|
||||||
|
this.jwt = JWT.decode(this.bearerToken);
|
||||||
|
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
|
||||||
|
this.httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).executor(executor).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initialize() {
|
||||||
|
deviceNameField.setText(determineHostname());
|
||||||
|
deviceNameField.textProperty().addListener(observable -> deviceNameAlreadyExists.set(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String determineHostname() {
|
||||||
|
try {
|
||||||
|
var hostName = InetAddress.getLocalHost().getHostName();
|
||||||
|
return Objects.requireNonNullElse(hostName, "");
|
||||||
|
} catch (IOException e) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void register() {
|
||||||
|
deviceNameAlreadyExists.set(false);
|
||||||
|
registerBtn.setContentDisplay(ContentDisplay.LEFT);
|
||||||
|
registerBtn.setDisable(true);
|
||||||
|
|
||||||
|
var deviceUri = URI.create(hubConfig.devicesResourceUrl + deviceId);
|
||||||
|
var deviceKey = keyPair.getPublic().getEncoded();
|
||||||
|
var dto = new CreateDeviceDto();
|
||||||
|
dto.id = deviceId;
|
||||||
|
dto.name = deviceNameField.getText();
|
||||||
|
dto.publicKey = Base64.getUrlEncoder().withoutPadding().encodeToString(deviceKey);
|
||||||
|
var json = toJson(dto);
|
||||||
|
var request = HttpRequest.newBuilder(deviceUri) //
|
||||||
|
.PUT(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8)) //
|
||||||
|
.header("Authorization", "Bearer " + bearerToken) //
|
||||||
|
.header("Content-Type", "application/json") //
|
||||||
|
.build();
|
||||||
|
httpClient.sendAsync(request, HttpResponse.BodyHandlers.discarding()) //
|
||||||
|
.thenApply(response -> {
|
||||||
|
if (EXPECTED_RESPONSE_CODES.contains(response.statusCode())) {
|
||||||
|
return response;
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("Server answered with unexpected status code " + response.statusCode());
|
||||||
|
}
|
||||||
|
}).handleAsync((response, throwable) -> {
|
||||||
|
if (response != null) {
|
||||||
|
this.handleResponse(response);
|
||||||
|
} else {
|
||||||
|
this.registrationFailed(throwable);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, Platform::runLater);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String toJson(CreateDeviceDto dto) {
|
||||||
|
try {
|
||||||
|
return JSON.writer().writeValueAsString(dto);
|
||||||
|
} catch (JacksonException e) {
|
||||||
|
throw new IllegalStateException("Failed to serialize DTO", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleResponse(HttpResponse<Void> voidHttpResponse) {
|
||||||
|
assert EXPECTED_RESPONSE_CODES.contains(voidHttpResponse.statusCode());
|
||||||
|
|
||||||
|
if (voidHttpResponse.statusCode() == 409) {
|
||||||
|
deviceNameAlreadyExists.set(true);
|
||||||
|
registerBtn.setContentDisplay(ContentDisplay.TEXT_ONLY);
|
||||||
|
registerBtn.setDisable(false);
|
||||||
|
} else {
|
||||||
|
LOG.debug("Device registration for hub instance {} successful.", hubConfig.authSuccessUrl);
|
||||||
|
window.setScene(registerSuccessScene.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registrationFailed(Throwable cause) {
|
||||||
|
LOG.warn("Device registration failed.", cause);
|
||||||
|
window.setScene(registerFailedScene.get());
|
||||||
|
result.completeExceptionally(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void close() {
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void windowClosed(WindowEvent windowEvent) {
|
||||||
|
result.cancel(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Getter */
|
||||||
|
|
||||||
|
public String getUserName() {
|
||||||
|
return jwt.getClaim("email").asString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//--- Getters & Setters
|
||||||
|
|
||||||
|
public BooleanProperty deviceNameAlreadyExistsProperty() {
|
||||||
|
return deviceNameAlreadyExists;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getDeviceNameAlreadyExists() {
|
||||||
|
return deviceNameAlreadyExists.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class CreateDeviceDto {
|
||||||
|
public String id;
|
||||||
|
public String name;
|
||||||
|
public final String type = "DESKTOP";
|
||||||
|
public String publicKey;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
package org.cryptomator.ui.keyloading.hub;
|
package org.cryptomator.ui.keyloading.hub;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.nimbusds.jose.JWEObject;
|
import com.nimbusds.jose.JWEObject;
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
@@ -8,6 +11,9 @@ import org.cryptomator.ui.common.FxmlFile;
|
|||||||
import org.cryptomator.ui.common.FxmlScene;
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
import org.cryptomator.ui.keyloading.KeyLoading;
|
import org.cryptomator.ui.keyloading.KeyLoading;
|
||||||
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
|
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
@@ -17,14 +23,16 @@ import javafx.scene.Scene;
|
|||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import javafx.stage.WindowEvent;
|
import javafx.stage.WindowEvent;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.UncheckedIOException;
|
import java.io.UncheckedIOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.http.HttpClient;
|
import java.net.http.HttpClient;
|
||||||
import java.net.http.HttpRequest;
|
import java.net.http.HttpRequest;
|
||||||
import java.net.http.HttpResponse;
|
import java.net.http.HttpResponse;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
@@ -33,25 +41,32 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||||||
@KeyLoadingScoped
|
@KeyLoadingScoped
|
||||||
public class ReceiveKeyController implements FxController {
|
public class ReceiveKeyController implements FxController {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ReceiveKeyController.class);
|
||||||
private static final String SCHEME_PREFIX = "hub+";
|
private static final String SCHEME_PREFIX = "hub+";
|
||||||
|
private static final ObjectMapper JSON = new ObjectMapper().setDefaultLeniency(true);
|
||||||
|
private static final Duration REQ_TIMEOUT = Duration.ofSeconds(10);
|
||||||
|
|
||||||
private final Stage window;
|
private final Stage window;
|
||||||
|
private final HubConfig hubConfig;
|
||||||
private final String deviceId;
|
private final String deviceId;
|
||||||
private final String bearerToken;
|
private final String bearerToken;
|
||||||
private final CompletableFuture<JWEObject> result;
|
private final CompletableFuture<ReceivedKey> result;
|
||||||
private final Lazy<Scene> registerDeviceScene;
|
private final Lazy<Scene> setupDeviceScene;
|
||||||
|
private final Lazy<Scene> legacyRegisterDeviceScene;
|
||||||
private final Lazy<Scene> unauthorizedScene;
|
private final Lazy<Scene> unauthorizedScene;
|
||||||
private final URI vaultBaseUri;
|
private final URI vaultBaseUri;
|
||||||
private final Lazy<Scene> invalidLicenseScene;
|
private final Lazy<Scene> invalidLicenseScene;
|
||||||
private final HttpClient httpClient;
|
private final HttpClient httpClient;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ReceiveKeyController(@KeyLoading Vault vault, ExecutorService executor, @KeyLoading Stage window, @Named("deviceId") String deviceId, @Named("bearerToken") AtomicReference<String> tokenRef, CompletableFuture<JWEObject> result, @FxmlScene(FxmlFile.HUB_REGISTER_DEVICE) Lazy<Scene> registerDeviceScene, @FxmlScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE) Lazy<Scene> unauthorizedScene, @FxmlScene(FxmlFile.HUB_INVALID_LICENSE) Lazy<Scene> invalidLicenseScene) {
|
public ReceiveKeyController(@KeyLoading Vault vault, ExecutorService executor, @KeyLoading Stage window, HubConfig hubConfig, @Named("deviceId") String deviceId, @Named("bearerToken") AtomicReference<String> tokenRef, CompletableFuture<ReceivedKey> result, @FxmlScene(FxmlFile.HUB_SETUP_DEVICE) Lazy<Scene> setupDeviceScene, @FxmlScene(FxmlFile.HUB_LEGACY_REGISTER_DEVICE) Lazy<Scene> legacyRegisterDeviceScene, @FxmlScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE) Lazy<Scene> unauthorizedScene, @FxmlScene(FxmlFile.HUB_INVALID_LICENSE) Lazy<Scene> invalidLicenseScene) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
|
this.hubConfig = hubConfig;
|
||||||
this.deviceId = deviceId;
|
this.deviceId = deviceId;
|
||||||
this.bearerToken = Objects.requireNonNull(tokenRef.get());
|
this.bearerToken = Objects.requireNonNull(tokenRef.get());
|
||||||
this.result = result;
|
this.result = result;
|
||||||
this.registerDeviceScene = registerDeviceScene;
|
this.setupDeviceScene = setupDeviceScene;
|
||||||
|
this.legacyRegisterDeviceScene = legacyRegisterDeviceScene;
|
||||||
this.unauthorizedScene = unauthorizedScene;
|
this.unauthorizedScene = unauthorizedScene;
|
||||||
this.vaultBaseUri = getVaultBaseUri(vault);
|
this.vaultBaseUri = getVaultBaseUri(vault);
|
||||||
this.invalidLicenseScene = invalidLicenseScene;
|
this.invalidLicenseScene = invalidLicenseScene;
|
||||||
@@ -61,23 +76,120 @@ public class ReceiveKeyController implements FxController {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
var keyUri = appendPath(vaultBaseUri, "/keys/" + deviceId);
|
requestVaultMasterkey();
|
||||||
var request = HttpRequest.newBuilder(keyUri) //
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* STEP 1 (Request): GET vault key for this user
|
||||||
|
*/
|
||||||
|
private void requestVaultMasterkey() {
|
||||||
|
var accessTokenUri = appendPath(vaultBaseUri, "/access-token");
|
||||||
|
var request = HttpRequest.newBuilder(accessTokenUri) //
|
||||||
.header("Authorization", "Bearer " + bearerToken) //
|
.header("Authorization", "Bearer " + bearerToken) //
|
||||||
.GET() //
|
.GET() //
|
||||||
|
.timeout(REQ_TIMEOUT) //
|
||||||
.build();
|
.build();
|
||||||
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream()) //
|
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.US_ASCII)) //
|
||||||
.thenAcceptAsync(this::loadedExistingKey, Platform::runLater) //
|
.thenAcceptAsync(this::receivedVaultMasterkey, Platform::runLater) //
|
||||||
.exceptionally(this::retrievalFailed);
|
.exceptionally(this::retrievalFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadedExistingKey(HttpResponse<InputStream> response) {
|
/**
|
||||||
|
* STEP 1 (Response): GET vault key for this user
|
||||||
|
*
|
||||||
|
* @param response Response
|
||||||
|
*/
|
||||||
|
private void receivedVaultMasterkey(HttpResponse<String> response) {
|
||||||
|
LOG.debug("GET {} -> Status Code {}", response.request().uri(), response.statusCode());
|
||||||
|
switch (response.statusCode()) {
|
||||||
|
case 200 -> requestUserKey(response.body());
|
||||||
|
case 402 -> licenseExceeded();
|
||||||
|
case 403, 410 -> accessNotGranted(); // or vault has been archived, effectively disallowing access - TODO: add specific dialog?
|
||||||
|
case 404 -> requestLegacyAccessToken();
|
||||||
|
default -> throw new IllegalStateException("Unexpected response " + response.statusCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* STEP 2 (Request): GET user key for this device
|
||||||
|
*/
|
||||||
|
private void requestUserKey(String encryptedVaultKey) {
|
||||||
|
var deviceTokenUri = URI.create(hubConfig.getApiBaseUrl() + "/devices/" + deviceId);
|
||||||
|
var request = HttpRequest.newBuilder(deviceTokenUri) //
|
||||||
|
.header("Authorization", "Bearer " + bearerToken) //
|
||||||
|
.GET() //
|
||||||
|
.timeout(REQ_TIMEOUT) //
|
||||||
|
.build();
|
||||||
|
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)) //
|
||||||
|
.thenAcceptAsync(response -> receivedUserKey(encryptedVaultKey, response), Platform::runLater) //
|
||||||
|
.exceptionally(this::retrievalFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* STEP 2 (Response): GET user key for this device
|
||||||
|
*
|
||||||
|
* @param response Response
|
||||||
|
*/
|
||||||
|
private void receivedUserKey(String encryptedVaultKey, HttpResponse<String> response) {
|
||||||
|
LOG.debug("GET {} -> Status Code {}", response.request().uri(), response.statusCode());
|
||||||
try {
|
try {
|
||||||
switch (response.statusCode()) {
|
switch (response.statusCode()) {
|
||||||
case 200 -> retrievalSucceeded(response);
|
case 200 -> {
|
||||||
|
var device = JSON.reader().readValue(response.body(), DeviceDto.class);
|
||||||
|
receivedBothEncryptedKeys(encryptedVaultKey, device.userPrivateKey);
|
||||||
|
}
|
||||||
|
case 404 -> needsDeviceSetup(); // TODO: using the setup code, we can theoretically immediately unlock
|
||||||
|
default -> throw new IllegalStateException("Unexpected response " + response.statusCode());
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void needsDeviceSetup() {
|
||||||
|
window.setScene(setupDeviceScene.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void receivedBothEncryptedKeys(String encryptedVaultKey, String encryptedUserKey) throws IOException {
|
||||||
|
try {
|
||||||
|
var vaultKeyJwe = JWEObject.parse(encryptedVaultKey);
|
||||||
|
var userKeyJwe = JWEObject.parse(encryptedUserKey);
|
||||||
|
result.complete(ReceivedKey.vaultKeyAndUserKey(vaultKeyJwe, userKeyJwe));
|
||||||
|
window.close();
|
||||||
|
} catch (ParseException e) {
|
||||||
|
throw new IOException("Failed to parse JWE", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LEGACY FALLBACK (Request): GET the legacy access token from Hub 1.x
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
private void requestLegacyAccessToken() {
|
||||||
|
var legacyAccessTokenUri = appendPath(vaultBaseUri, "/keys/" + deviceId);
|
||||||
|
var request = HttpRequest.newBuilder(legacyAccessTokenUri) //
|
||||||
|
.header("Authorization", "Bearer " + bearerToken) //
|
||||||
|
.GET() //
|
||||||
|
.timeout(REQ_TIMEOUT) //
|
||||||
|
.build();
|
||||||
|
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.US_ASCII)) //
|
||||||
|
.thenAcceptAsync(this::receivedLegacyAccessTokenResponse, Platform::runLater) //
|
||||||
|
.exceptionally(this::retrievalFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LEGACY FALLBACK (Response)
|
||||||
|
*
|
||||||
|
* @param response Response
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
private void receivedLegacyAccessTokenResponse(HttpResponse<String> response) {
|
||||||
|
try {
|
||||||
|
switch (response.statusCode()) {
|
||||||
|
case 200 -> receivedLegacyAccessTokenSuccess(response.body());
|
||||||
case 402 -> licenseExceeded();
|
case 402 -> licenseExceeded();
|
||||||
case 403, 410 -> accessNotGranted(); // or vault has been archived, effectively disallowing access
|
case 403, 410 -> accessNotGranted(); // or vault has been archived, effectively disallowing access
|
||||||
case 404 -> needsDeviceRegistration();
|
case 404 -> needsLegacyDeviceRegistration();
|
||||||
default -> throw new IOException("Unexpected response " + response.statusCode());
|
default -> throw new IOException("Unexpected response " + response.statusCode());
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -85,10 +197,11 @@ public class ReceiveKeyController implements FxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void retrievalSucceeded(HttpResponse<InputStream> response) throws IOException {
|
@Deprecated
|
||||||
|
private void receivedLegacyAccessTokenSuccess(String rawToken) throws IOException {
|
||||||
try {
|
try {
|
||||||
var string = HttpHelper.readBody(response);
|
var token = JWEObject.parse(rawToken);
|
||||||
result.complete(JWEObject.parse(string));
|
result.complete(ReceivedKey.legacyDeviceKey(token));
|
||||||
window.close();
|
window.close();
|
||||||
} catch (ParseException e) {
|
} catch (ParseException e) {
|
||||||
throw new IOException("Failed to parse JWE", e);
|
throw new IOException("Failed to parse JWE", e);
|
||||||
@@ -99,8 +212,9 @@ public class ReceiveKeyController implements FxController {
|
|||||||
window.setScene(invalidLicenseScene.get());
|
window.setScene(invalidLicenseScene.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void needsDeviceRegistration() {
|
@Deprecated
|
||||||
window.setScene(registerDeviceScene.get());
|
private void needsLegacyDeviceRegistration() {
|
||||||
|
window.setScene(legacyRegisterDeviceScene.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void accessNotGranted() {
|
private void accessNotGranted() {
|
||||||
@@ -132,14 +246,17 @@ public class ReceiveKeyController implements FxController {
|
|||||||
|
|
||||||
private static URI getVaultBaseUri(Vault vault) {
|
private static URI getVaultBaseUri(Vault vault) {
|
||||||
try {
|
try {
|
||||||
var kid = vault.getVaultConfigCache().get().getKeyId();
|
var url = vault.getVaultConfigCache().get().getKeyId();
|
||||||
assert kid.getScheme().startsWith(SCHEME_PREFIX);
|
assert url.getScheme().startsWith(SCHEME_PREFIX);
|
||||||
var hubUriScheme = kid.getScheme().substring(SCHEME_PREFIX.length());
|
var correctedScheme = url.getScheme().substring(SCHEME_PREFIX.length());
|
||||||
return new URI(hubUriScheme, kid.getSchemeSpecificPart(), kid.getFragment());
|
return new URI(correctedScheme, url.getSchemeSpecificPart(), url.getFragment());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new UncheckedIOException(e);
|
throw new UncheckedIOException(e);
|
||||||
} catch (URISyntaxException e) {
|
} catch (URISyntaxException e) {
|
||||||
throw new IllegalStateException("URI constructed from params known to be valid", e);
|
throw new IllegalStateException("URI constructed from params known to be valid", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
private record DeviceDto(@JsonProperty(value = "userPrivateKey", required = true) String userPrivateKey) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package org.cryptomator.ui.keyloading.hub;
|
||||||
|
|
||||||
|
import com.nimbusds.jose.JWEObject;
|
||||||
|
import org.cryptomator.cryptolib.api.Masterkey;
|
||||||
|
|
||||||
|
import java.security.interfaces.ECPrivateKey;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface ReceivedKey {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypts the vault key.
|
||||||
|
*
|
||||||
|
* @param deviceKey This device's private key.
|
||||||
|
* @return The decrypted vault key
|
||||||
|
*/
|
||||||
|
Masterkey decryptMasterkey(ECPrivateKey deviceKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an unlock response object from the user key + vault key.
|
||||||
|
*
|
||||||
|
* @param vaultKeyJwe a JWE containing the symmetric vault key, encrypted for this device's user.
|
||||||
|
* @param userKeyJwe a JWE containing the user's private key, encrypted for this device.
|
||||||
|
* @return Ciphertext received by Hub, which can be decrypted using this device's private key.
|
||||||
|
*/
|
||||||
|
static ReceivedKey vaultKeyAndUserKey(JWEObject vaultKeyJwe, JWEObject userKeyJwe) {
|
||||||
|
return deviceKey -> {
|
||||||
|
var userKey = JWEHelper.decryptUserKey(userKeyJwe, deviceKey);
|
||||||
|
return JWEHelper.decryptVaultKey(vaultKeyJwe, userKey);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an unlock response object from the received legacy "access token" JWE.
|
||||||
|
*
|
||||||
|
* @param vaultKeyJwe a JWE containing the symmetric vault key, encrypted for this device.
|
||||||
|
* @return Ciphertext received by Hub, which can be decrypted using this device's private key.
|
||||||
|
* @deprecated Only for compatibility with Hub 1.0 - 1.2
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
static ReceivedKey legacyDeviceKey(JWEObject vaultKeyJwe) {
|
||||||
|
return deviceKey -> JWEHelper.decryptVaultKey(vaultKeyJwe, deviceKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package org.cryptomator.ui.keyloading.hub;
|
package org.cryptomator.ui.keyloading.hub;
|
||||||
|
|
||||||
import com.auth0.jwt.JWT;
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.fasterxml.jackson.core.JacksonException;
|
import com.fasterxml.jackson.core.JacksonException;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.google.common.io.BaseEncoding;
|
import com.google.common.io.BaseEncoding;
|
||||||
@@ -20,6 +20,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
@@ -31,14 +32,16 @@ import javafx.stage.Stage;
|
|||||||
import javafx.stage.WindowEvent;
|
import javafx.stage.WindowEvent;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.URI;
|
|
||||||
import java.net.http.HttpClient;
|
import java.net.http.HttpClient;
|
||||||
import java.net.http.HttpRequest;
|
import java.net.http.HttpRequest;
|
||||||
import java.net.http.HttpResponse;
|
import java.net.http.HttpResponse;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.List;
|
import java.text.ParseException;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.CompletionException;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
@@ -47,7 +50,7 @@ public class RegisterDeviceController implements FxController {
|
|||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(RegisterDeviceController.class);
|
private static final Logger LOG = LoggerFactory.getLogger(RegisterDeviceController.class);
|
||||||
private static final ObjectMapper JSON = new ObjectMapper().setDefaultLeniency(true);
|
private static final ObjectMapper JSON = new ObjectMapper().setDefaultLeniency(true);
|
||||||
private static final List<Integer> EXPECTED_RESPONSE_CODES = List.of(201, 409);
|
private static final Duration REQ_TIMEOUT = Duration.ofSeconds(10);
|
||||||
|
|
||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final HubConfig hubConfig;
|
private final HubConfig hubConfig;
|
||||||
@@ -55,26 +58,27 @@ public class RegisterDeviceController implements FxController {
|
|||||||
private final Lazy<Scene> registerSuccessScene;
|
private final Lazy<Scene> registerSuccessScene;
|
||||||
private final Lazy<Scene> registerFailedScene;
|
private final Lazy<Scene> registerFailedScene;
|
||||||
private final String deviceId;
|
private final String deviceId;
|
||||||
private final P384KeyPair keyPair;
|
private final P384KeyPair deviceKeyPair;
|
||||||
private final CompletableFuture<JWEObject> result;
|
private final CompletableFuture<ReceivedKey> result;
|
||||||
private final DecodedJWT jwt;
|
|
||||||
private final HttpClient httpClient;
|
private final HttpClient httpClient;
|
||||||
private final BooleanProperty deviceNameAlreadyExists = new SimpleBooleanProperty(false);
|
|
||||||
|
|
||||||
|
private final BooleanProperty deviceNameAlreadyExists = new SimpleBooleanProperty(false);
|
||||||
|
private final BooleanProperty invalidSetupCode = new SimpleBooleanProperty(false);
|
||||||
|
private final BooleanProperty workInProgress = new SimpleBooleanProperty(false);
|
||||||
|
public TextField setupCodeField;
|
||||||
public TextField deviceNameField;
|
public TextField deviceNameField;
|
||||||
public Button registerBtn;
|
public Button registerBtn;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public RegisterDeviceController(@KeyLoading Stage window, ExecutorService executor, HubConfig hubConfig, @Named("deviceId") String deviceId, DeviceKey deviceKey, CompletableFuture<JWEObject> result, @Named("bearerToken") AtomicReference<String> bearerToken, @FxmlScene(FxmlFile.HUB_REGISTER_SUCCESS) Lazy<Scene> registerSuccessScene, @FxmlScene(FxmlFile.HUB_REGISTER_FAILED) Lazy<Scene> registerFailedScene) {
|
public RegisterDeviceController(@KeyLoading Stage window, ExecutorService executor, HubConfig hubConfig, @Named("deviceId") String deviceId, DeviceKey deviceKey, CompletableFuture<ReceivedKey> result, @Named("bearerToken") AtomicReference<String> bearerToken, @FxmlScene(FxmlFile.HUB_REGISTER_SUCCESS) Lazy<Scene> registerSuccessScene, @FxmlScene(FxmlFile.HUB_REGISTER_FAILED) Lazy<Scene> registerFailedScene) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.hubConfig = hubConfig;
|
this.hubConfig = hubConfig;
|
||||||
this.deviceId = deviceId;
|
this.deviceId = deviceId;
|
||||||
this.keyPair = Objects.requireNonNull(deviceKey.get());
|
this.deviceKeyPair = Objects.requireNonNull(deviceKey.get());
|
||||||
this.result = result;
|
this.result = result;
|
||||||
this.bearerToken = Objects.requireNonNull(bearerToken.get());
|
this.bearerToken = Objects.requireNonNull(bearerToken.get());
|
||||||
this.registerSuccessScene = registerSuccessScene;
|
this.registerSuccessScene = registerSuccessScene;
|
||||||
this.registerFailedScene = registerFailedScene;
|
this.registerFailedScene = registerFailedScene;
|
||||||
this.jwt = JWT.decode(this.bearerToken);
|
|
||||||
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
|
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
|
||||||
this.httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).executor(executor).build();
|
this.httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).executor(executor).build();
|
||||||
}
|
}
|
||||||
@@ -82,6 +86,13 @@ public class RegisterDeviceController implements FxController {
|
|||||||
public void initialize() {
|
public void initialize() {
|
||||||
deviceNameField.setText(determineHostname());
|
deviceNameField.setText(determineHostname());
|
||||||
deviceNameField.textProperty().addListener(observable -> deviceNameAlreadyExists.set(false));
|
deviceNameField.textProperty().addListener(observable -> deviceNameAlreadyExists.set(false));
|
||||||
|
deviceNameField.disableProperty().bind(workInProgress);
|
||||||
|
setupCodeField.textProperty().addListener(observable -> invalidSetupCode.set(false));
|
||||||
|
setupCodeField.disableProperty().bind(workInProgress);
|
||||||
|
var missingSetupCode = setupCodeField.textProperty().isEmpty();
|
||||||
|
var missingDeviceName = deviceNameField.textProperty().isEmpty();
|
||||||
|
registerBtn.disableProperty().bind(workInProgress.or(missingSetupCode).or(missingDeviceName));
|
||||||
|
registerBtn.contentDisplayProperty().bind(Bindings.when(workInProgress).then(ContentDisplay.LEFT).otherwise(ContentDisplay.TEXT_ONLY));
|
||||||
}
|
}
|
||||||
|
|
||||||
private String determineHostname() {
|
private String determineHostname() {
|
||||||
@@ -95,35 +106,62 @@ public class RegisterDeviceController implements FxController {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void register() {
|
public void register() {
|
||||||
deviceNameAlreadyExists.set(false);
|
workInProgress.set(true);
|
||||||
registerBtn.setContentDisplay(ContentDisplay.LEFT);
|
|
||||||
registerBtn.setDisable(true);
|
|
||||||
|
|
||||||
var keyUri = URI.create(hubConfig.devicesResourceUrl + deviceId);
|
var apiRootUrl = hubConfig.getApiBaseUrl();
|
||||||
var deviceKey = keyPair.getPublic().getEncoded();
|
|
||||||
var dto = new CreateDeviceDto(deviceId, deviceNameField.getText(), BaseEncoding.base64Url().omitPadding().encode(deviceKey));
|
var userReq = HttpRequest.newBuilder(apiRootUrl.resolve("users/me")) //
|
||||||
var json = toJson(dto);
|
.GET() //
|
||||||
var request = HttpRequest.newBuilder(keyUri) //
|
.timeout(REQ_TIMEOUT) //
|
||||||
.header("Authorization", "Bearer " + bearerToken) //
|
.header("Authorization", "Bearer " + bearerToken) //
|
||||||
.header("Content-Type", "application/json").PUT(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8)) //
|
.header("Content-Type", "application/json") //
|
||||||
.build();
|
.build();
|
||||||
httpClient.sendAsync(request, HttpResponse.BodyHandlers.discarding()) //
|
httpClient.sendAsync(userReq, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)) //
|
||||||
.thenApply(response -> {
|
.thenApply(response -> {
|
||||||
if (EXPECTED_RESPONSE_CODES.contains(response.statusCode())) {
|
if (response.statusCode() == 200) {
|
||||||
return response;
|
var dto = fromJson(response.body());
|
||||||
|
return Objects.requireNonNull(dto, "null or empty response body");
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("Server answered with unexpected status code " + response.statusCode());
|
throw new RuntimeException("Server answered with unexpected status code " + response.statusCode());
|
||||||
}
|
}
|
||||||
}).handleAsync((response, throwable) -> {
|
}).thenApply(user -> {
|
||||||
|
try {
|
||||||
|
assert user.privateKey != null; // api/vaults/{v}/user-tokens/me would have returned 403, if user wasn't fully set up yet
|
||||||
|
var userKey = JWEHelper.decryptUserKey(JWEObject.parse(user.privateKey), setupCodeField.getText());
|
||||||
|
return JWEHelper.encryptUserKey(userKey, deviceKeyPair.getPublic());
|
||||||
|
} catch (ParseException e) {
|
||||||
|
throw new RuntimeException("Server answered with unparsable user key", e);
|
||||||
|
}
|
||||||
|
}).thenCompose(jwe -> {
|
||||||
|
var now = Instant.now().toString();
|
||||||
|
var dto = new CreateDeviceDto(deviceId, deviceNameField.getText(), BaseEncoding.base64().encode(deviceKeyPair.getPublic().getEncoded()), "DESKTOP", jwe.serialize(), now);
|
||||||
|
var json = toJson(dto);
|
||||||
|
var deviceUri = apiRootUrl.resolve("devices/" + deviceId);
|
||||||
|
var putDeviceReq = HttpRequest.newBuilder(deviceUri) //
|
||||||
|
.PUT(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8)) //
|
||||||
|
.timeout(REQ_TIMEOUT) //
|
||||||
|
.header("Authorization", "Bearer " + bearerToken) //
|
||||||
|
.header("Content-Type", "application/json") //
|
||||||
|
.build();
|
||||||
|
return httpClient.sendAsync(putDeviceReq, HttpResponse.BodyHandlers.discarding());
|
||||||
|
}).whenCompleteAsync((response, throwable) -> {
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
this.handleResponse(response);
|
this.handleResponse(response);
|
||||||
} else {
|
} else {
|
||||||
this.registrationFailed(throwable);
|
this.setupFailed(throwable);
|
||||||
}
|
}
|
||||||
return null;
|
workInProgress.set(false);
|
||||||
}, Platform::runLater);
|
}, Platform::runLater);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private UserDto fromJson(String json) {
|
||||||
|
try {
|
||||||
|
return JSON.reader().readValue(json, UserDto.class);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException("Failed to deserialize DTO", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private String toJson(CreateDeviceDto dto) {
|
private String toJson(CreateDeviceDto dto) {
|
||||||
try {
|
try {
|
||||||
return JSON.writer().writeValueAsString(dto);
|
return JSON.writer().writeValueAsString(dto);
|
||||||
@@ -132,23 +170,26 @@ public class RegisterDeviceController implements FxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleResponse(HttpResponse<Void> voidHttpResponse) {
|
private void handleResponse(HttpResponse<Void> response) {
|
||||||
assert EXPECTED_RESPONSE_CODES.contains(voidHttpResponse.statusCode());
|
if (response.statusCode() == 201) {
|
||||||
|
|
||||||
if (voidHttpResponse.statusCode() == 409) {
|
|
||||||
deviceNameAlreadyExists.set(true);
|
|
||||||
registerBtn.setContentDisplay(ContentDisplay.TEXT_ONLY);
|
|
||||||
registerBtn.setDisable(false);
|
|
||||||
} else {
|
|
||||||
LOG.debug("Device registration for hub instance {} successful.", hubConfig.authSuccessUrl);
|
LOG.debug("Device registration for hub instance {} successful.", hubConfig.authSuccessUrl);
|
||||||
window.setScene(registerSuccessScene.get());
|
window.setScene(registerSuccessScene.get());
|
||||||
|
} else if (response.statusCode() == 409) {
|
||||||
|
deviceNameAlreadyExists.set(true);
|
||||||
|
} else {
|
||||||
|
setupFailed(new IllegalStateException("Unexpected http status code " + response.statusCode()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registrationFailed(Throwable cause) {
|
private void setupFailed(Throwable cause) {
|
||||||
LOG.warn("Device registration failed.", cause);
|
switch (cause) {
|
||||||
window.setScene(registerFailedScene.get());
|
case CompletionException e when e.getCause() instanceof JWEHelper.InvalidJweKeyException -> invalidSetupCode.set(true);
|
||||||
result.completeExceptionally(cause);
|
default -> {
|
||||||
|
LOG.warn("Device setup failed.", cause);
|
||||||
|
window.setScene(registerFailedScene.get());
|
||||||
|
result.completeExceptionally(cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -160,13 +201,6 @@ public class RegisterDeviceController implements FxController {
|
|||||||
result.cancel(true);
|
result.cancel(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Getter */
|
|
||||||
|
|
||||||
public String getUserName() {
|
|
||||||
return jwt.getClaim("email").asString();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//--- Getters & Setters
|
//--- Getters & Setters
|
||||||
|
|
||||||
public BooleanProperty deviceNameAlreadyExistsProperty() {
|
public BooleanProperty deviceNameAlreadyExistsProperty() {
|
||||||
@@ -177,5 +211,21 @@ public class RegisterDeviceController implements FxController {
|
|||||||
return deviceNameAlreadyExists.get();
|
return deviceNameAlreadyExists.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BooleanProperty invalidSetupCodeProperty() {
|
||||||
|
return invalidSetupCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isInvalidSetupCode() {
|
||||||
|
return invalidSetupCode.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
private record UserDto(String id, String name, String publicKey, String privateKey, String setupCode) {}
|
||||||
|
|
||||||
|
private record CreateDeviceDto(@JsonProperty(required = true) String id, //
|
||||||
|
@JsonProperty(required = true) String name, //
|
||||||
|
@JsonProperty(required = true) String publicKey, //
|
||||||
|
@JsonProperty(required = true, defaultValue = "DESKTOP") String type, //
|
||||||
|
@JsonProperty(required = true) String userPrivateKey, //
|
||||||
|
@JsonProperty(required = true) String creationTime) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ import java.util.concurrent.CompletableFuture;
|
|||||||
public class RegisterFailedController implements FxController {
|
public class RegisterFailedController implements FxController {
|
||||||
|
|
||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final CompletableFuture<JWEObject> result;
|
private final CompletableFuture<ReceivedKey> result;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public RegisterFailedController(@KeyLoading Stage window, CompletableFuture<JWEObject> result) {
|
public RegisterFailedController(@KeyLoading Stage window, CompletableFuture<ReceivedKey> result) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.result = result;
|
this.result = result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ import java.util.concurrent.CompletableFuture;
|
|||||||
public class UnauthorizedDeviceController implements FxController {
|
public class UnauthorizedDeviceController implements FxController {
|
||||||
|
|
||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final CompletableFuture<JWEObject> result;
|
private final CompletableFuture<ReceivedKey> result;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public UnauthorizedDeviceController(@KeyLoading Stage window, CompletableFuture<JWEObject> result) {
|
public UnauthorizedDeviceController(@KeyLoading Stage window, CompletableFuture<ReceivedKey> result) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.result = result;
|
this.result = result;
|
||||||
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
|
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ public interface MainWindowComponent {
|
|||||||
default Stage showMainWindow() {
|
default Stage showMainWindow() {
|
||||||
Stage stage = window();
|
Stage stage = window();
|
||||||
stage.setScene(scene().get());
|
stage.setScene(scene().get());
|
||||||
|
stage.setIconified(false);
|
||||||
stage.show();
|
stage.show();
|
||||||
stage.toFront();
|
stage.toFront();
|
||||||
stage.requestFocus();
|
stage.requestFocus();
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import javafx.stage.Stage;
|
|||||||
public class MainWindowSceneFactory extends DefaultSceneFactory {
|
public class MainWindowSceneFactory extends DefaultSceneFactory {
|
||||||
|
|
||||||
protected static final KeyCodeCombination SHORTCUT_N = new KeyCodeCombination(KeyCode.N, KeyCombination.SHORTCUT_DOWN);
|
protected static final KeyCodeCombination SHORTCUT_N = new KeyCodeCombination(KeyCode.N, KeyCombination.SHORTCUT_DOWN);
|
||||||
|
protected static final KeyCodeCombination SHORTCUT_O = new KeyCodeCombination(KeyCode.O, KeyCombination.SHORTCUT_DOWN);
|
||||||
|
|
||||||
private final Lazy<MainWindowTitleController> mainWindowTitleController;
|
private final Lazy<MainWindowTitleController> mainWindowTitleController;
|
||||||
private final Lazy<VaultListController> vaultListController;
|
private final Lazy<VaultListController> vaultListController;
|
||||||
@@ -34,6 +35,7 @@ public class MainWindowSceneFactory extends DefaultSceneFactory {
|
|||||||
} else {
|
} else {
|
||||||
scene.getAccelerators().put(SHORTCUT_W, mainWindowTitleController.get()::close);
|
scene.getAccelerators().put(SHORTCUT_W, mainWindowTitleController.get()::close);
|
||||||
}
|
}
|
||||||
scene.getAccelerators().put(SHORTCUT_N, vaultListController.get()::didClickAddVault);
|
scene.getAccelerators().put(SHORTCUT_N, vaultListController.get()::didClickAddNewVault);
|
||||||
|
scene.getAccelerators().put(SHORTCUT_O, vaultListController.get()::didClickAddExistingVault);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,15 +6,14 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javafx.beans.binding.Bindings;
|
|
||||||
import javafx.beans.binding.BooleanBinding;
|
import javafx.beans.binding.BooleanBinding;
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.geometry.Rectangle2D;
|
import javafx.geometry.Rectangle2D;
|
||||||
import javafx.scene.input.MouseEvent;
|
import javafx.scene.input.MouseEvent;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.stage.Screen;
|
import javafx.stage.Screen;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
import javafx.stage.WindowEvent;
|
||||||
|
|
||||||
@MainWindow
|
@MainWindow
|
||||||
public class ResizeController implements FxController {
|
public class ResizeController implements FxController {
|
||||||
@@ -53,48 +52,57 @@ public class ResizeController implements FxController {
|
|||||||
public void initialize() {
|
public void initialize() {
|
||||||
LOG.trace("init ResizeController");
|
LOG.trace("init ResizeController");
|
||||||
|
|
||||||
if (neverTouched()) {
|
if (!neverTouched()) {
|
||||||
settings.displayConfiguration.set(getMonitorSizes());
|
window.setHeight(settings.windowHeight.get() > window.getMinHeight() ? settings.windowHeight.get() : window.getMinHeight());
|
||||||
return;
|
window.setWidth(settings.windowWidth.get() > window.getMinWidth() ? settings.windowWidth.get() : window.getMinWidth());
|
||||||
} else {
|
window.setX(settings.windowXPosition.get());
|
||||||
if (didDisplayConfigurationChange()) {
|
window.setY(settings.windowYPosition.get());
|
||||||
//If the position is illegal, then the window appears on the main screen in the middle of the window.
|
|
||||||
Rectangle2D primaryScreenBounds = Screen.getPrimary().getBounds();
|
|
||||||
window.setX((primaryScreenBounds.getWidth() - window.getMinWidth()) / 2);
|
|
||||||
window.setY((primaryScreenBounds.getHeight() - window.getMinHeight()) / 2);
|
|
||||||
window.setWidth(window.getMinWidth());
|
|
||||||
window.setHeight(window.getMinHeight());
|
|
||||||
} else {
|
|
||||||
window.setHeight(settings.windowHeight.get() > window.getMinHeight() ? settings.windowHeight.get() : window.getMinHeight());
|
|
||||||
window.setWidth(settings.windowWidth.get() > window.getMinWidth() ? settings.windowWidth.get() : window.getMinWidth());
|
|
||||||
window.setX(settings.windowXPosition.get());
|
|
||||||
window.setY(settings.windowYPosition.get());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
savePositionalSettings();
|
|
||||||
|
window.setOnShowing(this::checkDisplayBounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean neverTouched() {
|
private boolean neverTouched() {
|
||||||
return (settings.windowHeight.get() == 0) && (settings.windowWidth.get() == 0) && (settings.windowXPosition.get() == 0) && (settings.windowYPosition.get() == 0);
|
return (settings.windowHeight.get() == 0) && (settings.windowWidth.get() == 0) && (settings.windowXPosition.get() == 0) && (settings.windowYPosition.get() == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean didDisplayConfigurationChange() {
|
private void checkDisplayBounds(WindowEvent evt) {
|
||||||
String currentDisplayConfiguration = getMonitorSizes();
|
// Minimizing a window in Windows and closing it could result in an out of bounds position at (x, y) = (-32000, -32000)
|
||||||
String settingsDisplayConfiguration = settings.displayConfiguration.get();
|
// See https://devblogs.microsoft.com/oldnewthing/20041028-00/?p=37453
|
||||||
boolean configurationHasChanged = !settingsDisplayConfiguration.equals(currentDisplayConfiguration);
|
// If the position is (-32000, -32000), restore to the last saved position
|
||||||
if (configurationHasChanged) settings.displayConfiguration.set(currentDisplayConfiguration);
|
if (window.getX() == -32000 && window.getY() == -32000) {
|
||||||
return configurationHasChanged;
|
window.setX(settings.windowXPosition.get());
|
||||||
|
window.setY(settings.windowYPosition.get());
|
||||||
|
window.setWidth(settings.windowWidth.get());
|
||||||
|
window.setHeight(settings.windowHeight.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOutOfDisplayBounds()) {
|
||||||
|
// If the position is illegal, then the window appears on the main screen in the middle of the window.
|
||||||
|
LOG.debug("Resetting window position due to insufficient screen overlap");
|
||||||
|
Rectangle2D primaryScreenBounds = Screen.getPrimary().getBounds();
|
||||||
|
window.setX((primaryScreenBounds.getWidth() - window.getMinWidth()) / 2);
|
||||||
|
window.setY((primaryScreenBounds.getHeight() - window.getMinHeight()) / 2);
|
||||||
|
window.setWidth(window.getMinWidth());
|
||||||
|
window.setHeight(window.getMinHeight());
|
||||||
|
savePositionalSettings();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getMonitorSizes() {
|
private boolean isOutOfDisplayBounds() {
|
||||||
ObservableList<Screen> screens = Screen.getScreens();
|
// define a rect which is inset on all sides from the window's rect:
|
||||||
StringBuilder sb = new StringBuilder();
|
final double x = window.getX() + 20; // 20px left
|
||||||
for (int i = 0; i < screens.size(); i++) {
|
final double y = window.getY() + 5; // 5px top
|
||||||
Rectangle2D screenBounds = screens.get(i).getBounds();
|
final double w = window.getWidth() - 40; // 20px left + 20px right
|
||||||
if (!sb.isEmpty()) sb.append(" ");
|
final double h = window.getHeight() - 25; // 5px top + 20px bottom
|
||||||
sb.append("displayId: " + i + ", " + screenBounds.getWidth() + "x" + screenBounds.getHeight() + ";");
|
return isRectangleOutOfScreen(x, y, 0, h) // Left pixel column
|
||||||
}
|
|| isRectangleOutOfScreen(x + w, y, 0, h) // Right pixel column
|
||||||
return sb.toString();
|
|| isRectangleOutOfScreen(x, y, w, 0) // Top pixel row
|
||||||
|
|| isRectangleOutOfScreen(x, y + h, w, 0); // Bottom pixel row
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isRectangleOutOfScreen(double x, double y, double width, double height) {
|
||||||
|
return Screen.getScreensForRectangle(x, y, width, height).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startResize(MouseEvent evt) {
|
private void startResize(MouseEvent evt) {
|
||||||
@@ -183,5 +191,4 @@ public class ResizeController implements FxController {
|
|||||||
public boolean isShowResizingArrows() {
|
public boolean isShowResizingArrows() {
|
||||||
return showResizingArrows.get();
|
return showResizingArrows.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -21,6 +21,9 @@ import javafx.beans.value.ObservableValue;
|
|||||||
import javafx.collections.ListChangeListener;
|
import javafx.collections.ListChangeListener;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.geometry.Side;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.ContextMenu;
|
||||||
import javafx.scene.control.ListView;
|
import javafx.scene.control.ListView;
|
||||||
import javafx.scene.input.ContextMenuEvent;
|
import javafx.scene.input.ContextMenuEvent;
|
||||||
import javafx.scene.input.DragEvent;
|
import javafx.scene.input.DragEvent;
|
||||||
@@ -34,6 +37,7 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -59,12 +63,23 @@ public class VaultListController implements FxController {
|
|||||||
private final RemoveVaultComponent.Builder removeVaultDialogue;
|
private final RemoveVaultComponent.Builder removeVaultDialogue;
|
||||||
private final VaultListManager vaultListManager;
|
private final VaultListManager vaultListManager;
|
||||||
private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty();
|
private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty();
|
||||||
|
private final ResourceBundle resourceBundle;
|
||||||
|
|
||||||
public ListView<Vault> vaultList;
|
public ListView<Vault> vaultList;
|
||||||
public StackPane root;
|
public StackPane root;
|
||||||
|
public Button addVaultBtn;
|
||||||
|
@FXML
|
||||||
|
private ContextMenu addVaultContextMenu;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
VaultListController(@MainWindow Stage mainWindow, ObservableList<Vault> vaults, ObjectProperty<Vault> selectedVault, VaultListCellFactory cellFactory, AddVaultWizardComponent.Builder addVaultWizard, RemoveVaultComponent.Builder removeVaultDialogue, VaultListManager vaultListManager) {
|
VaultListController(@MainWindow Stage mainWindow, //
|
||||||
|
ObservableList<Vault> vaults, //
|
||||||
|
ObjectProperty<Vault> selectedVault, //
|
||||||
|
VaultListCellFactory cellFactory, //
|
||||||
|
AddVaultWizardComponent.Builder addVaultWizard, //
|
||||||
|
RemoveVaultComponent.Builder removeVaultDialogue, //
|
||||||
|
VaultListManager vaultListManager, //
|
||||||
|
ResourceBundle resourceBundle) {
|
||||||
this.mainWindow = mainWindow;
|
this.mainWindow = mainWindow;
|
||||||
this.vaults = vaults;
|
this.vaults = vaults;
|
||||||
this.selectedVault = selectedVault;
|
this.selectedVault = selectedVault;
|
||||||
@@ -72,6 +87,7 @@ public class VaultListController implements FxController {
|
|||||||
this.addVaultWizard = addVaultWizard;
|
this.addVaultWizard = addVaultWizard;
|
||||||
this.removeVaultDialogue = removeVaultDialogue;
|
this.removeVaultDialogue = removeVaultDialogue;
|
||||||
this.vaultListManager = vaultListManager;
|
this.vaultListManager = vaultListManager;
|
||||||
|
this.resourceBundle = resourceBundle;
|
||||||
|
|
||||||
this.emptyVaultList = Bindings.isEmpty(vaults);
|
this.emptyVaultList = Bindings.isEmpty(vaults);
|
||||||
|
|
||||||
@@ -129,6 +145,15 @@ public class VaultListController implements FxController {
|
|||||||
root.setOnDragExited(this::handleDragEvent);
|
root.setOnDragExited(this::handleDragEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void toggleMenu() {
|
||||||
|
if (addVaultContextMenu.isShowing()) {
|
||||||
|
addVaultContextMenu.hide();
|
||||||
|
} else {
|
||||||
|
addVaultContextMenu.show(addVaultBtn, Side.BOTTOM, 0.0, 0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void deselect(MouseEvent released) {
|
private void deselect(MouseEvent released) {
|
||||||
if (released.getY() > (vaultList.getItems().size() * vaultList.fixedCellSizeProperty().get())) {
|
if (released.getY() > (vaultList.getItems().size() * vaultList.fixedCellSizeProperty().get())) {
|
||||||
vaultList.getSelectionModel().clearSelection();
|
vaultList.getSelectionModel().clearSelection();
|
||||||
@@ -144,8 +169,13 @@ public class VaultListController implements FxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void didClickAddVault() {
|
public void didClickAddNewVault() {
|
||||||
addVaultWizard.build().showAddVaultWizard();
|
addVaultWizard.build().showAddNewVaultWizard(resourceBundle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void didClickAddExistingVault() {
|
||||||
|
addVaultWizard.build().showAddExistingVaultWizard(resourceBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void pressedShortcutToRemoveVault() {
|
private void pressedShortcutToRemoveVault() {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.cryptomator.ui.preferences;
|
package org.cryptomator.ui.preferences;
|
||||||
|
|
||||||
|
import org.cryptomator.common.Environment;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.fxapp.UpdateChecker;
|
import org.cryptomator.ui.fxapp.UpdateChecker;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -19,6 +20,7 @@ public class PreferencesController implements FxController {
|
|||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(PreferencesController.class);
|
private static final Logger LOG = LoggerFactory.getLogger(PreferencesController.class);
|
||||||
|
|
||||||
|
private final Environment env;
|
||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final ObjectProperty<SelectedPreferencesTab> selectedTabProperty;
|
private final ObjectProperty<SelectedPreferencesTab> selectedTabProperty;
|
||||||
private final BooleanBinding updateAvailable;
|
private final BooleanBinding updateAvailable;
|
||||||
@@ -31,7 +33,8 @@ public class PreferencesController implements FxController {
|
|||||||
public Tab aboutTab;
|
public Tab aboutTab;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public PreferencesController(@PreferencesWindow Stage window, ObjectProperty<SelectedPreferencesTab> selectedTabProperty, UpdateChecker updateChecker) {
|
public PreferencesController(Environment env, @PreferencesWindow Stage window, ObjectProperty<SelectedPreferencesTab> selectedTabProperty, UpdateChecker updateChecker) {
|
||||||
|
this.env = env;
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.selectedTabProperty = selectedTabProperty;
|
this.selectedTabProperty = selectedTabProperty;
|
||||||
this.updateAvailable = updateChecker.latestVersionProperty().isNotNull();
|
this.updateAvailable = updateChecker.latestVersionProperty().isNotNull();
|
||||||
@@ -42,6 +45,9 @@ public class PreferencesController implements FxController {
|
|||||||
window.setOnShowing(this::windowWillAppear);
|
window.setOnShowing(this::windowWillAppear);
|
||||||
selectedTabProperty.addListener(observable -> this.selectChosenTab());
|
selectedTabProperty.addListener(observable -> this.selectChosenTab());
|
||||||
tabPane.getSelectionModel().selectedItemProperty().addListener(observable -> this.selectedTabChanged());
|
tabPane.getSelectionModel().selectedItemProperty().addListener(observable -> this.selectedTabChanged());
|
||||||
|
if (env.disableUpdateCheck()) {
|
||||||
|
tabPane.getTabs().remove(updatesTab);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void selectChosenTab() {
|
private void selectChosenTab() {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
|||||||
import org.cryptomator.cryptolib.api.Masterkey;
|
import org.cryptomator.cryptolib.api.Masterkey;
|
||||||
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.jetbrains.annotations.VisibleForTesting;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
@@ -58,7 +59,7 @@ public class RecoveryKeyFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// visible for testing
|
@VisibleForTesting
|
||||||
String createRecoveryKey(byte[] rawKey) {
|
String createRecoveryKey(byte[] rawKey) {
|
||||||
Preconditions.checkArgument(rawKey.length == 64, "key should be 64 bytes");
|
Preconditions.checkArgument(rawKey.length == 64, "key should be 64 bytes");
|
||||||
byte[] paddedKey = Arrays.copyOf(rawKey, 66);
|
byte[] paddedKey = Arrays.copyOf(rawKey, 66);
|
||||||
|
|||||||
@@ -795,6 +795,16 @@
|
|||||||
-fx-scale-shape: false;
|
-fx-scale-shape: false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* *
|
||||||
|
* Add Vault - MenuItem *
|
||||||
|
* *
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
.add-vault-menu-item {
|
||||||
|
-fx-padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* *
|
* *
|
||||||
* ProgressBar *
|
* ProgressBar *
|
||||||
|
|||||||
@@ -794,6 +794,16 @@
|
|||||||
-fx-scale-shape: false;
|
-fx-scale-shape: false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* *
|
||||||
|
* Add Vault - MenuItem *
|
||||||
|
* *
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
.add-vault-menu-item {
|
||||||
|
-fx-padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* *
|
* *
|
||||||
* ProgressBar *
|
* ProgressBar *
|
||||||
|
|||||||
@@ -24,9 +24,8 @@
|
|||||||
|
|
||||||
<Region VBox.vgrow="ALWAYS"/>
|
<Region VBox.vgrow="ALWAYS"/>
|
||||||
|
|
||||||
<ButtonBar buttonMinWidth="120" buttonOrder="B+X">
|
<ButtonBar buttonMinWidth="120" buttonOrder="+X">
|
||||||
<buttons>
|
<buttons>
|
||||||
<Button text="%generic.button.back" ButtonBar.buttonData="BACK_PREVIOUS" onAction="#back"/>
|
|
||||||
<Button fx:id="finishButton" text="%addvaultwizard.existing.chooseBtn" ButtonBar.buttonData="NEXT_FORWARD" onAction="#chooseFileAndNext" defaultButton="true"/>
|
<Button fx:id="finishButton" text="%addvaultwizard.existing.chooseBtn" ButtonBar.buttonData="NEXT_FORWARD" onAction="#chooseFileAndNext" defaultButton="true"/>
|
||||||
</buttons>
|
</buttons>
|
||||||
</ButtonBar>
|
</ButtonBar>
|
||||||
|
|||||||
@@ -68,9 +68,8 @@
|
|||||||
|
|
||||||
<Region VBox.vgrow="ALWAYS"/>
|
<Region VBox.vgrow="ALWAYS"/>
|
||||||
|
|
||||||
<ButtonBar buttonMinWidth="120" buttonOrder="B+X">
|
<ButtonBar buttonMinWidth="120" buttonOrder="+X">
|
||||||
<buttons>
|
<buttons>
|
||||||
<Button text="%generic.button.back" ButtonBar.buttonData="BACK_PREVIOUS" onAction="#back"/>
|
|
||||||
<Button text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" onAction="#next" defaultButton="true" disable="${!controller.validVaultName}"/>
|
<Button text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" onAction="#next" defaultButton="true" disable="${!controller.validVaultName}"/>
|
||||||
</buttons>
|
</buttons>
|
||||||
</ButtonBar>
|
</ButtonBar>
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
|
|
||||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
|
||||||
<?import javafx.geometry.Insets?>
|
|
||||||
<?import javafx.scene.control.Button?>
|
|
||||||
<?import javafx.scene.image.Image?>
|
|
||||||
<?import javafx.scene.image.ImageView?>
|
|
||||||
<?import javafx.scene.layout.Region?>
|
|
||||||
<?import javafx.scene.layout.VBox?>
|
|
||||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
|
||||||
xmlns="http://javafx.com/javafx"
|
|
||||||
fx:controller="org.cryptomator.ui.addvaultwizard.AddVaultWelcomeController"
|
|
||||||
prefWidth="450"
|
|
||||||
prefHeight="450"
|
|
||||||
spacing="12"
|
|
||||||
alignment="TOP_CENTER">
|
|
||||||
<padding>
|
|
||||||
<Insets topRightBottomLeft="24"/>
|
|
||||||
</padding>
|
|
||||||
<children>
|
|
||||||
<Region VBox.vgrow="ALWAYS"/>
|
|
||||||
|
|
||||||
<ImageView VBox.vgrow="ALWAYS" fitHeight="128" preserveRatio="true" smooth="true" cache="true">
|
|
||||||
<Image url="@../img/logo.png"/>
|
|
||||||
</ImageView>
|
|
||||||
|
|
||||||
<Region VBox.vgrow="ALWAYS"/>
|
|
||||||
|
|
||||||
<VBox alignment="CENTER" spacing="9">
|
|
||||||
<Button styleClass="button-large" text="%addvaultwizard.welcome.newButton" onAction="#createNewVault" prefWidth="Infinity">
|
|
||||||
<graphic>
|
|
||||||
<FontAwesome5IconView glyph="MAGIC" glyphSize="15"/>
|
|
||||||
</graphic>
|
|
||||||
</Button>
|
|
||||||
<Button styleClass="button-large" text="%addvaultwizard.welcome.existingButton" onAction="#chooseExistingVault" prefWidth="Infinity">
|
|
||||||
<graphic>
|
|
||||||
<FontAwesome5IconView glyph="FOLDER_OPEN" glyphSize="15"/>
|
|
||||||
</graphic>
|
|
||||||
</Button>
|
|
||||||
</VBox>
|
|
||||||
|
|
||||||
<Region VBox.vgrow="ALWAYS"/>
|
|
||||||
</children>
|
|
||||||
</VBox>
|
|
||||||
+1
-1
@@ -15,7 +15,7 @@
|
|||||||
<?import org.cryptomator.ui.controls.FontAwesome5Spinner?>
|
<?import org.cryptomator.ui.controls.FontAwesome5Spinner?>
|
||||||
<HBox xmlns:fx="http://javafx.com/fxml"
|
<HBox xmlns:fx="http://javafx.com/fxml"
|
||||||
xmlns="http://javafx.com/javafx"
|
xmlns="http://javafx.com/javafx"
|
||||||
fx:controller="org.cryptomator.ui.keyloading.hub.RegisterDeviceController"
|
fx:controller="org.cryptomator.ui.keyloading.hub.LegacyRegisterDeviceController"
|
||||||
minWidth="400"
|
minWidth="400"
|
||||||
maxWidth="400"
|
maxWidth="400"
|
||||||
minHeight="145"
|
minHeight="145"
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.ButtonBar?>
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.control.TextField?>
|
||||||
|
<?import javafx.scene.Group?>
|
||||||
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
<?import javafx.scene.layout.Region?>
|
||||||
|
<?import javafx.scene.layout.StackPane?>
|
||||||
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
<?import javafx.scene.shape.Circle?>
|
||||||
|
<?import org.cryptomator.ui.controls.FontAwesome5Spinner?>
|
||||||
|
<HBox xmlns:fx="http://javafx.com/fxml"
|
||||||
|
xmlns="http://javafx.com/javafx"
|
||||||
|
fx:controller="org.cryptomator.ui.keyloading.hub.RegisterDeviceController"
|
||||||
|
minWidth="400"
|
||||||
|
maxWidth="400"
|
||||||
|
minHeight="145"
|
||||||
|
spacing="12"
|
||||||
|
alignment="TOP_LEFT">
|
||||||
|
<padding>
|
||||||
|
<Insets topRightBottomLeft="12"/>
|
||||||
|
</padding>
|
||||||
|
<children>
|
||||||
|
<Group>
|
||||||
|
<StackPane>
|
||||||
|
<padding>
|
||||||
|
<Insets topRightBottomLeft="6"/>
|
||||||
|
</padding>
|
||||||
|
<Circle styleClass="glyph-icon-primary" radius="24"/>
|
||||||
|
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="INFO" glyphSize="24"/>
|
||||||
|
</StackPane>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<VBox HBox.hgrow="ALWAYS">
|
||||||
|
<Label styleClass="label-large" text="%hub.register.message" wrapText="true" textAlignment="LEFT">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="6" top="6"/>
|
||||||
|
</padding>
|
||||||
|
</Label>
|
||||||
|
<Label text="%hub.register.description" wrapText="true"/>
|
||||||
|
<HBox spacing="6" alignment="CENTER_LEFT">
|
||||||
|
<padding>
|
||||||
|
<Insets top="12"/>
|
||||||
|
</padding>
|
||||||
|
<Label text="%hub.register.setupCodeLabel" labelFor="$setupCodeField"/>
|
||||||
|
<TextField fx:id="setupCodeField" HBox.hgrow="ALWAYS"/>
|
||||||
|
</HBox>
|
||||||
|
<HBox spacing="6" alignment="CENTER_LEFT">
|
||||||
|
<padding>
|
||||||
|
<Insets top="12"/>
|
||||||
|
</padding>
|
||||||
|
<Label text="%hub.register.nameLabel" labelFor="$deviceNameField"/>
|
||||||
|
<TextField fx:id="deviceNameField" HBox.hgrow="ALWAYS"/>
|
||||||
|
</HBox>
|
||||||
|
<HBox alignment="TOP_RIGHT">
|
||||||
|
<Label text="%hub.register.occupiedMsg" textAlignment="RIGHT" alignment="CENTER_RIGHT" visible="${controller.deviceNameAlreadyExists}" managed="${controller.deviceNameAlreadyExists}" graphicTextGap="6">
|
||||||
|
<padding>
|
||||||
|
<Insets top="6"/>
|
||||||
|
</padding>
|
||||||
|
<graphic>
|
||||||
|
<FontAwesome5IconView glyph="TIMES" styleClass="glyph-icon-red"/>
|
||||||
|
</graphic>
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<Label text="%hub.register.invalidSetupCode" textAlignment="RIGHT" alignment="CENTER_RIGHT" visible="${controller.invalidSetupCode}" managed="${controller.invalidSetupCode}" graphicTextGap="6">
|
||||||
|
<padding>
|
||||||
|
<Insets top="6"/>
|
||||||
|
</padding>
|
||||||
|
<graphic>
|
||||||
|
<FontAwesome5IconView glyph="TIMES" styleClass="glyph-icon-red"/>
|
||||||
|
</graphic>
|
||||||
|
</Label>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
|
||||||
|
<ButtonBar buttonMinWidth="120" buttonOrder="+CU">
|
||||||
|
<buttons>
|
||||||
|
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
|
||||||
|
<Button fx:id="registerBtn" text="%hub.register.registerBtn" ButtonBar.buttonData="OTHER" defaultButton="true" onAction="#register" contentDisplay="TEXT_ONLY" >
|
||||||
|
<graphic>
|
||||||
|
<FontAwesome5Spinner glyphSize="12" />
|
||||||
|
</graphic>
|
||||||
|
</Button>
|
||||||
|
</buttons>
|
||||||
|
</ButtonBar>
|
||||||
|
</VBox>
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
@@ -8,6 +8,8 @@
|
|||||||
<?import javafx.scene.layout.StackPane?>
|
<?import javafx.scene.layout.StackPane?>
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
<?import javafx.scene.shape.Arc?>
|
<?import javafx.scene.shape.Arc?>
|
||||||
|
<?import javafx.scene.control.ContextMenu?>
|
||||||
|
<?import javafx.scene.control.MenuItem?>
|
||||||
<StackPane xmlns:fx="http://javafx.com/fxml"
|
<StackPane xmlns:fx="http://javafx.com/fxml"
|
||||||
xmlns="http://javafx.com/javafx"
|
xmlns="http://javafx.com/javafx"
|
||||||
fx:id="root"
|
fx:id="root"
|
||||||
@@ -26,11 +28,27 @@
|
|||||||
<Arc VBox.vgrow="NEVER" styleClass="onboarding-overlay-arc" type="OPEN" centerX="50" centerY="0" radiusY="100" radiusX="50" startAngle="0" length="-60" strokeWidth="1"/>
|
<Arc VBox.vgrow="NEVER" styleClass="onboarding-overlay-arc" type="OPEN" centerX="50" centerY="0" radiusY="100" radiusX="50" startAngle="0" length="-60" strokeWidth="1"/>
|
||||||
</VBox>
|
</VBox>
|
||||||
</StackPane>
|
</StackPane>
|
||||||
<Button styleClass="toolbar-button" text="%main.vaultlist.addVaultBtn" onAction="#didClickAddVault" alignment="BASELINE_CENTER" maxWidth="Infinity">
|
<Button fx:id="addVaultBtn" onAction="#toggleMenu" styleClass="toolbar-button" text="%main.vaultlist.addVaultBtn" alignment="BASELINE_CENTER" maxWidth="Infinity" contentDisplay="RIGHT">
|
||||||
<graphic>
|
<graphic>
|
||||||
<FontAwesome5IconView glyph="PLUS"/>
|
<FontAwesome5IconView glyph="CARET_DOWN"/>
|
||||||
</graphic>
|
</graphic>
|
||||||
</Button>
|
</Button>
|
||||||
|
<fx:define>
|
||||||
|
<ContextMenu fx:id="addVaultContextMenu">
|
||||||
|
<items>
|
||||||
|
<MenuItem styleClass="add-vault-menu-item" text="%main.vaultlist.addVaultBtn.menuItemNew" onAction="#didClickAddNewVault" >
|
||||||
|
<graphic>
|
||||||
|
<FontAwesome5IconView glyph="PLUS" textAlignment="CENTER" wrappingWidth="14" />
|
||||||
|
</graphic>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem styleClass="add-vault-menu-item" text="%main.vaultlist.addVaultBtn.menuItemExisting" onAction="#didClickAddExistingVault" >
|
||||||
|
<graphic>
|
||||||
|
<FontAwesome5IconView glyph="FOLDER_OPEN" textAlignment="CENTER" wrappingWidth="14" />
|
||||||
|
</graphic>
|
||||||
|
</MenuItem>
|
||||||
|
</items>
|
||||||
|
</ContextMenu>
|
||||||
|
</fx:define>
|
||||||
</VBox>
|
</VBox>
|
||||||
<Region styleClass="drag-n-drop-border" visible="${controller.draggingVaultOver}"/>
|
<Region styleClass="drag-n-drop-border" visible="${controller.draggingVaultOver}"/>
|
||||||
</StackPane>
|
</StackPane>
|
||||||
|
|||||||
@@ -41,10 +41,8 @@ traymenu.vault.reveal=Reveal
|
|||||||
|
|
||||||
# Add Vault Wizard
|
# Add Vault Wizard
|
||||||
addvaultwizard.title=Add Vault
|
addvaultwizard.title=Add Vault
|
||||||
## Welcome
|
|
||||||
addvaultwizard.welcome.newButton=Create New Vault
|
|
||||||
addvaultwizard.welcome.existingButton=Open Existing Vault
|
|
||||||
## New
|
## New
|
||||||
|
addvaultwizard.new.title=Add New Vault
|
||||||
### Name
|
### Name
|
||||||
addvaultwizard.new.nameInstruction=Choose a name for the vault
|
addvaultwizard.new.nameInstruction=Choose a name for the vault
|
||||||
addvaultwizard.new.namePrompt=Vault Name
|
addvaultwizard.new.namePrompt=Vault Name
|
||||||
@@ -94,6 +92,7 @@ addvault.new.readme.accessLocation.2=This is your vault's access location.
|
|||||||
addvault.new.readme.accessLocation.3=Any files added to this volume will be encrypted by Cryptomator. You can work on it like on any other drive/folder. This is only a decrypted view of its content, your files stay encrypted on your hard drive all the time.
|
addvault.new.readme.accessLocation.3=Any files added to this volume will be encrypted by Cryptomator. You can work on it like on any other drive/folder. This is only a decrypted view of its content, your files stay encrypted on your hard drive all the time.
|
||||||
addvault.new.readme.accessLocation.4=Feel free to remove this file.
|
addvault.new.readme.accessLocation.4=Feel free to remove this file.
|
||||||
## Existing
|
## Existing
|
||||||
|
addvaultwizard.existing.title=Add Existing Vault
|
||||||
addvaultwizard.existing.instruction=Choose the "vault.cryptomator" file of your existing vault. If only a file named "masterkey.cryptomator" exists, select that instead.
|
addvaultwizard.existing.instruction=Choose the "vault.cryptomator" file of your existing vault. If only a file named "masterkey.cryptomator" exists, select that instead.
|
||||||
addvaultwizard.existing.chooseBtn=Choose…
|
addvaultwizard.existing.chooseBtn=Choose…
|
||||||
addvaultwizard.existing.filePickerTitle=Select Vault File
|
addvaultwizard.existing.filePickerTitle=Select Vault File
|
||||||
@@ -139,7 +138,7 @@ unlock.error.customPath.message=Unable to mount vault to custom path
|
|||||||
unlock.error.customPath.description.notSupported=If you wish to keep using the custom path, please go to the preferences and select a volume type that supports it. Otherwise, go to the vault options and choose a supported mount point.
|
unlock.error.customPath.description.notSupported=If you wish to keep using the custom path, please go to the preferences and select a volume type that supports it. Otherwise, go to the vault options and choose a supported mount point.
|
||||||
unlock.error.customPath.description.notExists=The custom mount path does not exist. Either create it in your local filesystem or change it in the vault options.
|
unlock.error.customPath.description.notExists=The custom mount path does not exist. Either create it in your local filesystem or change it in the vault options.
|
||||||
unlock.error.customPath.description.inUse=The drive letter or custom mount path "%s" is already in use.
|
unlock.error.customPath.description.inUse=The drive letter or custom mount path "%s" is already in use.
|
||||||
unlock.error.customPath.description.hideawayNotDir=The temporary, hidden file "%3$s" used for unlock could not be removed. Please check the file and then delete it manually.
|
unlock.error.customPath.description.hideawayNotDir=The temporary, hidden file "%3$s" used for unlocking could not be removed. Please check the file and then delete it manually.
|
||||||
unlock.error.customPath.description.couldNotBeCleaned=Your vault could not be mounted to the path "%s". Please try again or choose a different path.
|
unlock.error.customPath.description.couldNotBeCleaned=Your vault could not be mounted to the path "%s". Please try again or choose a different path.
|
||||||
unlock.error.customPath.description.notEmptyDir=The custom mount path "%s" is not an empty folder. Please choose an empty folder and try again.
|
unlock.error.customPath.description.notEmptyDir=The custom mount path "%s" is not an empty folder. Please choose an empty folder and try again.
|
||||||
unlock.error.customPath.description.generic=You have selected a custom mount path for this vault, but using it failed with the message: %2$s
|
unlock.error.customPath.description.generic=You have selected a custom mount path for this vault, but using it failed with the message: %2$s
|
||||||
@@ -155,9 +154,11 @@ hub.auth.loginLink=Not redirected? Click here to open it.
|
|||||||
hub.receive.message=Processing response…
|
hub.receive.message=Processing response…
|
||||||
hub.receive.description=Cryptomator is receiving and processing the response from Hub. Please wait.
|
hub.receive.description=Cryptomator is receiving and processing the response from Hub. Please wait.
|
||||||
### Register Device
|
### Register Device
|
||||||
hub.register.message=Device name required
|
hub.register.message=New Device
|
||||||
hub.register.description=This seems to be the first Hub access from this device. In order to identify it for access authorization, you need to name this device.
|
hub.register.description=This is the first Hub access from this device. Please authorize it using your setup code.
|
||||||
hub.register.nameLabel=Device Name
|
hub.register.nameLabel=Device Name
|
||||||
|
hub.register.setupCodeLabel=Setup Code
|
||||||
|
hub.register.invalidSetupCode=Invalid Setup Code
|
||||||
hub.register.occupiedMsg=Name already in use
|
hub.register.occupiedMsg=Name already in use
|
||||||
hub.register.registerBtn=Confirm
|
hub.register.registerBtn=Confirm
|
||||||
### Registration Success
|
### Registration Success
|
||||||
@@ -367,7 +368,9 @@ main.vaultlist.contextMenu.unlock=Unlock…
|
|||||||
main.vaultlist.contextMenu.unlockNow=Unlock Now
|
main.vaultlist.contextMenu.unlockNow=Unlock Now
|
||||||
main.vaultlist.contextMenu.vaultoptions=Show Vault Options
|
main.vaultlist.contextMenu.vaultoptions=Show Vault Options
|
||||||
main.vaultlist.contextMenu.reveal=Reveal Drive
|
main.vaultlist.contextMenu.reveal=Reveal Drive
|
||||||
main.vaultlist.addVaultBtn=Add Vault
|
main.vaultlist.addVaultBtn=Add
|
||||||
|
main.vaultlist.addVaultBtn.menuItemNew=New Vault...
|
||||||
|
main.vaultlist.addVaultBtn.menuItemExisting=Existing Vault...
|
||||||
## Vault Detail
|
## Vault Detail
|
||||||
### Welcome
|
### Welcome
|
||||||
main.vaultDetail.welcomeOnboarding=Thanks for choosing Cryptomator to protect your files. If you need any assistance, check out our getting started guides:
|
main.vaultDetail.welcomeOnboarding=Thanks for choosing Cryptomator to protect your files. If you need any assistance, check out our getting started guides:
|
||||||
|
|||||||
@@ -40,10 +40,8 @@ traymenu.vault.reveal=إظهار
|
|||||||
|
|
||||||
# Add Vault Wizard
|
# Add Vault Wizard
|
||||||
addvaultwizard.title=أضِف مخزنًا
|
addvaultwizard.title=أضِف مخزنًا
|
||||||
## Welcome
|
|
||||||
addvaultwizard.welcome.newButton=إنشاء مخزن جديد
|
|
||||||
addvaultwizard.welcome.existingButton=افتح مخزن موجود
|
|
||||||
## New
|
## New
|
||||||
|
addvaultwizard.new.title=إضافة مخزن جديد
|
||||||
### Name
|
### Name
|
||||||
addvaultwizard.new.nameInstruction=اختر اسم للمخزن
|
addvaultwizard.new.nameInstruction=اختر اسم للمخزن
|
||||||
addvaultwizard.new.namePrompt=اسم الخزينة
|
addvaultwizard.new.namePrompt=اسم الخزينة
|
||||||
@@ -88,11 +86,12 @@ addvault.new.readme.storageLocation.8=2. فتح المخزن باستخدام Cr
|
|||||||
addvault.new.readme.storageLocation.9=3. افتح موقع الوصول بالنقر على زر "إظهار".
|
addvault.new.readme.storageLocation.9=3. افتح موقع الوصول بالنقر على زر "إظهار".
|
||||||
addvault.new.readme.storageLocation.10=إذا كنت بحاجة إلى المساعدة، قم بالاطلاع على كتيب الوثائق: %s
|
addvault.new.readme.storageLocation.10=إذا كنت بحاجة إلى المساعدة، قم بالاطلاع على كتيب الوثائق: %s
|
||||||
addvault.new.readme.accessLocation.fileName=مرحبا.rtf
|
addvault.new.readme.accessLocation.fileName=مرحبا.rtf
|
||||||
addvault.new.readme.accessLocation.1=🔐️ المجلدات المشفرة 🔐️
|
addvault.new.readme.accessLocation.1=🔐️ وحدة التخزين مشفرة 🔐️
|
||||||
addvault.new.readme.accessLocation.2=هذا هو موقع الوصول للخرنة الخاص بك.
|
addvault.new.readme.accessLocation.2=هذا هو موقع الوصول للخرنة الخاص بك.
|
||||||
addvault.new.readme.accessLocation.3=سيتم تشفير أي ملفات تضاف إلى هذا المجلد من قبل Cryptomator. يمكنك العمل عليه كأي مجلد آخر. هذه فقط طريقة عرض غير مشفرة لمحتوياتها، ملفاتك تبقى مشفرة على القرص الصلب الخاص بك طوال الوقت.
|
addvault.new.readme.accessLocation.3=سيتم تشفير أي ملفات تضاف إلى هذا المجلد من قبل Cryptomator. يمكنك العمل عليه كأي مجلد آخر. هذه فقط طريقة عرض غير مشفرة لمحتوياتها، ملفاتك تبقى مشفرة على القرص الصلب الخاص بك طوال الوقت.
|
||||||
addvault.new.readme.accessLocation.4=لا تتردد في إزالة هذا الملف.
|
addvault.new.readme.accessLocation.4=لا تتردد في إزالة هذا الملف.
|
||||||
## Existing
|
## Existing
|
||||||
|
addvaultwizard.existing.title=أضِف مخزن موجود
|
||||||
addvaultwizard.existing.instruction=اختر ملف "vault.cryptomator" من خزينتك الحالية. إذا كان يوجد فقط ملف اسمه "masterkey.cryptomator"، اختره عوضاً عنه.
|
addvaultwizard.existing.instruction=اختر ملف "vault.cryptomator" من خزينتك الحالية. إذا كان يوجد فقط ملف اسمه "masterkey.cryptomator"، اختره عوضاً عنه.
|
||||||
addvaultwizard.existing.chooseBtn=اختر…
|
addvaultwizard.existing.chooseBtn=اختر…
|
||||||
addvaultwizard.existing.filePickerTitle=حدد مِلَفّ المخزن
|
addvaultwizard.existing.filePickerTitle=حدد مِلَفّ المخزن
|
||||||
@@ -135,10 +134,10 @@ unlock.success.rememberChoice=تذكر اختياري ولا تظهر هذا م
|
|||||||
unlock.success.revealBtn=اظهار القرص
|
unlock.success.revealBtn=اظهار القرص
|
||||||
## Failure
|
## Failure
|
||||||
unlock.error.customPath.message=غير قادر على تركيب المخزن إلى المسار المخصص
|
unlock.error.customPath.message=غير قادر على تركيب المخزن إلى المسار المخصص
|
||||||
unlock.error.customPath.description.notSupported=إذا كنت ترغب في الاستمرار في استخدام المسار المخصص، يرجى الذَّهاب إلى التفضيلات وتحديد نوع واسطة التخزين الذي يدعمها. خلاف ذلك، انتقل إلى خيارات المخزن واختر نقطة تركيب مدعومة.
|
unlock.error.customPath.description.notSupported=إذا كنت ترغب في الاستمرار في استخدام المسار المخصص، يرجى الذَّهاب إلى التفضيلات وتحديد نوع وحدة التخزين\n الذي يدعمها. خلاف ذلك، انتقل إلى خيارات المخزن واختر نقطة تركيب مدعومة.
|
||||||
unlock.error.customPath.description.notExists=مسار التركيب المخصص غير موجود. قم بإنشائه في نظام الملفات المحلي الخاص بك أو اختر غيره في خيارات الخزانة.
|
unlock.error.customPath.description.notExists=مسار التركيب المخصص غير موجود. قم بإنشائه في نظام الملفات المحلي الخاص بك أو اختر غيره في خيارات الخزانة.
|
||||||
unlock.error.customPath.description.inUse=حرف القرص أو مسار التركيب المخصص "%s" قيد الاستخدام بالفعل.
|
unlock.error.customPath.description.inUse=حرف القرص أو مسار التركيب المخصص "%s" قيد الاستخدام بالفعل.
|
||||||
unlock.error.customPath.description.hideawayNotDir=لا يمكن إزالة الملف المؤقت المخفي "%3$s" المستخدم للفتح. الرجاء التحقق من الملف ثم حذفه يدوياً.
|
unlock.error.customPath.description.hideawayNotDir=لا يمكن إزالة المِلَفّ المؤقت المخفي "%3$s" المستخدم لفتح القُفْل. الرجاء التحقق من المِلَفّ ثم حذفه يدوياً.
|
||||||
unlock.error.customPath.description.couldNotBeCleaned=لا يمكن تركيب خزانتك على المسار "%s". الرجاء المحاولة مرة أخرى أو اختيار مسار مختلف.
|
unlock.error.customPath.description.couldNotBeCleaned=لا يمكن تركيب خزانتك على المسار "%s". الرجاء المحاولة مرة أخرى أو اختيار مسار مختلف.
|
||||||
unlock.error.customPath.description.notEmptyDir=مسار التركيب المخصص "%s" ليس مجلد فارغ. الرجاء اختيار مجلد فارغ وحاول مرة أخرى.
|
unlock.error.customPath.description.notEmptyDir=مسار التركيب المخصص "%s" ليس مجلد فارغ. الرجاء اختيار مجلد فارغ وحاول مرة أخرى.
|
||||||
unlock.error.customPath.description.generic=لقد اخترت مسار تركيب مخصص لهذه الخزانة، ولكن استخدامه فشل مع الرسالة: %2$s
|
unlock.error.customPath.description.generic=لقد اخترت مسار تركيب مخصص لهذه الخزانة، ولكن استخدامه فشل مع الرسالة: %2$s
|
||||||
@@ -249,22 +248,52 @@ health.result.severityFilter.info=معلومات
|
|||||||
health.result.severityFilter.warn=تحذير
|
health.result.severityFilter.warn=تحذير
|
||||||
health.result.severityFilter.crit=حرج
|
health.result.severityFilter.crit=حرج
|
||||||
health.result.severityTip.good=الخطورة: جيد\nبنية المخزن عادية.
|
health.result.severityTip.good=الخطورة: جيد\nبنية المخزن عادية.
|
||||||
|
health.result.severityTip.info=الخطورة: معلومات\nبنية المخزن سليمة، إصلاح المقترح.
|
||||||
|
health.result.severityTip.warn=الخطورة: تحذير\nبنية المخزن تالفة، ننصح لإصلاح.
|
||||||
|
health.result.severityTip.crit=الخطورة: حرجة\nبنية المخزن تالفة، وتم تحديد فقدان البيانات.
|
||||||
|
health.result.fixStateFilter.all=حالة لإصلاح - الكل
|
||||||
|
health.result.fixStateFilter.fixable=قابلة للإصلاح
|
||||||
|
health.result.fixStateFilter.notFixable=غير قابل للإصلاح
|
||||||
|
health.result.fixStateFilter.fixing=يتم الإصلاح…
|
||||||
|
health.result.fixStateFilter.fixed=تم الإصلاح
|
||||||
|
health.result.fixStateFilter.fixFailed=فشل الإصلاح
|
||||||
## Fix Application
|
## Fix Application
|
||||||
|
health.fix.fixBtn=إصلاح
|
||||||
|
health.fix.successTip=تم الإصلاح بنجاح
|
||||||
|
health.fix.failTip=فشل الإصلاح، راجع السجل للحصول على التفاصيل
|
||||||
|
|
||||||
# Preferences
|
# Preferences
|
||||||
preferences.title=تفضيلات
|
preferences.title=تفضيلات
|
||||||
## General
|
## General
|
||||||
preferences.general=عام
|
preferences.general=عام
|
||||||
preferences.general.startHidden=إخفاء النافذة عند بدء تشغيل Cryptomator
|
preferences.general.startHidden=إخفاء النافذة عند بدء تشغيل Cryptomator
|
||||||
|
preferences.general.autoCloseVaults=اقفل الخزانات المفتوحة تلقائياً عند الإقلاع عن التطبيق
|
||||||
preferences.general.debugLogging=تمكين سجلات التصحيح
|
preferences.general.debugLogging=تمكين سجلات التصحيح
|
||||||
preferences.general.debugDirectory=عرض ملفات السجل
|
preferences.general.debugDirectory=عرض ملفات السجل
|
||||||
preferences.general.autoStart=تشغيل Cryptomator عند بدء تشغيل النظام
|
preferences.general.autoStart=تشغيل Cryptomator عند بدء تشغيل النظام
|
||||||
preferences.general.keychainBackend=تخزين كلمات المرور مع
|
preferences.general.keychainBackend=تخزين كلمات المرور مع
|
||||||
## Interface
|
## Interface
|
||||||
|
preferences.interface=الواجهة
|
||||||
|
preferences.interface.theme=الشكل والمظهر
|
||||||
|
preferences.interface.theme.automatic=تلقائي
|
||||||
preferences.interface.theme.dark=مظلم (أسود)
|
preferences.interface.theme.dark=مظلم (أسود)
|
||||||
preferences.interface.theme.light=فاتح (أبيض)
|
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
|
## Volume
|
||||||
preferences.volume=القرص الإفتراضي
|
preferences.volume=القرص الإفتراضي
|
||||||
|
preferences.volume.type=نوع وحدة التخزين
|
||||||
|
preferences.volume.type.automatic=تلقائي
|
||||||
|
preferences.volume.docsTooltip=افتح الوثائق لمعرفة المزيد عن مختلف أنواع وحدة التخزين.
|
||||||
|
preferences.volume.fuseRestartRequired=لتطبيق التغييرات، يحتاج Cryptomator إلى إعادة التشغيل.
|
||||||
|
preferences.volume.tcp.port=منفذ TCP
|
||||||
|
preferences.volume.supportedFeatures=يدعم نوع وحدة تخزين المختار الميزات التالية:
|
||||||
## Updates
|
## Updates
|
||||||
preferences.updates=تحديثات
|
preferences.updates=تحديثات
|
||||||
preferences.updates.currentVersion=الإصدار الحالي: %s
|
preferences.updates.currentVersion=الإصدار الحالي: %s
|
||||||
@@ -310,7 +339,7 @@ main.vaultlist.contextMenu.unlock=فتح…
|
|||||||
main.vaultlist.contextMenu.unlockNow=افتح الان
|
main.vaultlist.contextMenu.unlockNow=افتح الان
|
||||||
main.vaultlist.contextMenu.vaultoptions=إظهار خيارات المخزن
|
main.vaultlist.contextMenu.vaultoptions=إظهار خيارات المخزن
|
||||||
main.vaultlist.contextMenu.reveal=اظهار القرص
|
main.vaultlist.contextMenu.reveal=اظهار القرص
|
||||||
main.vaultlist.addVaultBtn=أضِف مخزنًا
|
main.vaultlist.addVaultBtn=إضافة
|
||||||
## Vault Detail
|
## Vault Detail
|
||||||
### Welcome
|
### Welcome
|
||||||
main.vaultDetail.welcomeOnboarding=شكرا لاختيار Cryptomator لحماية ملفاتك. إذا كنت بحاجة إلى أية مساعدة، تحقق من دليل وتعليمات الإستخدام:
|
main.vaultDetail.welcomeOnboarding=شكرا لاختيار Cryptomator لحماية ملفاتك. إذا كنت بحاجة إلى أية مساعدة، تحقق من دليل وتعليمات الإستخدام:
|
||||||
@@ -350,8 +379,8 @@ wrongFileAlert.message=هل حاولت تشفير هذه الملفات؟
|
|||||||
wrongFileAlert.description=لهذا الغرض، بوفر Cryptomator مجلداً في مدير ملفات النظام الخاص بك.
|
wrongFileAlert.description=لهذا الغرض، بوفر Cryptomator مجلداً في مدير ملفات النظام الخاص بك.
|
||||||
wrongFileAlert.instruction.0=لتشفير الملفات، قم باتباع الخطوات التالية:
|
wrongFileAlert.instruction.0=لتشفير الملفات، قم باتباع الخطوات التالية:
|
||||||
wrongFileAlert.instruction.1=1. فتح المخزن الخاص بك.
|
wrongFileAlert.instruction.1=1. فتح المخزن الخاص بك.
|
||||||
wrongFileAlert.instruction.2=2. انقر على "إظهار" لفتح المجلد في مدير الملفات الخاص بك.
|
wrongFileAlert.instruction.2=2. انقر على "إظهار" لفتح وحدة التخزين في مدير ملفاتك.
|
||||||
wrongFileAlert.instruction.3=3. أضف ملفاتك إلى هذا المجلد.
|
wrongFileAlert.instruction.3=3. أضِف ملفاتك إلى هذه وحدة التخزين.
|
||||||
wrongFileAlert.link=لمزيد من المساعدة، قم بزيارة
|
wrongFileAlert.link=لمزيد من المساعدة، قم بزيارة
|
||||||
|
|
||||||
# Vault Options
|
# Vault Options
|
||||||
|
|||||||
@@ -37,9 +37,6 @@ traymenu.vault.reveal=Паказаць
|
|||||||
|
|
||||||
# Add Vault Wizard
|
# Add Vault Wizard
|
||||||
addvaultwizard.title=Дадаць скарбніцу
|
addvaultwizard.title=Дадаць скарбніцу
|
||||||
## Welcome
|
|
||||||
addvaultwizard.welcome.newButton=Стварыць новую скарбніцу
|
|
||||||
addvaultwizard.welcome.existingButton=Адчыніць існуючую скарбніцу
|
|
||||||
## New
|
## New
|
||||||
### Name
|
### Name
|
||||||
addvaultwizard.new.nameInstruction=Абраць назву для скарбніцы
|
addvaultwizard.new.nameInstruction=Абраць назву для скарбніцы
|
||||||
@@ -353,7 +350,7 @@ main.vaultlist.contextMenu.unlock=Адамкнуць…
|
|||||||
main.vaultlist.contextMenu.unlockNow=Разамкнуць зараз
|
main.vaultlist.contextMenu.unlockNow=Разамкнуць зараз
|
||||||
main.vaultlist.contextMenu.vaultoptions=Паказаць параметры скарбніцы
|
main.vaultlist.contextMenu.vaultoptions=Паказаць параметры скарбніцы
|
||||||
main.vaultlist.contextMenu.reveal=Паказаць дыск
|
main.vaultlist.contextMenu.reveal=Паказаць дыск
|
||||||
main.vaultlist.addVaultBtn=Дадаць скарбніцу
|
main.vaultlist.addVaultBtn=Дадаць
|
||||||
## Vault Detail
|
## Vault Detail
|
||||||
### Welcome
|
### Welcome
|
||||||
main.vaultDetail.welcomeOnboarding=Дзякуй, што ты абраў Cryptomator для абароны тваіх файлаў. Калі табе патрэбна дапамога, калі ласка, паглядзі нашы інструкцыі:
|
main.vaultDetail.welcomeOnboarding=Дзякуй, што ты абраў Cryptomator для абароны тваіх файлаў. Калі табе патрэбна дапамога, калі ласка, паглядзі нашы інструкцыі:
|
||||||
|
|||||||
@@ -35,9 +35,6 @@ traymenu.vault.reveal=Разкриване
|
|||||||
|
|
||||||
# Add Vault Wizard
|
# Add Vault Wizard
|
||||||
addvaultwizard.title=Добавяне на хранилище
|
addvaultwizard.title=Добавяне на хранилище
|
||||||
## Welcome
|
|
||||||
addvaultwizard.welcome.newButton=Ново хранилище
|
|
||||||
addvaultwizard.welcome.existingButton=Отваряне на хранилище
|
|
||||||
## New
|
## New
|
||||||
### Name
|
### Name
|
||||||
addvaultwizard.new.nameInstruction=Изберете име на хранилището
|
addvaultwizard.new.nameInstruction=Изберете име на хранилището
|
||||||
@@ -156,6 +153,7 @@ hub.invalidLicense.message=Лиценза за Hub е недействителе
|
|||||||
|
|
||||||
# Lock
|
# Lock
|
||||||
## Force
|
## Force
|
||||||
|
lock.forced.retryBtn=Повторен опит
|
||||||
## Failure
|
## Failure
|
||||||
|
|
||||||
# Migration
|
# Migration
|
||||||
@@ -216,7 +214,6 @@ main.vaultlist.contextMenu.unlock=Отключване…
|
|||||||
main.vaultlist.contextMenu.unlockNow=Отключване сега
|
main.vaultlist.contextMenu.unlockNow=Отключване сега
|
||||||
main.vaultlist.contextMenu.vaultoptions=Настройки на хранилището
|
main.vaultlist.contextMenu.vaultoptions=Настройки на хранилището
|
||||||
main.vaultlist.contextMenu.reveal=Разкриване на диска
|
main.vaultlist.contextMenu.reveal=Разкриване на диска
|
||||||
main.vaultlist.addVaultBtn=Добавяне на хранилище
|
|
||||||
## Vault Detail
|
## Vault Detail
|
||||||
### Welcome
|
### Welcome
|
||||||
### Locked
|
### Locked
|
||||||
|
|||||||
@@ -33,9 +33,6 @@ traymenu.vault.lock=লক করুন
|
|||||||
|
|
||||||
# Add Vault Wizard
|
# Add Vault Wizard
|
||||||
addvaultwizard.title=ভোল্ট যুক্ত করুন
|
addvaultwizard.title=ভোল্ট যুক্ত করুন
|
||||||
## Welcome
|
|
||||||
addvaultwizard.welcome.newButton=নতুন ভোল্ট তৈরি করুন
|
|
||||||
addvaultwizard.welcome.existingButton=বিদ্যমান কোনো ভোল্ট খুলুন
|
|
||||||
## New
|
## New
|
||||||
### Name
|
### Name
|
||||||
addvaultwizard.new.nameInstruction=ভোল্ট এর একটি নাম দেন
|
addvaultwizard.new.nameInstruction=ভোল্ট এর একটি নাম দেন
|
||||||
@@ -139,7 +136,6 @@ lock.forced.retryBtn=পুনরায় চেষ্টা করুন
|
|||||||
main.closeBtn.tooltip=বন্ধ করুন
|
main.closeBtn.tooltip=বন্ধ করুন
|
||||||
## Vault List
|
## Vault List
|
||||||
main.vaultlist.contextMenu.lock=লক করুন
|
main.vaultlist.contextMenu.lock=লক করুন
|
||||||
main.vaultlist.addVaultBtn=ভোল্ট যুক্ত করুন
|
|
||||||
## Vault Detail
|
## Vault Detail
|
||||||
### Welcome
|
### Welcome
|
||||||
### Locked
|
### Locked
|
||||||
|
|||||||
@@ -31,9 +31,6 @@ traymenu.vault.reveal=Otkrij
|
|||||||
|
|
||||||
# Add Vault Wizard
|
# Add Vault Wizard
|
||||||
addvaultwizard.title=Dodaj sef
|
addvaultwizard.title=Dodaj sef
|
||||||
## Welcome
|
|
||||||
addvaultwizard.welcome.newButton=Kreiraj novi sef
|
|
||||||
addvaultwizard.welcome.existingButton=Otvori postojeći sef
|
|
||||||
## New
|
## New
|
||||||
### Name
|
### Name
|
||||||
addvaultwizard.new.nameInstruction=Izaberi naziv za sef
|
addvaultwizard.new.nameInstruction=Izaberi naziv za sef
|
||||||
@@ -219,7 +216,6 @@ main.vaultlist.contextMenu.unlock=Otključaj…
|
|||||||
main.vaultlist.contextMenu.unlockNow=Otključaj sada
|
main.vaultlist.contextMenu.unlockNow=Otključaj sada
|
||||||
main.vaultlist.contextMenu.vaultoptions=Pokaži opcije sefa
|
main.vaultlist.contextMenu.vaultoptions=Pokaži opcije sefa
|
||||||
main.vaultlist.contextMenu.reveal=Otkrij pogon
|
main.vaultlist.contextMenu.reveal=Otkrij pogon
|
||||||
main.vaultlist.addVaultBtn=Dodaj sef
|
|
||||||
## Vault Detail
|
## Vault Detail
|
||||||
### Welcome
|
### Welcome
|
||||||
main.vaultDetail.welcomeOnboarding=Hvala što ste izabrali Cryptomator za zaštitu podataka. Ako vam je potrebna pomoć, pogledajte naše vodiče za početak:
|
main.vaultDetail.welcomeOnboarding=Hvala što ste izabrali Cryptomator za zaštitu podataka. Ako vam je potrebna pomoć, pogledajte naše vodiče za početak:
|
||||||
|
|||||||
@@ -37,9 +37,6 @@ traymenu.vault.reveal=Mostra
|
|||||||
|
|
||||||
# Add Vault Wizard
|
# Add Vault Wizard
|
||||||
addvaultwizard.title=Afegir una caixa forta
|
addvaultwizard.title=Afegir una caixa forta
|
||||||
## Welcome
|
|
||||||
addvaultwizard.welcome.newButton=Crea una caixa forta nova
|
|
||||||
addvaultwizard.welcome.existingButton=Obri una caixa forta existent
|
|
||||||
## New
|
## New
|
||||||
### Name
|
### Name
|
||||||
addvaultwizard.new.nameInstruction=Introduiu el nom de la caixa forta
|
addvaultwizard.new.nameInstruction=Introduiu el nom de la caixa forta
|
||||||
@@ -351,7 +348,7 @@ main.vaultlist.contextMenu.unlock=Desbloca…
|
|||||||
main.vaultlist.contextMenu.unlockNow=Desbloqueja ara
|
main.vaultlist.contextMenu.unlockNow=Desbloqueja ara
|
||||||
main.vaultlist.contextMenu.vaultoptions=Opcions de la caixa forta
|
main.vaultlist.contextMenu.vaultoptions=Opcions de la caixa forta
|
||||||
main.vaultlist.contextMenu.reveal=Mostra la unitat
|
main.vaultlist.contextMenu.reveal=Mostra la unitat
|
||||||
main.vaultlist.addVaultBtn=Afegir una caixa forta
|
main.vaultlist.addVaultBtn=Afegir
|
||||||
## Vault Detail
|
## Vault Detail
|
||||||
### Welcome
|
### Welcome
|
||||||
main.vaultDetail.welcomeOnboarding=Gràcies per escollir Cryptomator per protegir els vostres fitxers. Si vos cal ajuda, llegiu les nostres guies per donar els Primers passos:
|
main.vaultDetail.welcomeOnboarding=Gràcies per escollir Cryptomator per protegir els vostres fitxers. Si vos cal ajuda, llegiu les nostres guies per donar els Primers passos:
|
||||||
|
|||||||
@@ -38,9 +38,6 @@ traymenu.vault.reveal=Zobrazit
|
|||||||
|
|
||||||
# Add Vault Wizard
|
# Add Vault Wizard
|
||||||
addvaultwizard.title=Přidat trezor
|
addvaultwizard.title=Přidat trezor
|
||||||
## Welcome
|
|
||||||
addvaultwizard.welcome.newButton=Vytvořit nový trezor
|
|
||||||
addvaultwizard.welcome.existingButton=Otevřít existující trezor
|
|
||||||
## New
|
## New
|
||||||
### Name
|
### Name
|
||||||
addvaultwizard.new.nameInstruction=Zvolte jméno trezoru
|
addvaultwizard.new.nameInstruction=Zvolte jméno trezoru
|
||||||
@@ -341,7 +338,7 @@ main.vaultlist.contextMenu.unlock=Odemknout…
|
|||||||
main.vaultlist.contextMenu.unlockNow=Odemknout nyní
|
main.vaultlist.contextMenu.unlockNow=Odemknout nyní
|
||||||
main.vaultlist.contextMenu.vaultoptions=Zobrazit možnosti trezoru
|
main.vaultlist.contextMenu.vaultoptions=Zobrazit možnosti trezoru
|
||||||
main.vaultlist.contextMenu.reveal=Zobrazit jednotku
|
main.vaultlist.contextMenu.reveal=Zobrazit jednotku
|
||||||
main.vaultlist.addVaultBtn=Přidat trezor
|
main.vaultlist.addVaultBtn=Přidat
|
||||||
## Vault Detail
|
## Vault Detail
|
||||||
### Welcome
|
### Welcome
|
||||||
main.vaultDetail.welcomeOnboarding=Děkujeme, že jste si vybrali Cryptomator pro ochranu vašich souborů. Pokud potřebujete pomoc, podívejte se na naše návody:
|
main.vaultDetail.welcomeOnboarding=Děkujeme, že jste si vybrali Cryptomator pro ochranu vašich souborů. Pokud potřebujete pomoc, podívejte se na naše návody:
|
||||||
|
|||||||
@@ -37,9 +37,6 @@ traymenu.vault.reveal=Vis
|
|||||||
|
|
||||||
# Add Vault Wizard
|
# Add Vault Wizard
|
||||||
addvaultwizard.title=Tilføj boks
|
addvaultwizard.title=Tilføj boks
|
||||||
## Welcome
|
|
||||||
addvaultwizard.welcome.newButton=Opret ny boks
|
|
||||||
addvaultwizard.welcome.existingButton=Open eksisterende boks
|
|
||||||
## New
|
## New
|
||||||
### Name
|
### Name
|
||||||
addvaultwizard.new.nameInstruction=Vælg et navn til boksen
|
addvaultwizard.new.nameInstruction=Vælg et navn til boksen
|
||||||
@@ -358,7 +355,7 @@ main.vaultlist.contextMenu.unlock=Lås op…
|
|||||||
main.vaultlist.contextMenu.unlockNow=Lås op nu
|
main.vaultlist.contextMenu.unlockNow=Lås op nu
|
||||||
main.vaultlist.contextMenu.vaultoptions=Vis boksindstillinger
|
main.vaultlist.contextMenu.vaultoptions=Vis boksindstillinger
|
||||||
main.vaultlist.contextMenu.reveal=Vis drev
|
main.vaultlist.contextMenu.reveal=Vis drev
|
||||||
main.vaultlist.addVaultBtn=Tilføj boks
|
main.vaultlist.addVaultBtn=Tilføj
|
||||||
## Vault Detail
|
## Vault Detail
|
||||||
### Welcome
|
### Welcome
|
||||||
main.vaultDetail.welcomeOnboarding=Tak fordi du valgte Cryptomator til at beskytte dine filer. Hvis du har brug for hjælp, så tjek vores guider for at komme i gang:
|
main.vaultDetail.welcomeOnboarding=Tak fordi du valgte Cryptomator til at beskytte dine filer. Hvis du har brug for hjælp, så tjek vores guider for at komme i gang:
|
||||||
|
|||||||
@@ -40,10 +40,8 @@ traymenu.vault.reveal=Anzeigen
|
|||||||
|
|
||||||
# Add Vault Wizard
|
# Add Vault Wizard
|
||||||
addvaultwizard.title=Tresor hinzufügen
|
addvaultwizard.title=Tresor hinzufügen
|
||||||
## Welcome
|
|
||||||
addvaultwizard.welcome.newButton=Neuen Tresor erstellen
|
|
||||||
addvaultwizard.welcome.existingButton=Bestehenden Tresor öffnen
|
|
||||||
## New
|
## New
|
||||||
|
addvaultwizard.new.title=Neuen Tresor hinzufügen
|
||||||
### Name
|
### Name
|
||||||
addvaultwizard.new.nameInstruction=Wähle einen Namen für den Tresor
|
addvaultwizard.new.nameInstruction=Wähle einen Namen für den Tresor
|
||||||
addvaultwizard.new.namePrompt=Tresorname
|
addvaultwizard.new.namePrompt=Tresorname
|
||||||
@@ -93,6 +91,7 @@ addvault.new.readme.accessLocation.2=Dies ist der Zugangsort deines Tresors.
|
|||||||
addvault.new.readme.accessLocation.3=Alle zu diesem Laufwerk hinzugefügten Dateien werden von Cryptomator verschlüsselt. Du kannst mit diesem arbeiten wie mit jedem anderen Laufwerk bzw. Ordner. Dies ist lediglich eine unverschlüsselte Ansicht des Laufwerkinhalts; auf deiner Festplatte bleiben deine Dateien weiterhin verschlüsselt.
|
addvault.new.readme.accessLocation.3=Alle zu diesem Laufwerk hinzugefügten Dateien werden von Cryptomator verschlüsselt. Du kannst mit diesem arbeiten wie mit jedem anderen Laufwerk bzw. Ordner. Dies ist lediglich eine unverschlüsselte Ansicht des Laufwerkinhalts; auf deiner Festplatte bleiben deine Dateien weiterhin verschlüsselt.
|
||||||
addvault.new.readme.accessLocation.4=Du kannst diese Datei löschen.
|
addvault.new.readme.accessLocation.4=Du kannst diese Datei löschen.
|
||||||
## Existing
|
## Existing
|
||||||
|
addvaultwizard.existing.title=Bestehenden Tresor hinzufügen
|
||||||
addvaultwizard.existing.instruction=Wähle die Datei „vault.cryptomator“ deines bestehenden Tresors aus. Falls nur eine Datei mit der Bezeichnung „masterkey.cryptomator“ vorhanden ist, nutze stattdessen diese.
|
addvaultwizard.existing.instruction=Wähle die Datei „vault.cryptomator“ deines bestehenden Tresors aus. Falls nur eine Datei mit der Bezeichnung „masterkey.cryptomator“ vorhanden ist, nutze stattdessen diese.
|
||||||
addvaultwizard.existing.chooseBtn=Durchsuchen …
|
addvaultwizard.existing.chooseBtn=Durchsuchen …
|
||||||
addvaultwizard.existing.filePickerTitle=Tresordatei auswählen
|
addvaultwizard.existing.filePickerTitle=Tresordatei auswählen
|
||||||
@@ -138,7 +137,7 @@ unlock.error.customPath.message=Tresor kann nicht in benutzerdefinierten Pfad ei
|
|||||||
unlock.error.customPath.description.notSupported=Wenn du weiterhin den benutzerdefinierten Pfad verwenden möchtest, öffne die allgemeinen Einstellungen und wähle einen unterstützten Laufwerkstyp. Andernfalls gehe zu den Tresor-Optionen und wähle einen unterstützten Einhängepunkt.
|
unlock.error.customPath.description.notSupported=Wenn du weiterhin den benutzerdefinierten Pfad verwenden möchtest, öffne die allgemeinen Einstellungen und wähle einen unterstützten Laufwerkstyp. Andernfalls gehe zu den Tresor-Optionen und wähle einen unterstützten Einhängepunkt.
|
||||||
unlock.error.customPath.description.notExists=Der benutzerdefinierte Einhängepunkt existiert nicht. Erstelle ihn in deinem lokalen Dateisystem oder ändere ihn in den Tresor-Optionen.
|
unlock.error.customPath.description.notExists=Der benutzerdefinierte Einhängepunkt existiert nicht. Erstelle ihn in deinem lokalen Dateisystem oder ändere ihn in den Tresor-Optionen.
|
||||||
unlock.error.customPath.description.inUse=Der Laufwerksbuchstabe oder benutzerdefinierte Einhängepunkt „%s“ wird bereits verwendet.
|
unlock.error.customPath.description.inUse=Der Laufwerksbuchstabe oder benutzerdefinierte Einhängepunkt „%s“ wird bereits verwendet.
|
||||||
unlock.error.customPath.description.hideawayNotDir=Die temporäre, versteckte Datei "%3$s", die für das Entsperren verwendet wurde, konnte nicht entfernt werden. Bitte überprüfe die Datei und lösche sie manuell.
|
unlock.error.customPath.description.hideawayNotDir=Die temporäre, versteckte Datei „%3$s“, die für das Entsperren verwendet wurde, konnte nicht entfernt werden. Bitte überprüfe die Datei und lösche sie manuell.
|
||||||
unlock.error.customPath.description.couldNotBeCleaned=Dein Tresor konnte nicht in den Pfad „%s“ eingehängt werden. Bitte versuche es erneut oder wähle einen anderen Pfad aus.
|
unlock.error.customPath.description.couldNotBeCleaned=Dein Tresor konnte nicht in den Pfad „%s“ eingehängt werden. Bitte versuche es erneut oder wähle einen anderen Pfad aus.
|
||||||
unlock.error.customPath.description.notEmptyDir=Der benutzerdefinierte Einhängepunkt "%s" ist kein leerer Ordner. Bitte wähle einen leeren Ordner und versuche es erneut.
|
unlock.error.customPath.description.notEmptyDir=Der benutzerdefinierte Einhängepunkt "%s" ist kein leerer Ordner. Bitte wähle einen leeren Ordner und versuche es erneut.
|
||||||
unlock.error.customPath.description.generic=Du hast für diesen Tresor einen benutzerdefinierten Einhängepunkt ausgewählt, aber dessen Verwendung ist mit folgender Meldung fehlgeschlagen: %2$s
|
unlock.error.customPath.description.generic=Du hast für diesen Tresor einen benutzerdefinierten Einhängepunkt ausgewählt, aber dessen Verwendung ist mit folgender Meldung fehlgeschlagen: %2$s
|
||||||
@@ -366,7 +365,9 @@ main.vaultlist.contextMenu.unlock=Entsperren …
|
|||||||
main.vaultlist.contextMenu.unlockNow=Jetzt entsperren
|
main.vaultlist.contextMenu.unlockNow=Jetzt entsperren
|
||||||
main.vaultlist.contextMenu.vaultoptions=Tresoroptionen anzeigen
|
main.vaultlist.contextMenu.vaultoptions=Tresoroptionen anzeigen
|
||||||
main.vaultlist.contextMenu.reveal=Laufwerk anzeigen
|
main.vaultlist.contextMenu.reveal=Laufwerk anzeigen
|
||||||
main.vaultlist.addVaultBtn=Tresor hinzufügen
|
main.vaultlist.addVaultBtn=Hinzufügen
|
||||||
|
main.vaultlist.addVaultBtn.menuItemNew=Neuer Tresor …
|
||||||
|
main.vaultlist.addVaultBtn.menuItemExisting=Bestehender Tresor …
|
||||||
## Vault Detail
|
## Vault Detail
|
||||||
### Welcome
|
### Welcome
|
||||||
main.vaultDetail.welcomeOnboarding=Danke, dass du zum Schutz deiner Dateien Cryptomator gewählt hast. Falls du Hilfe brauchst, schau dir unsere Anleitungen an:
|
main.vaultDetail.welcomeOnboarding=Danke, dass du zum Schutz deiner Dateien Cryptomator gewählt hast. Falls du Hilfe brauchst, schau dir unsere Anleitungen an:
|
||||||
|
|||||||
@@ -40,10 +40,8 @@ traymenu.vault.reveal=Αποκάλυψη
|
|||||||
|
|
||||||
# Add Vault Wizard
|
# Add Vault Wizard
|
||||||
addvaultwizard.title=Προσθήκη Κρύπτης
|
addvaultwizard.title=Προσθήκη Κρύπτης
|
||||||
## Welcome
|
|
||||||
addvaultwizard.welcome.newButton=Δημιουργία Νέας Κρύπτης
|
|
||||||
addvaultwizard.welcome.existingButton=Άνοιγμα Υπάρχοντος Κρύπτης
|
|
||||||
## New
|
## New
|
||||||
|
addvaultwizard.new.title=Προσθήκη Νέας Κρύπτης
|
||||||
### Name
|
### Name
|
||||||
addvaultwizard.new.nameInstruction=Διαλέξτε ένα όνομα για την κρύπτη
|
addvaultwizard.new.nameInstruction=Διαλέξτε ένα όνομα για την κρύπτη
|
||||||
addvaultwizard.new.namePrompt=Όνομα Κρύπτης
|
addvaultwizard.new.namePrompt=Όνομα Κρύπτης
|
||||||
@@ -93,6 +91,7 @@ addvault.new.readme.accessLocation.2=Αυτή είναι η τοποθεσία
|
|||||||
addvault.new.readme.accessLocation.3=Κάθε αρχείο που θα προστεθεί σε αυτό τον τόμο θα κωδικοποιηθεί από το Cryptomator. Μπορείτε να το επεξεργαστείτε όπως θα κάνατε σε κάθε δίσκο/φάκελο. Αυτή είναι μόνο για αποκρυπτογραφημένη μορφή των περιεχομένων του, τα αρχεία σας μένουν κωδικοποιημένα στον σκληρό σας δίσκο συνέχεια.
|
addvault.new.readme.accessLocation.3=Κάθε αρχείο που θα προστεθεί σε αυτό τον τόμο θα κωδικοποιηθεί από το Cryptomator. Μπορείτε να το επεξεργαστείτε όπως θα κάνατε σε κάθε δίσκο/φάκελο. Αυτή είναι μόνο για αποκρυπτογραφημένη μορφή των περιεχομένων του, τα αρχεία σας μένουν κωδικοποιημένα στον σκληρό σας δίσκο συνέχεια.
|
||||||
addvault.new.readme.accessLocation.4=Μπορείτε ελεύθερα να αφαιρέσετε το αρχείο.
|
addvault.new.readme.accessLocation.4=Μπορείτε ελεύθερα να αφαιρέσετε το αρχείο.
|
||||||
## Existing
|
## Existing
|
||||||
|
addvaultwizard.existing.title=Προσθήκη Υπάρχοντος Κρύπτης
|
||||||
addvaultwizard.existing.instruction=Επιλέξτε το αρχείο "vault.cryptomator" της υπάρχοντος κρύπτης. Αν υπάρχει μόνο ένα αρχείο με όνομα "masterkey.cryptomator", επιλέξτε αυτό.
|
addvaultwizard.existing.instruction=Επιλέξτε το αρχείο "vault.cryptomator" της υπάρχοντος κρύπτης. Αν υπάρχει μόνο ένα αρχείο με όνομα "masterkey.cryptomator", επιλέξτε αυτό.
|
||||||
addvaultwizard.existing.chooseBtn=Επιλογή…
|
addvaultwizard.existing.chooseBtn=Επιλογή…
|
||||||
addvaultwizard.existing.filePickerTitle=Επιλέξτε Αρχείο Κρύπτης
|
addvaultwizard.existing.filePickerTitle=Επιλέξτε Αρχείο Κρύπτης
|
||||||
@@ -138,7 +137,7 @@ unlock.error.customPath.message=Αδυναμία προσάρτησης της
|
|||||||
unlock.error.customPath.description.notSupported=Εάν θέλετε να συνεχίσετε να χρησιμοποιείτε την προσαρμοσμένη διαδρομή, μεταβείτε στις προτιμήσεις και επιλέξτε έναν τύπο τόμου που την υποστηρίζει. Διαφορετικά, μεταβείτε στις επιλογές της κρύπτης και επιλέξτε ένα υποστηριζόμενο σημείο προσάρτησης.
|
unlock.error.customPath.description.notSupported=Εάν θέλετε να συνεχίσετε να χρησιμοποιείτε την προσαρμοσμένη διαδρομή, μεταβείτε στις προτιμήσεις και επιλέξτε έναν τύπο τόμου που την υποστηρίζει. Διαφορετικά, μεταβείτε στις επιλογές της κρύπτης και επιλέξτε ένα υποστηριζόμενο σημείο προσάρτησης.
|
||||||
unlock.error.customPath.description.notExists=Η προσαρμοσμένη διαδρομή προσάρτησης δεν υπάρχει. Είτε δημιουργήστε την στο τοπικό σύστημα αρχείων σας είτε αλλάξτε την στις επιλογές κρύπτης.
|
unlock.error.customPath.description.notExists=Η προσαρμοσμένη διαδρομή προσάρτησης δεν υπάρχει. Είτε δημιουργήστε την στο τοπικό σύστημα αρχείων σας είτε αλλάξτε την στις επιλογές κρύπτης.
|
||||||
unlock.error.customPath.description.inUse=Το γράμμα μονάδας δίσκου ή προσαρμοσμένη διαδρομή προσάρτησης "%s" είναι ήδη σε χρήση.
|
unlock.error.customPath.description.inUse=Το γράμμα μονάδας δίσκου ή προσαρμοσμένη διαδρομή προσάρτησης "%s" είναι ήδη σε χρήση.
|
||||||
unlock.error.customPath.description.hideawayNotDir=Το προσωρινό, κρυφό αρχείο "%3$s" που χρησιμοποιείται για το ξεκλείδωμα δεν μπορεί να αφαιρεθεί. Παρακαλώ ελέγξτε το αρχείο και στη συνέχεια διαγράψτε το χειροκίνητα.
|
unlock.error.customPath.description.hideawayNotDir=Το προσωρινό, κρυφό αρχείο "%3$s" που χρησιμοποιείται για το ξεκλείδωμα δεν μπορεί να αφαιρεθεί. Παρακαλούμε ελέγξτε το αρχείο και στη συνέχεια διαγράψτε το χειροκίνητα.
|
||||||
unlock.error.customPath.description.couldNotBeCleaned=Η κρύπτη σας δεν μπορεί να τοποθετηθεί στη διαδρομή "%s". Παρακαλώ δοκιμάστε ξανά ή επιλέξτε διαφορετική διαδρομή.
|
unlock.error.customPath.description.couldNotBeCleaned=Η κρύπτη σας δεν μπορεί να τοποθετηθεί στη διαδρομή "%s". Παρακαλώ δοκιμάστε ξανά ή επιλέξτε διαφορετική διαδρομή.
|
||||||
unlock.error.customPath.description.notEmptyDir=Η προσαρμοσμένη διαδρομή προσάρτησης "%s" δεν είναι ένας άδειος φάκελος. Παρακαλώ επιλέξτε έναν άδειο φάκελο και προσπαθήστε ξανά.
|
unlock.error.customPath.description.notEmptyDir=Η προσαρμοσμένη διαδρομή προσάρτησης "%s" δεν είναι ένας άδειος φάκελος. Παρακαλώ επιλέξτε έναν άδειο φάκελο και προσπαθήστε ξανά.
|
||||||
unlock.error.customPath.description.generic=Έχετε επιλέξει μια προσαρμοσμένη διαδρομή προσάρτησης για αυτή την κρύπτη, αλλά η χρήση της απέτυχε με το μήνυμα: %2$s
|
unlock.error.customPath.description.generic=Έχετε επιλέξει μια προσαρμοσμένη διαδρομή προσάρτησης για αυτή την κρύπτη, αλλά η χρήση της απέτυχε με το μήνυμα: %2$s
|
||||||
@@ -366,7 +365,9 @@ main.vaultlist.contextMenu.unlock=Ξεκλείδωμα…
|
|||||||
main.vaultlist.contextMenu.unlockNow=Ξεκλείδωμα τώρα
|
main.vaultlist.contextMenu.unlockNow=Ξεκλείδωμα τώρα
|
||||||
main.vaultlist.contextMenu.vaultoptions=Εμφάνιση επιλογών Vault
|
main.vaultlist.contextMenu.vaultoptions=Εμφάνιση επιλογών Vault
|
||||||
main.vaultlist.contextMenu.reveal=Αποκάλυψη εικονικού δίσκου
|
main.vaultlist.contextMenu.reveal=Αποκάλυψη εικονικού δίσκου
|
||||||
main.vaultlist.addVaultBtn=Προσθήκη Κρύπτης
|
main.vaultlist.addVaultBtn=Προσθήκη
|
||||||
|
main.vaultlist.addVaultBtn.menuItemNew=Νέα Κρύπτη...
|
||||||
|
main.vaultlist.addVaultBtn.menuItemExisting=Υπάρχουσα Κρύπτη...
|
||||||
## Vault Detail
|
## Vault Detail
|
||||||
### Welcome
|
### Welcome
|
||||||
main.vaultDetail.welcomeOnboarding=Ευχαριστούμε που επιλέξατε το Cryptomator για να προστατεύσετε τα αρχεία σας. Αν χρειάζεστε οποιαδήποτε βοήθεια, ελέγξτε τους οδηγούς για αρχάριους:
|
main.vaultDetail.welcomeOnboarding=Ευχαριστούμε που επιλέξατε το Cryptomator για να προστατεύσετε τα αρχεία σας. Αν χρειάζεστε οποιαδήποτε βοήθεια, ελέγξτε τους οδηγούς για αρχάριους:
|
||||||
|
|||||||
@@ -40,10 +40,8 @@ traymenu.vault.reveal=Revelar
|
|||||||
|
|
||||||
# Add Vault Wizard
|
# Add Vault Wizard
|
||||||
addvaultwizard.title=Añadir bóveda
|
addvaultwizard.title=Añadir bóveda
|
||||||
## Welcome
|
|
||||||
addvaultwizard.welcome.newButton=Crear bóveda nueva
|
|
||||||
addvaultwizard.welcome.existingButton=Abrir bóveda existente
|
|
||||||
## New
|
## New
|
||||||
|
addvaultwizard.new.title=Añadir nueva bóveda
|
||||||
### Name
|
### Name
|
||||||
addvaultwizard.new.nameInstruction=Elegir un nombre para la bóveda
|
addvaultwizard.new.nameInstruction=Elegir un nombre para la bóveda
|
||||||
addvaultwizard.new.namePrompt=Nombre de la bóveda
|
addvaultwizard.new.namePrompt=Nombre de la bóveda
|
||||||
@@ -93,6 +91,7 @@ addvault.new.readme.accessLocation.2=Este es el lugar de acceso de la bóveda.
|
|||||||
addvault.new.readme.accessLocation.3=Los archivos agregados a este volumen serán cifrados por Cryptomator. Se puede trabajar con ellos como en cualquier otra unidad/carpeta. Esta es sólo una vista descifrada del contenido, sus archivos permanecen cifrados en el disco duro todo el tiempo.
|
addvault.new.readme.accessLocation.3=Los archivos agregados a este volumen serán cifrados por Cryptomator. Se puede trabajar con ellos como en cualquier otra unidad/carpeta. Esta es sólo una vista descifrada del contenido, sus archivos permanecen cifrados en el disco duro todo el tiempo.
|
||||||
addvault.new.readme.accessLocation.4=No dude en eliminar este archivo.
|
addvault.new.readme.accessLocation.4=No dude en eliminar este archivo.
|
||||||
## Existing
|
## Existing
|
||||||
|
addvaultwizard.existing.title=Añadir bóveda existente
|
||||||
addvaultwizard.existing.instruction=Elija el archivo "vault.cryptomator" de su bóveda existente. Si solo existe un archivo llamado "masterkey.cryptomator", selecciónelo en su lugar.
|
addvaultwizard.existing.instruction=Elija el archivo "vault.cryptomator" de su bóveda existente. Si solo existe un archivo llamado "masterkey.cryptomator", selecciónelo en su lugar.
|
||||||
addvaultwizard.existing.chooseBtn=Elegir…
|
addvaultwizard.existing.chooseBtn=Elegir…
|
||||||
addvaultwizard.existing.filePickerTitle=Seleccionar archivo de bóveda
|
addvaultwizard.existing.filePickerTitle=Seleccionar archivo de bóveda
|
||||||
@@ -366,7 +365,9 @@ main.vaultlist.contextMenu.unlock=Desbloquear…
|
|||||||
main.vaultlist.contextMenu.unlockNow=Desbloquear ahora
|
main.vaultlist.contextMenu.unlockNow=Desbloquear ahora
|
||||||
main.vaultlist.contextMenu.vaultoptions=Mostrar opciones de la bóveda
|
main.vaultlist.contextMenu.vaultoptions=Mostrar opciones de la bóveda
|
||||||
main.vaultlist.contextMenu.reveal=Revelar unidad
|
main.vaultlist.contextMenu.reveal=Revelar unidad
|
||||||
main.vaultlist.addVaultBtn=Añadir bóveda
|
main.vaultlist.addVaultBtn=Añadir
|
||||||
|
main.vaultlist.addVaultBtn.menuItemNew=Nueva bóveda...
|
||||||
|
main.vaultlist.addVaultBtn.menuItemExisting=Bóveda existente...
|
||||||
## Vault Detail
|
## Vault Detail
|
||||||
### Welcome
|
### Welcome
|
||||||
main.vaultDetail.welcomeOnboarding=Gracias por elegir Cryptomator para proteger sus archivos. En caso de necesitar ayuda, revise nuestras guías:
|
main.vaultDetail.welcomeOnboarding=Gracias por elegir Cryptomator para proteger sus archivos. En caso de necesitar ayuda, revise nuestras guías:
|
||||||
|
|||||||
@@ -30,9 +30,6 @@ traymenu.vault.reveal=ظاهر
|
|||||||
|
|
||||||
# Add Vault Wizard
|
# Add Vault Wizard
|
||||||
addvaultwizard.title=اضافه کردن گاوصندوق
|
addvaultwizard.title=اضافه کردن گاوصندوق
|
||||||
## Welcome
|
|
||||||
addvaultwizard.welcome.newButton=ساخت گاوصندوق جدید
|
|
||||||
addvaultwizard.welcome.existingButton=باز کردن گاوصندوق موجود
|
|
||||||
## New
|
## New
|
||||||
### Name
|
### Name
|
||||||
addvaultwizard.new.nameInstruction=یک نام برای گاوصندوق انتخاب کنید
|
addvaultwizard.new.nameInstruction=یک نام برای گاوصندوق انتخاب کنید
|
||||||
@@ -112,7 +109,6 @@ main.closeBtn.tooltip=ببند
|
|||||||
main.supporterCertificateMissing.tooltip=لطفا کمک مالی در نظر بگیرند
|
main.supporterCertificateMissing.tooltip=لطفا کمک مالی در نظر بگیرند
|
||||||
## Vault List
|
## Vault List
|
||||||
main.vaultlist.contextMenu.lock=قفل
|
main.vaultlist.contextMenu.lock=قفل
|
||||||
main.vaultlist.addVaultBtn=اضافه کردن گاوصندوق
|
|
||||||
## Vault Detail
|
## Vault Detail
|
||||||
### Welcome
|
### Welcome
|
||||||
### Locked
|
### Locked
|
||||||
|
|||||||
@@ -37,9 +37,6 @@ traymenu.vault.reveal=Paljasta
|
|||||||
|
|
||||||
# Add Vault Wizard
|
# Add Vault Wizard
|
||||||
addvaultwizard.title=Lisää Vault
|
addvaultwizard.title=Lisää Vault
|
||||||
## Welcome
|
|
||||||
addvaultwizard.welcome.newButton=Luo Uusi Vault
|
|
||||||
addvaultwizard.welcome.existingButton=Avaa Olemassaoleva Vault
|
|
||||||
## New
|
## New
|
||||||
### Name
|
### Name
|
||||||
addvaultwizard.new.nameInstruction=Anna uusi nimi Vaultille
|
addvaultwizard.new.nameInstruction=Anna uusi nimi Vaultille
|
||||||
@@ -233,7 +230,6 @@ main.preferencesBtn.tooltip=Asetukset
|
|||||||
main.vaultlist.contextMenu.lock=Lukitse
|
main.vaultlist.contextMenu.lock=Lukitse
|
||||||
main.vaultlist.contextMenu.unlockNow=Avaa Nyt
|
main.vaultlist.contextMenu.unlockNow=Avaa Nyt
|
||||||
main.vaultlist.contextMenu.reveal=Paljasta Asema
|
main.vaultlist.contextMenu.reveal=Paljasta Asema
|
||||||
main.vaultlist.addVaultBtn=Lisää Vault
|
|
||||||
## Vault Detail
|
## Vault Detail
|
||||||
### Welcome
|
### Welcome
|
||||||
### Locked
|
### Locked
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ error.description=Oops! Hindi inaasahan ng Cryptomator na ito'y mangyari. Maaari
|
|||||||
error.hyperlink.lookup=Hanapin ang solusyon
|
error.hyperlink.lookup=Hanapin ang solusyon
|
||||||
error.hyperlink.report=I-report ang problema
|
error.hyperlink.report=I-report ang problema
|
||||||
error.technicalDetails=Mga detalye:
|
error.technicalDetails=Mga detalye:
|
||||||
|
error.existingSolutionDescription=Hindi inaasahan ng Cryptomator na mangyayari ito. Ngunit nakakita kami ng kasalukuyang solusyon para sa error na ito. Mangyaring tingnan ang sumusunod na link.
|
||||||
|
error.hyperlink.solution=Hanapin ang solusyon
|
||||||
|
error.lookupPermissionMessage=Maaaring maghanap ng solusyon ang Cryptomator para sa problemang ito online. Magpapadala ito ng kahilingan sa aming database ng problema mula sa iyong IP address.
|
||||||
|
error.dismiss=I-dismiss
|
||||||
error.lookUpSolution=Itignan ang solusyon
|
error.lookUpSolution=Itignan ang solusyon
|
||||||
|
|
||||||
# Defaults
|
# Defaults
|
||||||
@@ -36,10 +40,8 @@ traymenu.vault.reveal=Ipakita
|
|||||||
|
|
||||||
# Add Vault Wizard
|
# Add Vault Wizard
|
||||||
addvaultwizard.title=Magdagdag ng Vault
|
addvaultwizard.title=Magdagdag ng Vault
|
||||||
## Welcome
|
|
||||||
addvaultwizard.welcome.newButton=Gumawa ng Bagong Vault
|
|
||||||
addvaultwizard.welcome.existingButton=Magbukas ng Umiiral na Vault
|
|
||||||
## New
|
## New
|
||||||
|
addvaultwizard.new.title=Magdagdag ng Bagong Vault
|
||||||
### Name
|
### Name
|
||||||
addvaultwizard.new.nameInstruction=Pangalanan ang vault
|
addvaultwizard.new.nameInstruction=Pangalanan ang vault
|
||||||
addvaultwizard.new.namePrompt=Pangalan ng Vault
|
addvaultwizard.new.namePrompt=Pangalan ng Vault
|
||||||
@@ -50,34 +52,78 @@ addvaultwizard.new.locationPrompt=…
|
|||||||
addvaultwizard.new.directoryPickerLabel=Custom Location
|
addvaultwizard.new.directoryPickerLabel=Custom Location
|
||||||
addvaultwizard.new.directoryPickerButton=Mamili…
|
addvaultwizard.new.directoryPickerButton=Mamili…
|
||||||
addvaultwizard.new.directoryPickerTitle=Pumili ng Direktoryo
|
addvaultwizard.new.directoryPickerTitle=Pumili ng Direktoryo
|
||||||
|
addvaultwizard.new.fileAlreadyExists=Mayroon nang file o direktoryo na may pangalan ng vault
|
||||||
|
addvaultwizard.new.locationDoesNotExist=Ang isang direktoryo sa tinukoy na landas ay hindi umiiral o hindi ma-access
|
||||||
|
addvaultwizard.new.locationIsNotWritable=Walang access sa pagsulat sa tinukoy na landas
|
||||||
|
addvaultwizard.new.locationIsOk=Angkop na lokasyon para sa iyong vault
|
||||||
|
addvaultwizard.new.invalidName=Di-wastong pangalan ng vault
|
||||||
|
addvaultwizard.new.validName=Wastong pangalan ng vault
|
||||||
|
addvaultwizard.new.validCharacters.message=Maaaring naglalaman ang pangalan ng vault ng mga sumusunod na character:
|
||||||
|
addvaultwizard.new.validCharacters.chars=Mga character ng salita (hal. a, ж o 수)
|
||||||
|
addvaultwizard.new.validCharacters.numbers=Numero
|
||||||
|
addvaultwizard.new.validCharacters.dashes=Hyphen (%s) o underscore (%s)
|
||||||
### Expert Settings
|
### Expert Settings
|
||||||
|
addvaultwizard.new.expertSettings.enableExpertSettingsCheckbox=Paganahin ang mga setting ng eksperto
|
||||||
|
addvaultwizard.new.expertSettings.shorteningThreshold.invalid=Maglagay ng value sa pagitan ng 36 at 220 (default 220)
|
||||||
addvaultwizard.new.expertSettings.shorteningThreshold.tooltip=Ibukas ang dokumentasyon para matuto pa.
|
addvaultwizard.new.expertSettings.shorteningThreshold.tooltip=Ibukas ang dokumentasyon para matuto pa.
|
||||||
|
addvaultwizard.new.expertSettings.shorteningThreshold.title=Pinakamataas na haba ng mga naka-encrypt na pangalan ng file
|
||||||
|
addvaultwizard.new.expertSettings.shorteningThreshold.valid=Wasto
|
||||||
### Password
|
### Password
|
||||||
addvaultwizard.new.createVaultBtn=Gumawa ng bagong Vault
|
addvaultwizard.new.createVaultBtn=Gumawa ng bagong Vault
|
||||||
|
addvaultwizard.new.generateRecoveryKeyChoice=Hindi mo maa-access ang iyong data nang wala ang iyong password. Gusto mo ba ng recovery key para sa kaso na nawala mo ang iyong password?
|
||||||
|
addvaultwizard.new.generateRecoveryKeyChoice.yes=Oo pakiusap, mas ligtas kaysa sorry
|
||||||
|
addvaultwizard.new.generateRecoveryKeyChoice.no=Hindi, salamat, hindi ko mawawala ang aking password
|
||||||
### Information
|
### Information
|
||||||
|
addvault.new.readme.storageLocation.fileName=MAHALAGA.rtf
|
||||||
|
addvault.new.readme.storageLocation.1=⚠️ VAULT FILES ⚠️
|
||||||
addvault.new.readme.storageLocation.2=Ito ang lokasyon ng iyong vault na imbakan.
|
addvault.new.readme.storageLocation.2=Ito ang lokasyon ng iyong vault na imbakan.
|
||||||
addvault.new.readme.storageLocation.3=HUWAG
|
addvault.new.readme.storageLocation.3=HUWAG
|
||||||
|
addvault.new.readme.storageLocation.4=• baguhin ang anumang mga file sa loob ng direktoryong ito o
|
||||||
|
addvault.new.readme.storageLocation.5=• i-paste ang anumang mga file para sa pag-encrypt sa direktoryong ito.
|
||||||
addvault.new.readme.storageLocation.6=Kung gusto mo i-encrypt ang mga files at makita nang nilalaman ng vault, gawin ang nakasabi:
|
addvault.new.readme.storageLocation.6=Kung gusto mo i-encrypt ang mga files at makita nang nilalaman ng vault, gawin ang nakasabi:
|
||||||
addvault.new.readme.storageLocation.7=1. Idagdag ang vault na ito sa Cryptomator.
|
addvault.new.readme.storageLocation.7=1. Idagdag ang vault na ito sa Cryptomator.
|
||||||
|
addvault.new.readme.storageLocation.8=2. I-unlock ang vault sa Cryptomator.
|
||||||
|
addvault.new.readme.storageLocation.9=3. Buksan ang lokasyon ng pag-access sa pamamagitan ng pag-click sa pindutang "Ibunyag".
|
||||||
|
addvault.new.readme.storageLocation.10=Kung kailangan mo ng tulong, bisitahin ang dokumentasyon: %s
|
||||||
|
addvault.new.readme.accessLocation.fileName=WELCOME.rtf
|
||||||
|
addvault.new.readme.accessLocation.1=🔐️ Naka-encrypt na VOLUME 🔐️
|
||||||
addvault.new.readme.accessLocation.2=Ito ang lokasyon ng iyong vault.
|
addvault.new.readme.accessLocation.2=Ito ang lokasyon ng iyong vault.
|
||||||
|
addvault.new.readme.accessLocation.3=Ang anumang mga file na idinagdag sa volume na ito ay ie-encrypt ng Cryptomator. Maaari mong gawin ito tulad ng sa anumang iba pang drive/folder. Ito ay isang decrypted view lamang ng nilalaman nito, ang iyong mga file ay mananatiling naka-encrypt sa iyong hard drive sa lahat ng oras.
|
||||||
|
addvault.new.readme.accessLocation.4=Huwag mag-atubiling tanggalin ang file na ito.
|
||||||
## Existing
|
## Existing
|
||||||
|
addvaultwizard.existing.title=Magdagdag ng Umiiral na Vault
|
||||||
|
addvaultwizard.existing.instruction=Piliin ang "vault.cryptomator" file ng iyong kasalukuyang vault. Kung mayroon lamang file na may pangalang "masterkey.cryptomator," piliin iyon sa halip.
|
||||||
addvaultwizard.existing.chooseBtn=Mamili…
|
addvaultwizard.existing.chooseBtn=Mamili…
|
||||||
|
addvaultwizard.existing.filePickerTitle=Piliin ang Vault File
|
||||||
|
addvaultwizard.existing.filePickerMimeDesc=Cryptomator Vault
|
||||||
## Success
|
## Success
|
||||||
|
addvaultwizard.success.nextStepsInstructions=Idinagdag ang vault na "%s".\nKailangan mong i-unlock ang vault na ito para ma-access o magdagdag ng mga content. Bilang kahalili, maaari mo itong i-unlock sa anumang susunod na oras.
|
||||||
|
addvaultwizard.success.unlockNow=I-unlock Ngayon
|
||||||
|
|
||||||
# Remove Vault
|
# Remove Vault
|
||||||
removeVault.title=Tanggalin %s
|
removeVault.title=Tanggalin %s
|
||||||
removeVault.message=Itangal ang vault?
|
removeVault.message=Itangal ang vault?
|
||||||
|
removeVault.description=Makakalimutan lang nito ang Cryptomator tungkol sa vault na ito. Maaari mo itong idagdag muli. Walang matatanggal na mga naka-encrypt na file mula sa iyong hard drive.
|
||||||
removeVault.confirmBtn=Itangal ang vault
|
removeVault.confirmBtn=Itangal ang vault
|
||||||
|
|
||||||
# Change Password
|
# Change Password
|
||||||
|
changepassword.title=Palitan ANG password
|
||||||
|
changepassword.enterOldPassword=Ilagay ang kasalukuyang password para sa "%s"
|
||||||
|
changepassword.finalConfirmation=Naiintindihan ko na hindi ko maa-access ang aking data kung nakalimutan ko ang aking password
|
||||||
|
|
||||||
# Forget Password
|
# Forget Password
|
||||||
|
forgetPassword.title=Kalimutan ang Password
|
||||||
forgetPassword.message=Nakalimutan ang iyong password?
|
forgetPassword.message=Nakalimutan ang iyong password?
|
||||||
|
forgetPassword.description=Tatanggalin nito ang naka-save na password ng vault na ito mula sa keychain ng iyong system.
|
||||||
|
forgetPassword.confirmBtn=Kalimutan ang Password
|
||||||
|
|
||||||
# Unlock
|
# Unlock
|
||||||
|
unlock.title=I-unlock ang "%s"
|
||||||
|
unlock.passwordPrompt=Ipasok ang password para sa "%s":
|
||||||
unlock.savePassword=Maalala ang password
|
unlock.savePassword=Maalala ang password
|
||||||
unlock.unlockBtn=I-unlock
|
unlock.unlockBtn=I-unlock
|
||||||
## Select
|
## Select
|
||||||
|
unlock.chooseMasterkey.message=Hindi nahanap ang masterkey file
|
||||||
unlock.chooseMasterkey.filePickerTitle=Piliin ang masterkey file
|
unlock.chooseMasterkey.filePickerTitle=Piliin ang masterkey file
|
||||||
unlock.chooseMasterkey.filePickerMimeDesc=Cryptomator Masterkey
|
unlock.chooseMasterkey.filePickerMimeDesc=Cryptomator Masterkey
|
||||||
## Success
|
## Success
|
||||||
@@ -103,6 +149,7 @@ lock.forced.retryBtn=Subukan muli
|
|||||||
## Start
|
## Start
|
||||||
## Run
|
## Run
|
||||||
## Success
|
## Success
|
||||||
|
migration.success.unlockNow=I-unlock Ngayon
|
||||||
## Missing file system capabilities
|
## Missing file system capabilities
|
||||||
## Impossible
|
## Impossible
|
||||||
|
|
||||||
@@ -139,10 +186,12 @@ main.closeBtn.tooltip=Isara
|
|||||||
main.preferencesBtn.tooltip=Mga Kagustuhan
|
main.preferencesBtn.tooltip=Mga Kagustuhan
|
||||||
## Vault List
|
## Vault List
|
||||||
main.vaultlist.contextMenu.lock=I-lock
|
main.vaultlist.contextMenu.lock=I-lock
|
||||||
main.vaultlist.addVaultBtn=Magdagdag ng Vault
|
main.vaultlist.contextMenu.unlockNow=I-unlock Ngayon
|
||||||
|
main.vaultlist.addVaultBtn=Idagdag
|
||||||
## Vault Detail
|
## Vault Detail
|
||||||
### Welcome
|
### Welcome
|
||||||
### Locked
|
### Locked
|
||||||
|
main.vaultDetail.unlockNowBtn=I-unlock Ngayon
|
||||||
### Unlocked
|
### Unlocked
|
||||||
main.vaultDetail.lockBtn=I-lock
|
main.vaultDetail.lockBtn=I-lock
|
||||||
### Missing
|
### Missing
|
||||||
@@ -158,6 +207,7 @@ vaultOptions.general.vaultName=Pangalan ng Vault
|
|||||||
## Mount
|
## Mount
|
||||||
vaultOptions.mount.mountPoint.directoryPickerButton=Mamili…
|
vaultOptions.mount.mountPoint.directoryPickerButton=Mamili…
|
||||||
## Master Key
|
## Master Key
|
||||||
|
vaultOptions.masterkey.changePasswordBtn=Palitan ANG password
|
||||||
## Hub
|
## Hub
|
||||||
|
|
||||||
# Recovery Key
|
# Recovery Key
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ error.hyperlink.report=Signaler cette erreur
|
|||||||
error.technicalDetails=Détails :
|
error.technicalDetails=Détails :
|
||||||
error.existingSolutionDescription=Cryptomator ne s'attendait pas à ce que cela se produise. Mais nous avons trouvé une solution existante pour cette erreur. Veuillez consulter le lien suivant.
|
error.existingSolutionDescription=Cryptomator ne s'attendait pas à ce que cela se produise. Mais nous avons trouvé une solution existante pour cette erreur. Veuillez consulter le lien suivant.
|
||||||
error.hyperlink.solution=Rechercher la solution
|
error.hyperlink.solution=Rechercher la solution
|
||||||
error.lookupPermissionMessage=Cryptomator peut rechercher une solution en ligne. Une requête sera envoyée à notre base de données de problèmes à partir de votre adresse IP.
|
error.lookupPermissionMessage=Cryptomator peut trouver une solution à ce problème en ligne. Cela enverra une requête à notre base de données de problèmes à partir de votre adresse IP.
|
||||||
error.dismiss=Ignorer
|
error.dismiss=Ignorer
|
||||||
error.lookUpSolution=Rechercher une solution
|
error.lookUpSolution=Rechercher la solution
|
||||||
|
|
||||||
# Defaults
|
# Defaults
|
||||||
defaults.vault.vaultName=Coffre
|
defaults.vault.vaultName=Coffre
|
||||||
@@ -40,10 +40,8 @@ traymenu.vault.reveal=Révéler
|
|||||||
|
|
||||||
# Add Vault Wizard
|
# Add Vault Wizard
|
||||||
addvaultwizard.title=Ajouter un coffre
|
addvaultwizard.title=Ajouter un coffre
|
||||||
## Welcome
|
|
||||||
addvaultwizard.welcome.newButton=Créer un nouveau coffre
|
|
||||||
addvaultwizard.welcome.existingButton=Ouvrir un coffre existant
|
|
||||||
## New
|
## New
|
||||||
|
addvaultwizard.new.title=Ajouter un nouveau coffre
|
||||||
### Name
|
### Name
|
||||||
addvaultwizard.new.nameInstruction=Choisir un nom pour le coffre
|
addvaultwizard.new.nameInstruction=Choisir un nom pour le coffre
|
||||||
addvaultwizard.new.namePrompt=Nom du coffre
|
addvaultwizard.new.namePrompt=Nom du coffre
|
||||||
@@ -93,6 +91,7 @@ addvault.new.readme.accessLocation.2=Ceci est le chemin d'accès de votre coffre
|
|||||||
addvault.new.readme.accessLocation.3=Tous les fichiers ajoutés à ce volume seront chiffrés par Cryptomator. Vous pouvez l'utiliser comme n'importe quel lecteur/répertoire. Ceci est seulement une vue déchiffrée de son contenu, vos fichiers restent chiffrés dans votre disque dur en permanence.
|
addvault.new.readme.accessLocation.3=Tous les fichiers ajoutés à ce volume seront chiffrés par Cryptomator. Vous pouvez l'utiliser comme n'importe quel lecteur/répertoire. Ceci est seulement une vue déchiffrée de son contenu, vos fichiers restent chiffrés dans votre disque dur en permanence.
|
||||||
addvault.new.readme.accessLocation.4=Vous pouvez supprimer ce fichier.
|
addvault.new.readme.accessLocation.4=Vous pouvez supprimer ce fichier.
|
||||||
## Existing
|
## Existing
|
||||||
|
addvaultwizard.existing.title=Ajouter un coffre existant
|
||||||
addvaultwizard.existing.instruction=Choisissez le fichier « vault.cryptomator » de votre volume existant. Si seul le fichier « masterkey.cryptomator » est présent, sélectionnez celui-là.
|
addvaultwizard.existing.instruction=Choisissez le fichier « vault.cryptomator » de votre volume existant. Si seul le fichier « masterkey.cryptomator » est présent, sélectionnez celui-là.
|
||||||
addvaultwizard.existing.chooseBtn=Choisir...
|
addvaultwizard.existing.chooseBtn=Choisir...
|
||||||
addvaultwizard.existing.filePickerTitle=Sélectionnez le fichier correspondant au volume chiffré
|
addvaultwizard.existing.filePickerTitle=Sélectionnez le fichier correspondant au volume chiffré
|
||||||
@@ -138,7 +137,7 @@ unlock.error.customPath.message=Impossible de monter le coffre vers le chemin pe
|
|||||||
unlock.error.customPath.description.notSupported=Si vous souhaitez continuer à utiliser le chemin personnalisé, veuillez aller dans les préférences et sélectionner un type de volume qui le prend en charge. Sinon, allez dans les options du coffre et choisissez un point de montage pris en charge.
|
unlock.error.customPath.description.notSupported=Si vous souhaitez continuer à utiliser le chemin personnalisé, veuillez aller dans les préférences et sélectionner un type de volume qui le prend en charge. Sinon, allez dans les options du coffre et choisissez un point de montage pris en charge.
|
||||||
unlock.error.customPath.description.notExists=Le chemin de montage personnalisé n'existe pas. Créez-le dans votre système de fichiers local ou modifiez-le dans les options du coffre.
|
unlock.error.customPath.description.notExists=Le chemin de montage personnalisé n'existe pas. Créez-le dans votre système de fichiers local ou modifiez-le dans les options du coffre.
|
||||||
unlock.error.customPath.description.inUse=La lettre de lecteur ou le point de montage personnalisé «%s» est déjà utilisé.
|
unlock.error.customPath.description.inUse=La lettre de lecteur ou le point de montage personnalisé «%s» est déjà utilisé.
|
||||||
unlock.error.customPath.description.hideawayNotDir=Le fichier temporaire et caché « %3$s » utilisé pour le déverrouillage n’a pas pu être supprimé. Veuillez vérifier le fichier puis le supprimer manuellement.
|
unlock.error.customPath.description.hideawayNotDir=Le fichier temporaire et caché «%3$s» utilisé pour déverrouiller n'a pas pu être supprimé. Veuillez vérifier le fichier puis le supprimer manuellement.
|
||||||
unlock.error.customPath.description.couldNotBeCleaned=Votre coffre n'a pas pu être monté au point "%s". Veuillez réessayer ou choisissez un autre point.
|
unlock.error.customPath.description.couldNotBeCleaned=Votre coffre n'a pas pu être monté au point "%s". Veuillez réessayer ou choisissez un autre point.
|
||||||
unlock.error.customPath.description.notEmptyDir=Le chemin de montage personnalisé "%s" n'est pas un dossier vide. Veuillez choisir un dossier vide et réessayez.
|
unlock.error.customPath.description.notEmptyDir=Le chemin de montage personnalisé "%s" n'est pas un dossier vide. Veuillez choisir un dossier vide et réessayez.
|
||||||
unlock.error.customPath.description.generic=Vous avez sélectionné un point de montage personnalisé pour ce coffre, mais son utilisation a échoué avec le message : %2$s
|
unlock.error.customPath.description.generic=Vous avez sélectionné un point de montage personnalisé pour ce coffre, mais son utilisation a échoué avec le message : %2$s
|
||||||
@@ -366,7 +365,9 @@ main.vaultlist.contextMenu.unlock=Déverrouiller…
|
|||||||
main.vaultlist.contextMenu.unlockNow=Déverrouiller maintenant
|
main.vaultlist.contextMenu.unlockNow=Déverrouiller maintenant
|
||||||
main.vaultlist.contextMenu.vaultoptions=Afficher les options du volume chiffré
|
main.vaultlist.contextMenu.vaultoptions=Afficher les options du volume chiffré
|
||||||
main.vaultlist.contextMenu.reveal=Afficher le lecteur
|
main.vaultlist.contextMenu.reveal=Afficher le lecteur
|
||||||
main.vaultlist.addVaultBtn=Ajouter un volume chiffré
|
main.vaultlist.addVaultBtn=Ajouter
|
||||||
|
main.vaultlist.addVaultBtn.menuItemNew=Nouveau coffre...
|
||||||
|
main.vaultlist.addVaultBtn.menuItemExisting=Coffre existant...
|
||||||
## Vault Detail
|
## Vault Detail
|
||||||
### Welcome
|
### Welcome
|
||||||
main.vaultDetail.welcomeOnboarding=Merci d'avoir choisi Cryptomator pour protéger vos fichiers. Si vous avez besoin d'aide, consultez nos guides de démarrage :
|
main.vaultDetail.welcomeOnboarding=Merci d'avoir choisi Cryptomator pour protéger vos fichiers. Si vous avez besoin d'aide, consultez nos guides de démarrage :
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ error.message=Produciuse un erro
|
|||||||
# Tray Menu
|
# Tray Menu
|
||||||
|
|
||||||
# Add Vault Wizard
|
# Add Vault Wizard
|
||||||
## Welcome
|
|
||||||
## New
|
## New
|
||||||
### Name
|
### Name
|
||||||
### Location
|
### Location
|
||||||
|
|||||||
@@ -35,9 +35,6 @@ traymenu.vault.reveal=חשוף
|
|||||||
|
|
||||||
# Add Vault Wizard
|
# Add Vault Wizard
|
||||||
addvaultwizard.title=הוספת כספת
|
addvaultwizard.title=הוספת כספת
|
||||||
## Welcome
|
|
||||||
addvaultwizard.welcome.newButton=צור כספת חדשה
|
|
||||||
addvaultwizard.welcome.existingButton=פתיחת כספת קיימת
|
|
||||||
## New
|
## New
|
||||||
### Name
|
### Name
|
||||||
addvaultwizard.new.nameInstruction=בחירת שם עבור הכספת
|
addvaultwizard.new.nameInstruction=בחירת שם עבור הכספת
|
||||||
@@ -127,6 +124,7 @@ unlock.success.revealBtn=חשוף את הכונן
|
|||||||
unlock.error.customPath.message=כשלון בקישור הכספת לנתיב הידני שהוגדר
|
unlock.error.customPath.message=כשלון בקישור הכספת לנתיב הידני שהוגדר
|
||||||
unlock.error.customPath.description.notSupported=אם ברצונך להשתמש בנתיב ידני, אנא גש להעדפות ובחר סוג volume שתומך בכך. אחרת, לך לאפשרויות הכספת ובחר אפשרות יעד קישור נתמך.
|
unlock.error.customPath.description.notSupported=אם ברצונך להשתמש בנתיב ידני, אנא גש להעדפות ובחר סוג volume שתומך בכך. אחרת, לך לאפשרויות הכספת ובחר אפשרות יעד קישור נתמך.
|
||||||
unlock.error.customPath.description.notExists=יעד הקישור הידני לא קיים. או שתיצור אותו במערכת הקבצים המקומית או שנה אותו באפשרויות הכספת.
|
unlock.error.customPath.description.notExists=יעד הקישור הידני לא קיים. או שתיצור אותו במערכת הקבצים המקומית או שנה אותו באפשרויות הכספת.
|
||||||
|
unlock.error.customPath.description.hideawayNotDir=הקובץ הזמני, הנסתר %3$s עבור פתיחה לא ניתן להסרה. יש לבדוק את הקובץ ולמחוק אותו ידנית.
|
||||||
## Hub
|
## Hub
|
||||||
hub.noKeychain.message=לא ניתן לגשת למפתח המכשיר
|
hub.noKeychain.message=לא ניתן לגשת למפתח המכשיר
|
||||||
hub.noKeychain.description=כדאי לשחרר כספות האב נדרש מפתח מכשיר שיאובטח בצרור מפתחות. כדאי להמשיך, אפשר ״%s״ ובחר את צרור המפתחות בהעדפות.
|
hub.noKeychain.description=כדאי לשחרר כספות האב נדרש מפתח מכשיר שיאובטח בצרור מפתחות. כדאי להמשיך, אפשר ״%s״ ובחר את צרור המפתחות בהעדפות.
|
||||||
@@ -351,7 +349,7 @@ main.vaultlist.contextMenu.unlock=שחרר נעילה…
|
|||||||
main.vaultlist.contextMenu.unlockNow=בטל נעילה כעת
|
main.vaultlist.contextMenu.unlockNow=בטל נעילה כעת
|
||||||
main.vaultlist.contextMenu.vaultoptions=הצג את אפשרויות הכספת
|
main.vaultlist.contextMenu.vaultoptions=הצג את אפשרויות הכספת
|
||||||
main.vaultlist.contextMenu.reveal=חשוף את הכונן
|
main.vaultlist.contextMenu.reveal=חשוף את הכונן
|
||||||
main.vaultlist.addVaultBtn=יצירת כספת
|
main.vaultlist.addVaultBtn=הוספה
|
||||||
## Vault Detail
|
## Vault Detail
|
||||||
### Welcome
|
### Welcome
|
||||||
main.vaultDetail.welcomeOnboarding=תודה שבחרת ב- Cryptomator להגן על הקבצים שלך. אם אתה זקוק לסיוע, אנא עיין במדריכים שלנו:
|
main.vaultDetail.welcomeOnboarding=תודה שבחרת ב- Cryptomator להגן על הקבצים שלך. אם אתה זקוק לסיוע, אנא עיין במדריכים שלנו:
|
||||||
|
|||||||
@@ -35,9 +35,6 @@ traymenu.vault.reveal=प्रत्यक्ष करें
|
|||||||
|
|
||||||
# Add Vault Wizard
|
# Add Vault Wizard
|
||||||
addvaultwizard.title=वाउल्ट डालें
|
addvaultwizard.title=वाउल्ट डालें
|
||||||
## Welcome
|
|
||||||
addvaultwizard.welcome.newButton=नया वाउल्ट बनाएं
|
|
||||||
addvaultwizard.welcome.existingButton=मौजूदा वाउल्ट खोलें
|
|
||||||
## New
|
## New
|
||||||
### Name
|
### Name
|
||||||
addvaultwizard.new.nameInstruction=वाउल्ट के लिए नाम चुनें
|
addvaultwizard.new.nameInstruction=वाउल्ट के लिए नाम चुनें
|
||||||
@@ -212,7 +209,6 @@ main.vaultlist.contextMenu.unlock=अनलॉक करें...
|
|||||||
main.vaultlist.contextMenu.unlockNow=अब अनलॉक करें
|
main.vaultlist.contextMenu.unlockNow=अब अनलॉक करें
|
||||||
main.vaultlist.contextMenu.vaultoptions=वॉल्ट के विकल्प दिखाए
|
main.vaultlist.contextMenu.vaultoptions=वॉल्ट के विकल्प दिखाए
|
||||||
main.vaultlist.contextMenu.reveal=फोल्डर खोलें
|
main.vaultlist.contextMenu.reveal=फोल्डर खोलें
|
||||||
main.vaultlist.addVaultBtn=वाउल्ट डालें
|
|
||||||
## Vault Detail
|
## Vault Detail
|
||||||
### Welcome
|
### Welcome
|
||||||
main.vaultDetail.welcomeOnboarding=Cryptomator को अपनी फाइल्स सिराक्षित रखने को चुनने के लिए धन्यवाद। अगर आपको सहायता चाइये, तो हमारी गेटिंग स्टार्टेड गाइगाइड्स देखिये:
|
main.vaultDetail.welcomeOnboarding=Cryptomator को अपनी फाइल्स सिराक्षित रखने को चुनने के लिए धन्यवाद। अगर आपको सहायता चाइये, तो हमारी गेटिंग स्टार्टेड गाइगाइड्स देखिये:
|
||||||
|
|||||||
@@ -35,9 +35,6 @@ traymenu.vault.reveal=Otkrij
|
|||||||
|
|
||||||
# Add Vault Wizard
|
# Add Vault Wizard
|
||||||
addvaultwizard.title=Dodaj trezor
|
addvaultwizard.title=Dodaj trezor
|
||||||
## Welcome
|
|
||||||
addvaultwizard.welcome.newButton=Izradi novi trezor
|
|
||||||
addvaultwizard.welcome.existingButton=Otvori postojeći trezor
|
|
||||||
## New
|
## New
|
||||||
### Name
|
### Name
|
||||||
addvaultwizard.new.nameInstruction=Odaberi ime za trezor
|
addvaultwizard.new.nameInstruction=Odaberi ime za trezor
|
||||||
@@ -277,7 +274,7 @@ main.vaultlist.contextMenu.unlock=Otključaj…
|
|||||||
main.vaultlist.contextMenu.unlockNow=Otključaj sada
|
main.vaultlist.contextMenu.unlockNow=Otključaj sada
|
||||||
main.vaultlist.contextMenu.vaultoptions=Prikaži opcije trezora
|
main.vaultlist.contextMenu.vaultoptions=Prikaži opcije trezora
|
||||||
main.vaultlist.contextMenu.reveal=Otkrij pogon
|
main.vaultlist.contextMenu.reveal=Otkrij pogon
|
||||||
main.vaultlist.addVaultBtn=Dodaj trezor
|
main.vaultlist.addVaultBtn=Dodaj
|
||||||
## Vault Detail
|
## Vault Detail
|
||||||
### Welcome
|
### Welcome
|
||||||
main.vaultDetail.welcomeOnboarding=Hvala što ste odabrali Cryptomator za zaštitu Vaših datoteka. Ukoliko trebate pomoć, provjerite naše vodiče za početak rada:
|
main.vaultDetail.welcomeOnboarding=Hvala što ste odabrali Cryptomator za zaštitu Vaših datoteka. Ukoliko trebate pomoć, provjerite naše vodiče za početak rada:
|
||||||
|
|||||||
@@ -37,9 +37,6 @@ traymenu.vault.reveal=Megmutatás
|
|||||||
|
|
||||||
# Add Vault Wizard
|
# Add Vault Wizard
|
||||||
addvaultwizard.title=Széf hozzáadása
|
addvaultwizard.title=Széf hozzáadása
|
||||||
## Welcome
|
|
||||||
addvaultwizard.welcome.newButton=Új széf létrehozása
|
|
||||||
addvaultwizard.welcome.existingButton=Meglévő széf megnyitása
|
|
||||||
## New
|
## New
|
||||||
### Name
|
### Name
|
||||||
addvaultwizard.new.nameInstruction=Válasszon egy nevet az új széf számára
|
addvaultwizard.new.nameInstruction=Válasszon egy nevet az új széf számára
|
||||||
@@ -135,7 +132,6 @@ unlock.error.customPath.message=Nem lehet csatolni a széfet az egyéni útvonal
|
|||||||
unlock.error.customPath.description.notSupported=Ha szeretné továbbra is az egyéni útvonalat használni, kérem, menjen a beállításokba és válasszon egy kötet típust, amely támogatja azt. Máskülönben, menjen a széf opciókhoz és válasszon egy támogatott csatoláspontot.
|
unlock.error.customPath.description.notSupported=Ha szeretné továbbra is az egyéni útvonalat használni, kérem, menjen a beállításokba és válasszon egy kötet típust, amely támogatja azt. Máskülönben, menjen a széf opciókhoz és válasszon egy támogatott csatoláspontot.
|
||||||
unlock.error.customPath.description.notExists=Az egyéni csatolás útvonal nem létezik. Hozza létre a helyi fájlrendszerében vagy változtassa meg a széf opciókban.
|
unlock.error.customPath.description.notExists=Az egyéni csatolás útvonal nem létezik. Hozza létre a helyi fájlrendszerében vagy változtassa meg a széf opciókban.
|
||||||
unlock.error.customPath.description.inUse=A meghajtó betűjele vagy a csatolási útvonal: "%s" már foglalt.
|
unlock.error.customPath.description.inUse=A meghajtó betűjele vagy a csatolási útvonal: "%s" már foglalt.
|
||||||
unlock.error.customPath.description.hideawayNotDir=A feloldáshoz használt, "%3$s" nevű ideiglenes, rejtett fájlt nem lehet eltávolítani. Kérjük, ellenőrizze a fájlt, majd törölje manuálisan.
|
|
||||||
unlock.error.customPath.description.couldNotBeCleaned=A széfet nem lehetett a "%s" elérési útvonalra csatlakoztatni. Kérjük, próbálja meg újra, vagy válasszon másik elérési útvonalat.
|
unlock.error.customPath.description.couldNotBeCleaned=A széfet nem lehetett a "%s" elérési útvonalra csatlakoztatni. Kérjük, próbálja meg újra, vagy válasszon másik elérési útvonalat.
|
||||||
unlock.error.customPath.description.notEmptyDir=A megadott "%s" elérési útvonal nem egy üres mappa. Kérjük, válasszon egy üres mappát, és próbálja meg újra.
|
unlock.error.customPath.description.notEmptyDir=A megadott "%s" elérési útvonal nem egy üres mappa. Kérjük, válasszon egy üres mappát, és próbálja meg újra.
|
||||||
unlock.error.customPath.description.generic=Egyéni csatolási útvonalat választott ehhez a széfhez, de használatakor ez a hibaüzenet érkezett: %2$s
|
unlock.error.customPath.description.generic=Egyéni csatolási útvonalat választott ehhez a széfhez, de használatakor ez a hibaüzenet érkezett: %2$s
|
||||||
@@ -363,7 +359,7 @@ main.vaultlist.contextMenu.unlock=Feloldás…
|
|||||||
main.vaultlist.contextMenu.unlockNow=Azonnali feloldás
|
main.vaultlist.contextMenu.unlockNow=Azonnali feloldás
|
||||||
main.vaultlist.contextMenu.vaultoptions=Széf beállítások
|
main.vaultlist.contextMenu.vaultoptions=Széf beállítások
|
||||||
main.vaultlist.contextMenu.reveal=Széf megjelenítése
|
main.vaultlist.contextMenu.reveal=Széf megjelenítése
|
||||||
main.vaultlist.addVaultBtn=Széf hozzáadása
|
main.vaultlist.addVaultBtn=Hozzáadás
|
||||||
## Vault Detail
|
## Vault Detail
|
||||||
### Welcome
|
### Welcome
|
||||||
main.vaultDetail.welcomeOnboarding=Köszönjük, hogy a Cryptomator programot választotta a fájlai védelmére. Ha segítségre van szüksége, akkor olvassa el a kezdő útmutatónk lépéseit:
|
main.vaultDetail.welcomeOnboarding=Köszönjük, hogy a Cryptomator programot választotta a fájlai védelmére. Ha segítségre van szüksége, akkor olvassa el a kezdő útmutatónk lépéseit:
|
||||||
|
|||||||
@@ -35,9 +35,6 @@ traymenu.vault.reveal=Perlihatkan
|
|||||||
|
|
||||||
# Add Vault Wizard
|
# Add Vault Wizard
|
||||||
addvaultwizard.title=Tambah Vault
|
addvaultwizard.title=Tambah Vault
|
||||||
## Welcome
|
|
||||||
addvaultwizard.welcome.newButton=Buat Vault Baru
|
|
||||||
addvaultwizard.welcome.existingButton=Buka Vault yang Tersedia
|
|
||||||
## New
|
## New
|
||||||
### Name
|
### Name
|
||||||
addvaultwizard.new.nameInstruction=Beri nama untuk vault berikut
|
addvaultwizard.new.nameInstruction=Beri nama untuk vault berikut
|
||||||
@@ -277,7 +274,6 @@ main.vaultlist.contextMenu.unlock=Buka Kunci…
|
|||||||
main.vaultlist.contextMenu.unlockNow=Buka Kunci Sekarang
|
main.vaultlist.contextMenu.unlockNow=Buka Kunci Sekarang
|
||||||
main.vaultlist.contextMenu.vaultoptions=Tampilkan Opsi Vault
|
main.vaultlist.contextMenu.vaultoptions=Tampilkan Opsi Vault
|
||||||
main.vaultlist.contextMenu.reveal=Buka Drive
|
main.vaultlist.contextMenu.reveal=Buka Drive
|
||||||
main.vaultlist.addVaultBtn=Tambah Vault
|
|
||||||
## Vault Detail
|
## Vault Detail
|
||||||
### Welcome
|
### Welcome
|
||||||
main.vaultDetail.welcomeOnboarding=Terima kasih telah memilih Cryptomator untuk melindungi file Anda. Jika Anda memerlukan bantuan, lihat panduan awal kami:
|
main.vaultDetail.welcomeOnboarding=Terima kasih telah memilih Cryptomator untuk melindungi file Anda. Jika Anda memerlukan bantuan, lihat panduan awal kami:
|
||||||
|
|||||||
@@ -40,10 +40,8 @@ traymenu.vault.reveal=Mostra
|
|||||||
|
|
||||||
# Add Vault Wizard
|
# Add Vault Wizard
|
||||||
addvaultwizard.title=Aggiungi Cassaforte
|
addvaultwizard.title=Aggiungi Cassaforte
|
||||||
## Welcome
|
|
||||||
addvaultwizard.welcome.newButton=Crea Nuova Cassaforte
|
|
||||||
addvaultwizard.welcome.existingButton=Apri Cassaforte Esistente
|
|
||||||
## New
|
## New
|
||||||
|
addvaultwizard.new.title=Aggiungi una Nuova Cassaforte
|
||||||
### Name
|
### Name
|
||||||
addvaultwizard.new.nameInstruction=Scegli un nome per la cassaforte
|
addvaultwizard.new.nameInstruction=Scegli un nome per la cassaforte
|
||||||
addvaultwizard.new.namePrompt=Nome della Cassaforte
|
addvaultwizard.new.namePrompt=Nome della Cassaforte
|
||||||
@@ -93,6 +91,7 @@ addvault.new.readme.accessLocation.2=Questa è la posizione d'accesso della tua
|
|||||||
addvault.new.readme.accessLocation.3=Ogni file aggiunto a questo volume sarà crittografato da Cryptomator. Puoi lavorarci come su ogni altra unità/cartella. Questa è solo una vista decrittografata del suo contenuto, i tuoi file restano sempre crittografati sul tuo disco rigido.
|
addvault.new.readme.accessLocation.3=Ogni file aggiunto a questo volume sarà crittografato da Cryptomator. Puoi lavorarci come su ogni altra unità/cartella. Questa è solo una vista decrittografata del suo contenuto, i tuoi file restano sempre crittografati sul tuo disco rigido.
|
||||||
addvault.new.readme.accessLocation.4=Sentiti libero di rimuovere questo file.
|
addvault.new.readme.accessLocation.4=Sentiti libero di rimuovere questo file.
|
||||||
## Existing
|
## Existing
|
||||||
|
addvaultwizard.existing.title=Aggiungi una Cassaforte Esistente
|
||||||
addvaultwizard.existing.instruction=Scegliere il file "vault.cryptomator" della tua cassaforte. Se esiste solo un file chiamato "masterkey.cryptomator", allora scegli quello.
|
addvaultwizard.existing.instruction=Scegliere il file "vault.cryptomator" della tua cassaforte. Se esiste solo un file chiamato "masterkey.cryptomator", allora scegli quello.
|
||||||
addvaultwizard.existing.chooseBtn=Scegli…
|
addvaultwizard.existing.chooseBtn=Scegli…
|
||||||
addvaultwizard.existing.filePickerTitle=Seleziona file cassaforte
|
addvaultwizard.existing.filePickerTitle=Seleziona file cassaforte
|
||||||
@@ -138,7 +137,7 @@ unlock.error.customPath.message=Impossibile montare la cassaforte sul percorso p
|
|||||||
unlock.error.customPath.description.notSupported=Se desideri continuare a utilizzare il percorso personalizzato, vai alle preferenze e seleziona un tipo di volume che lo supporta. Altrimenti, vai alle opzioni della cassaforte e scegli un punto di montaggio supportato.
|
unlock.error.customPath.description.notSupported=Se desideri continuare a utilizzare il percorso personalizzato, vai alle preferenze e seleziona un tipo di volume che lo supporta. Altrimenti, vai alle opzioni della cassaforte e scegli un punto di montaggio supportato.
|
||||||
unlock.error.customPath.description.notExists=Il percorso di mount personalizzato non esiste. Crealo nel tuo file system locale o cambialo nelle opzioni della cassaforte.
|
unlock.error.customPath.description.notExists=Il percorso di mount personalizzato non esiste. Crealo nel tuo file system locale o cambialo nelle opzioni della cassaforte.
|
||||||
unlock.error.customPath.description.inUse=La lettera di unità o il percorso di montaggio selezionato "%s" è già in uso.
|
unlock.error.customPath.description.inUse=La lettera di unità o il percorso di montaggio selezionato "%s" è già in uso.
|
||||||
unlock.error.customPath.description.hideawayNotDir=Il file provvisorio nascosto "%3$s" utilizzato per sbloccare non può essere rimosso. Controlla il file ed eliminalo manualmente.
|
unlock.error.customPath.description.hideawayNotDir=Impossibile rimuovere il file temporaneo nascosto "%3$s" utilizzato per lo sblocco. Controllare il file e quindi eliminarlo manualmente.
|
||||||
unlock.error.customPath.description.couldNotBeCleaned=La tua cassaforte non può essere montata sul percorso "%s". Riprova o scegli un percorso diverso.
|
unlock.error.customPath.description.couldNotBeCleaned=La tua cassaforte non può essere montata sul percorso "%s". Riprova o scegli un percorso diverso.
|
||||||
unlock.error.customPath.description.notEmptyDir=Il percorso di montaggio selezionato "%s" non è una cartella vuota. Scegli una cartella vuota e riprova.
|
unlock.error.customPath.description.notEmptyDir=Il percorso di montaggio selezionato "%s" non è una cartella vuota. Scegli una cartella vuota e riprova.
|
||||||
unlock.error.customPath.description.generic=Hai selezionato un percorso di montaggio personalizzato per questa cassaforte, ma il suo utilizzo non è riuscito con il messaggio: %2$s
|
unlock.error.customPath.description.generic=Hai selezionato un percorso di montaggio personalizzato per questa cassaforte, ma il suo utilizzo non è riuscito con il messaggio: %2$s
|
||||||
@@ -366,7 +365,9 @@ main.vaultlist.contextMenu.unlock=Sblocca…
|
|||||||
main.vaultlist.contextMenu.unlockNow=Sblocca Ora
|
main.vaultlist.contextMenu.unlockNow=Sblocca Ora
|
||||||
main.vaultlist.contextMenu.vaultoptions=Mostra le Opzioni della Cassaforte
|
main.vaultlist.contextMenu.vaultoptions=Mostra le Opzioni della Cassaforte
|
||||||
main.vaultlist.contextMenu.reveal=Rivela Unità
|
main.vaultlist.contextMenu.reveal=Rivela Unità
|
||||||
main.vaultlist.addVaultBtn=Aggiungi Cassaforte
|
main.vaultlist.addVaultBtn=Aggiungi
|
||||||
|
main.vaultlist.addVaultBtn.menuItemNew=Nuova Cassaforte...
|
||||||
|
main.vaultlist.addVaultBtn.menuItemExisting=Cassaforte esistente...
|
||||||
## Vault Detail
|
## Vault Detail
|
||||||
### Welcome
|
### Welcome
|
||||||
main.vaultDetail.welcomeOnboarding=Grazie per aver scelto Cryptomator per proteggere i tuoi file. Se necessiti d'assistenza, dai un'occhiata alle nostre guide per iniziare:
|
main.vaultDetail.welcomeOnboarding=Grazie per aver scelto Cryptomator per proteggere i tuoi file. Se necessiti d'assistenza, dai un'occhiata alle nostre guide per iniziare:
|
||||||
|
|||||||
@@ -40,10 +40,8 @@ traymenu.vault.reveal=表示
|
|||||||
|
|
||||||
# Add Vault Wizard
|
# Add Vault Wizard
|
||||||
addvaultwizard.title=金庫を追加
|
addvaultwizard.title=金庫を追加
|
||||||
## Welcome
|
|
||||||
addvaultwizard.welcome.newButton=新しい金庫を作成
|
|
||||||
addvaultwizard.welcome.existingButton=すでにある金庫を開く
|
|
||||||
## New
|
## New
|
||||||
|
addvaultwizard.new.title=新しい金庫を追加
|
||||||
### Name
|
### Name
|
||||||
addvaultwizard.new.nameInstruction=金庫の名前を入力してください
|
addvaultwizard.new.nameInstruction=金庫の名前を入力してください
|
||||||
addvaultwizard.new.namePrompt=金庫の名前
|
addvaultwizard.new.namePrompt=金庫の名前
|
||||||
@@ -93,6 +91,7 @@ addvault.new.readme.accessLocation.2=ここは金庫のアクセス先です。
|
|||||||
addvault.new.readme.accessLocation.3=このボリュームに追加したファイルは Cryptomator によって暗号化されます。一般的なドライブ/フォルダー上のように作業することができます。ここでは復号したコンテンツのビューにすぎず、ファイルは常にハードドライブ上で暗号化されています。
|
addvault.new.readme.accessLocation.3=このボリュームに追加したファイルは Cryptomator によって暗号化されます。一般的なドライブ/フォルダー上のように作業することができます。ここでは復号したコンテンツのビューにすぎず、ファイルは常にハードドライブ上で暗号化されています。
|
||||||
addvault.new.readme.accessLocation.4=このファイルはいつでも削除できます。
|
addvault.new.readme.accessLocation.4=このファイルはいつでも削除できます。
|
||||||
## Existing
|
## Existing
|
||||||
|
addvaultwizard.existing.title=既存の金庫を追加
|
||||||
addvaultwizard.existing.instruction=すでにある金庫の "vault.cryptomator" ファイルを選択してください。"masterkey.cryptomator" という名前のファイルだけが存在する場合は、このファイルを代わりに選択してください。
|
addvaultwizard.existing.instruction=すでにある金庫の "vault.cryptomator" ファイルを選択してください。"masterkey.cryptomator" という名前のファイルだけが存在する場合は、このファイルを代わりに選択してください。
|
||||||
addvaultwizard.existing.chooseBtn=選択...
|
addvaultwizard.existing.chooseBtn=選択...
|
||||||
addvaultwizard.existing.filePickerTitle=金庫のファイルを選択
|
addvaultwizard.existing.filePickerTitle=金庫のファイルを選択
|
||||||
@@ -366,7 +365,9 @@ main.vaultlist.contextMenu.unlock=解錠...
|
|||||||
main.vaultlist.contextMenu.unlockNow=今すぐ解錠
|
main.vaultlist.contextMenu.unlockNow=今すぐ解錠
|
||||||
main.vaultlist.contextMenu.vaultoptions=金庫のオプションを表示
|
main.vaultlist.contextMenu.vaultoptions=金庫のオプションを表示
|
||||||
main.vaultlist.contextMenu.reveal=ドライブを表示
|
main.vaultlist.contextMenu.reveal=ドライブを表示
|
||||||
main.vaultlist.addVaultBtn=金庫を追加
|
main.vaultlist.addVaultBtn=追加
|
||||||
|
main.vaultlist.addVaultBtn.menuItemNew=新しい金庫...
|
||||||
|
main.vaultlist.addVaultBtn.menuItemExisting=既存の金庫...
|
||||||
## Vault Detail
|
## Vault Detail
|
||||||
### Welcome
|
### Welcome
|
||||||
main.vaultDetail.welcomeOnboarding=ファイルを保護するために Cryptomator を選んでいただきありがとうございます。ヘルプが必要であれば、スタートガイドをご覧ください:
|
main.vaultDetail.welcomeOnboarding=ファイルを保護するために Cryptomator を選んでいただきありがとうございます。ヘルプが必要であれば、スタートガイドをご覧ください:
|
||||||
|
|||||||
@@ -35,9 +35,6 @@ traymenu.vault.reveal=표시
|
|||||||
|
|
||||||
# Add Vault Wizard
|
# Add Vault Wizard
|
||||||
addvaultwizard.title=Vault 추가
|
addvaultwizard.title=Vault 추가
|
||||||
## Welcome
|
|
||||||
addvaultwizard.welcome.newButton=새 Vault 생성
|
|
||||||
addvaultwizard.welcome.existingButton=기존 Vault 열기
|
|
||||||
## New
|
## New
|
||||||
### Name
|
### Name
|
||||||
addvaultwizard.new.nameInstruction=새 Vault의 이름을 입력하십시요
|
addvaultwizard.new.nameInstruction=새 Vault의 이름을 입력하십시요
|
||||||
@@ -134,10 +131,15 @@ hub.register.registerBtn=확인
|
|||||||
### Registration Success
|
### Registration Success
|
||||||
### Registration Failed
|
### Registration Failed
|
||||||
### Unauthorized
|
### Unauthorized
|
||||||
|
hub.unauthorized.message=액세스 거부
|
||||||
|
hub.unauthorized.description=귀하의 기기는 아직 이 저장소에 액세스할 수 있는 권한이 없습니다. Vault 소유자에게 승인을 요청하세요.
|
||||||
### License Exceeded
|
### License Exceeded
|
||||||
|
hub.invalidLicense.message=Hub 라이선스가 잘못되었습니다.
|
||||||
|
hub.invalidLicense.description=Cryptomator Hub 인스턴스에 잘못된 라이선스가 있습니다. 라이센스를 업그레이드하거나 갱신하려면 허브 관리자에게 알리십시오.
|
||||||
|
|
||||||
# Lock
|
# Lock
|
||||||
## Force
|
## Force
|
||||||
|
lock.forced.message=잠금 실패
|
||||||
lock.forced.description=대기 중인 작동이나 파일이 열려있어 "%s"를 잠그는데 실패하였습니다. 이 Vault를 강제로 잠글 수 있으나, 입/출력의 중단은 저장되지 않은 데이터의 유실을 초래할 수 있습니다.
|
lock.forced.description=대기 중인 작동이나 파일이 열려있어 "%s"를 잠그는데 실패하였습니다. 이 Vault를 강제로 잠글 수 있으나, 입/출력의 중단은 저장되지 않은 데이터의 유실을 초래할 수 있습니다.
|
||||||
lock.forced.retryBtn=재시도
|
lock.forced.retryBtn=재시도
|
||||||
lock.forced.forceBtn=강제 잠금
|
lock.forced.forceBtn=강제 잠금
|
||||||
@@ -178,6 +180,7 @@ migration.impossible.moreInfo=Vault를 이전 버전으로 계속 열수 있습
|
|||||||
health.title="%s"의 상태 검사
|
health.title="%s"의 상태 검사
|
||||||
health.intro.header=상태 검사
|
health.intro.header=상태 검사
|
||||||
health.intro.text=상태 검사는 Vault의 내부 구조의 문제점을 점검하고 해결할 수 있는 기능입니다. 다음 사항을 유의하시기 바랍니다:
|
health.intro.text=상태 검사는 Vault의 내부 구조의 문제점을 점검하고 해결할 수 있는 기능입니다. 다음 사항을 유의하시기 바랍니다:
|
||||||
|
health.intro.remarkFix=모든 문제를 해결할 수 있는 것은 아닙니다.
|
||||||
health.intro.remarkBackup=데이터가 손상된 경우 백업만이 유일한 해결책입니다.
|
health.intro.remarkBackup=데이터가 손상된 경우 백업만이 유일한 해결책입니다.
|
||||||
health.intro.affirmation=나는 위 정보를 읽고 정말 이해했습니다.
|
health.intro.affirmation=나는 위 정보를 읽고 정말 이해했습니다.
|
||||||
## Start Failure
|
## Start Failure
|
||||||
@@ -186,6 +189,7 @@ health.fail.ioError=설정 파일에 접근하는 중 에러가 발생했습니
|
|||||||
health.fail.parseError=Vault 설정을 파싱하는 중 에러가 발생했습니다.
|
health.fail.parseError=Vault 설정을 파싱하는 중 에러가 발생했습니다.
|
||||||
health.fail.moreInfo=더 많은 정보
|
health.fail.moreInfo=더 많은 정보
|
||||||
## Check Selection
|
## Check Selection
|
||||||
|
health.checkList.description=검사할 항목을 왼쪽 목록에서 선택하거나 아래 버튼을 사용하세요.
|
||||||
health.checkList.selectAllButton=모든 항목 선택
|
health.checkList.selectAllButton=모든 항목 선택
|
||||||
health.checkList.deselectAllButton=모든 항목 선택 해제
|
health.checkList.deselectAllButton=모든 항목 선택 해제
|
||||||
health.check.runBatchBtn=선택된 검사항목 실행
|
health.check.runBatchBtn=선택된 검사항목 실행
|
||||||
@@ -195,6 +199,8 @@ health.check.detail.checkScheduled=검사가 예약되었습니다.
|
|||||||
health.check.detail.checkRunning=검사가 현재 실행중입니다...
|
health.check.detail.checkRunning=검사가 현재 실행중입니다...
|
||||||
health.check.detail.checkSkipped=선택된 검사항목이 없습니다.
|
health.check.detail.checkSkipped=선택된 검사항목이 없습니다.
|
||||||
health.check.detail.checkFinished=검사가 성공적으로 완료되었습니다.
|
health.check.detail.checkFinished=검사가 성공적으로 완료되었습니다.
|
||||||
|
health.check.detail.checkFinishedAndFound=검사가 완료되었습니다. 검사 결과를 확인해주세요.
|
||||||
|
health.check.detail.checkCancelled=검사가 취소되었습니다
|
||||||
health.check.detail.listFilters.label=필터
|
health.check.detail.listFilters.label=필터
|
||||||
health.check.exportBtn=보고서 내보내기
|
health.check.exportBtn=보고서 내보내기
|
||||||
## Result view
|
## Result view
|
||||||
@@ -228,9 +234,12 @@ preferences.interface.showMinimizeButton=최소화 버튼 표시
|
|||||||
preferences.interface.showTrayIcon=트레이 아이콘 보기 (재시작 필요)
|
preferences.interface.showTrayIcon=트레이 아이콘 보기 (재시작 필요)
|
||||||
## Volume
|
## Volume
|
||||||
preferences.volume=가상 드라이브
|
preferences.volume=가상 드라이브
|
||||||
|
preferences.volume.type=볼륨 유형
|
||||||
preferences.volume.type.automatic=자동
|
preferences.volume.type.automatic=자동
|
||||||
preferences.volume.tcp.port=TCP 포트
|
preferences.volume.tcp.port=TCP 포트
|
||||||
preferences.volume.supportedFeatures=현재 선택한 볼륨 타입은 다음과 같은 기능들을 지원합니다:
|
preferences.volume.supportedFeatures=현재 선택한 볼륨 타입은 다음과 같은 기능들을 지원합니다:
|
||||||
|
preferences.volume.feature.mountFlags=사용자 정의 마운트 설정
|
||||||
|
preferences.volume.feature.readOnly=읽기 전용 마운트
|
||||||
## Updates
|
## Updates
|
||||||
preferences.updates=업데이트
|
preferences.updates=업데이트
|
||||||
preferences.updates.currentVersion=현재 버전: %s
|
preferences.updates.currentVersion=현재 버전: %s
|
||||||
@@ -265,9 +274,11 @@ stats.read.accessCount=총 읽기 횟수: %d
|
|||||||
stats.write.throughput.idle=쓰기: 대기중
|
stats.write.throughput.idle=쓰기: 대기중
|
||||||
stats.write.throughput.mibs=쓰기: %.2f MiB/s
|
stats.write.throughput.mibs=쓰기: %.2f MiB/s
|
||||||
stats.write.total.data.none=데이터 기록됨: -
|
stats.write.total.data.none=데이터 기록됨: -
|
||||||
|
stats.write.total.data.kib=데이터 쓰기: %.1f KiB
|
||||||
stats.write.total.data.mib=데이터 쓰기: %.1f MiB
|
stats.write.total.data.mib=데이터 쓰기: %.1f MiB
|
||||||
stats.write.total.data.gib=데이터 쓰기: %.1f GiB
|
stats.write.total.data.gib=데이터 쓰기: %.1f GiB
|
||||||
stats.encr.total.data.none=데이터 암호화: -
|
stats.encr.total.data.none=데이터 암호화: -
|
||||||
|
stats.encr.total.data.kib=데이터 암호화: %.1f KiB
|
||||||
stats.encr.total.data.mib=데이터 암호화: %.1f MiB
|
stats.encr.total.data.mib=데이터 암호화: %.1f MiB
|
||||||
stats.encr.total.data.gib=데이터 암호화: %.1f GiB
|
stats.encr.total.data.gib=데이터 암호화: %.1f GiB
|
||||||
stats.write.accessCount=총 쓰기 횟수: %d
|
stats.write.accessCount=총 쓰기 횟수: %d
|
||||||
@@ -289,7 +300,7 @@ main.vaultlist.contextMenu.unlock=잠금해제...
|
|||||||
main.vaultlist.contextMenu.unlockNow=지금 잠금해제
|
main.vaultlist.contextMenu.unlockNow=지금 잠금해제
|
||||||
main.vaultlist.contextMenu.vaultoptions=Vault 옵션 보기
|
main.vaultlist.contextMenu.vaultoptions=Vault 옵션 보기
|
||||||
main.vaultlist.contextMenu.reveal=드라이브 표시
|
main.vaultlist.contextMenu.reveal=드라이브 표시
|
||||||
main.vaultlist.addVaultBtn=Vault 추가
|
main.vaultlist.addVaultBtn=추가
|
||||||
## Vault Detail
|
## Vault Detail
|
||||||
### Welcome
|
### Welcome
|
||||||
main.vaultDetail.welcomeOnboarding=파일을 보호하기 위해 Cryptomator를 선택해주셔서 감사합니다. 만약 다른 도움이 필요하시면, 시작안내서를 참조하시기 바랍니다.
|
main.vaultDetail.welcomeOnboarding=파일을 보호하기 위해 Cryptomator를 선택해주셔서 감사합니다. 만약 다른 도움이 필요하시면, 시작안내서를 참조하시기 바랍니다.
|
||||||
@@ -303,10 +314,12 @@ main.vaultDetail.passwordSavedInKeychain=비밀번호 저장됨
|
|||||||
main.vaultDetail.unlockedStatus=잠금해제됨
|
main.vaultDetail.unlockedStatus=잠금해제됨
|
||||||
main.vaultDetail.accessLocation=이 Vault의 내용은 다음의 경로에서 접근할 수 있습니다:
|
main.vaultDetail.accessLocation=이 Vault의 내용은 다음의 경로에서 접근할 수 있습니다:
|
||||||
main.vaultDetail.revealBtn=드라이브 표시
|
main.vaultDetail.revealBtn=드라이브 표시
|
||||||
|
main.vaultDetail.copyUri=URI 복사
|
||||||
main.vaultDetail.lockBtn=잠금
|
main.vaultDetail.lockBtn=잠금
|
||||||
main.vaultDetail.bytesPerSecondRead=읽기:
|
main.vaultDetail.bytesPerSecondRead=읽기:
|
||||||
main.vaultDetail.bytesPerSecondWritten=쓰기:
|
main.vaultDetail.bytesPerSecondWritten=쓰기:
|
||||||
main.vaultDetail.throughput.idle=대기
|
main.vaultDetail.throughput.idle=대기
|
||||||
|
main.vaultDetail.throughput.kbps=%.1f KiB/s
|
||||||
main.vaultDetail.throughput.mbps=%.1f MiB/s
|
main.vaultDetail.throughput.mbps=%.1f MiB/s
|
||||||
main.vaultDetail.stats=Vault 통계
|
main.vaultDetail.stats=Vault 통계
|
||||||
### Missing
|
### Missing
|
||||||
@@ -363,6 +376,7 @@ vaultOptions.masterkey.recoveryKeyExplanation=복구 키는 비밀번호를 잊
|
|||||||
vaultOptions.masterkey.showRecoveryKeyBtn=복구 키 표시
|
vaultOptions.masterkey.showRecoveryKeyBtn=복구 키 표시
|
||||||
vaultOptions.masterkey.recoverPasswordBtn=비밀번호 재설정
|
vaultOptions.masterkey.recoverPasswordBtn=비밀번호 재설정
|
||||||
## Hub
|
## Hub
|
||||||
|
vaultOptions.hub=복구
|
||||||
|
|
||||||
# Recovery Key
|
# Recovery Key
|
||||||
## Display Recovery Key
|
## Display Recovery Key
|
||||||
@@ -374,6 +388,7 @@ recoveryKey.display.StorageHints=매우 안전한곳에 보관하십시요. 예
|
|||||||
## Reset Password
|
## Reset Password
|
||||||
### Enter Recovery Key
|
### Enter Recovery Key
|
||||||
recoveryKey.recover.title=비밀번호 바꾸기
|
recoveryKey.recover.title=비밀번호 바꾸기
|
||||||
|
recoveryKey.recover.prompt="%s"의 복구키를 입력하십시요:
|
||||||
recoveryKey.recover.correctKey=올바른 복구 키 입니다
|
recoveryKey.recover.correctKey=올바른 복구 키 입니다
|
||||||
recoveryKey.recover.wrongKey=이 복구 키는 다른 vault의 키입니다
|
recoveryKey.recover.wrongKey=이 복구 키는 다른 vault의 키입니다
|
||||||
recoveryKey.recover.invalidKey=해당 복구 키는 유효하지 않습니다
|
recoveryKey.recover.invalidKey=해당 복구 키는 유효하지 않습니다
|
||||||
@@ -404,5 +419,6 @@ quit.message=잠금 해제된 vault들이 존재합니다
|
|||||||
quit.lockAndQuitBtn=Vault 잠금 후 종료하기
|
quit.lockAndQuitBtn=Vault 잠금 후 종료하기
|
||||||
|
|
||||||
# Forced Quit
|
# Forced Quit
|
||||||
|
quit.forced.forceAndQuitBtn=Vault 강제 잠금 후 종료하기
|
||||||
|
|
||||||
# Update Reminder
|
# Update Reminder
|
||||||
@@ -35,9 +35,6 @@ traymenu.vault.reveal=Atklāt
|
|||||||
|
|
||||||
# Add Vault Wizard
|
# Add Vault Wizard
|
||||||
addvaultwizard.title=Pievienot glabātuvi
|
addvaultwizard.title=Pievienot glabātuvi
|
||||||
## Welcome
|
|
||||||
addvaultwizard.welcome.newButton=Izveidot jaunu glabātuvi
|
|
||||||
addvaultwizard.welcome.existingButton=Atvērt esošu glabātuvi
|
|
||||||
## New
|
## New
|
||||||
### Name
|
### Name
|
||||||
addvaultwizard.new.nameInstruction=Izvēlies glabātuves nosaukumu
|
addvaultwizard.new.nameInstruction=Izvēlies glabātuves nosaukumu
|
||||||
@@ -190,7 +187,6 @@ main.vaultlist.emptyList.onboardingInstruction=Spied šeit, lai pievienotu glab
|
|||||||
main.vaultlist.contextMenu.lock=Aizslēgt
|
main.vaultlist.contextMenu.lock=Aizslēgt
|
||||||
main.vaultlist.contextMenu.unlockNow=Atslēgt tagad
|
main.vaultlist.contextMenu.unlockNow=Atslēgt tagad
|
||||||
main.vaultlist.contextMenu.reveal=Atklāt disku
|
main.vaultlist.contextMenu.reveal=Atklāt disku
|
||||||
main.vaultlist.addVaultBtn=Pievienot glabātuvi
|
|
||||||
## Vault Detail
|
## Vault Detail
|
||||||
### Welcome
|
### Welcome
|
||||||
main.vaultDetail.welcomeOnboarding=Paldies, ka izvēlējāties Cryptomator lai aizsargātu jūsu datus. Ja jums nepieciešama palīdzība, iepazīstieties ar mūsu darba sākšanas ceļvežiem:
|
main.vaultDetail.welcomeOnboarding=Paldies, ka izvēlējāties Cryptomator lai aizsargātu jūsu datus. Ja jums nepieciešama palīdzība, iepazīstieties ar mūsu darba sākšanas ceļvežiem:
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user