diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml new file mode 100644 index 000000000..aab954476 --- /dev/null +++ b/.github/workflows/appimage.yml @@ -0,0 +1,151 @@ +name: Build AppImage + +on: + release: + types: [published] + workflow_dispatch: + +env: + JAVA_VERSION: 17 + +jobs: + build: + name: Build AppImage + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Setup Java + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: ${{ env.JAVA_VERSION }} + cache: 'maven' + - id: versions + name: Apply version information + run: | + if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then + SEM_VER_STR=${GITHUB_REF##*/} + mvn versions:set -DnewVersion=${SEM_VER_STR} + else + SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout` + fi + SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'` + REVCOUNT=`git rev-list --count HEAD` + echo "::set-output name=semVerStr::${SEM_VER_STR}" + echo "::set-output name=semVerNum::${SEM_VER_NUM}" + echo "::set-output name=revNum::${REVCOUNT}" + - name: Validate Version + uses: skymatic/semver-validation-action@v1 + with: + version: ${{ steps.versions.outputs.semVerStr }} + - name: Run maven + run: mvn -B clean package -Pdependency-check,linux -DskipTests + - name: Patch target dir + run: | + cp LICENSE.txt target + cp dist/linux/launcher.sh target + cp target/cryptomator-*.jar target/mods + - name: Run jlink + run: > + ${JAVA_HOME}/bin/jlink + --verbose + --output runtime + --module-path "${JAVA_HOME}/jmods" + --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr + --no-header-files + --no-man-pages + --strip-debug + --compress=1 + - name: Run jpackage + run: > + ${JAVA_HOME}/bin/jpackage + --verbose + --type app-image + --runtime-image runtime + --input target/libs + --module-path target/mods + --module org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator + --dest appdir + --name Cryptomator + --vendor "Skymatic GmbH" + --copyright "(C) 2016 - 2022 Skymatic GmbH" + --app-version "${{ steps.versions.outputs.semVerNum }}.${{ steps.versions.outputs.revNum }}" + --java-options "-Xss5m" + --java-options "-Xmx256m" + --java-options "-Dcryptomator.appVersion=\"${{ steps.versions.outputs.semVerStr }}\"" + --java-options "-Dfile.encoding=\"utf-8\"" + --java-options "-Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\"" + --java-options "-Dcryptomator.pluginDir=\"~/.local/share/Cryptomator/plugins\"" + --java-options "-Dcryptomator.settingsPath=\"~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json\"" + --java-options "-Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\"" + --java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\"" + --java-options "-Dcryptomator.showTrayIcon=false" + --java-options "-Dcryptomator.buildNumber=\"appimage-${{ steps.versions.outputs.revNum }}\"" + --resource-dir dist/linux/resources + - name: Patch Cryptomator.AppDir + run: | + mv appdir/Cryptomator Cryptomator.AppDir + cp -r dist/linux/appimage/resources/AppDir/* Cryptomator.AppDir/ + envsubst '${REVISION_NO} ${SEMVER_STR}' < dist/linux/appimage/resources/AppDir/bin/cryptomator.sh > Cryptomator.AppDir/bin/cryptomator.sh + cp dist/linux/common/org.cryptomator.Cryptomator256.png Cryptomator.AppDir/usr/share/icons/hicolor/256x256/apps/org.cryptomator.Cryptomator.png + cp dist/linux/common/org.cryptomator.Cryptomator512.png Cryptomator.AppDir/usr/share/icons/hicolor/512x512/apps/org.cryptomator.Cryptomator.png + cp dist/linux/common/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg + cp dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml Cryptomator.AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.metainfo.xml + cp dist/linux/common/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/usr/share/applications/org.cryptomator.Cryptomator.desktop + cp dist/linux/common/application-vnd.cryptomator.vault.xml Cryptomator.AppDir/usr/share/mime/packages/application-vnd.cryptomator.vault.xml + ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/org.cryptomator.Cryptomator.svg + ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/Cryptomator.svg + ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/.DirIcon + ln -s usr/share/applications/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/Cryptomator.desktop + ln -s bin/cryptomator.sh Cryptomator.AppDir/AppRun + env: + REVISION_NO: ${{ steps.versions.outputs.revNum }} + SEMVER_STR: ${{ steps.versions.outputs.semVerStr }} + - name: Extract libjffi.so # workaround for https://github.com/cryptomator/cryptomator-linux/issues/27 + run: | + JFFI_NATIVE_JAR=`ls lib/app/ | grep -e 'jffi-[1-9]\.[0-9]\{1,2\}.[0-9]\{1,2\}-native.jar'` + ${JAVA_HOME}/bin/jar -xf lib/app/${JFFI_NATIVE_JAR} /jni/x86_64-Linux/ + mv jni/x86_64-Linux/* lib/app/libjffi.so + working-directory: Cryptomator.AppDir + - name: Download AppImageKit + run: | + curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-x86_64.AppImage -o appimagetool.AppImage + chmod +x appimagetool.AppImage + ./appimagetool.AppImage --appimage-extract + - name: Prepare GPG-Agent for signing with key 615D449FE6E6A235 + run: | + echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import + echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --dry-run --sign README.md + env: + GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} + - name: Build AppImage + run: > + ./squashfs-root/AppRun Cryptomator.AppDir cryptomator-${{ steps.versions.outputs.semVerStr }}-x86_64.AppImage + -u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-x86_64.AppImage.zsync' + --sign --sign-key=615D449FE6E6A235 --sign-args="--batch --pinentry-mode loopback" + - name: Create detached GPG signatures + run: | + gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage + gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage.zsync + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: appimage + path: | + cryptomator-*.AppImage + cryptomator-*.AppImage.zsync + cryptomator-*.asc + if-no-files-found: error + - name: Publish AppImage on GitHub Releases + if: startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v1 + with: + fail_on_unmatched_files: true + token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} + files: | + cryptomator-*.AppImage + cryptomator-*.zsync + cryptomator-*.asc \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2ffc3070e..97d46ae9e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,12 +24,41 @@ jobs: distribution: 'temurin' java-version: ${{ env.JAVA_VERSION }} cache: 'maven' + - name: Cache SonarCloud packages + uses: actions/cache@v2 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar - name: Build and Test - run: mvn -B clean install jacoco:report -Pcoverage,dependency-check + run: > + xvfb-run + mvn -B verify + jacoco:report + org.sonarsource.scanner.maven:sonar-maven-plugin:sonar + -Pcoverage,dependency-check + -Dsonar.projectKey=cryptomator_cryptomator + -Dsonar.organization=cryptomator + -Dsonar.host.url=https://sonarcloud.io + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - name: Upload code coverage report id: codacyCoverageReporter if: "github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'pr:safe')" run: bash <(curl -Ls https://coverage.codacy.com/get.sh) env: CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }} - continue-on-error: true \ No newline at end of file + continue-on-error: true + - name: Draft a release + if: startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v1 + with: + draft: true + discussion_category_name: releases + token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} + generate_release_notes: true + body: |- + :construction: Work in Progress + + --- diff --git a/.github/workflows/debian.yml b/.github/workflows/debian.yml new file mode 100644 index 000000000..f7893bbaa --- /dev/null +++ b/.github/workflows/debian.yml @@ -0,0 +1,118 @@ +name: Build Debian Package + +on: + release: + types: [published] + workflow_dispatch: + inputs: + dput: + description: 'Upload to PPA' + required: true + default: false + type: boolean + +env: + JAVA_VERSION: 17 + +jobs: + build: + name: Build Debian Package + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Install build tools + run: | + sudo apt-get update + sudo apt-get install debhelper devscripts dput + - name: Setup Java + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: ${{ env.JAVA_VERSION }} + cache: 'maven' + - id: versions + name: Apply version information + run: | + if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then + SEM_VER_STR=${GITHUB_REF##*/} + mvn versions:set -DnewVersion=${SEM_VER_STR} + else + SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout` + fi + SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'` + REVCOUNT=`git rev-list --count HEAD` + echo "::set-output name=semVerStr::${SEM_VER_STR}" + echo "::set-output name=semVerNum::${SEM_VER_NUM}" + echo "::set-output name=revNum::${REVCOUNT}" + echo "::set-output name=ppaVerStr::${SEM_VER_STR/-/\~}-${REVCOUNT}" + - name: Validate Version + uses: skymatic/semver-validation-action@v1 + with: + version: ${{ steps.versions.outputs.semVerStr }} + - name: Run maven + run: mvn -B clean package -Pdependency-check,linux -DskipTests + - name: Create orig.tar.gz with common/ libs/ mods/ + run: | + mkdir pkgdir + cp -r target/libs pkgdir + cp -r target/mods pkgdir + cp -r dist/linux/common/ pkgdir + cp target/cryptomator-*.jar pkgdir/mods + tar -cJf cryptomator_${{ steps.versions.outputs.ppaVerStr }}.orig.tar.xz -C pkgdir . + - name: Patch and rename pkgdir + run: | + cp -r dist/linux/debian/ pkgdir + export RFC2822_TIMESTAMP=`date --rfc-2822` + envsubst '${SEMVER_STR} ${VERSION_NUM} ${REVISION_NUM}' < dist/linux/debian/rules > pkgdir/debian/rules + envsubst '${PPA_VERSION} ${RFC2822_TIMESTAMP}' < dist/linux/debian/changelog > pkgdir/debian/changelog + find . -name "*.jar" >> pkgdir/debian/source/include-binaries + mv pkgdir cryptomator_${{ steps.versions.outputs.ppaVerStr }} + env: + SEMVER_STR: ${{ steps.versions.outputs.semVerStr }} + VERSION_NUM: ${{ steps.versions.outputs.semVerNum }} + REVISION_NUM: ${{ steps.versions.outputs.revNum }} + PPA_VERSION: ${{ steps.versions.outputs.ppaVerStr }}-0ppa1 + - name: Prepare GPG-Agent for signing with key 615D449FE6E6A235 + run: | + echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import + echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --dry-run --sign README.md + env: + GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} + - name: debuild + run: | + debuild -S -sa -d + debuild -b -sa -d + env: + DEBSIGN_PROGRAM: gpg --batch --pinentry-mode loopback + DEBSIGN_KEYID: 615D449FE6E6A235 + working-directory: cryptomator_${{ steps.versions.outputs.ppaVerStr }} + - name: Create detached GPG signatures + run: | + gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator_*_amd64.deb + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: linux-deb-package + path: | + cryptomator_*.dsc + cryptomator_*.orig.tar.xz + cryptomator_*.debian.tar.xz + cryptomator_*_source.buildinfo + cryptomator_*_source.changes + cryptomator_*_amd64.deb + cryptomator_*.asc + - name: Publish on PPA + if: startsWith(github.ref, 'refs/tags/') || github.event.inputs.dput == 'true' + run: dput ppa:sebastian-stenzel/cryptomator-beta cryptomator_*_source.changes + - name: Publish Debian package on GitHub Releases + if: startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v1 + with: + fail_on_unmatched_files: true + token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} + files: | + cryptomator_*_amd64.deb + cryptomator_*.asc \ No newline at end of file diff --git a/.github/workflows/mac-dmg.yml b/.github/workflows/mac-dmg.yml new file mode 100644 index 000000000..66af92d6d --- /dev/null +++ b/.github/workflows/mac-dmg.yml @@ -0,0 +1,232 @@ +name: Build macOS .dmg + +on: + release: + types: [published] + workflow_dispatch: + +env: + JAVA_VERSION: 17 + +jobs: + build: + name: Build Cryptomator.app + runs-on: macos-11 + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Setup Java + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: ${{ env.JAVA_VERSION }} + cache: 'maven' + - id: versions + name: Apply version information + run: | + if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then + SEM_VER_STR=${GITHUB_REF##*/} + mvn versions:set -DnewVersion=${SEM_VER_STR} + else + SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout` + fi + SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'` + REVCOUNT=`git rev-list --count HEAD` + echo "::set-output name=semVerStr::${SEM_VER_STR}" + echo "::set-output name=semVerNum::${SEM_VER_NUM}" + echo "::set-output name=revNum::${REVCOUNT}" + - name: Validate Version + uses: skymatic/semver-validation-action@v1 + with: + version: ${{ steps.versions.outputs.semVerStr }} + - name: Run maven + run: mvn -B clean package -Pdependency-check,mac -DskipTests + - name: Patch target dir + run: | + cp LICENSE.txt target + cp dist/mac/launcher.sh target + cp target/cryptomator-*.jar target/mods + - name: Run jlink + run: > + ${JAVA_HOME}/bin/jlink + --verbose + --output runtime + --module-path "${JAVA_HOME}/jmods" + --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr + --strip-native-commands + --no-header-files + --no-man-pages + --strip-debug + --compress=1 + - name: Run jpackage + run: > + ${JAVA_HOME}/bin/jpackage + --verbose + --type app-image + --runtime-image runtime + --input target/libs + --module-path target/mods + --module org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator + --dest appdir + --name Cryptomator + --vendor "Skymatic GmbH" + --copyright "(C) 2016 - 2022 Skymatic GmbH" + --app-version "${{ steps.versions.outputs.semVerNum }}" + --java-options "-Xss5m" + --java-options "-Xmx256m" + --java-options "-Dcryptomator.appVersion=\"${{ steps.versions.outputs.semVerStr }}\"" + --java-options "-Dfile.encoding=\"utf-8\"" + --java-options "-Dapple.awt.enableTemplateImages=true" + --java-options "-Dcryptomator.logDir=\"~/Library/Logs/Cryptomator\"" + --java-options "-Dcryptomator.pluginDir=\"~/Library/Application Support/Cryptomator/Plugins\"" + --java-options "-Dcryptomator.settingsPath=\"~/Library/Application Support/Cryptomator/settings.json\"" + --java-options "-Dcryptomator.ipcSocketPath=\"~/Library/Application Support/Cryptomator/ipc.socket\"" + --java-options "-Dcryptomator.showTrayIcon=true" + --java-options "-Dcryptomator.buildNumber=\"dmg-${{ steps.versions.outputs.revNum }}\"" + --mac-package-identifier org.cryptomator + --resource-dir dist/mac/resources + - name: Patch Cryptomator.app + run: | + mv appdir/Cryptomator.app Cryptomator.app + mv dist/mac/resources/Cryptomator-Vault.icns Cryptomator.app/Contents/Resources/ + sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NO}|g" Cryptomator.app/Contents/Info.plist + sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NO}|g" Cryptomator.app/Contents/Info.plist + env: + VERSION_NO: ${{ steps.versions.outputs.semVerNum }} + REVISION_NO: ${{ steps.versions.outputs.revNum }} + - name: Install codesign certificate + run: | + # create variables + CERTIFICATE_PATH=$RUNNER_TEMP/codesign.p12 + KEYCHAIN_PATH=$RUNNER_TEMP/codesign.keychain-db + + # import certificate and provisioning profile from secrets + echo -n "$CODESIGN_P12_BASE64" | base64 --decode --output $CERTIFICATE_PATH + + # create temporary keychain + security create-keychain -p "$CODESIGN_TMP_KEYCHAIN_PW" $KEYCHAIN_PATH + security set-keychain-settings -lut 900 $KEYCHAIN_PATH + security unlock-keychain -p "$CODESIGN_TMP_KEYCHAIN_PW" $KEYCHAIN_PATH + + # import certificate to keychain + security import $CERTIFICATE_PATH -P "$CODESIGN_P12_PW" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH + security list-keychain -d user -s $KEYCHAIN_PATH + env: + CODESIGN_P12_BASE64: ${{ secrets.MACOS_CODESIGN_P12_BASE64 }} + CODESIGN_P12_PW: ${{ secrets.MACOS_CODESIGN_P12_PW }} + CODESIGN_TMP_KEYCHAIN_PW: ${{ secrets.MACOS_CODESIGN_TMP_KEYCHAIN_PW }} + - name: Codesign + run: | + find Cryptomator.app/Contents/runtime/Contents/MacOS -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \; + for JAR_PATH in `find Cryptomator.app -name "*.jar"`; do + if [[ `unzip -l ${JAR_PATH} | grep '.dylib\|.jnilib'` ]]; then + JAR_FILENAME=$(basename ${JAR_PATH}) + OUTPUT_PATH=${JAR_PATH%.*} + echo "Codesigning libs in ${JAR_FILENAME}..." + unzip -q ${JAR_PATH} -d ${OUTPUT_PATH} + find ${OUTPUT_PATH} -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \; + find ${OUTPUT_PATH} -name '*.jnilib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \; + rm ${JAR_PATH} + pushd ${OUTPUT_PATH} > /dev/null + zip -qr ../${JAR_FILENAME} * + popd > /dev/null + rm -r ${OUTPUT_PATH} + fi + done + echo "Codesigning Cryptomator.app..." + codesign --force --deep --entitlements dist/mac/Cryptomator.entitlements -o runtime -s ${CODESIGN_IDENTITY} Cryptomator.app + env: + CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }} + - name: Prepare .dmg contents + run: | + mkdir dmg + mv Cryptomator.app dmg + cp dist/mac/dmg/resources/macFUSE.webloc dmg + ls -l dmg + - name: Install create-dmg + run: | + brew install create-dmg + create-dmg --help + - name: Create .dmg + run: > + create-dmg + --volname Cryptomator + --volicon "dist/mac/dmg/resources/Cryptomator-Volume.icns" + --background "dist/mac/dmg/resources/Cryptomator-background.tiff" + --window-pos 400 100 + --window-size 640 694 + --icon-size 128 + --icon "Cryptomator.app" 128 245 + --hide-extension "Cryptomator.app" + --icon "macFUSE.webloc" 320 501 + --hide-extension "macFUSE.webloc" + --app-drop-link 512 245 + --eula "dist/mac/dmg/resources/license.rtf" + --icon ".background" 128 758 + --icon ".fseventsd" 320 758 + --icon ".VolumeIcon.icns" 512 758 + Cryptomator-${VERSION_NO}.dmg dmg + env: + VERSION_NO: ${{ steps.versions.outputs.semVerNum }} + - name: Install notarization credentials + if: startsWith(github.ref, 'refs/tags/') + run: | + # create temporary keychain + KEYCHAIN_PATH=$RUNNER_TEMP/notarization.keychain-db + security create-keychain -p "${NOTARIZATION_TMP_KEYCHAIN_PW}" ${KEYCHAIN_PATH} + security set-keychain-settings -lut 900 ${KEYCHAIN_PATH} + security unlock-keychain -p "${NOTARIZATION_TMP_KEYCHAIN_PW}" ${KEYCHAIN_PATH} + + # import credentials from secrets + sudo xcode-select -s /Applications/Xcode_13.0.app + xcrun notarytool store-credentials "${NOTARIZATION_KEYCHAIN_PROFILE}" --apple-id "${NOTARIZATION_APPLE_ID}" --password "${NOTARIZATION_PW}" --team-id "${NOTARIZATION_TEAM_ID}" --keychain "${KEYCHAIN_PATH}" + env: + NOTARIZATION_KEYCHAIN_PROFILE: ${{ secrets.MACOS_NOTARIZATION_KEYCHAIN_PROFILE }} + NOTARIZATION_APPLE_ID: ${{ secrets.MACOS_NOTARIZATION_APPLE_ID }} + NOTARIZATION_PW: ${{ secrets.MACOS_NOTARIZATION_PW }} + NOTARIZATION_TEAM_ID: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }} + NOTARIZATION_TMP_KEYCHAIN_PW: ${{ secrets.MACOS_NOTARIZATION_TMP_KEYCHAIN_PW }} + - name: Notarize .dmg + if: startsWith(github.ref, 'refs/tags/') + run: | + KEYCHAIN_PATH=$RUNNER_TEMP/notarization.keychain-db + sudo xcode-select -s /Applications/Xcode_13.0.app + xcrun notarytool submit Cryptomator-*.dmg --keychain-profile "${NOTARIZATION_KEYCHAIN_PROFILE}" --keychain "${KEYCHAIN_PATH}" --wait + xcrun stapler staple Cryptomator-*.dmg + env: + NOTARIZATION_KEYCHAIN_PROFILE: ${{ secrets.MACOS_NOTARIZATION_KEYCHAIN_PROFILE }} + - name: Add possible alpha/beta tags to installer name + run: mv Cryptomator-*.dmg Cryptomator-${{ steps.versions.outputs.semVerStr }}.dmg + - name: Create detached GPG signature with key 615D449FE6E6A235 + run: | + echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import + echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a Cryptomator-*.dmg + env: + GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} + - name: Clean up codesign certificate + if: ${{ always() }} + run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db + continue-on-error: true + - name: Clean up notarization credentials + if: ${{ always() }} + run: security delete-keychain $RUNNER_TEMP/notarization.keychain-db + continue-on-error: true + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: dmg + path: Cryptomator-*.dmg + if-no-files-found: error + - name: Publish dmg on GitHub Releases + if: startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v1 + with: + fail_on_unmatched_files: true + token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} + files: | + Cryptomator-*.dmg + Cryptomator-*.asc + + diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index c529ca09b..79a0d5a21 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -23,4 +23,4 @@ jobs: java-version: ${{ env.JAVA_VERSION }} cache: 'maven' - name: Build and Test - run: mvn -B clean install jacoco:report -Pcoverage,dependency-check \ No newline at end of file + run: xvfb-run mvn -B clean install jacoco:report -Pcoverage,dependency-check \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 39b6ce77d..000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,626 +0,0 @@ -name: Installers and Release - -on: - workflow_dispatch: - inputs: - semver: - description: 'SemVer' - required: true - default: '0.99.99-SNAPSHOT' - push: - tags: # see https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet - - '[0-9]+.[0-9]+.[0-9]+' - - '[0-9]+.[0-9]+.[0-9]+-*' - -env: - JAVA_VERSION: 17 - -defaults: - run: - shell: bash - -jobs: - -# -# Buildkit -# - buildkit: - name: Build ${{ matrix.profile }}-buildkit - runs-on: ${{ matrix.os }} - strategy: - fail-fast: true - matrix: - include: - - os: ubuntu-latest - profile: linux - - os: windows-latest - profile: win - - os: macos-latest - profile: mac - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-java@v2 - with: - distribution: 'temurin' - java-version: ${{ env.JAVA_VERSION }} - cache: 'maven' - - name: Ensure to use tagged version - run: mvn versions:set -DnewVersion=${GITHUB_REF##*/} # use shell parameter expansion to strip of 'refs/tags' - if: startsWith(github.ref, 'refs/tags/') - - name: Build and Test - run: mvn -B clean package -Pdependency-check,${{ matrix.profile }} - - name: Patch buildkit - run: | - cp LICENSE.txt target - cp dist/${{ matrix.profile }}/launcher* target - cp target/cryptomator-*.jar target/mods - - name: Upload ${{ matrix.profile }}-buildkit - uses: actions/upload-artifact@v2 - with: - name: ${{ matrix.profile }}-buildkit - path: | - target/libs - target/mods - target/LICENSE.txt - target/launcher* - if-no-files-found: error - -# -# Release Metadata -# - metadata: - name: Determine Version Metadata - runs-on: ubuntu-latest - outputs: - semVerNum: ${{ steps.versions.outputs.semVerNum }} - semVerStr: ${{ steps.versions.outputs.semVerStr }} - ppaVerStr: ${{ steps.versions.outputs.ppaVerStr }} - revNum: ${{ steps.versions.outputs.revNum }} - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - id: versions - run: | - if [[ $GITHUB_REF == refs/tags/* ]]; then - SEM_VER_STR=${GITHUB_REF##*/} - else - SEM_VER_STR=${{ github.event.inputs.semver }} - fi - SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'` - REVCOUNT=`git rev-list --count HEAD` - echo "::set-output name=semVerStr::${SEM_VER_STR}" - echo "::set-output name=semVerNum::${SEM_VER_NUM}" - echo "::set-output name=ppaVerStr::${SEM_VER_STR/-/\~}-${REVCOUNT}" - echo "::set-output name=revNum::${REVCOUNT}" - - uses: skymatic/semver-validation-action@v1 - with: - version: ${{ steps.versions.outputs.semVerStr }} - -# -# Application Directory -# - appdir: - name: Create ${{ matrix.profile }}-appdir - needs: [buildkit, metadata] - runs-on: ${{ matrix.os }} - strategy: - fail-fast: true - matrix: - include: - - os: ubuntu-latest - profile: linux - jpackageoptions: > - --app-version "${{ needs.metadata.outputs.semVerNum }}.${{ needs.metadata.outputs.revNum }}" - --java-options "-Dfile.encoding=\"utf-8\"" - --java-options "-Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\"" - --java-options "-Dcryptomator.pluginDir=\"~/.local/share/Cryptomator/plugins\"" - --java-options "-Dcryptomator.settingsPath=\"~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json\"" - --java-options "-Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\"" - --java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\"" - --java-options "-Dcryptomator.showTrayIcon=false" - --java-options "-Dcryptomator.buildNumber=\"appimage-${{ needs.metadata.outputs.revNum }}\"" - --resource-dir dist/linux/resources - - os: windows-latest - profile: win - jpackageoptions: > - --app-version "${{ needs.metadata.outputs.semVerNum }}.${{ needs.metadata.outputs.revNum }}" - --java-options "-Dfile.encoding=\"utf-8\"" - --java-options "-Dcryptomator.logDir=\"~/AppData/Roaming/Cryptomator\"" - --java-options "-Dcryptomator.pluginDir=\"~/AppData/Roaming/Cryptomator/Plugins\"" - --java-options "-Dcryptomator.settingsPath=\"~/AppData/Roaming/Cryptomator/settings.json\"" - --java-options "-Dcryptomator.ipcSocketPath=\"~/AppData/Roaming/Cryptomator/ipc.socket\"" - --java-options "-Dcryptomator.keychainPath=\"~/AppData/Roaming/Cryptomator/keychain.json\"" - --java-options "-Dcryptomator.mountPointsDir=\"~/Cryptomator\"" - --java-options "-Dcryptomator.showTrayIcon=true" - --java-options "-Dcryptomator.buildNumber=\"msi-${{ needs.metadata.outputs.revNum }}\"" - --resource-dir dist/win/resources - --icon dist/win/resources/Cryptomator.ico - - os: macos-latest - profile: mac - jpackageoptions: > - --app-version "${{ needs.metadata.outputs.semVerNum }}" - --java-options "-Dfile.encoding=\"utf-8\"" - --java-options "-Dapple.awt.enableTemplateImages=true" - --java-options "-Dcryptomator.logDir=\"~/Library/Logs/Cryptomator\"" - --java-options "-Dcryptomator.pluginDir=\"~/Library/Application Support/Cryptomator/Plugins\"" - --java-options "-Dcryptomator.settingsPath=\"~/Library/Application Support/Cryptomator/settings.json\"" - --java-options "-Dcryptomator.ipcSocketPath=\"~/Library/Application Support/Cryptomator/ipc.socket\"" - --java-options "-Dcryptomator.showTrayIcon=true" - --java-options "-Dcryptomator.buildNumber=\"dmg-${{ needs.metadata.outputs.revNum }}\"" - --mac-package-identifier org.cryptomator - --resource-dir dist/mac/resources - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-java@v2 - with: - distribution: 'temurin' - java-version: ${{ env.JAVA_VERSION }} - - name: Download ${{ matrix.profile }}-buildkit - uses: actions/download-artifact@v2 - with: - name: ${{ matrix.profile }}-buildkit - path: buildkit - - name: Create Runtime Image - run: > - ${JAVA_HOME}/bin/jlink - --verbose - --output runtime - --module-path "${JAVA_HOME}/jmods" - --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility - --no-header-files - --no-man-pages - --strip-debug - --compress=1 - - name: Create App Directory - run: > - ${JAVA_HOME}/bin/jpackage - --verbose - --type app-image - --runtime-image runtime - --input buildkit/libs - --module-path buildkit/mods - --module org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator - --dest appdir - --name Cryptomator - --vendor "Skymatic GmbH" - --copyright "(C) 2016 - 2021 Skymatic GmbH" - --java-options "-Xss5m" - --java-options "-Xmx256m" - --java-options "-Dcryptomator.appVersion=\"${{ needs.metadata.outputs.semVerStr }}\"" - ${{ matrix.jpackageoptions }} - - name: Create appdir.tar - run: tar -cvf appdir.tar appdir - - name: Upload ${{ matrix.profile }}-appdir - uses: actions/upload-artifact@v2 - with: - name: ${{ matrix.profile }}-appdir - path: appdir.tar - if-no-files-found: error - -# -# Linux PPA Source Package -# - ppa: - name: Upload source package to PPA - needs: [buildkit, metadata] - runs-on: ubuntu-18.04 - steps: - - uses: actions/checkout@v2 - - name: install build tools - run: | - sudo apt-get update - sudo apt-get install debhelper devscripts dput - - name: Download linux-buildkit - uses: actions/download-artifact@v2 - with: - name: linux-buildkit - path: pkgdir - - name: create orig.tar.gz - run: tar -cJf cryptomator_${{ needs.metadata.outputs.ppaVerStr }}.orig.tar.xz -C pkgdir . - - name: patch and rename pkgdir - run: | - cp -r dist/linux/debian/ pkgdir - cp -r dist/linux/resources/ pkgdir - export RFC2822_TIMESTAMP=`date --rfc-2822` - envsubst '${VERSION_STR} ${VERSION_NUM} ${REVISION_NUM}' < dist/linux/debian/rules > pkgdir/debian/rules - envsubst '${VERSION_STR}' < dist/linux/debian/org.cryptomator.Cryptomator.desktop > pkgdir/debian/org.cryptomator.Cryptomator.desktop - envsubst '${PPA_VERSION} ${RFC2822_TIMESTAMP}' < dist/linux/debian/changelog > pkgdir/debian/changelog - find . -name "*.jar" >> pkgdir/debian/source/include-binaries - mv pkgdir cryptomator_${{ needs.metadata.outputs.ppaVerStr }} - env: - VERSION_STR: ${{ needs.metadata.outputs.semVerStr }} - VERSION_NUM: ${{ needs.metadata.outputs.semVerNum }} - REVISION_NUM: ${{ needs.metadata.outputs.revNum }} - PPA_VERSION: ${{ needs.metadata.outputs.ppaVerStr }}-0ppa1 - - name: import gpg key 615D449FE6E6A235 - run: | - echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import - echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --dry-run --sign dist/linux/debian/rules - env: - GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} - GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} - - name: debuild - run: debuild -S -sa -d - env: - DEBSIGN_PROGRAM: gpg --batch --pinentry-mode loopback - DEBSIGN_KEYID: 615D449FE6E6A235 - working-directory: cryptomator_${{ needs.metadata.outputs.ppaVerStr }} - - name: Upload artifacts - uses: actions/upload-artifact@v2 - with: - name: linux-deb-source-package - path: | - cryptomator_*.dsc - cryptomator_*.orig.tar.xz - cryptomator_*.debian.tar.xz - cryptomator_*_source.changes - cryptomator_*_source.buildinfo - - name: dput to beta repo - run: dput ppa:sebastian-stenzel/cryptomator-beta cryptomator_${PPA_VERSION}_source.changes - env: - PPA_VERSION: ${{ needs.metadata.outputs.ppaVerStr }}-0ppa1 - -# -# Linux Cryptomator.AppImage -# - linux-appimage: - name: Build Cryptomator.AppImage - runs-on: ubuntu-latest - needs: [appdir, metadata] - steps: - - uses: actions/checkout@v2 - - name: Download linux-appdir - uses: actions/download-artifact@v2 - with: - name: linux-appdir - - name: Untar appdir.tar - run: | - tar -xvf appdir.tar - - name: Patch Cryptomator.AppDir - run: | - mv appdir/Cryptomator Cryptomator.AppDir - cp -r dist/linux/appimage/resources/AppDir/* Cryptomator.AppDir/ - envsubst '${REVISION_NO} ${SEMVER_STR}' < dist/linux/appimage/resources/AppDir/bin/cryptomator.sh > Cryptomator.AppDir/bin/cryptomator.sh - ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/org.cryptomator.Cryptomator.svg - ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/Cryptomator.svg - ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/.DirIcon - ln -s usr/share/applications/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/Cryptomator.desktop - ln -s bin/cryptomator.sh Cryptomator.AppDir/AppRun - env: - REVISION_NO: ${{ needs.metadata.outputs.revNum }} - SEMVER_STR: ${{ needs.metadata.outputs.semVerStr }} - - name: Extract libjffi.so # workaround for https://github.com/cryptomator/cryptomator-linux/issues/27 - run: | - JFFI_NATIVE_JAR=`ls lib/app/ | grep -e 'jffi-[1-9]\.[0-9]\{1,2\}.[0-9]\{1,2\}-native.jar'` - ${JAVA_HOME}/bin/jar -xf lib/app/${JFFI_NATIVE_JAR} /jni/x86_64-Linux/ - mv jni/x86_64-Linux/* lib/app/libjffi.so - working-directory: Cryptomator.AppDir - - name: Download AppImageKit - run: | - curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-x86_64.AppImage -o appimagetool.AppImage - chmod +x appimagetool.AppImage - ./appimagetool.AppImage --appimage-extract - - name: Prepare GPG-Agent for signing with key 615D449FE6E6A235 - run: | - echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import - echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --dry-run --sign Cryptomator.AppDir/AppRun - env: - GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} - GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} - - name: Build AppImage - run: > - ./squashfs-root/AppRun Cryptomator.AppDir cryptomator-${{ needs.metadata.outputs.semVerStr }}-x86_64.AppImage - -u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-x86_64.AppImage.zsync' - --sign --sign-key=615D449FE6E6A235 --sign-args="--batch --pinentry-mode loopback" - - name: Upload AppImage - uses: actions/upload-artifact@v2 - with: - name: linux-appimage - path: | - cryptomator-*.AppImage - cryptomator-*.AppImage.zsync - if-no-files-found: error - -# -# macOS Cryptomator.app -# - mac-app: - name: Build Cryptomator.app - runs-on: macos-latest - needs: [appdir, metadata] - steps: - - uses: actions/checkout@v2 - - name: Download mac-appdir - uses: actions/download-artifact@v2 - with: - name: mac-appdir - - name: Untar appdir.tar - run: tar -xvf appdir.tar - - name: Patch Cryptomator.app - run: | - mv appdir/Cryptomator.app Cryptomator.app - mv dist/mac/resources/Cryptomator-Vault.icns Cryptomator.app/Contents/Resources/ - sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NO}|g" Cryptomator.app/Contents/Info.plist - sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NO}|g" Cryptomator.app/Contents/Info.plist - env: - VERSION_NO: ${{ needs.metadata.outputs.semVerNum }} - REVISION_NO: ${{ needs.metadata.outputs.revNum }} - - name: Install codesign certificate - env: - CODESIGN_P12_BASE64: ${{ secrets.MACOS_CODESIGN_P12_BASE64 }} - CODESIGN_P12_PW: ${{ secrets.MACOS_CODESIGN_P12_PW }} - CODESIGN_TMP_KEYCHAIN_PW: ${{ secrets.MACOS_CODESIGN_TMP_KEYCHAIN_PW }} - run: | - # create variables - CERTIFICATE_PATH=$RUNNER_TEMP/codesign.p12 - KEYCHAIN_PATH=$RUNNER_TEMP/codesign.keychain-db - - # import certificate and provisioning profile from secrets - echo -n "$CODESIGN_P12_BASE64" | base64 --decode --output $CERTIFICATE_PATH - - # create temporary keychain - security create-keychain -p "$CODESIGN_TMP_KEYCHAIN_PW" $KEYCHAIN_PATH - security set-keychain-settings -lut 900 $KEYCHAIN_PATH - security unlock-keychain -p "$CODESIGN_TMP_KEYCHAIN_PW" $KEYCHAIN_PATH - - # import certificate to keychain - security import $CERTIFICATE_PATH -P "$CODESIGN_P12_PW" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH - security list-keychain -d user -s $KEYCHAIN_PATH - - name: Codesign - env: - CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }} - run: | - find Cryptomator.app/Contents/runtime/Contents/MacOS -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \; - for JAR_PATH in `find Cryptomator.app -name "*.jar"`; do - if [[ `unzip -l ${JAR_PATH} | grep '.dylib\|.jnilib'` ]]; then - JAR_FILENAME=$(basename ${JAR_PATH}) - OUTPUT_PATH=${JAR_PATH%.*} - echo "Codesigning libs in ${JAR_FILENAME}..." - unzip -q ${JAR_PATH} -d ${OUTPUT_PATH} - find ${OUTPUT_PATH} -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \; - find ${OUTPUT_PATH} -name '*.jnilib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \; - rm ${JAR_PATH} - pushd ${OUTPUT_PATH} > /dev/null - zip -qr ../${JAR_FILENAME} * - popd > /dev/null - rm -r ${OUTPUT_PATH} - fi - done - echo "Codesigning Cryptomator.app..." - codesign --force --deep --entitlements dist/mac/Cryptomator.entitlements -o runtime -s ${CODESIGN_IDENTITY} Cryptomator.app - - name: Clean up codesign certificate - if: ${{ always() }} - run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db - - name: Create app.tar - run: tar -cvf app.tar Cryptomator.app - - name: Upload mac-app - uses: actions/upload-artifact@v2 - with: - name: mac-app - path: app.tar - if-no-files-found: error - -# -# macOS Cryptomator.dmg -# - mac-dmg: - name: Build Cryptomator.dmg - runs-on: macos-11 - needs: [mac-app, metadata] - steps: - - uses: actions/checkout@v2 - - name: Download mac-appdir - uses: actions/download-artifact@v2 - with: - name: mac-app - - name: Untar app.tar - run: tar -xvf app.tar - - name: Prepare .dmg contents - run: | - mkdir dmg - mv Cryptomator.app dmg - cp dist/mac/dmg/resources/macFUSE.webloc dmg - ls -l dmg - - name: Install create-dmg - run: | - brew install create-dmg - create-dmg --help - - name: Create .dmg - run: > - create-dmg - --volname Cryptomator - --volicon "dist/mac/dmg/resources/Cryptomator-Volume.icns" - --background "dist/mac/dmg/resources/Cryptomator-background.tiff" - --window-pos 400 100 - --window-size 640 694 - --icon-size 128 - --icon "Cryptomator.app" 128 245 - --hide-extension "Cryptomator.app" - --icon "macFUSE.webloc" 320 501 - --hide-extension "macFUSE.webloc" - --app-drop-link 512 245 - --eula "dist/mac/dmg/resources/license.rtf" - --icon ".background" 128 758 - --icon ".fseventsd" 320 758 - --icon ".VolumeIcon.icns" 512 758 - Cryptomator-${VERSION_NO}.dmg dmg - env: - VERSION_NO: ${{ needs.metadata.outputs.semVerNum }} - - name: Install notarization credentials - env: - NOTARIZATION_KEYCHAIN_PROFILE: ${{ secrets.MACOS_NOTARIZATION_KEYCHAIN_PROFILE }} - NOTARIZATION_APPLE_ID: ${{ secrets.MACOS_NOTARIZATION_APPLE_ID }} - NOTARIZATION_PW: ${{ secrets.MACOS_NOTARIZATION_PW }} - NOTARIZATION_TEAM_ID: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }} - NOTARIZATION_TMP_KEYCHAIN_PW: ${{ secrets.MACOS_NOTARIZATION_TMP_KEYCHAIN_PW }} - run: | - # create temporary keychain - KEYCHAIN_PATH=$RUNNER_TEMP/notarization.keychain-db - security create-keychain -p "${NOTARIZATION_TMP_KEYCHAIN_PW}" ${KEYCHAIN_PATH} - security set-keychain-settings -lut 900 ${KEYCHAIN_PATH} - security unlock-keychain -p "${NOTARIZATION_TMP_KEYCHAIN_PW}" ${KEYCHAIN_PATH} - - # import credentials from secrets - sudo xcode-select -s /Applications/Xcode_13.0.app - xcrun notarytool store-credentials "${NOTARIZATION_KEYCHAIN_PROFILE}" --apple-id "${NOTARIZATION_APPLE_ID}" --password "${NOTARIZATION_PW}" --team-id "${NOTARIZATION_TEAM_ID}" --keychain "${KEYCHAIN_PATH}" - - name: Notarize .dmg - env: - NOTARIZATION_KEYCHAIN_PROFILE: ${{ secrets.MACOS_NOTARIZATION_KEYCHAIN_PROFILE }} - run: | - KEYCHAIN_PATH=$RUNNER_TEMP/notarization.keychain-db - sudo xcode-select -s /Applications/Xcode_13.0.app - xcrun notarytool submit Cryptomator-*.dmg --keychain-profile "${NOTARIZATION_KEYCHAIN_PROFILE}" --keychain "${KEYCHAIN_PATH}" --wait - xcrun stapler staple Cryptomator-*.dmg - - name: Clean up notarization credentials - if: ${{ always() }} - run: security delete-keychain $RUNNER_TEMP/notarization.keychain-db - - name: Add possible alpha/beta tags to installer name - run: mv Cryptomator-*.dmg Cryptomator-${{ needs.metadata.outputs.semVerStr }}.dmg - - name: Upload mac-dmg - uses: actions/upload-artifact@v2 - with: - name: mac-dmg - path: Cryptomator-*.dmg - if-no-files-found: error - -# -# MSI package -# - win-msi: - name: Build Cryptomator.msi - runs-on: windows-latest - needs: [appdir, metadata] - steps: - - uses: actions/checkout@v2 - - name: Download win-appdir - uses: actions/download-artifact@v2 - with: - name: win-appdir - - name: Untar appdir.tar - run: tar -xvf appdir.tar - - uses: actions/setup-java@v2 - with: - distribution: 'temurin' - java-version: ${{ env.JAVA_VERSION }} - - name: Patch Application Directory - run: | - cp dist/win/contrib/* appdir/Cryptomator - - name: Fix permissions - run: attrib -r appdir/Cryptomator/Cryptomator.exe - shell: pwsh - - name: Codesign - uses: skymatic/code-sign-action@v1 - with: - certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }} - password: ${{ secrets.WIN_CODESIGN_P12_PW }} - certificatesha1: FF52240075AD7D14AF25629FDF69635357C7D14B - description: Cryptomator - timestampUrl: 'http://timestamp.digicert.com' - folder: appdir/Cryptomator - recursive: true - - name: Create MSI - run: > - ${JAVA_HOME}/bin/jpackage - --verbose - --type msi - --win-upgrade-uuid bda45523-42b1-4cae-9354-a45475ed4775 - --app-image appdir/Cryptomator - --dest installer - --name Cryptomator - --vendor "Skymatic GmbH" - --copyright "(C) 2016 - 2021 Skymatic GmbH" - --app-version "${{ needs.metadata.outputs.semVerNum }}" - --win-menu - --win-dir-chooser - --win-shortcut-prompt - --win-update-url "https:\\cryptomator.org" - --win-menu-group Cryptomator - --resource-dir dist/win/resources - --license-file dist/win/resources/license.rtf - --file-associations dist/win/resources/FAvaultFile.properties - env: - JP_WIXWIZARD_RESOURCES: ${{ github.workspace }}/dist/win/resources # requires abs path, used in resources/main.wxs - - name: Codesign MSI - uses: skymatic/code-sign-action@v1 - with: - certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }} - password: ${{ secrets.WIN_CODESIGN_P12_PW }} - certificatesha1: FF52240075AD7D14AF25629FDF69635357C7D14B - description: Cryptomator Installer - timestampUrl: 'http://timestamp.digicert.com' - folder: installer - - name: Add possible alpha/beta tags to installer name - run: mv installer/Cryptomator-*.msi installer/Cryptomator-${{ needs.metadata.outputs.semVerStr }}-x64.msi - - name: Upload win-msi - uses: actions/upload-artifact@v2 - with: - name: win-msi - path: installer/*.msi - if-no-files-found: error - -# -# Release -# - release: - name: Draft a release on Github - runs-on: ubuntu-latest - needs: [metadata,linux-appimage,mac-dmg,win-msi,ppa] - if: startsWith(github.ref, 'refs/tags/') && github.repository == 'cryptomator/cryptomator' - steps: - - uses: actions/checkout@v2 - - name: Create tarball - run: git archive --prefix="cryptomator-${{ needs.metadata.outputs.semVerStr }}/" -o "cryptomator-${{ needs.metadata.outputs.semVerStr }}.tar.gz" ${{ github.ref }} - - name: Download linux appimage - uses: actions/download-artifact@v2 - with: - name: linux-appimage - - name: Download macOS dmg - uses: actions/download-artifact@v2 - with: - name: mac-dmg - - name: Download Windows msi - uses: actions/download-artifact@v2 - with: - name: win-msi - - name: Create detached GPG signature for all release files with key 615D449FE6E6A235 - run: | - echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import - for FILE in `find . -name "*.AppImage" -o -name "*.dmg" -o -name "*.msi" -o -name "*.zsync" -o -name "*.tar.gz"`; do - echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a ${FILE} - done - env: - GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} - GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} - - name: Compute SHA256 checksums of release artifacts - run: | - SHA256_SUMS=`find . -name "*.AppImage" -o -name "*.dmg" -o -name "*.msi" -o -name "*.tar.gz" | xargs sha256sum` - echo "SHA256_SUMS<> $GITHUB_ENV - echo "${SHA256_SUMS}" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - continue-on-error: true - - name: Create release draft - uses: softprops/action-gh-release@v1 - with: - draft: true - fail_on_unmatched_files: true - discussion_category_name: releases - token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} - files: | - *.AppImage - *.zsync - *.asc - *.dmg - *.msi - body: | - :construction: Work in Progress - ## What's new - ## Bugfixes - ## Misc - --- - :scroll: A complete list of closed issues is available [here](LINK) - --- - :floppy_disk: SHA-256 checksums of release artifacts: - ``` - ${{ env.SHA256_SUMS }} - ``` diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml new file mode 100644 index 000000000..25a0575fb --- /dev/null +++ b/.github/workflows/win-exe.yml @@ -0,0 +1,274 @@ +name: Build Windows Installer + +on: + release: + types: [published] + workflow_dispatch: + +env: + JAVA_VERSION: 17 + WINFSP_MSI: https://github.com/winfsp/winfsp/releases/download/v1.10/winfsp-1.10.22006.msi + +defaults: + run: + shell: bash + +jobs: + build-msi: + name: Build .msi Installer + runs-on: windows-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Setup Java + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: ${{ env.JAVA_VERSION }} + cache: 'maven' + - id: versions + name: Apply version information + run: | + if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then + SEM_VER_STR=${GITHUB_REF##*/} + mvn versions:set -DnewVersion=${SEM_VER_STR} + else + SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout` + fi + SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'` + REVCOUNT=`git rev-list --count HEAD` + echo "::set-output name=semVerStr::${SEM_VER_STR}" + echo "::set-output name=semVerNum::${SEM_VER_NUM}" + echo "::set-output name=revNum::${REVCOUNT}" + - name: Validate Version + uses: skymatic/semver-validation-action@v1 + with: + version: ${{ steps.versions.outputs.semVerStr }} + - name: Run maven + run: mvn -B clean package -Pdependency-check,win -DskipTests + - name: Patch target dir + run: | + cp LICENSE.txt target + cp dist/linux/launcher.sh target + cp target/cryptomator-*.jar target/mods + - name: Run jlink + run: > + ${JAVA_HOME}/bin/jlink + --verbose + --output runtime + --module-path "${JAVA_HOME}/jmods" + --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr + --strip-native-commands + --no-header-files + --no-man-pages + --strip-debug + --compress=1 + - name: Run jpackage + run: > + ${JAVA_HOME}/bin/jpackage + --verbose + --type app-image + --runtime-image runtime + --input target/libs + --module-path target/mods + --module org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator + --dest appdir + --name Cryptomator + --vendor "Skymatic GmbH" + --copyright "(C) 2016 - 2022 Skymatic GmbH" + --app-version "${{ steps.versions.outputs.semVerNum }}.${{ steps.versions.outputs.revNum }}" + --java-options "-Xss5m" + --java-options "-Xmx256m" + --java-options "-Dfile.encoding=\"utf-8\"" + --java-options "-Dcryptomator.logDir=\"~/AppData/Roaming/Cryptomator\"" + --java-options "-Dcryptomator.pluginDir=\"~/AppData/Roaming/Cryptomator/Plugins\"" + --java-options "-Dcryptomator.settingsPath=\"~/AppData/Roaming/Cryptomator/settings.json\"" + --java-options "-Dcryptomator.ipcSocketPath=\"~/AppData/Roaming/Cryptomator/ipc.socket\"" + --java-options "-Dcryptomator.keychainPath=\"~/AppData/Roaming/Cryptomator/keychain.json\"" + --java-options "-Dcryptomator.mountPointsDir=\"~/Cryptomator\"" + --java-options "-Dcryptomator.showTrayIcon=true" + --java-options "-Dcryptomator.buildNumber=\"msi-${{ steps.versions.outputs.revNum }}\"" + --resource-dir dist/win/resources + --icon dist/win/resources/Cryptomator.ico + - name: Patch Application Directory + run: | + cp dist/win/contrib/* appdir/Cryptomator + - name: Fix permissions + run: attrib -r appdir/Cryptomator/Cryptomator.exe + shell: pwsh + - name: Codesign + uses: skymatic/code-sign-action@v1 + with: + certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }} + password: ${{ secrets.WIN_CODESIGN_P12_PW }} + certificatesha1: FF52240075AD7D14AF25629FDF69635357C7D14B + description: Cryptomator + timestampUrl: 'http://timestamp.digicert.com' + folder: appdir/Cryptomator + recursive: true + - name: Generate license + run: > + mvn -B license:add-third-party + "-Dlicense.thirdPartyFilename=license.rtf" + "-Dlicense.fileTemplate=dist/win/resources/licenseTemplate.ftl" + "-Dlicense.outputDirectory=dist/win/resources" + - name: Create MSI + run: > + ${JAVA_HOME}/bin/jpackage + --verbose + --type msi + --win-upgrade-uuid bda45523-42b1-4cae-9354-a45475ed4775 + --app-image appdir/Cryptomator + --dest installer + --name Cryptomator + --vendor "Skymatic GmbH" + --copyright "(C) 2016 - 2022 Skymatic GmbH" + --app-version "${{ steps.versions.outputs.semVerNum }}" + --win-menu + --win-dir-chooser + --win-shortcut-prompt + --win-update-url "https:\\cryptomator.org" + --win-menu-group Cryptomator + --resource-dir dist/win/resources + --license-file dist/win/resources/license.rtf + --file-associations dist/win/resources/FAvaultFile.properties + env: + JP_WIXWIZARD_RESOURCES: ${{ github.workspace }}/dist/win/resources # requires abs path, used in resources/main.wxs + - name: Codesign MSI + uses: skymatic/code-sign-action@v1 + with: + certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }} + password: ${{ secrets.WIN_CODESIGN_P12_PW }} + certificatesha1: FF52240075AD7D14AF25629FDF69635357C7D14B + description: Cryptomator Installer + timestampUrl: 'http://timestamp.digicert.com' + folder: installer + - name: Add possible alpha/beta tags to installer name + run: mv installer/Cryptomator-*.msi Cryptomator-${{ steps.versions.outputs.semVerStr }}-x64.msi + - name: Create detached GPG signature with key 615D449FE6E6A235 + run: | + echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import + echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a Cryptomator-*.msi + env: + GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: msi + path: | + Cryptomator-*.msi + Cryptomator-*.asc + if-no-files-found: error + - name: Publish .msi on GitHub Releases + if: startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v1 + with: + fail_on_unmatched_files: true + token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} + files: | + *.msi + *.asc + outputs: + semVerNum: ${{ steps.versions.outputs.semVerNum }} + semVerStr: ${{ steps.versions.outputs.semVerStr }} + revNum: ${{ steps.versions.outputs.revNum }} + + build-exe: + name: Build .exe installer + runs-on: windows-latest + needs: [build-msi] + steps: + - uses: actions/checkout@v2 + - name: Download .msi + uses: actions/download-artifact@v2 + with: + name: msi + path: dist/win/bundle/resources + - name: Strip version info from msi file name + run: mv dist/win/bundle/resources/Cryptomator*.msi dist/win/bundle/resources/Cryptomator.msi + - uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: ${{ env.JAVA_VERSION }} + cache: 'maven' + - name: Generate license + run: > + mvn -B license:add-third-party + "-Dlicense.thirdPartyFilename=license.rtf" + "-Dlicense.fileTemplate=dist/win/bundle/resources/licenseTemplate.ftl" + "-Dlicense.outputDirectory=dist/win/bundle/resources" + - name: Download WinFsp + run: + curl --output dist/win/bundle/resources/winfsp.msi -L ${{ env.WINFSP_MSI }} + - name: Compile to wixObj file + run: > + "${WIX}/bin/candle.exe" dist/win/bundle/bundleWithWinfsp.wxs + -ext WixBalExtension + -out dist/win/bundle/ + -dBundleVersion="${{ needs.build-msi.outputs.semVerNum }}.${{ needs.build-msi.outputs.revNum }}" + -dBundleVendor="Skymatic GmbH" + -dBundleCopyright="(C) 2016 - 2022 Skymatic GmbH" + -dAboutUrl="https://cryptomator.org" + -dHelpUrl="https://cryptomator.org/contact" + -dUpdateUrl="https://cryptomator.org/downloads/" + - name: Create executable with linker + run: > + "${WIX}/bin/light.exe" -b dist/win/ dist/win/bundle/bundleWithWinfsp.wixobj + -ext WixBalExtension + -out installer/unsigned/Cryptomator.exe + - name: Detach burn engine in preparation to sign + run: > + "${WIX}/bin/insignia.exe" + -ib installer/unsigned/Cryptomator.exe + -o tmp/engine.exe + - name: Codesign burn engine + uses: skymatic/code-sign-action@v1 + with: + certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }} + password: ${{ secrets.WIN_CODESIGN_P12_PW }} + certificatesha1: FF52240075AD7D14AF25629FDF69635357C7D14B + description: Cryptomator Installer + timestampUrl: 'http://timestamp.digicert.com' + folder: tmp + - name: Reattach signed burn engine to installer + run : > + "${WIX}/bin/insignia.exe" + -ab tmp/engine.exe installer/unsigned/Cryptomator.exe + -o installer/Cryptomator.exe + - name: Codesign EXE + uses: skymatic/code-sign-action@v1 + with: + certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }} + password: ${{ secrets.WIN_CODESIGN_P12_PW }} + certificatesha1: FF52240075AD7D14AF25629FDF69635357C7D14B + description: Cryptomator Installer + timestampUrl: 'http://timestamp.digicert.com' + folder: installer + - name: Add possible alpha/beta tags to installer name + run: mv installer/Cryptomator.exe Cryptomator-${{ needs.build-msi.outputs.semVerStr }}-x64.exe + - name: Create detached GPG signature with key 615D449FE6E6A235 + run: | + echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import + echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a Cryptomator-*.exe + env: + GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: exe + path: | + Cryptomator-*.exe + Cryptomator-*.asc + if-no-files-found: error + - name: Publish .msi on GitHub Releases + if: startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v1 + with: + fail_on_unmatched_files: true + token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} + files: | + Cryptomator-*.exe + Cryptomator-*.asc \ No newline at end of file diff --git a/.gitignore b/.gitignore index be67207df..5c84c0dfb 100644 --- a/.gitignore +++ b/.gitignore @@ -21,8 +21,9 @@ pom.xml.versionsBackup .idea/dictionaries/** !.idea/dictionaries/dict_* .idea/compiler.xml -.idea/encodings.xml .idea/jarRepositories.xml .idea/uiDesigner.xml .idea/**/libraries/ -*.iml \ No newline at end of file +*.iml + +hs_err_pid*.log \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 000000000..63574ec0a --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/dist/linux/appimage/build.sh b/dist/linux/appimage/build.sh index 24d636bb4..6dd670df2 100755 --- a/dist/linux/appimage/build.sh +++ b/dist/linux/appimage/build.sh @@ -9,6 +9,7 @@ command -v mvn >/dev/null 2>&1 || { echo >&2 "mvn not found."; exit 1; } command -v curl >/dev/null 2>&1 || { echo >&2 "curl not found."; exit 1; } VERSION=$(mvn -f ../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout) +SEMVER_STR=${VERSION} # compile mvn -B -f ../../../pom.xml clean package -DskipTests -Plinux @@ -18,7 +19,7 @@ cp ../../../target/cryptomator-*.jar ../../../target/mods ${JAVA_HOME}/bin/jlink \ --output runtime \ --module-path "${JAVA_HOME}/jmods" \ - --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility \ + --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \ --no-header-files \ --no-man-pages \ --strip-debug \ @@ -35,7 +36,7 @@ ${JAVA_HOME}/bin/jpackage \ --dest . \ --name Cryptomator \ --vendor "Skymatic GmbH" \ - --copyright "(C) 2016 - 2021 Skymatic GmbH" \ + --copyright "(C) 2016 - 2022 Skymatic GmbH" \ --java-options "-Xss5m" \ --java-options "-Xmx256m" \ --app-version "${VERSION}.${REVISION_NO}" \ @@ -54,6 +55,12 @@ mv Cryptomator Cryptomator.AppDir cp -r resources/AppDir/* Cryptomator.AppDir/ chmod +x Cryptomator.AppDir/lib/runtime/bin/java envsubst '${REVISION_NO}' < resources/AppDir/bin/cryptomator.sh > Cryptomator.AppDir/bin/cryptomator.sh +cp ../common/org.cryptomator.Cryptomator256.png Cryptomator.AppDir/usr/share/icons/hicolor/256x256/apps/org.cryptomator.Cryptomator.png +cp ../common/org.cryptomator.Cryptomator512.png Cryptomator.AppDir/usr/share/icons/hicolor/512x512/apps/org.cryptomator.Cryptomator.png +cp ../common/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg +cp ../common/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/usr/share/applications/org.cryptomator.Cryptomator.desktop +cp ../common/org.cryptomator.Cryptomator.metainfo.xml Cryptomator.AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.metainfo.xml +cp ../common/application-vnd.cryptomator.vault.xml Cryptomator.AppDir/usr/share/mime/packages/application-vnd.cryptomator.vault.xml ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/org.cryptomator.Cryptomator.svg ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/Cryptomator.svg ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/.DirIcon diff --git a/dist/linux/appimage/resources/AppDir/usr/share/applications/.gitkeep b/dist/linux/appimage/resources/AppDir/usr/share/applications/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/256x256/apps/.gitkeep b/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/256x256/apps/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/512x512/apps/.gitkeep b/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/512x512/apps/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/scalable/apps/.gitkeep b/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/scalable/apps/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg b/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg deleted file mode 100644 index b2e12a3c3..000000000 --- a/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/dist/linux/appimage/resources/AppDir/usr/share/metainfo/.gitkeep b/dist/linux/appimage/resources/AppDir/usr/share/metainfo/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/dist/linux/appimage/resources/AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.appdata.xml b/dist/linux/appimage/resources/AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.appdata.xml deleted file mode 100644 index ad4af6c70..000000000 --- a/dist/linux/appimage/resources/AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.appdata.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - org.cryptomator.Cryptomator - FSFAP - GPL-3.0-or-later - Cryptomator - Multi-platform client-side encryption tool optimized for cloud storages - -

- Cryptomator offers multi-platform transparent client-side encryption of your files in the cloud. -

-

- Features: -

    -
  • Works with Dropbox, Google Drive, OneDrive, ownCloud, Nextcloud and any other cloud storage service which synchronizes with a local directory
  • -
  • Open Source means: No backdoors, control is better than trust
  • -
  • Client-side: No accounts, no data shared with any online service
  • -
  • Totally transparent: Just work on the virtual drive as if it were a USB flash drive
  • -
  • AES encryption with 256-bit key length
  • -
  • File names get encrypted
  • -
  • Folder structure gets obfuscated
  • -
  • Use as many vaults in your Dropbox as you want, each having individual passwords
  • -
  • One thousand commits for the security of your data!! :tada:
  • -
-

-

- Privacy: -

    -
  • 256-bit keys (unlimited strength policy bundled with native binaries)
  • -
  • Scrypt key derivation
  • -
  • Cryptographically secure random numbers for salts, IVs and the masterkey of course
  • -
  • Sensitive data is wiped from the heap asap
  • -
  • Lightweight: Complexity kills security
  • -
-

-

- Consistency: -

    -
  • HMAC over file contents to recognize changed ciphertext before decryption
  • -
  • I/O operations are transactional and atomic, if the filesystems support it
  • -
  • Each file contains all information needed for decryption (except for the key of course), no common metadata means no Single Point of Failure
  • -
-

-
- - Office - Security - FileTools - Java - - http://cryptomator.org - https://github.com/cryptomator/cryptomator/issues - https://community.cryptomator.org/c/kb/faq - https://community.cryptomator.org/ - https://cryptomator.org/ - - none - none - none - none - mild - - Cryptomator - - cryptomator - - org.cryptomator.Cryptomator.desktop -
diff --git a/dist/linux/appimage/resources/AppDir/usr/share/mime/packages/.gitkeep b/dist/linux/appimage/resources/AppDir/usr/share/mime/packages/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/dist/linux/debian/cryptomator-vault.xml b/dist/linux/common/application-vnd.cryptomator.vault.xml similarity index 77% rename from dist/linux/debian/cryptomator-vault.xml rename to dist/linux/common/application-vnd.cryptomator.vault.xml index eeb4bc537..3b602f230 100644 --- a/dist/linux/debian/cryptomator-vault.xml +++ b/dist/linux/common/application-vnd.cryptomator.vault.xml @@ -1,6 +1,6 @@ - + Cryptomator Vault Metadata diff --git a/dist/linux/appimage/resources/AppDir/usr/share/applications/org.cryptomator.Cryptomator.desktop b/dist/linux/common/org.cryptomator.Cryptomator.desktop similarity index 71% rename from dist/linux/appimage/resources/AppDir/usr/share/applications/org.cryptomator.Cryptomator.desktop rename to dist/linux/common/org.cryptomator.Cryptomator.desktop index 3e1b34830..1872b9f38 100644 --- a/dist/linux/appimage/resources/AppDir/usr/share/applications/org.cryptomator.Cryptomator.desktop +++ b/dist/linux/common/org.cryptomator.Cryptomator.desktop @@ -6,5 +6,6 @@ Icon=org.cryptomator.Cryptomator Terminal=false Type=Application Categories=Utility;Security;FileTools; +StartupNotify=true StartupWMClass=org.cryptomator.launcher.Cryptomator -MimeType=application/vnd.cryptomator.encrypted;application/x-vnd.cryptomator.vault-metadata; +MimeType=application/vnd.cryptomator.encrypted;application/vnd.cryptomator.vault; diff --git a/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml b/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml new file mode 100644 index 000000000..bdd0e178e --- /dev/null +++ b/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml @@ -0,0 +1,71 @@ + + + + org.cryptomator.Cryptomator + FSFAP + GPL-3.0-or-later + Cryptomator + Multi-platform client-side encryption tool optimized for cloud storages + + +

+ Cryptomator provides transparent, client-side encryption for your cloud. Protect your documents from unauthorized + access. Cryptomator is free and open source software, so you can rest assured there are no backdoors. +

+

+ Cryptomator encrypts file contents and names using AES. Your passphrase is protected against bruteforcing attempts + using scrypt. Directory structures get obfuscated. The only thing which cannot be encrypted without breaking your + cloud synchronization is the modification date of your files. +

+

+ Cryptomator is a free and open source software licensed under the GPLv3. This allows anyone to check our code. It + is impossible to introduce backdoors for third parties. Also we cannot hide vulnerabilities. And the best thing + is: There is no need to trust us, as you can control us! +

+

+ Vendor lock-ins are impossible. Even if we decided to stop development: The source code is already cloned by + hundreds of other developers. As you don't need an account, you will never stand in front of locked doors. +

+
+ + + Office + Security + FileTools + + + org.cryptomator.Cryptomator.desktop + + cryptomator + application/vnd.cryptomator.vault + application/vnd.cryptomator.encrypted + + + + + Light theme + https://user-images.githubusercontent.com/11858409/156986109-6e58f59c-8b8c-4501-b33b-bb1e33007cea.png + + + Dark theme + https://user-images.githubusercontent.com/11858409/156986113-6c5d7801-86e0-4643-bc2f-aff9d95d3ce0.png + + + + https://cryptomator.org/ + https://github.com/cryptomator/cryptomator/issues/ + https://cryptomator.org/donate + https://community.cryptomator.org/c/kb/faq + https://community.cryptomator.org/ + https://translate.cryptomator.org + + Skymatic GmbH + + + mild + + + + + +
diff --git a/dist/linux/debian/org.cryptomator.Cryptomator.svg b/dist/linux/common/org.cryptomator.Cryptomator.svg similarity index 99% rename from dist/linux/debian/org.cryptomator.Cryptomator.svg rename to dist/linux/common/org.cryptomator.Cryptomator.svg index 19d80d49c..c76d99fb9 100644 --- a/dist/linux/debian/org.cryptomator.Cryptomator.svg +++ b/dist/linux/common/org.cryptomator.Cryptomator.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/256x256/apps/org.cryptomator.Cryptomator.png b/dist/linux/common/org.cryptomator.Cryptomator256.png similarity index 100% rename from dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/256x256/apps/org.cryptomator.Cryptomator.png rename to dist/linux/common/org.cryptomator.Cryptomator256.png diff --git a/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/512x512/apps/org.cryptomator.Cryptomator.png b/dist/linux/common/org.cryptomator.Cryptomator512.png similarity index 100% rename from dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/512x512/apps/org.cryptomator.Cryptomator.png rename to dist/linux/common/org.cryptomator.Cryptomator512.png diff --git a/dist/linux/debian/copyright b/dist/linux/debian/copyright index ba6980bb8..34be0a4c9 100644 --- a/dist/linux/debian/copyright +++ b/dist/linux/debian/copyright @@ -4,11 +4,11 @@ Upstream-Contact: Cryptomator Source: https://cryptomator.org Files: * -Copyright: 2016-2021 Skymatic GmbH +Copyright: 2016-2022 Skymatic GmbH License: GPL-3+ Files: debian/org.cryptomator.Cryptomator.appdata.xml -Copyright: 2016-2021 Skymatic GmbH +Copyright: 2016-2022 Skymatic GmbH License: FSFAP License: GPL-3+ @@ -36,4 +36,4 @@ License: FSFAP Copying and distribution of this file, with or without modification, are permitted in any medium without royalty provided the copyright notice and this notice are preserved. This file is offered as-is, without any - warranty. \ No newline at end of file + warranty. diff --git a/dist/linux/debian/cryptomator.install b/dist/linux/debian/cryptomator.install index 0d5e0b31c..d3ff8cf1f 100644 --- a/dist/linux/debian/cryptomator.install +++ b/dist/linux/debian/cryptomator.install @@ -1,7 +1,8 @@ cryptomator usr/lib debian/cryptomator.sh usr/lib/cryptomator/bin -debian/org.cryptomator.Cryptomator.desktop usr/share/applications -debian/org.cryptomator.Cryptomator.svg usr/share/icons/hicolor/scalable/apps -debian/org.cryptomator.Cryptomator.png usr/share/icons/hicolor/512x512/apps -debian/org.cryptomator.Cryptomator.appdata.xml usr/share/metainfo -debian/cryptomator-vault.xml usr/share/mime/packages \ No newline at end of file +common/org.cryptomator.Cryptomator.desktop usr/share/applications +common/org.cryptomator.Cryptomator.svg usr/share/icons/hicolor/scalable/apps +common/org.cryptomator.Cryptomator256.png usr/share/icons/hicolor/256x256/apps +common/org.cryptomator.Cryptomator512.png usr/share/icons/hicolor/512x512/apps +common/org.cryptomator.Cryptomator.metainfo.xml usr/share/metainfo +common/application-vnd.cryptomator.vault.xml usr/share/mime/packages \ No newline at end of file diff --git a/dist/linux/debian/org.cryptomator.Cryptomator.appdata.xml b/dist/linux/debian/org.cryptomator.Cryptomator.appdata.xml deleted file mode 100644 index ad4af6c70..000000000 --- a/dist/linux/debian/org.cryptomator.Cryptomator.appdata.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - org.cryptomator.Cryptomator - FSFAP - GPL-3.0-or-later - Cryptomator - Multi-platform client-side encryption tool optimized for cloud storages - -

- Cryptomator offers multi-platform transparent client-side encryption of your files in the cloud. -

-

- Features: -

    -
  • Works with Dropbox, Google Drive, OneDrive, ownCloud, Nextcloud and any other cloud storage service which synchronizes with a local directory
  • -
  • Open Source means: No backdoors, control is better than trust
  • -
  • Client-side: No accounts, no data shared with any online service
  • -
  • Totally transparent: Just work on the virtual drive as if it were a USB flash drive
  • -
  • AES encryption with 256-bit key length
  • -
  • File names get encrypted
  • -
  • Folder structure gets obfuscated
  • -
  • Use as many vaults in your Dropbox as you want, each having individual passwords
  • -
  • One thousand commits for the security of your data!! :tada:
  • -
-

-

- Privacy: -

    -
  • 256-bit keys (unlimited strength policy bundled with native binaries)
  • -
  • Scrypt key derivation
  • -
  • Cryptographically secure random numbers for salts, IVs and the masterkey of course
  • -
  • Sensitive data is wiped from the heap asap
  • -
  • Lightweight: Complexity kills security
  • -
-

-

- Consistency: -

    -
  • HMAC over file contents to recognize changed ciphertext before decryption
  • -
  • I/O operations are transactional and atomic, if the filesystems support it
  • -
  • Each file contains all information needed for decryption (except for the key of course), no common metadata means no Single Point of Failure
  • -
-

-
- - Office - Security - FileTools - Java - - http://cryptomator.org - https://github.com/cryptomator/cryptomator/issues - https://community.cryptomator.org/c/kb/faq - https://community.cryptomator.org/ - https://cryptomator.org/ - - none - none - none - none - mild - - Cryptomator - - cryptomator - - org.cryptomator.Cryptomator.desktop -
diff --git a/dist/linux/debian/org.cryptomator.Cryptomator.desktop b/dist/linux/debian/org.cryptomator.Cryptomator.desktop deleted file mode 100644 index d8a5925bd..000000000 --- a/dist/linux/debian/org.cryptomator.Cryptomator.desktop +++ /dev/null @@ -1,11 +0,0 @@ -[Desktop Entry] -Name=Cryptomator -Version=${VERSION_STR} -Comment=Cloud Storage Encryption Utility -Exec=/usr/bin/cryptomator %f -Icon=org.cryptomator.Cryptomator -Terminal=false -Type=Application -Categories=Utility;Security;FileTools; -StartupWMClass=org.cryptomator.launcher.Cryptomator -MimeType=application/vnd.cryptomator.encrypted;application/x-vnd.cryptomator.vault-metadata; \ No newline at end of file diff --git a/dist/linux/debian/org.cryptomator.Cryptomator.png b/dist/linux/debian/org.cryptomator.Cryptomator.png deleted file mode 100644 index 9c8635111..000000000 Binary files a/dist/linux/debian/org.cryptomator.Cryptomator.png and /dev/null differ diff --git a/dist/linux/debian/postinst b/dist/linux/debian/postinst index 2af574c6b..5668a5e29 100644 --- a/dist/linux/debian/postinst +++ b/dist/linux/debian/postinst @@ -24,7 +24,7 @@ case "$1" in mkdir -p /usr/share/desktop-directories fi xdg-desktop-menu install --novendor /usr/share/applications/org.cryptomator.Cryptomator.desktop - xdg-mime install /usr/share/mime/packages/cryptomator-vault.xml + xdg-mime install /usr/share/mime/packages/application-vnd.cryptomator.vault.xml ;; abort-upgrade|abort-remove|abort-deconfigure) diff --git a/dist/linux/debian/prerm b/dist/linux/debian/prerm index cace6816e..41a54cf33 100644 --- a/dist/linux/debian/prerm +++ b/dist/linux/debian/prerm @@ -22,7 +22,7 @@ case "$1" in echo Removing shortcut xdg-desktop-menu uninstall --novendor /usr/share/applications/org.cryptomator.Cryptomator.desktop - xdg-mime uninstall /usr/share/mime/packages/cryptomator-vault.xml + xdg-mime uninstall /usr/share/mime/packages/application-vnd.cryptomator.vault.xml ;; failed-upgrade) diff --git a/dist/linux/debian/rules b/dist/linux/debian/rules index b36819f8b..e4f824394 100755 --- a/dist/linux/debian/rules +++ b/dist/linux/debian/rules @@ -11,11 +11,14 @@ override_dh_auto_clean: rm -rf runtime rm -rf cryptomator rm -rf debian/cryptomator + rm -rf resources override_dh_auto_build: + mkdir resources + ln -s ../common/org.cryptomator.Cryptomator512.png resources/cryptomator.png jlink \ --output runtime \ - --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility \ + --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \ --no-header-files \ --no-man-pages \ --strip-debug \ @@ -29,7 +32,7 @@ override_dh_auto_build: --dest . \ --name cryptomator \ --vendor "Skymatic GmbH" \ - --copyright "(C) 2016 - 2021 Skymatic GmbH" \ + --copyright "(C) 2016 - 2022 Skymatic GmbH" \ --java-options "-Xss5m" \ --java-options "-Xmx256m" \ --java-options "-Dfile.encoding=\"utf-8\"" \ @@ -39,8 +42,8 @@ override_dh_auto_build: --java-options "-Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\"" \ --java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\"" \ --java-options "-Dcryptomator.showTrayIcon=false" \ - --java-options "-Dcryptomator.buildNumber=\"ppa-${REVISION_NUM}\"" \ - --java-options "-Dcryptomator.appVersion=\"${VERSION_STR}\"" \ + --java-options "-Dcryptomator.buildNumber=\"deb-${REVISION_NUM}\"" \ + --java-options "-Dcryptomator.appVersion=\"${SEMVER_STR}\"" \ --app-version "${VERSION_NUM}.${REVISION_NUM}" \ --resource-dir resources \ --verbose diff --git a/dist/linux/debian/source/include-binaries b/dist/linux/debian/source/include-binaries index adc7cabd8..8b9254c65 100644 --- a/dist/linux/debian/source/include-binaries +++ b/dist/linux/debian/source/include-binaries @@ -1,2 +1,2 @@ -debian/org.cryptomator.Cryptomator.png -resources/cryptomator.png +common/org.cryptomator.Cryptomator256.png +common/org.cryptomator.Cryptomator512.png diff --git a/dist/mac/dmg/.gitignore b/dist/mac/dmg/.gitignore index c186170c9..b8ef35283 100644 --- a/dist/mac/dmg/.gitignore +++ b/dist/mac/dmg/.gitignore @@ -1,4 +1,5 @@ # created during build +Cryptomator.app/ runtime/ dmg/ -*.dmg +*.dmg \ No newline at end of file diff --git a/dist/mac/dmg/build.sh b/dist/mac/dmg/build.sh index b8d17cbe0..c90411acb 100755 --- a/dist/mac/dmg/build.sh +++ b/dist/mac/dmg/build.sh @@ -22,8 +22,8 @@ VERSION_NO=`mvn -f../../../pom.xml help:evaluate -Dexpression=project.version -q # check preconditions if [ -z "${JAVA_HOME}" ]; then echo "JAVA_HOME not set. Run using JAVA_HOME=/path/to/jdk ./build.sh"; exit 1; fi -command -v mvn >/dev/null 2>&1 || { echo >&2 "mvn not found."; exit 1; } -command -v create-dmg >/dev/null 2>&1 || { echo >&2 "create-dmg not found."; exit 1; } +command -v mvn >/dev/null 2>&1 || { echo >&2 "mvn not found. Fix by 'brew install maven'."; exit 1; } +command -v create-dmg >/dev/null 2>&1 || { echo >&2 "create-dmg not found. Fix by 'brew install create-dmg'."; exit 1; } if [ -n "${CODESIGN_IDENTITY}" ]; then command -v codesign >/dev/null 2>&1 || { echo >&2 "codesign not found. Fix by 'xcode-select --install'."; exit 1; } if [[ ! `security find-identity -v -p codesigning | grep -w "${CODESIGN_IDENTITY}"` ]]; then echo "Given codesign identity is invalid."; exit 1; fi @@ -37,7 +37,7 @@ cp ../../../target/cryptomator-*.jar ../../../target/mods ${JAVA_HOME}/bin/jlink \ --output runtime \ --module-path "${JAVA_HOME}/jmods" \ - --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility \ + --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \ --no-header-files \ --no-man-pages \ --strip-debug \ @@ -54,7 +54,7 @@ ${JAVA_HOME}/bin/jpackage \ --dest . \ --name Cryptomator \ --vendor "Skymatic GmbH" \ - --copyright "(C) 2016 - 2021 Skymatic GmbH" \ + --copyright "(C) 2016 - 2022 Skymatic GmbH" \ --java-options "-Xss5m" \ --java-options "-Xmx256m" \ --java-options "-Dcryptomator.appVersion=\"${VERSION_NO}\"" \ diff --git a/dist/mac/dmg/resources/license.rtf b/dist/mac/dmg/resources/license.rtf index ae2b7ece4..72730adb8 100644 --- a/dist/mac/dmg/resources/license.rtf +++ b/dist/mac/dmg/resources/license.rtf @@ -10,7 +10,7 @@ \f1\b0 \ \ -\f0\b \'a9 2016 \'96 2021 Skymatic GmbH +\f0\b \'a9 2016 \'96 2022 Skymatic GmbH \f1\b0 \ \ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\ @@ -97,4 +97,4 @@ You should have received a copy of the GNU General Public License along with thi SIL OFL 1.1 License:\ - Font Awesome 5.12.0 ({\field{\*\fldinst{HYPERLINK "https://fontawesome.com/"}}{\fldrslt https://fontawesome.com/}})\ \ -} \ No newline at end of file +} diff --git a/dist/win/.gitignore b/dist/win/.gitignore index 2b66ddbed..9cce929df 100644 --- a/dist/win/.gitignore +++ b/dist/win/.gitignore @@ -1,3 +1,7 @@ runtime Cryptomator -installer \ No newline at end of file +installer +*.wixobj +*.pdb +*.msi +license.rtf \ No newline at end of file diff --git a/dist/win/build.ps1 b/dist/win/build.ps1 index 8ed5d9193..0b0e953bc 100644 --- a/dist/win/build.ps1 +++ b/dist/win/build.ps1 @@ -1,11 +1,14 @@ +# check parameters +$clean = $args[0] -eq "fresh" + # check preconditions -if ((Get-Command "git" -ErrorAction SilentlyContinue) -eq $null) -{ +if ((Get-Command "git" -ErrorAction SilentlyContinue) -eq $null) +{ Write-Host "Unable to find git.exe in your PATH (try: choco install git)" exit 1 } -if ((Get-Command "mvn" -ErrorAction SilentlyContinue) -eq $null) -{ +if ((Get-Command "mvn" -ErrorAction SilentlyContinue) -eq $null) +{ Write-Host "Unable to find mvn.cmd in your PATH (try: choco install maven)" exit 1 } @@ -21,21 +24,34 @@ Write-Output "`$revisionNo=$revisionNo" Write-Output "`$buildDir=$buildDir" Write-Output "`$Env:JAVA_HOME=$Env:JAVA_HOME" +$vendor = "Skymatic GmbH" +$copyright = "(C) 2016 - 2022 Skymatic GmbH" + # compile &mvn -B -f $buildDir/../../pom.xml clean package -DskipTests -Pwin Copy-Item "$buildDir\..\..\target\cryptomator-*.jar" -Destination "$buildDir\..\..\target\mods" # add runtime +$runtimeImagePath = '.\runtime' +if ($clean -and (Test-Path -Path $runtimeImagePath)) { + Remove-Item -Path $runtimeImagePath -Force -Recurse +} + & "$Env:JAVA_HOME\bin\jlink" ` --verbose ` --output runtime ` --module-path "$Env:JAVA_HOME/jmods" ` - --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility ` + --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr ` --no-header-files ` --no-man-pages ` --strip-debug ` --compress=1 +$appPath = '.\Cryptomator' +if ($clean -and (Test-Path -Path $appPath)) { + Remove-Item -Path $appPath -Force -Recurse +} + # create app dir & "$Env:JAVA_HOME\bin\jpackage" ` --verbose ` @@ -46,8 +62,8 @@ Copy-Item "$buildDir\..\..\target\cryptomator-*.jar" -Destination "$buildDir\..\ --module org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator ` --dest . ` --name Cryptomator ` - --vendor "Skymatic GmbH" ` - --copyright "(C) 2016 - 2021 Skymatic GmbH" ` + --vendor $vendor ` + --copyright $copyright ` --java-options "-Xss5m" ` --java-options "-Xmx256m" ` --java-options "-Dcryptomator.appVersion=`"$semVerNo`"" ` @@ -64,11 +80,21 @@ Copy-Item "$buildDir\..\..\target\cryptomator-*.jar" -Destination "$buildDir\..\ --resource-dir resources ` --icon resources/Cryptomator.ico +#Create RTF license for msi +&mvn -B -f $buildDir/../../pom.xml license:add-third-party ` + "-Dlicense.thirdPartyFilename=license.rtf" ` + "-Dlicense.fileTemplate=$buildDir\resources\licenseTemplate.ftl" ` + "-Dlicense.outputDirectory=$buildDir\resources\" + # patch app dir Copy-Item "contrib\*" -Destination "Cryptomator" attrib -r "Cryptomator\Cryptomator.exe" -# create .msi bundle +$aboutUrl="https://cryptomator.org" +$updateUrl="https://cryptomator.org/downloads/" +$helpUrl="https://cryptomator.org/contact/" + +# create .msi $Env:JP_WIXWIZARD_RESOURCES = "$buildDir\resources" & "$Env:JAVA_HOME\bin\jpackage" ` --verbose ` @@ -77,14 +103,41 @@ $Env:JP_WIXWIZARD_RESOURCES = "$buildDir\resources" --app-image Cryptomator ` --dest installer ` --name Cryptomator ` - --vendor "Skymatic GmbH" ` - --copyright "(C) 2016 - 2021 Skymatic GmbH" ` + --vendor $vendor ` + --copyright $copyright ` --app-version "$semVerNo" ` --win-menu ` --win-dir-chooser ` --win-shortcut-prompt ` - --win-update-url "https:\\cryptomator.org" ` + --win-update-url $updateUrl ` --win-menu-group Cryptomator ` --resource-dir resources ` + --about-url $aboutUrl ` --license-file resources/license.rtf ` - --file-associations resources/FAvaultFile.properties \ No newline at end of file + --file-associations resources/FAvaultFile.properties + +#Create RTF license for bundle +&mvn -B -f $buildDir/../../pom.xml license:add-third-party ` + "-Dlicense.thirdPartyFilename=license.rtf" ` + "-Dlicense.fileTemplate=$buildDir\bundle\resources\licenseTemplate.ftl" ` + "-Dlicense.outputDirectory=$buildDir\bundle\resources\" + +# download Winfsp +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$ProgressPreference = 'SilentlyContinue' # disables Invoke-WebRequest's progress bar, which slows down downloads to a few bytes/s +$winfspMsiUrl = "https://github.com/winfsp/winfsp/releases/download/v1.10/winfsp-1.10.22006.msi" +Write-Output "Downloading ${winfspMsiUrl}..." +Invoke-WebRequest $winfspMsiUrl -OutFile ".\bundle\resources\winfsp.msi" # redirects are followed by default + +# copy MSI to bundle resources +Copy-Item ".\installer\Cryptomator-*.msi" -Destination ".\bundle\resources\Cryptomator.msi" + +# create bundle including winfsp +& "$env:WIX\bin\candle.exe" .\bundle\bundleWithWinfsp.wxs -ext WixBalExtension -out bundle\ ` + -dBundleVersion="$semVerNo.$revisionNo" ` + -dBundleVendor="$vendor" ` + -dBundleCopyright="$copyright" ` + -dAboutUrl="$aboutUrl" ` + -dHelpUrl="$helpUrl" ` + -dUpdateUrl="$updateUrl" +& "$env:WIX\bin\light.exe" -b . .\bundle\BundlewithWinfsp.wixobj -ext WixBalExtension -out installer\CryptomatorBundle.exe \ No newline at end of file diff --git a/dist/win/bundle/bundleWithWinfsp.wxs b/dist/win/bundle/bundleWithWinfsp.wxs new file mode 100644 index 000000000..91093df7d --- /dev/null +++ b/dist/win/bundle/bundleWithWinfsp.wxs @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/dist/win/bundle/customBootstrapperTheme.wxl b/dist/win/bundle/customBootstrapperTheme.wxl new file mode 100644 index 000000000..cc464ce9c --- /dev/null +++ b/dist/win/bundle/customBootstrapperTheme.wxl @@ -0,0 +1,64 @@ + + + + + + [WixBundleName] Setup + [WixBundleName] + Welcome + This Setup will install [WixBundleName] and additional dependencies on your computer. + Version [WixBundleVersion] + Are you sure you want to cancel? + Previous version + Setup Help + /install | /repair | /uninstall | /layout [directory] - installs, repairs, uninstalls or + creates a complete local copy of the bundle in directory. Install is the default. + +/passive | /quiet - displays minimal UI with no prompts or displays no UI and + no prompts. By default UI and all prompts are displayed. + +/norestart - suppress any attempts to restart. By default UI will prompt before restart. +/log log.txt - logs to a specific file. By default a log file is created in %TEMP%. + &Close + [WixBundleName] <a href="#">license terms</a>. + I &agree to the license terms and conditions + &Options + &Install + &Close + Setup Options + Install location: + &Browse + &OK + &Cancel + Setup Progress + Processing: + Initializing... + &Cancel + Modify Setup + &Repair + &Uninstall + &Close + Repair Successfully Completed + Uninstall Successfully Completed + Installation Successfully Completed + Setup Successful + &Launch + You must restart your computer before you can use the software. + &Restart + &Close + Setup Failed + Setup Failed + Uninstall Failed + Repair Failed + One or more issues caused the setup to fail. Please fix the issues and then retry setup. For more information see the <a href="#">log file</a>. + You must restart your computer to complete the rollback of the software. + &Restart + &Close + Files In Use + The following applications are using files that need to be updated: + Close the &applications and attempt to restart them. + &Do not close applications. A reboot will be required. + &OK + &Cancel + No action was taken as a system reboot is required. + diff --git a/dist/win/bundle/customBootstrapperTheme.xml b/dist/win/bundle/customBootstrapperTheme.xml new file mode 100644 index 000000000..792f51a35 --- /dev/null +++ b/dist/win/bundle/customBootstrapperTheme.xml @@ -0,0 +1,90 @@ + + + + + + + #(loc.Caption) + Segoe UI + Segoe UI + Segoe UI + Segoe UI + Segoe UI + + + #(loc.Title) + + + + #(loc.Title) + #(loc.HelpHeader) + #(loc.HelpText) + + + + #(loc.Title) + + #(loc.InstallHeader) + #(loc.InstallMessage) + + #(loc.InstallAcceptCheckbox) + #(loc.InstallVersion) + + + + + + #(loc.Title) + #(loc.FilesInUseHeader) + #(loc.FilesInUseLabel) + A + + + + + + + + + #(loc.Title) + + #(loc.ProgressHeader) + #(loc.ProgressLabel) + #(loc.OverallProgressPackageText) + + + + + + #(loc.Title) + #(loc.ModifyHeader) + + + + + + #(loc.Title) + + #(loc.SuccessHeader) + #(loc.SuccessInstallHeader) + #(loc.SuccessRepairHeader) + #(loc.SuccessUninstallHeader) + + #(loc.SuccessRestartText) + + + + + #(loc.Title) + + #(loc.FailureHeader) + #(loc.FailureInstallHeader) + #(loc.FailureUninstallHeader) + #(loc.FailureRepairHeader) + #(loc.FailureHyperlinkLogText) + + #(loc.FailureRestartText) + + + + diff --git a/dist/win/bundle/resources/Cryptomator.ico b/dist/win/bundle/resources/Cryptomator.ico new file mode 100644 index 000000000..7d1d8be88 Binary files /dev/null and b/dist/win/bundle/resources/Cryptomator.ico differ diff --git a/dist/win/bundle/resources/licenseTemplate.ftl b/dist/win/bundle/resources/licenseTemplate.ftl new file mode 100644 index 000000000..bd137a1a1 --- /dev/null +++ b/dist/win/bundle/resources/licenseTemplate.ftl @@ -0,0 +1,41 @@ +<#function artifactFormat p> + <#if p.name?index_of('Unnamed') > -1> + <#return p.artifactId + " (" + p.groupId + ":" + p.artifactId + ":" + p.version + " - {{\\field{\\*\\fldinst{HYPERLINK " + (p.url!"no url defined") + "}}{\\fldrslt{" + (p.url!"no url defined") + "\\ul0\\cf0}}}}\\f0\\fs16 ) "> + <#else> + <#return p.name + " (" + p.groupId + ":" + p.artifactId + ":" + p.version + " - {{\\field{\\*\\fldinst{HYPERLINK " + (p.url!"no url defined") + "}}{\\fldrslt{" + (p.url!"no url defined") + "\\ul0\\cf0}}}}\\f0\\fs16 ) "> + + +{\rtf1\ansi\ansicpg1252\deff0\nouicompat{\fonttbl{\f0\fnil\fcharset0 Arial;}} +{\colortbl ;\red0\green0\blue255;} +\viewkind4\uc1 +\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\b\fs16\lang7 Cryptomator is distributed under the GPLv3 License, found below. Please see the bottom of this document for any other license applicable to code used within Cryptomator.\b0\par +\par +\b\'a9 2016 \endash 2022 Skymatic GmbH\b0\par +\par +This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\par +\par +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\par +\par +You should have received a copy of the GNU General Public License along with this program. If not, see {{\field{\*\fldinst{HYPERLINK http://www.gnu.org/licenses/ }}{\fldrslt{http://www.gnu.org/licenses/\ul0\cf0}}}}\f0\fs16 .\par +\par + +\b Cryptomator uses ${dependencyMap?size} third-party dependencies under the following licenses:\b0\par +<#list licenseMap as e> +<#assign license = e.getKey()/> +<#assign projects = e.getValue()/> +<#if projects?size > 0> +\tab ${license}:\par +<#list projects as project> +\tab\tab- ${artifactFormat(project)}\par + + + +\par +\b Cryptomator uses other third-party assets under the following licenses:\b0\par +\tab SIL OFL 1.1 License:\par +\tab\tab - Font Awesome 5.12.0 ({{\field{\*\fldinst{HYPERLINK https://fontawesome.com/ }}{\fldrslt{https://fontawesome.com/\ul0\cf0}}}}\f0\fs16 )\b\par +\par +\b Cryptomator dynamically links to third-party libraries under the following license:\b0\par +\tab Uncategorized License:\par +\tab\tab - WinFsp - Windows File System Proxy, Copyright (C) Bill Zissimopoulos ({{\field{\*\fldinst{HYPERLINK https://github.com/billziss-gh/winfsp }}{\fldrslt{https://github.com/billziss-gh/winfsp\ul0\cf0}}}}\f0\fs16 )\b\par +} \ No newline at end of file diff --git a/dist/win/bundle/resources/logo.png b/dist/win/bundle/resources/logo.png new file mode 100644 index 000000000..fdfdd6235 Binary files /dev/null and b/dist/win/bundle/resources/logo.png differ diff --git a/dist/win/bundle/resources/logoSide.png b/dist/win/bundle/resources/logoSide.png new file mode 100644 index 000000000..e1629e8d6 Binary files /dev/null and b/dist/win/bundle/resources/logoSide.png differ diff --git a/dist/win/contrib/dokan1.dll b/dist/win/contrib/dokan1.dll index badc12b2a..194945aed 100755 Binary files a/dist/win/contrib/dokan1.dll and b/dist/win/contrib/dokan1.dll differ diff --git a/dist/win/resources/license.rtf b/dist/win/resources/license.rtf deleted file mode 100644 index 6782bebab..000000000 --- a/dist/win/resources/license.rtf +++ /dev/null @@ -1,85 +0,0 @@ -{\rtf1\ansi\ansicpg1252\deff0\nouicompat{\fonttbl{\f0\fnil\fcharset0 Arial;}} -{\colortbl ;\red0\green0\blue255;} -{\*\generator Riched20 10.0.17134}\viewkind4\uc1 -\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\b\fs16\lang7 Cryptomator is distributed under the GPLv3 License, found below. Please see the bottom of this document for any other license applicable to code used within Cryptomator.\b0\par -\par -\b\'a9 2016 \endash 2021 Skymatic GmbH\b0\par -\par -This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\par -\par -This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\par -\par -You should have received a copy of the GNU General Public License along with this program. If not, see {{\field{\*\fldinst{HYPERLINK http://www.gnu.org/licenses/ }}{\fldrslt{http://www.gnu.org/licenses/\ul0\cf0}}}}\f0\fs16 .\par -\par -\b Cryptomator uses 40 third-party dependencies under the following licenses:\b0\par -\tab Apache License v2.0:\par -\tab\tab - jffi (com.github.jnr:jffi:1.2.23 - {{\field{\*\fldinst{HYPERLINK http://github.com/jnr/jffi }}{\fldrslt{http://github.com/jnr/jffi\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - jnr-a64asm (com.github.jnr:jnr-a64asm:1.0.0 - {{\field{\*\fldinst{HYPERLINK http://nexus.sonatype.org/oss-repository-hosting.html/jnr-a64asm }}{\fldrslt{http://nexus.sonatype.org/oss-repository-hosting.html/jnr-a64asm\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - jnr-constants (com.github.jnr:jnr-constants:0.9.15 - {{\field{\*\fldinst{HYPERLINK http://github.com/jnr/jnr-constants }}{\fldrslt{http://github.com/jnr/jnr-constants\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - jnr-ffi (com.github.jnr:jnr-ffi:2.1.12 - {{\field{\*\fldinst{HYPERLINK http://github.com/jnr/jnr-ffi }}{\fldrslt{http://github.com/jnr/jnr-ffi\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Gson (com.google.code.gson:gson:2.8.7 - {{\field{\*\fldinst{HYPERLINK https://github.com/google/gson/gson }}{\fldrslt{https://github.com/google/gson/gson\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Dagger (com.google.dagger:dagger:2.38.1 - {{\field{\*\fldinst{HYPERLINK https://github.com/google/dagger }}{\fldrslt{https://github.com/google/dagger\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Guava InternalFutureFailureAccess and InternalFutures (com.google.guava:failureaccess:1.0.1 - {{\field{\*\fldinst{HYPERLINK https://github.com/google/guava/failureaccess }}{\fldrslt{https://github.com/google/guava/failureaccess\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Guava: Google Core Libraries for Java (com.google.guava:guava:30.1.1-jre - {{\field{\*\fldinst{HYPERLINK https://github.com/google/guava/guava }}{\fldrslt{https://github.com/google/guava/guava\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Apache Commons CLI (commons-cli:commons-cli:1.4 - {{\field{\*\fldinst{HYPERLINK http://commons.apache.org/proper/commons-cli/ }}{\fldrslt{http://commons.apache.org/proper/commons-cli/\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - javax.inject (javax.inject:javax.inject:1 - {{\field{\*\fldinst{HYPERLINK http://code.google.com/p/atinject/ }}{\fldrslt{http://code.google.com/p/atinject/\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Java Native Access (net.java.dev.jna:jna:5.7.0 - {{\field{\*\fldinst{HYPERLINK https://github.com/java-native-access/jna }}{\fldrslt{https://github.com/java-native-access/jna\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Java Native Access Platform (net.java.dev.jna:jna-platform:5.7.0 - {{\field{\*\fldinst{HYPERLINK https://github.com/java-native-access/jna }}{\fldrslt{https://github.com/java-native-access/jna\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Apache Commons Lang (org.apache.commons:commons-lang3:3.12.0 - {{\field{\*\fldinst{HYPERLINK https://commons.apache.org/proper/commons-lang/ }}{\fldrslt{https://commons.apache.org/proper/commons-lang/\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Apache HttpCore (org.apache.httpcomponents:httpcore:4.4.14 - {{\field{\*\fldinst{HYPERLINK http://hc.apache.org/httpcomponents-core-ga }}{\fldrslt{http://hc.apache.org/httpcomponents-core-ga\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Jackrabbit WebDAV Library (org.apache.jackrabbit:jackrabbit-webdav:2.21.5 - {{\field{\*\fldinst{HYPERLINK http://jackrabbit.apache.org/jackrabbit-webdav/ }}{\fldrslt{http://jackrabbit.apache.org/jackrabbit-webdav/\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Jetty :: Http Utility (org.eclipse.jetty:jetty-http:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-http }}{\fldrslt{https://eclipse.org/jetty/jetty-http\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Jetty :: IO Utility (org.eclipse.jetty:jetty-io:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-io }}{\fldrslt{https://eclipse.org/jetty/jetty-io\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Jetty :: Security (org.eclipse.jetty:jetty-security:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-security }}{\fldrslt{https://eclipse.org/jetty/jetty-security\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Jetty :: Server Core (org.eclipse.jetty:jetty-server:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-server }}{\fldrslt{https://eclipse.org/jetty/jetty-server\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-servlet }}{\fldrslt{https://eclipse.org/jetty/jetty-servlet\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Jetty :: Utilities (org.eclipse.jetty:jetty-util:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-util }}{\fldrslt{https://eclipse.org/jetty/jetty-util\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Jetty :: Servlet API and Schemas for JPMS and OSGi (org.eclipse.jetty.toolchain:jetty-servlet-api:4.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-servlet-api }}{\fldrslt{https://eclipse.org/jetty/jetty-servlet-api\ul0\cf0}}}}\f0\fs16 )\par -\tab BSD:\par -\tab\tab - asm (org.ow2.asm:asm:7.1 - {{\field{\*\fldinst{HYPERLINK http://asm.ow2.org/ }}{\fldrslt{http://asm.ow2.org/\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - asm-analysis (org.ow2.asm:asm-analysis:7.1 - {{\field{\*\fldinst{HYPERLINK http://asm.ow2.org/ }}{\fldrslt{http://asm.ow2.org/\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - asm-commons (org.ow2.asm:asm-commons:7.1 - {{\field{\*\fldinst{HYPERLINK http://asm.ow2.org/ }}{\fldrslt{http://asm.ow2.org/\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - asm-tree (org.ow2.asm:asm-tree:7.1 - {{\field{\*\fldinst{HYPERLINK http://asm.ow2.org/ }}{\fldrslt{http://asm.ow2.org/\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - asm-util (org.ow2.asm:asm-util:7.1 - {{\field{\*\fldinst{HYPERLINK http://asm.ow2.org/ }}{\fldrslt{http://asm.ow2.org/\ul0\cf0}}}}\f0\fs16 )\par -\tab Eclipse Public License - Version 1.0:\par -\tab\tab - Jetty :: Servlet API and Schemas for JPMS and OSGi (org.eclipse.jetty.toolchain:jetty-servlet-api:4.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-servlet-api }}{\fldrslt{https://eclipse.org/jetty/jetty-servlet-api\ul0\cf0}}}}\f0\fs16 )\par -\tab Eclipse Public License - Version 2.0:\par -\tab\tab - Jetty :: Http Utility (org.eclipse.jetty:jetty-http:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-http }}{\fldrslt{https://eclipse.org/jetty/jetty-http\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Jetty :: IO Utility (org.eclipse.jetty:jetty-io:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-io }}{\fldrslt{https://eclipse.org/jetty/jetty-io\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Jetty :: Security (org.eclipse.jetty:jetty-security:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-security }}{\fldrslt{https://eclipse.org/jetty/jetty-security\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Jetty :: Server Core (org.eclipse.jetty:jetty-server:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-server }}{\fldrslt{https://eclipse.org/jetty/jetty-server\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-servlet }}{\fldrslt{https://eclipse.org/jetty/jetty-servlet\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Jetty :: Utilities (org.eclipse.jetty:jetty-util:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-util }}{\fldrslt{https://eclipse.org/jetty/jetty-util\ul0\cf0}}}}\f0\fs16 )\par -\tab Eclipse Public License - v 1.0:\par -\tab\tab - Logback Classic Module (ch.qos.logback:logback-classic:1.2.3 - {{\field{\*\fldinst{HYPERLINK http://logback.qos.ch/logback-classic }}{\fldrslt{http://logback.qos.ch/logback-classic\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Logback Core Module (ch.qos.logback:logback-core:1.2.3 - {{\field{\*\fldinst{HYPERLINK http://logback.qos.ch/logback-core }}{\fldrslt{http://logback.qos.ch/logback-core\ul0\cf0}}}}\f0\fs16 )\par -\tab Eclipse Public License - v 2.0:\par -\tab\tab - jnr-posix (com.github.jnr:jnr-posix:3.0.54 - {{\field{\*\fldinst{HYPERLINK http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix }}{\fldrslt{http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix\ul0\cf0}}}}\f0\fs16 )\par -\tab GNU Lesser General Public License:\par -\tab\tab - Logback Classic Module (ch.qos.logback:logback-classic:1.2.3 - {{\field{\*\fldinst{HYPERLINK http://logback.qos.ch/logback-classic }}{\fldrslt{http://logback.qos.ch/logback-classic\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Logback Core Module (ch.qos.logback:logback-core:1.2.3 - {{\field{\*\fldinst{HYPERLINK http://logback.qos.ch/logback-core }}{\fldrslt{http://logback.qos.ch/logback-core\ul0\cf0}}}}\f0\fs16 )\par -\tab GPLv2:\par -\tab\tab - jnr-posix (com.github.jnr:jnr-posix:3.0.54 - {{\field{\*\fldinst{HYPERLINK http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix }}{\fldrslt{http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix\ul0\cf0}}}}\f0\fs16 )\par -\tab GPLv2+CE:\par -\tab\tab - javafx-base (org.openjfx:javafx-base:16 - {{\field{\*\fldinst{HYPERLINK https://openjdk.java.net/projects/openjfx/javafx-base/ }}{\fldrslt{https://openjdk.java.net/projects/openjfx/javafx-base/\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - javafx-controls (org.openjfx:javafx-controls:16 - {{\field{\*\fldinst{HYPERLINK https://openjdk.java.net/projects/openjfx/javafx-controls/ }}{\fldrslt{https://openjdk.java.net/projects/openjfx/javafx-controls/\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - javafx-fxml (org.openjfx:javafx-fxml:16 - {{\field{\*\fldinst{HYPERLINK https://openjdk.java.net/projects/openjfx/javafx-fxml/ }}{\fldrslt{https://openjdk.java.net/projects/openjfx/javafx-fxml/\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - javafx-graphics (org.openjfx:javafx-graphics:16 - {{\field{\*\fldinst{HYPERLINK https://openjdk.java.net/projects/openjfx/javafx-graphics/ }}{\fldrslt{https://openjdk.java.net/projects/openjfx/javafx-graphics/\ul0\cf0}}}}\f0\fs16 )\par -\tab LGPL 2.1:\par -\tab\tab - jnr-posix (com.github.jnr:jnr-posix:3.0.54 - {{\field{\*\fldinst{HYPERLINK http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix }}{\fldrslt{http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Java Native Access (net.java.dev.jna:jna:5.7.0 - {{\field{\*\fldinst{HYPERLINK https://github.com/java-native-access/jna }}{\fldrslt{https://github.com/java-native-access/jna\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Java Native Access Platform (net.java.dev.jna:jna-platform:5.7.0 - {{\field{\*\fldinst{HYPERLINK https://github.com/java-native-access/jna }}{\fldrslt{https://github.com/java-native-access/jna\ul0\cf0}}}}\f0\fs16 )\par -\tab MIT License:\par -\tab\tab - java jwt (com.auth0:java-jwt:3.18.1 - {{\field{\*\fldinst{HYPERLINK https://github.com/auth0/java-jwt }}{\fldrslt{https://github.com/auth0/java-jwt\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - jnr-x86asm (com.github.jnr:jnr-x86asm:1.0.2 - {{\field{\*\fldinst{HYPERLINK http://github.com/jnr/jnr-x86asm }}{\fldrslt{http://github.com/jnr/jnr-x86asm\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - jnr-fuse (com.github.serceman:jnr-fuse:0.5.5 - {{\field{\*\fldinst{HYPERLINK https://github.com/SerCeMan/jnr-fuse }}{\fldrslt{https://github.com/SerCeMan/jnr-fuse\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - zxcvbn4j (com.nulab-inc:zxcvbn:1.5.2 - {{\field{\*\fldinst{HYPERLINK https://github.com/nulab/zxcvbn4j }}{\fldrslt{https://github.com/nulab/zxcvbn4j\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - SLF4J API Module (org.slf4j:slf4j-api:1.7.31 - {{\field{\*\fldinst{HYPERLINK http://www.slf4j.org }}{\fldrslt{http://www.slf4j.org\ul0\cf0}}}}\f0\fs16 )\par -\tab The BSD 2-Clause License:\par -\tab\tab - EasyBind (com.tobiasdiez:easybind:2.2 - {{\field{\*\fldinst{HYPERLINK https://github.com/tobiasdiez/EasyBind }}{\fldrslt{https://github.com/tobiasdiez/EasyBind\ul0\cf0}}}}\f0\fs16 )\par -\par -\b Cryptomator uses other third-party assets under the following licenses:\b0\par -\tab SIL OFL 1.1 License:\par -\tab\tab - Font Awesome 5.12.0 ({{\field{\*\fldinst{HYPERLINK https://fontawesome.com/ }}{\fldrslt{https://fontawesome.com/\ul0\cf0}}}}\f0\fs16 )\b\par -} - \ No newline at end of file diff --git a/dist/win/resources/licenseTemplate.ftl b/dist/win/resources/licenseTemplate.ftl new file mode 100644 index 000000000..d442e6538 --- /dev/null +++ b/dist/win/resources/licenseTemplate.ftl @@ -0,0 +1,37 @@ +<#function artifactFormat p> + <#if p.name?index_of('Unnamed') > -1> + <#return p.artifactId + " (" + p.groupId + ":" + p.artifactId + ":" + p.version + " - {{\\field{\\*\\fldinst{HYPERLINK " + (p.url!"no url defined") + "}}{\\fldrslt{" + (p.url!"no url defined") + "\\ul0\\cf0}}}}\\f0\\fs16 ) "> + <#else> + <#return p.name + " (" + p.groupId + ":" + p.artifactId + ":" + p.version + " - {{\\field{\\*\\fldinst{HYPERLINK " + (p.url!"no url defined") + "}}{\\fldrslt{" + (p.url!"no url defined") + "\\ul0\\cf0}}}}\\f0\\fs16 ) "> + + +{\rtf1\ansi\ansicpg1252\deff0\nouicompat{\fonttbl{\f0\fnil\fcharset0 Arial;}} +{\colortbl ;\red0\green0\blue255;} +\viewkind4\uc1 +\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\b\fs16\lang7 Cryptomator is distributed under the GPLv3 License, found below. Please see the bottom of this document for any other license applicable to code used within Cryptomator.\b0\par +\par +\b\'a9 2016 \endash 2022 Skymatic GmbH\b0\par +\par +This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\par +\par +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\par +\par +You should have received a copy of the GNU General Public License along with this program. If not, see {{\field{\*\fldinst{HYPERLINK http://www.gnu.org/licenses/ }}{\fldrslt{http://www.gnu.org/licenses/\ul0\cf0}}}}\f0\fs16 .\par +\par + +\b Cryptomator uses ${dependencyMap?size} third-party dependencies under the following licenses:\b0\par +<#list licenseMap as e> +<#assign license = e.getKey()/> +<#assign projects = e.getValue()/> +<#if projects?size > 0> +\tab ${license}:\par +<#list projects as project> +\tab\tab- ${artifactFormat(project)}\par + + + +\par +\b Cryptomator uses other third-party assets under the following licenses:\b0\par +\tab SIL OFL 1.1 License:\par +\tab\tab - Font Awesome 5.12.0 ({{\field{\*\fldinst{HYPERLINK https://fontawesome.com/ }}{\fldrslt{https://fontawesome.com/\ul0\cf0}}}}\f0\fs16 )\b\par +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 14287a907..b304a8bbe 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.cryptomator cryptomator - 1.6.6 + 1.6.7 Cryptomator Desktop App @@ -34,24 +34,28 @@ 1.0.1 1.3.3 1.3.3 - 1.2.6 + 1.2.7 - 17.0.1 + 18 3.12.0 - 3.18.2 + 3.19.0 2.2 - 31.0-jre - 2.40.3 - 2.8.9 + 31.1-jre + 2.41 + 2.9.0 1.5.2 - 1.7.32 - 1.2.8 + 1.7.36 + 1.2.11 5.8.1 - 3.12.4 + 4.4.0 2.2 + + + 7.0.0 + 0.8.7 @@ -228,7 +232,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.10.1 org.apache.maven.plugins @@ -238,7 +242,7 @@ org.apache.maven.plugins maven-dependency-plugin - 3.2.0 + 3.3.0 org.apache.maven.plugins @@ -253,17 +257,17 @@ org.apache.maven.plugins maven-jar-plugin - 3.2.0 + 3.2.2 org.jacoco jacoco-maven-plugin - 0.8.7 + ${jacoco.version} org.owasp dependency-check-maven - 6.3.1 + ${dependency-check.version} diff --git a/src/main/java/org/cryptomator/common/Passphrase.java b/src/main/java/org/cryptomator/common/Passphrase.java new file mode 100644 index 000000000..cf64c7a10 --- /dev/null +++ b/src/main/java/org/cryptomator/common/Passphrase.java @@ -0,0 +1,108 @@ +package org.cryptomator.common; + +import javax.security.auth.Destroyable; +import java.util.Arrays; + +/** + * A destroyable CharSequence. + */ +public class Passphrase implements Destroyable, CharSequence { + + private final char[] data; + private final int offset; + private final int length; + private boolean destroyed; + + /** + * Wraps (doesn't copy) the given data. + * + * @param data The wrapped data. Any changes to this will be reflected in this passphrase + */ + public Passphrase(char[] data) { + this(data, 0, data.length); + } + + /** + * Wraps (doesn't copy) a subarray of the given data. + * + * @param data The wrapped data. Any changes to this will be reflected in this passphrase + * @param offset The subarray offset, i.e. the first character of this passphrase + * @param length The subarray length, i.e. the length of this passphrase + */ + public Passphrase(char[] data, int offset, int length) { + if (offset < 0 || length < 0 || offset + length > data.length) { + throw new IndexOutOfBoundsException("[%1$d %1$d + %2$d[ not within [0, %3$d[".formatted(offset, length, data.length)); + } + this.data = data; + this.offset = offset; + this.length = length; + } + + public static Passphrase copyOf(CharSequence cs) { + char[] result = new char[cs.length()]; + for (int i = 0; i < cs.length(); i++) { + result[i] = cs.charAt(i); + } + return new Passphrase(result); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Passphrase that = (Passphrase) o; + // time-constant comparison + int diff = 0; + for (int i = 0; i < length; i++) { + diff |= charAt(i) ^ that.charAt(i); + } + return diff == 0; + } + + @Override + public int hashCode() { + // basically Arrays.hashCode, but only for a certain subarray + int result = 1; + for (int i = 0; i < length; i++) { + result = 31 * result + charAt(i); + } + return result; + } + + @Override + public String toString() { + return new String(data, offset, length); + } + + @Override + public int length() { + return length; + } + + @Override + public char charAt(int index) { + if (index < 0 || index >= length) { + throw new IndexOutOfBoundsException("%d not within [0, %d[".formatted(index, length)); + } + return data[offset + index]; + } + + @Override + public Passphrase subSequence(int start, int end) { + if (start < 0 || end < 0 || end > length || start > end) { + throw new IndexOutOfBoundsException("[%d, %d[ not within [0, %d[".formatted(start, end, length)); + } + return new Passphrase(Arrays.copyOfRange(data, offset + start, offset + end)); + } + + @Override + public boolean isDestroyed() { + return destroyed; + } + + @Override + public void destroy() { + Arrays.fill(data, offset, offset + length, '\0'); + destroyed = true; + } +} diff --git a/src/main/java/org/cryptomator/common/mountpoint/CustomDriveLetterChooser.java b/src/main/java/org/cryptomator/common/mountpoint/CustomDriveLetterChooser.java index 1a42aa5ad..02f75d4a1 100644 --- a/src/main/java/org/cryptomator/common/mountpoint/CustomDriveLetterChooser.java +++ b/src/main/java/org/cryptomator/common/mountpoint/CustomDriveLetterChooser.java @@ -5,6 +5,9 @@ import org.cryptomator.common.settings.VaultSettings; import org.cryptomator.common.vaults.Volume; import javax.inject.Inject; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Files; +import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Optional; @@ -27,4 +30,13 @@ class CustomDriveLetterChooser implements MountPointChooser { public Optional chooseMountPoint(Volume caller) { return this.vaultSettings.getWinDriveLetter().map(letter -> letter.charAt(0) + ":\\").map(Paths::get); } + + @Override + public boolean prepare(Volume caller, Path driveLetter) throws InvalidMountPointException { + if (!Files.notExists(driveLetter, LinkOption.NOFOLLOW_LINKS)) { + //Drive already exists OR can't be determined + throw new InvalidMountPointException(new FileAlreadyExistsException(driveLetter.toString())); + } + return false; + } } diff --git a/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java b/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java index 5f1a7fedd..80c5b067b 100644 --- a/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java +++ b/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java @@ -4,6 +4,7 @@ import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.Environment; import org.cryptomator.common.settings.VaultSettings; import org.cryptomator.common.settings.VolumeImpl; +import org.cryptomator.common.vaults.MountPointRequirement; import org.cryptomator.common.vaults.Volume; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -11,10 +12,10 @@ import org.slf4j.LoggerFactory; import javax.inject.Inject; import java.io.IOException; import java.nio.file.DirectoryNotEmptyException; -import java.nio.file.DirectoryStream; import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; import java.nio.file.LinkOption; +import java.nio.file.NoSuchFileException; import java.nio.file.NotDirectoryException; import java.nio.file.Path; import java.nio.file.Paths; @@ -22,6 +23,9 @@ import java.util.Optional; class CustomMountPointChooser implements MountPointChooser { + private static final String HIDEAWAY_PREFIX = ".~$"; + private static final String HIDEAWAY_SUFFIX = ".tmp"; + private static final String WIN_HIDDEN = "dos:hidden"; private static final Logger LOG = LoggerFactory.getLogger(CustomMountPointChooser.class); private final VaultSettings vaultSettings; @@ -35,7 +39,6 @@ class CustomMountPointChooser implements MountPointChooser { @Override public boolean isApplicable(Volume caller) { - //Disable if useExperimentalFuse is required (Win + Fuse), but set to false return caller.getImplementationType() != VolumeImpl.FUSE || !SystemUtils.IS_OS_WINDOWS || environment.useExperimentalFuse(); } @@ -47,49 +50,102 @@ class CustomMountPointChooser implements MountPointChooser { @Override public boolean prepare(Volume caller, Path mountPoint) throws InvalidMountPointException { - switch (caller.getMountPointRequirement()) { - case PARENT_NO_MOUNT_POINT -> prepareParentNoMountPoint(mountPoint); - case EMPTY_MOUNT_POINT -> prepareEmptyMountPoint(mountPoint); - case NONE -> { - //Requirement "NONE" doesn't make any sense here. - //No need to prepare/verify a Mountpoint without requiring one... + return switch (caller.getMountPointRequirement()) { + case PARENT_NO_MOUNT_POINT -> { + prepareParentNoMountPoint(mountPoint); + LOG.debug("Successfully checked custom mount point: {}", mountPoint); + yield true; + } + case EMPTY_MOUNT_POINT -> { + prepareEmptyMountPoint(mountPoint); + LOG.debug("Successfully checked custom mount point: {}", mountPoint); + yield false; + } + case NONE, UNUSED_ROOT_DIR, PARENT_OPT_MOUNT_POINT -> { throw new InvalidMountPointException(new IllegalStateException("Illegal MountPointRequirement")); } - default -> { - //Currently the case for "PARENT_OPT_MOUNT_POINT" - throw new InvalidMountPointException(new IllegalStateException("Not implemented")); - } - } - LOG.debug("Successfully checked custom mount point: {}", mountPoint); - return false; + }; } - private void prepareParentNoMountPoint(Path mountPoint) throws InvalidMountPointException { - //This the case on Windows when using FUSE - //See https://github.com/billziss-gh/winfsp/issues/320 - Path parent = mountPoint.getParent(); - if (!Files.isDirectory(parent)) { - throw new InvalidMountPointException(new NotDirectoryException(parent.toString())); - } - //We must use #notExists() here because notExists =/= !exists (see docs) - if (!Files.notExists(mountPoint, LinkOption.NOFOLLOW_LINKS)) { - //File exists OR can't be determined - throw new InvalidMountPointException(new FileAlreadyExistsException(mountPoint.toString())); + //This is case on Windows when using FUSE + //See https://github.com/billziss-gh/winfsp/issues/320 + void prepareParentNoMountPoint(Path mountPoint) throws InvalidMountPointException { + Path hideaway = getHideaway(mountPoint); + var mpExists = Files.exists(mountPoint, LinkOption.NOFOLLOW_LINKS); + var hideExists = Files.exists(hideaway, LinkOption.NOFOLLOW_LINKS); + + //TODO: possible improvement by just deleting an _empty_ hideaway + if (mpExists && hideExists) { //both resources exist (whatever type) + throw new InvalidMountPointException(new FileAlreadyExistsException(hideaway.toString())); + } else if (!mpExists && !hideExists) { //neither mountpoint nor hideaway exist + throw new InvalidMountPointException(new NoSuchFileException(mountPoint.toString())); + } else if (!mpExists) { //only hideaway exists + checkIsDirectory(hideaway); + LOG.info("Mountpoint {} for winfsp mount seems to be not properly cleaned up. Will be fixed on unmount.", mountPoint); + try { + if (SystemUtils.IS_OS_WINDOWS) { + Files.setAttribute(hideaway, WIN_HIDDEN, true, LinkOption.NOFOLLOW_LINKS); + } + } catch (IOException e) { + throw new InvalidMountPointException(e); + } + } else { //only mountpoint exists + try { + checkIsDirectory(mountPoint); + checkIsEmpty(mountPoint); + + Files.move(mountPoint, hideaway); + if (SystemUtils.IS_OS_WINDOWS) { + Files.setAttribute(hideaway, WIN_HIDDEN, true, LinkOption.NOFOLLOW_LINKS); + } + } catch (IOException e) { + throw new InvalidMountPointException(e); + } } } private void prepareEmptyMountPoint(Path mountPoint) throws InvalidMountPointException { //This is the case for Windows when using Dokany and for Linux and Mac - if (!Files.isDirectory(mountPoint)) { - throw new InvalidMountPointException(new NotDirectoryException(mountPoint.toString())); - } - try (DirectoryStream ds = Files.newDirectoryStream(mountPoint)) { - if (ds.iterator().hasNext()) { - throw new InvalidMountPointException(new DirectoryNotEmptyException(mountPoint.toString())); - } + checkIsDirectory(mountPoint); + try { + checkIsEmpty(mountPoint); } catch (IOException exception) { throw new InvalidMountPointException("IOException while checking folder content", exception); } } + @Override + public void cleanup(Volume caller, Path mountPoint) { + if (caller.getMountPointRequirement() == MountPointRequirement.PARENT_NO_MOUNT_POINT) { + Path hideaway = getHideaway(mountPoint); + try { + Files.move(hideaway, mountPoint); + if (SystemUtils.IS_OS_WINDOWS) { + Files.setAttribute(mountPoint, WIN_HIDDEN, false); + } + } catch (IOException e) { + LOG.error("Unable to clean up mountpoint {} for Winfsp mounting.", mountPoint, e); + } + } + } + + private void checkIsDirectory(Path toCheck) throws InvalidMountPointException { + if (!Files.isDirectory(toCheck, LinkOption.NOFOLLOW_LINKS)) { + throw new InvalidMountPointException(new NotDirectoryException(toCheck.toString())); + } + } + + private void checkIsEmpty(Path toCheck) throws InvalidMountPointException, IOException { + try (var dirStream = Files.list(toCheck)) { + if (dirStream.findFirst().isPresent()) { + throw new InvalidMountPointException(new DirectoryNotEmptyException(toCheck.toString())); + } + } + } + + //visible for testing + Path getHideaway(Path mountPoint) { + return mountPoint.resolveSibling(HIDEAWAY_PREFIX + mountPoint.getFileName().toString() + HIDEAWAY_SUFFIX); + } + } diff --git a/src/main/java/org/cryptomator/common/mountpoint/MountPointHelper.java b/src/main/java/org/cryptomator/common/mountpoint/MountPointHelper.java index 704f2f62d..fe64902bd 100644 --- a/src/main/java/org/cryptomator/common/mountpoint/MountPointHelper.java +++ b/src/main/java/org/cryptomator/common/mountpoint/MountPointHelper.java @@ -66,9 +66,9 @@ class MountPointHelper { private void clearIrregularUnmountDebris(Path dirContainingMountPoints) { IOException cleanupFailed = new IOException("Cleanup failed"); - try { + try (var ds = Files.newDirectoryStream(dirContainingMountPoints)) { LOG.debug("Performing cleanup of mountpoint dir {}.", dirContainingMountPoints); - for (Path p : Files.newDirectoryStream(dirContainingMountPoints)) { + for (Path p : ds) { try { var attr = Files.readAttributes(p, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS); if (attr.isOther() && attr.isDirectory()) { // yes, this is possible with windows junction points -.- @@ -113,8 +113,10 @@ class MountPointHelper { } private void ensureIsEmpty(Path dir) throws IOException { - if (Files.newDirectoryStream(dir).iterator().hasNext()) { - throw new DirectoryNotEmptyException(dir.toString()); + try (var ds = Files.newDirectoryStream(dir)) { + if (ds.iterator().hasNext()){ + throw new DirectoryNotEmptyException(dir.toString()); + } } } } diff --git a/src/main/java/org/cryptomator/common/mountpoint/TemporaryMountPointChooser.java b/src/main/java/org/cryptomator/common/mountpoint/TemporaryMountPointChooser.java index eb1d8d0b1..bcda3d8f2 100644 --- a/src/main/java/org/cryptomator/common/mountpoint/TemporaryMountPointChooser.java +++ b/src/main/java/org/cryptomator/common/mountpoint/TemporaryMountPointChooser.java @@ -65,7 +65,7 @@ class TemporaryMountPointChooser implements MountPointChooser { throw new InvalidMountPointException(new IllegalStateException("Illegal MountPointRequirement")); } default -> { - //Currently the case for "PARENT_OPT_MOUNT_POINT" + //Currently the case for "UNUSED_ROOT_DIR, PARENT_OPT_MOUNT_POINT" throw new InvalidMountPointException(new IllegalStateException("Not implemented")); } } diff --git a/src/main/java/org/cryptomator/common/vaults/DokanyVolume.java b/src/main/java/org/cryptomator/common/vaults/DokanyVolume.java index c998761d0..c08642073 100644 --- a/src/main/java/org/cryptomator/common/vaults/DokanyVolume.java +++ b/src/main/java/org/cryptomator/common/vaults/DokanyVolume.java @@ -86,7 +86,7 @@ public class DokanyVolume extends AbstractVolume { @Override public MountPointRequirement getMountPointRequirement() { - return MountPointRequirement.EMPTY_MOUNT_POINT; + return this.vaultSettings.getWinDriveLetter().isPresent() ? MountPointRequirement.UNUSED_ROOT_DIR : MountPointRequirement.EMPTY_MOUNT_POINT; } public static boolean isSupportedStatic() { diff --git a/src/main/java/org/cryptomator/common/vaults/FuseVolume.java b/src/main/java/org/cryptomator/common/vaults/FuseVolume.java index a1579fdaf..0321cfa0f 100644 --- a/src/main/java/org/cryptomator/common/vaults/FuseVolume.java +++ b/src/main/java/org/cryptomator/common/vaults/FuseVolume.java @@ -4,6 +4,7 @@ import com.google.common.collect.Iterators; import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.mountpoint.InvalidMountPointException; import org.cryptomator.common.mountpoint.MountPointChooser; +import org.cryptomator.common.settings.VaultSettings; import org.cryptomator.common.settings.VolumeImpl; import org.cryptomator.cryptofs.CryptoFileSystem; import org.cryptomator.frontend.fuse.mount.EnvironmentVariables; @@ -28,11 +29,14 @@ public class FuseVolume extends AbstractVolume { private static final Logger LOG = LoggerFactory.getLogger(FuseVolume.class); private static final Pattern NON_WHITESPACE_OR_QUOTED = Pattern.compile("[^\\s\"']+|\"([^\"]*)\"|'([^']*)'"); // Thanks to https://stackoverflow.com/a/366532 + private final VaultSettings vaultSettings; + private Mount mount; @Inject - public FuseVolume(@Named("orderedMountPointChoosers") Iterable choosers) { + public FuseVolume(VaultSettings vaultSettings, @Named("orderedMountPointChoosers") Iterable choosers) { super(choosers); + this.vaultSettings = vaultSettings; } @Override @@ -50,7 +54,7 @@ public class FuseVolume extends AbstractVolume { .withFileNameTranscoder(mounter.defaultFileNameTranscoder()) // .build(); this.mount = mounter.mount(root, envVars, onExitAction); - } catch ( FuseMountException | FuseNotSupportedException e) { + } catch (FuseMountException | FuseNotSupportedException e) { throw new VolumeException("Unable to mount Filesystem", e); } } @@ -119,7 +123,10 @@ public class FuseVolume extends AbstractVolume { @Override public MountPointRequirement getMountPointRequirement() { - return SystemUtils.IS_OS_WINDOWS ? MountPointRequirement.PARENT_NO_MOUNT_POINT : MountPointRequirement.EMPTY_MOUNT_POINT; + if (!SystemUtils.IS_OS_WINDOWS) { + return MountPointRequirement.EMPTY_MOUNT_POINT; + } + return this.vaultSettings.getWinDriveLetter().isPresent() ? MountPointRequirement.UNUSED_ROOT_DIR : MountPointRequirement.PARENT_NO_MOUNT_POINT; } public static boolean isSupportedStatic() { diff --git a/src/main/java/org/cryptomator/common/vaults/MountPointRequirement.java b/src/main/java/org/cryptomator/common/vaults/MountPointRequirement.java index 84a798e59..deec61e1a 100644 --- a/src/main/java/org/cryptomator/common/vaults/MountPointRequirement.java +++ b/src/main/java/org/cryptomator/common/vaults/MountPointRequirement.java @@ -6,6 +6,11 @@ package org.cryptomator.common.vaults; */ public enum MountPointRequirement { + /** + * The Mountpoint needs to be a filesystem root and must not exist. + */ + UNUSED_ROOT_DIR, + /** * No Mountpoint on the local filesystem required. (e.g. WebDAV) */ diff --git a/src/main/java/org/cryptomator/ipc/Server.java b/src/main/java/org/cryptomator/ipc/Server.java index 6058a608f..770373681 100644 --- a/src/main/java/org/cryptomator/ipc/Server.java +++ b/src/main/java/org/cryptomator/ipc/Server.java @@ -7,9 +7,11 @@ import java.io.EOFException; import java.io.IOException; import java.net.StandardProtocolFamily; import java.net.UnixDomainSocketAddress; +import java.nio.channels.AlreadyBoundException; import java.nio.channels.AsynchronousCloseException; import java.nio.channels.ClosedChannelException; import java.nio.channels.ServerSocketChannel; +import java.nio.channels.UnsupportedAddressTypeException; import java.nio.file.Files; import java.nio.file.Path; import java.util.concurrent.Executor; @@ -29,10 +31,18 @@ class Server implements IpcCommunicator { public static Server create(Path socketPath) throws IOException { Files.createDirectories(socketPath.getParent()); var address = UnixDomainSocketAddress.of(socketPath); - var serverSocketChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX); - serverSocketChannel.bind(address); - LOG.info("Spawning IPC server listening on socket {}", socketPath); - return new Server(serverSocketChannel, socketPath); + ServerSocketChannel ch = null; + try { + ch = ServerSocketChannel.open(StandardProtocolFamily.UNIX); + ch.bind(address); + LOG.info("Spawning IPC server listening on socket {}", socketPath); + return new Server(ch, socketPath); + } catch (IOException | AlreadyBoundException | UnsupportedAddressTypeException e) { + if (ch != null) { + ch.close(); + } + throw e; + } } @Override diff --git a/src/main/java/org/cryptomator/logging/LoggerModule.java b/src/main/java/org/cryptomator/logging/LoggerModule.java index 4866655e3..5031fe3be 100644 --- a/src/main/java/org/cryptomator/logging/LoggerModule.java +++ b/src/main/java/org/cryptomator/logging/LoggerModule.java @@ -79,8 +79,9 @@ public class LoggerModule { @Singleton @Named("fileAppender") static Appender provideFileAppender(LoggerContext context, PatternLayoutEncoder encoder, Environment environment) { - if (environment.getLogDir().isPresent()) { - Path logDir = environment.getLogDir().get(); + var optionalLogDir = environment.getLogDir(); + if (optionalLogDir.isPresent()) { + Path logDir = optionalLogDir.get(); RollingFileAppender appender = new RollingFileAppender<>(); appender.setContext(context); appender.setFile(logDir.resolve(LOGFILE_NAME).toString()); @@ -110,9 +111,10 @@ public class LoggerModule { @Singleton @Named("upgradeAppender") static Appender provideUpgradeAppender(LoggerContext context, PatternLayoutEncoder encoder, Environment environment) { - if (environment.getLogDir().isPresent()) { + var optionalLogDir = environment.getLogDir(); + if (optionalLogDir.isPresent()) { FileAppender appender = new FileAppender<>(); - appender.setFile(environment.getLogDir().get().resolve(UPGRADE_FILENAME).toString()); + appender.setFile(optionalLogDir.get().resolve(UPGRADE_FILENAME).toString()); appender.setContext(context); appender.setEncoder(encoder); appender.start(); diff --git a/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java b/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java index 35f2be069..1fd463432 100644 --- a/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java +++ b/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java @@ -35,14 +35,13 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.ResourceBundle; -import java.util.UUID; @AddVaultWizardScoped public class CreateNewVaultLocationController implements FxController { private static final Logger LOG = LoggerFactory.getLogger(CreateNewVaultLocationController.class); private static final Path DEFAULT_CUSTOM_VAULT_PATH = Paths.get(System.getProperty("user.home")); - private static final String TEMP_FILE_FORMAT = "cryptomator-%s.tmp"; + private static final String TEMP_FILE_FORMAT = ".locationTest.cryptomator.tmp"; private final Stage window; private final Lazy chooseNameScene; @@ -112,7 +111,7 @@ public class CreateNewVaultLocationController implements FxController { } private boolean isActuallyWritable(Path p) { - Path tmpFile = p.resolve(String.format(TEMP_FILE_FORMAT, UUID.randomUUID())); + Path tmpFile = p.resolve(TEMP_FILE_FORMAT); try (var chan = Files.newByteChannel(tmpFile, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE, StandardOpenOption.DELETE_ON_CLOSE)) { return true; } catch (IOException e) { diff --git a/src/main/java/org/cryptomator/ui/common/FxmlFile.java b/src/main/java/org/cryptomator/ui/common/FxmlFile.java index b8d5bbff0..bc952f9d1 100644 --- a/src/main/java/org/cryptomator/ui/common/FxmlFile.java +++ b/src/main/java/org/cryptomator/ui/common/FxmlFile.java @@ -42,7 +42,7 @@ public enum FxmlFile { this.ressourcePathString = ressourcePathString; } - String getRessourcePathString() { + public String getRessourcePathString() { return ressourcePathString; } } diff --git a/src/main/java/org/cryptomator/ui/common/FxmlLoaderFactory.java b/src/main/java/org/cryptomator/ui/common/FxmlLoaderFactory.java index c10054ef4..dcff93aab 100644 --- a/src/main/java/org/cryptomator/ui/common/FxmlLoaderFactory.java +++ b/src/main/java/org/cryptomator/ui/common/FxmlLoaderFactory.java @@ -22,6 +22,10 @@ public class FxmlLoaderFactory { this.resourceBundle = resourceBundle; } + public static FxmlLoaderFactory forController(T controller, Function sceneFactory, ResourceBundle resourceBundle) { + return new FxmlLoaderFactory(Map.of(controller.getClass(), () -> controller), sceneFactory, resourceBundle); + } + /** * @return A new FXMLLoader instance */ diff --git a/src/main/java/org/cryptomator/ui/common/UserInteractionLock.java b/src/main/java/org/cryptomator/ui/common/UserInteractionLock.java deleted file mode 100644 index 4eba62552..000000000 --- a/src/main/java/org/cryptomator/ui/common/UserInteractionLock.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.cryptomator.ui.common; - -import javafx.application.Platform; -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.ReadOnlyBooleanProperty; -import javafx.beans.property.SimpleBooleanProperty; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -public class UserInteractionLock { - - private final Lock lock = new ReentrantLock(); - private final Condition condition = lock.newCondition(); - private final BooleanProperty awaitingInteraction = new SimpleBooleanProperty(); - private volatile E state; - - public UserInteractionLock(E initialValue) { - this.state = initialValue; - } - - public synchronized void reset(E value) { - this.state = value; - } - - public void interacted(E result) { - assert Platform.isFxApplicationThread(); - lock.lock(); - try { - state = result; - awaitingInteraction.set(false); - condition.signal(); - } finally { - lock.unlock(); - } - } - - public E awaitInteraction() throws InterruptedException { - assert !Platform.isFxApplicationThread(); - lock.lock(); - try { - Platform.runLater(() -> awaitingInteraction.set(true)); - condition.await(); - return state; - } finally { - lock.unlock(); - } - } - - public ReadOnlyBooleanProperty awaitingInteraction() { - return awaitingInteraction; - } - -} diff --git a/src/main/java/org/cryptomator/ui/controls/NiceSecurePasswordField.java b/src/main/java/org/cryptomator/ui/controls/NiceSecurePasswordField.java index 4a4e43fff..4d09707b9 100644 --- a/src/main/java/org/cryptomator/ui/controls/NiceSecurePasswordField.java +++ b/src/main/java/org/cryptomator/ui/controls/NiceSecurePasswordField.java @@ -1,5 +1,7 @@ package org.cryptomator.ui.controls; +import org.cryptomator.common.Passphrase; + import javafx.beans.Observable; import javafx.beans.binding.Bindings; import javafx.beans.property.StringProperty; @@ -82,7 +84,7 @@ public class NiceSecurePasswordField extends StackPane { return passwordField.textProperty(); } - public CharSequence getCharacters() { + public Passphrase getCharacters() { return passwordField.getCharacters(); } diff --git a/src/main/java/org/cryptomator/ui/controls/SecurePasswordField.java b/src/main/java/org/cryptomator/ui/controls/SecurePasswordField.java index 0290f512d..66df79394 100644 --- a/src/main/java/org/cryptomator/ui/controls/SecurePasswordField.java +++ b/src/main/java/org/cryptomator/ui/controls/SecurePasswordField.java @@ -9,6 +9,7 @@ package org.cryptomator.ui.controls; import com.google.common.base.Strings; +import org.cryptomator.common.Passphrase; import javafx.application.Platform; import javafx.beans.NamedArg; @@ -28,7 +29,6 @@ import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyCombination; import javafx.scene.input.KeyEvent; import javafx.scene.input.TransferMode; -import java.nio.CharBuffer; import java.text.Normalizer; import java.text.Normalizer.Form; import java.util.Arrays; @@ -203,8 +203,8 @@ public class SecurePasswordField extends TextField { * @see #wipe() */ @Override - public CharSequence getCharacters() { - return CharBuffer.wrap(content, 0, length); + public Passphrase getCharacters() { + return new Passphrase(content, 0, length); } /** diff --git a/src/main/java/org/cryptomator/ui/fxapp/UpdateCheckerTask.java b/src/main/java/org/cryptomator/ui/fxapp/UpdateCheckerTask.java index d9de7f2da..032148cea 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/UpdateCheckerTask.java +++ b/src/main/java/org/cryptomator/ui/fxapp/UpdateCheckerTask.java @@ -23,7 +23,7 @@ public class UpdateCheckerTask extends Task { private static final Logger LOG = LoggerFactory.getLogger(UpdateCheckerTask.class); - private static final long MAX_RESPONSE_SIZE = 10 * 1024; // 10kb should be sufficient. protect against flooding + private static final long MAX_RESPONSE_SIZE = 10L * 1024; // 10kb should be sufficient. protect against flooding private static final Gson GSON = new GsonBuilder().setLenient().create(); private final HttpClient httpClient; diff --git a/src/main/java/org/cryptomator/ui/keyloading/KeyLoadingModule.java b/src/main/java/org/cryptomator/ui/keyloading/KeyLoadingModule.java index bff757b1a..616e7e5e0 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/KeyLoadingModule.java +++ b/src/main/java/org/cryptomator/ui/keyloading/KeyLoadingModule.java @@ -26,7 +26,7 @@ abstract class KeyLoadingModule { @Provides @KeyLoading @KeyLoadingScoped - static KeyLoadingStrategy provideKeyLoaderProvider(@KeyLoading Vault vault, Map> strategies) { + static KeyLoadingStrategy provideKeyLoadingStrategy(@KeyLoading Vault vault, Map> strategies) { try { String scheme = vault.getVaultConfigCache().get().getKeyId().getScheme(); var fallback = KeyLoadingStrategy.failed(new IllegalArgumentException("Unsupported key id " + scheme)); diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileComponent.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileComponent.java new file mode 100644 index 000000000..a548cd47d --- /dev/null +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileComponent.java @@ -0,0 +1,25 @@ +package org.cryptomator.ui.keyloading.masterkeyfile; + +import dagger.Subcomponent; + +import javafx.scene.Scene; +import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; + +@ChooseMasterkeyFileScoped +@Subcomponent(modules = {ChooseMasterkeyFileModule.class}) +public interface ChooseMasterkeyFileComponent { + + @ChooseMasterkeyFileScoped + Scene chooseMasterkeyScene(); + + @ChooseMasterkeyFileScoped + CompletableFuture result(); + + @Subcomponent.Builder + interface Builder { + + ChooseMasterkeyFileComponent build(); + } + +} diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileController.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileController.java new file mode 100644 index 000000000..11cf7bd6b --- /dev/null +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileController.java @@ -0,0 +1,57 @@ +package org.cryptomator.ui.keyloading.masterkeyfile; + +import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.keyloading.KeyLoading; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javafx.fxml.FXML; +import javafx.stage.FileChooser; +import javafx.stage.Stage; +import javafx.stage.WindowEvent; +import java.io.File; +import java.nio.file.Path; +import java.util.ResourceBundle; +import java.util.concurrent.CompletableFuture; + +@ChooseMasterkeyFileScoped +public class ChooseMasterkeyFileController implements FxController { + + private static final Logger LOG = LoggerFactory.getLogger(ChooseMasterkeyFileController.class); + + private final Stage window; + private final CompletableFuture result; + private final ResourceBundle resourceBundle; + + @Inject + public ChooseMasterkeyFileController(@KeyLoading Stage window, CompletableFuture result, ResourceBundle resourceBundle) { + this.window = window; + this.result = result; + this.resourceBundle = resourceBundle; + this.window.setOnHiding(this::windowClosed); + } + + @FXML + public void cancel() { + window.close(); + } + + private void windowClosed(WindowEvent windowEvent) { + result.cancel(true); + } + + @FXML + public void proceed() { + LOG.trace("proceed()"); + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle(resourceBundle.getString("unlock.chooseMasterkey.filePickerTitle")); + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Cryptomator Masterkey", "*.cryptomator")); + File masterkeyFile = fileChooser.showOpenDialog(window); + if (masterkeyFile != null) { + LOG.debug("Chose masterkey file: {}", masterkeyFile); + result.complete(masterkeyFile.toPath()); + } + } + +} diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileModule.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileModule.java new file mode 100644 index 000000000..21ae2b26c --- /dev/null +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileModule.java @@ -0,0 +1,29 @@ +package org.cryptomator.ui.keyloading.masterkeyfile; + +import dagger.Module; +import dagger.Provides; +import org.cryptomator.ui.common.DefaultSceneFactory; +import org.cryptomator.ui.common.FxmlFile; +import org.cryptomator.ui.common.FxmlLoaderFactory; + +import javafx.scene.Scene; +import java.nio.file.Path; +import java.util.ResourceBundle; +import java.util.concurrent.CompletableFuture; + +@Module +interface ChooseMasterkeyFileModule { + + @Provides + @ChooseMasterkeyFileScoped + static CompletableFuture provideResult() { + return new CompletableFuture<>(); + } + + @Provides + @ChooseMasterkeyFileScoped + static Scene provideChooseMasterkeyScene(ChooseMasterkeyFileController controller, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) { + return FxmlLoaderFactory.forController(controller, sceneFactory, resourceBundle).createScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE); + } + +} diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileScoped.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileScoped.java new file mode 100644 index 000000000..4bf8c5c24 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileScoped.java @@ -0,0 +1,13 @@ +package org.cryptomator.ui.keyloading.masterkeyfile; + +import javax.inject.Scope; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Scope +@Documented +@Retention(RetentionPolicy.RUNTIME) +@interface ChooseMasterkeyFileScoped { + +} diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingFinisher.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingFinisher.java deleted file mode 100644 index 44d7ebfb0..000000000 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingFinisher.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.cryptomator.ui.keyloading.masterkeyfile; - -import org.cryptomator.common.keychain.KeychainManager; -import org.cryptomator.common.vaults.Vault; -import org.cryptomator.integrations.keychain.KeychainAccessException; -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 java.nio.CharBuffer; -import java.util.Arrays; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; - -@KeyLoadingScoped -class MasterkeyFileLoadingFinisher { - - private static final Logger LOG = LoggerFactory.getLogger(MasterkeyFileLoadingFinisher.class); - - private final Vault vault; - private final Optional storedPassword; - private final AtomicReference enteredPassword; - private final AtomicBoolean shouldSavePassword; - private final KeychainManager keychain; - - @Inject - MasterkeyFileLoadingFinisher(@KeyLoading Vault vault, @Named("savedPassword") Optional storedPassword, AtomicReference enteredPassword, @Named("savePassword") AtomicBoolean shouldSavePassword, KeychainManager keychain) { - this.vault = vault; - this.storedPassword = storedPassword; - this.enteredPassword = enteredPassword; - this.shouldSavePassword = shouldSavePassword; - this.keychain = keychain; - } - - public void cleanup(boolean successfullyUnlocked) { - if (successfullyUnlocked && shouldSavePassword.get()) { - savePasswordToSystemkeychain(); - } - wipePassword(storedPassword.orElse(null)); - wipePassword(enteredPassword.getAndSet(null)); - } - - private void savePasswordToSystemkeychain() { - if (keychain.isSupported()) { - try { - keychain.storePassphrase(vault.getId(), vault.getDisplayName(), CharBuffer.wrap(enteredPassword.get())); - } catch (KeychainAccessException e) { - LOG.error("Failed to store passphrase in system keychain.", e); - } - } - } - - private void wipePassword(char[] pw) { - if (pw != null) { - Arrays.fill(pw, ' '); - } - } -} diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java index 901eacfb9..9375b0cff 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java @@ -8,52 +8,17 @@ import dagger.multibindings.StringKey; import org.cryptomator.common.keychain.KeychainManager; import org.cryptomator.common.vaults.Vault; import org.cryptomator.integrations.keychain.KeychainAccessException; -import org.cryptomator.ui.common.FxController; -import org.cryptomator.ui.common.FxControllerKey; -import org.cryptomator.ui.common.FxmlFile; -import org.cryptomator.ui.common.FxmlLoaderFactory; -import org.cryptomator.ui.common.FxmlScene; -import org.cryptomator.ui.common.UserInteractionLock; import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent; import org.cryptomator.ui.keyloading.KeyLoading; import org.cryptomator.ui.keyloading.KeyLoadingScoped; import org.cryptomator.ui.keyloading.KeyLoadingStrategy; -import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Named; -import javafx.scene.Scene; -import java.nio.file.Path; import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; -@Module(subcomponents = {ForgetPasswordComponent.class}) -public abstract class MasterkeyFileLoadingModule { - - private static final Logger LOG = LoggerFactory.getLogger(MasterkeyFileLoadingModule.class); - - public enum PasswordEntry { - PASSWORD_ENTERED, - CANCELED - } - - public enum MasterkeyFileProvision { - MASTERKEYFILE_PROVIDED, - CANCELED - } - - @Provides - @KeyLoadingScoped - static UserInteractionLock providePasswordEntryLock() { - return new UserInteractionLock<>(null); - } - - @Provides - @KeyLoadingScoped - static UserInteractionLock provideMasterkeyFileProvisionLock() { - return new UserInteractionLock<>(null); - } +@Module(subcomponents = {ForgetPasswordComponent.class, PassphraseEntryComponent.class, ChooseMasterkeyFileComponent.class}) +public interface MasterkeyFileLoadingModule { @Provides @Named("savedPassword") @@ -65,55 +30,12 @@ public abstract class MasterkeyFileLoadingModule { try { return Optional.ofNullable(keychain.loadPassphrase(vault.getId())); } catch (KeychainAccessException e) { - LOG.error("Failed to load entry from system keychain.", e); + LoggerFactory.getLogger(MasterkeyFileLoadingModule.class).error("Failed to load entry from system keychain.", e); return Optional.empty(); } } } - @Provides - @KeyLoadingScoped - static AtomicReference provideUserProvidedMasterkeyPath() { - return new AtomicReference<>(); - } - - @Provides - @KeyLoadingScoped - static AtomicReference providePassword(@Named("savedPassword") Optional storedPassword) { - return new AtomicReference<>(storedPassword.orElse(null)); - } - - @Provides - @Named("savePassword") - @KeyLoadingScoped - static AtomicBoolean provideSavePasswordFlag(@Named("savedPassword") Optional storedPassword) { - return new AtomicBoolean(storedPassword.isPresent()); - } - - @Provides - @FxmlScene(FxmlFile.UNLOCK_ENTER_PASSWORD) - @KeyLoadingScoped - static Scene provideUnlockScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) { - return fxmlLoaders.createScene(FxmlFile.UNLOCK_ENTER_PASSWORD); - } - - @Provides - @FxmlScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE) - @KeyLoadingScoped - static Scene provideUnlockSelectMasterkeyFileScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) { - return fxmlLoaders.createScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE); - } - - @Binds - @IntoMap - @FxControllerKey(PassphraseEntryController.class) - abstract FxController bindUnlockController(PassphraseEntryController controller); - - @Binds - @IntoMap - @FxControllerKey(SelectMasterkeyFileController.class) - abstract FxController bindUnlockSelectMasterkeyFileController(SelectMasterkeyFileController controller); - @Binds @IntoMap @KeyLoadingScoped diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java index 1fa7dd986..b4964f9a0 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java @@ -1,32 +1,33 @@ package org.cryptomator.ui.keyloading.masterkeyfile; import com.google.common.base.Preconditions; -import dagger.Lazy; +import org.cryptomator.common.Passphrase; +import org.cryptomator.common.keychain.KeychainManager; import org.cryptomator.common.vaults.Vault; import org.cryptomator.cryptofs.common.BackupHelper; import org.cryptomator.cryptolib.api.InvalidPassphraseException; import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; import org.cryptomator.cryptolib.common.MasterkeyFileAccess; +import org.cryptomator.integrations.keychain.KeychainAccessException; import org.cryptomator.ui.common.Animations; -import org.cryptomator.ui.common.FxmlFile; -import org.cryptomator.ui.common.FxmlScene; -import org.cryptomator.ui.common.UserInteractionLock; import org.cryptomator.ui.keyloading.KeyLoading; import org.cryptomator.ui.keyloading.KeyLoadingStrategy; import org.cryptomator.ui.unlock.UnlockCancelledException; import javax.inject.Inject; +import javax.inject.Named; import javafx.application.Platform; -import javafx.scene.Scene; import javafx.stage.Stage; import javafx.stage.Window; import java.io.IOException; import java.net.URI; -import java.nio.CharBuffer; import java.nio.file.Files; import java.nio.file.Path; -import java.util.concurrent.atomic.AtomicReference; +import java.util.Optional; +import java.util.ResourceBundle; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; @KeyLoading public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { @@ -36,28 +37,26 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { private final Vault vault; private final MasterkeyFileAccess masterkeyFileAccess; private final Stage window; - private final Lazy passphraseEntryScene; - private final Lazy selectMasterkeyFileScene; - private final UserInteractionLock passwordEntryLock; - private final UserInteractionLock masterkeyFileProvisionLock; - private final AtomicReference password; - private final AtomicReference filePath; - private final MasterkeyFileLoadingFinisher finisher; + private final PassphraseEntryComponent.Builder passphraseEntry; + private final ChooseMasterkeyFileComponent.Builder masterkeyFileChoice; + private final KeychainManager keychain; + private final ResourceBundle resourceBundle; - private boolean wrongPassword; + private Passphrase passphrase; + private boolean savePassphrase; + private boolean wrongPassphrase; @Inject - public MasterkeyFileLoadingStrategy(@KeyLoading Vault vault, MasterkeyFileAccess masterkeyFileAccess, @KeyLoading Stage window, @FxmlScene(FxmlFile.UNLOCK_ENTER_PASSWORD) Lazy passphraseEntryScene, @FxmlScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE) Lazy selectMasterkeyFileScene, UserInteractionLock passwordEntryLock, UserInteractionLock masterkeyFileProvisionLock, AtomicReference password, AtomicReference filePath, MasterkeyFileLoadingFinisher finisher) { + public MasterkeyFileLoadingStrategy(@KeyLoading Vault vault, MasterkeyFileAccess masterkeyFileAccess, @KeyLoading Stage window, @Named("savedPassword") Optional savedPassphrase, PassphraseEntryComponent.Builder passphraseEntry, ChooseMasterkeyFileComponent.Builder masterkeyFileChoice, KeychainManager keychain, ResourceBundle resourceBundle) { this.vault = vault; this.masterkeyFileAccess = masterkeyFileAccess; this.window = window; - this.passphraseEntryScene = passphraseEntryScene; - this.selectMasterkeyFileScene = selectMasterkeyFileScene; - this.passwordEntryLock = passwordEntryLock; - this.masterkeyFileProvisionLock = masterkeyFileProvisionLock; - this.password = password; - this.filePath = filePath; - this.finisher = finisher; + this.passphraseEntry = passphraseEntry; + this.masterkeyFileChoice = masterkeyFileChoice; + this.keychain = keychain; + this.resourceBundle = resourceBundle; + this.passphrase = savedPassphrase.map(Passphrase::new).orElse(null); + this.savePassphrase = savedPassphrase.isPresent(); } @Override @@ -66,9 +65,11 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { try { Path filePath = vault.getPath().resolve(keyId.getSchemeSpecificPart()); if (!Files.exists(filePath)) { - filePath = getAlternateMasterkeyFilePath(); + filePath = askUserForMasterkeyFilePath(); + } + if (passphrase == null) { + askForPassphrase(); } - CharSequence passphrase = getPassphrase(); var masterkey = masterkeyFileAccess.load(filePath, passphrase); //backup if (filePath.startsWith(vault.getPath())) { @@ -90,8 +91,9 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { @Override public boolean recoverFromException(MasterkeyLoadingFailedException exception) { if (exception instanceof InvalidPassphraseException) { - this.wrongPassword = true; - password.set(null); + this.wrongPassphrase = true; + passphrase.destroy(); + this.passphrase = null; return true; // reattempting key load } else { return false; // nothing we can do @@ -100,23 +102,29 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { @Override public void cleanup(boolean unlockedSuccessfully) { - finisher.cleanup(unlockedSuccessfully); - } - - private Path getAlternateMasterkeyFilePath() throws UnlockCancelledException, InterruptedException { - if (filePath.get() == null) { - return switch (askUserForMasterkeyFilePath()) { - case MASTERKEYFILE_PROVIDED -> filePath.get(); - case CANCELED -> throw new UnlockCancelledException("Choosing masterkey file cancelled."); - }; - } else { - return filePath.get(); + if (unlockedSuccessfully && savePassphrase) { + savePasswordToSystemkeychain(passphrase); + } + if (passphrase != null) { + passphrase.destroy(); } } - private MasterkeyFileLoadingModule.MasterkeyFileProvision askUserForMasterkeyFilePath() throws InterruptedException { + private void savePasswordToSystemkeychain(Passphrase passphrase) { + if (keychain.isSupported()) { + try { + keychain.storePassphrase(vault.getId(), vault.getDisplayName(), passphrase); + } catch (KeychainAccessException e) { + LOG.error("Failed to store passphrase in system keychain.", e); + } + } + } + + private Path askUserForMasterkeyFilePath() throws InterruptedException { + var comp = masterkeyFileChoice.build(); Platform.runLater(() -> { - window.setScene(selectMasterkeyFileScene.get()); + window.setScene(comp.chooseMasterkeyScene()); + window.setTitle(resourceBundle.getString("unlock.chooseMasterkey.title").formatted(vault.getDisplayName())); window.show(); Window owner = window.getOwner(); if (owner != null) { @@ -126,24 +134,20 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { window.centerOnScreen(); } }); - return masterkeyFileProvisionLock.awaitInteraction(); - } - - private CharSequence getPassphrase() throws UnlockCancelledException, InterruptedException { - if (password.get() == null) { - return switch (askForPassphrase()) { - case PASSWORD_ENTERED -> CharBuffer.wrap(password.get()); - case CANCELED -> throw new UnlockCancelledException("Password entry cancelled."); - }; - } else { - // e.g. pre-filled from keychain or previous unlock attempt - return CharBuffer.wrap(password.get()); + try { + return comp.result().get(); + } catch (CancellationException e) { + throw new UnlockCancelledException("Choosing masterkey file cancelled."); + } catch (ExecutionException e) { + throw new MasterkeyLoadingFailedException("Failed to select masterkey file.", e); } } - private MasterkeyFileLoadingModule.PasswordEntry askForPassphrase() throws InterruptedException { + private void askForPassphrase() throws InterruptedException { + var comp = passphraseEntry.savedPassword(passphrase).build(); Platform.runLater(() -> { - window.setScene(passphraseEntryScene.get()); + window.setScene(comp.passphraseEntryScene()); + window.setTitle(resourceBundle.getString("unlock.title").formatted(vault.getDisplayName())); window.show(); Window owner = window.getOwner(); if (owner != null) { @@ -152,11 +156,19 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { } else { window.centerOnScreen(); } - if (wrongPassword) { + if (wrongPassphrase) { Animations.createShakeWindowAnimation(window).play(); } }); - return passwordEntryLock.awaitInteraction(); + try { + var result = comp.result().get(); + this.passphrase = result.passphrase(); + this.savePassphrase = result.savePassphrase(); + } catch (CancellationException e) { + throw new UnlockCancelledException("Password entry cancelled."); + } catch (ExecutionException e) { + throw new MasterkeyLoadingFailedException("Failed to ask for password.", e); + } } } diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryComponent.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryComponent.java new file mode 100644 index 000000000..5e072efd0 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryComponent.java @@ -0,0 +1,31 @@ +package org.cryptomator.ui.keyloading.masterkeyfile; + +import dagger.BindsInstance; +import dagger.Subcomponent; +import org.cryptomator.common.Nullable; +import org.cryptomator.common.Passphrase; + +import javax.inject.Named; +import javafx.scene.Scene; +import java.util.concurrent.CompletableFuture; + +@PassphraseEntryScoped +@Subcomponent(modules = {PassphraseEntryModule.class}) +public interface PassphraseEntryComponent { + + @PassphraseEntryScoped + Scene passphraseEntryScene(); + + @PassphraseEntryScoped + CompletableFuture result(); + + @Subcomponent.Builder + interface Builder { + + @BindsInstance + PassphraseEntryComponent.Builder savedPassword(@Nullable @Named("savedPassword") Passphrase savedPassword); + + PassphraseEntryComponent build(); + } + +} diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryController.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryController.java index f6ce79e51..35b1b1903 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryController.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryController.java @@ -1,16 +1,14 @@ package org.cryptomator.ui.keyloading.masterkeyfile; +import org.cryptomator.common.Nullable; import org.cryptomator.common.keychain.KeychainManager; import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.common.FxController; -import org.cryptomator.ui.common.UserInteractionLock; +import org.cryptomator.common.Passphrase; import org.cryptomator.ui.common.WeakBindings; -import org.cryptomator.ui.controls.FontAwesome5IconView; import org.cryptomator.ui.controls.NiceSecurePasswordField; import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent; import org.cryptomator.ui.keyloading.KeyLoading; -import org.cryptomator.ui.keyloading.KeyLoadingScoped; -import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingModule.PasswordEntry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,8 +19,8 @@ import javafx.animation.Interpolator; import javafx.animation.KeyFrame; import javafx.animation.KeyValue; import javafx.animation.Timeline; +import javafx.application.Platform; import javafx.beans.binding.Bindings; -import javafx.beans.binding.BooleanBinding; import javafx.beans.binding.ObjectBinding; import javafx.beans.binding.StringBinding; import javafx.beans.property.BooleanProperty; @@ -37,33 +35,27 @@ import javafx.scene.transform.Translate; import javafx.stage.Stage; import javafx.stage.WindowEvent; import javafx.util.Duration; -import java.util.Arrays; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.CompletableFuture; -@KeyLoadingScoped +@PassphraseEntryScoped public class PassphraseEntryController implements FxController { private static final Logger LOG = LoggerFactory.getLogger(PassphraseEntryController.class); private final Stage window; private final Vault vault; - private final AtomicReference password; - private final AtomicBoolean savePassword; - private final Optional savedPassword; - private final UserInteractionLock passwordEntryLock; + private final CompletableFuture result; + private final Passphrase savedPassword; private final ForgetPasswordComponent.Builder forgetPassword; private final KeychainManager keychain; - private final ObjectBinding unlockButtonContentDisplay; - private final BooleanBinding userInteractionDisabled; - private final BooleanProperty unlockButtonDisabled; private final StringBinding vaultName; + private final BooleanProperty unlockInProgress = new SimpleBooleanProperty(); + private final ObjectBinding unlockButtonContentDisplay = Bindings.createObjectBinding(this::getUnlockButtonContentDisplay, unlockInProgress); + private final BooleanProperty unlockButtonDisabled = new SimpleBooleanProperty(); /* FXML */ public NiceSecurePasswordField passwordField; public CheckBox savePasswordCheckbox; - public FontAwesome5IconView unlockInProgressView; public ImageView face; public ImageView leftArm; public ImageView rightArm; @@ -72,29 +64,25 @@ public class PassphraseEntryController implements FxController { public Animation unlockAnimation; @Inject - public PassphraseEntryController(@KeyLoading Stage window, @KeyLoading Vault vault, AtomicReference password, @Named("savePassword") AtomicBoolean savePassword, @Named("savedPassword") Optional savedPassword, UserInteractionLock passwordEntryLock, ForgetPasswordComponent.Builder forgetPassword, KeychainManager keychain) { + public PassphraseEntryController(@KeyLoading Stage window, @KeyLoading Vault vault, CompletableFuture result, @Nullable @Named("savedPassword") Passphrase savedPassword, ForgetPasswordComponent.Builder forgetPassword, KeychainManager keychain) { this.window = window; this.vault = vault; - this.password = password; - this.savePassword = savePassword; + this.result = result; this.savedPassword = savedPassword; - this.passwordEntryLock = passwordEntryLock; this.forgetPassword = forgetPassword; this.keychain = keychain; - this.unlockButtonContentDisplay = Bindings.createObjectBinding(this::getUnlockButtonContentDisplay, passwordEntryLock.awaitingInteraction()); - this.userInteractionDisabled = passwordEntryLock.awaitingInteraction().not(); - this.unlockButtonDisabled = new SimpleBooleanProperty(); this.vaultName = WeakBindings.bindString(vault.displayNameProperty()); - this.window.setOnHiding(this::windowClosed); + window.setOnHiding(this::windowClosed); + result.whenCompleteAsync((r, t) -> unlockInProgress.set(false), Platform::runLater); } @FXML public void initialize() { - savePasswordCheckbox.setSelected(savedPassword.isPresent()); - if (password.get() != null) { - passwordField.setPassword(password.get()); + if (savedPassword != null) { + savePasswordCheckbox.setSelected(true); + passwordField.setPassword(savedPassword); } - unlockButtonDisabled.bind(userInteractionDisabled.or(passwordField.textProperty().isEmpty())); + unlockButtonDisabled.bind(unlockInProgress.or(passwordField.textProperty().isEmpty())); var leftArmTranslation = new Translate(24, 0); var leftArmRotation = new Rotate(60, 16, 30, 0); @@ -132,7 +120,7 @@ public class PassphraseEntryController implements FxController { new KeyFrame(Duration.millis(1000), faceVisible) // ); - passwordEntryLock.awaitingInteraction().addListener(observable -> stopUnlockAnimation()); + result.whenCompleteAsync((r, t) -> stopUnlockAnimation()); } @FXML @@ -141,26 +129,17 @@ public class PassphraseEntryController implements FxController { } private void windowClosed(WindowEvent windowEvent) { - // if not already interacted, mark this workflow as cancelled: - if (passwordEntryLock.awaitingInteraction().get()) { - LOG.debug("Unlock canceled by user."); - passwordEntryLock.interacted(PasswordEntry.CANCELED); - } + LOG.debug("Unlock canceled by user."); + result.cancel(true); } @FXML public void unlock() { LOG.trace("UnlockController.unlock()"); + unlockInProgress.set(true); CharSequence pwFieldContents = passwordField.getCharacters(); - char[] newPw = new char[pwFieldContents.length()]; - for (int i = 0; i < pwFieldContents.length(); i++) { - newPw[i] = pwFieldContents.charAt(i); - } - char[] oldPw = password.getAndSet(newPw); - if (oldPw != null) { - Arrays.fill(oldPw, ' '); - } - passwordEntryLock.interacted(PasswordEntry.PASSWORD_ENTERED); + Passphrase pw = Passphrase.copyOf(pwFieldContents); + result.complete(new PassphraseEntryResult(pw, savePasswordCheckbox.isSelected())); startUnlockAnimation(); } @@ -184,8 +163,7 @@ public class PassphraseEntryController implements FxController { @FXML private void didClickSavePasswordCheckbox() { - savePassword.set(savePasswordCheckbox.isSelected()); - if (!savePasswordCheckbox.isSelected() && savedPassword.isPresent()) { + if (!savePasswordCheckbox.isSelected() && savedPassword != null) { forgetPassword.vault(vault).owner(window).build().showForgetPassword().thenAccept(forgotten -> savePasswordCheckbox.setSelected(!forgotten)); } } @@ -205,15 +183,15 @@ public class PassphraseEntryController implements FxController { } public ContentDisplay getUnlockButtonContentDisplay() { - return passwordEntryLock.awaitingInteraction().get() ? ContentDisplay.TEXT_ONLY : ContentDisplay.LEFT; + return unlockInProgress.get() ? ContentDisplay.LEFT : ContentDisplay.TEXT_ONLY; } - public BooleanBinding userInteractionDisabledProperty() { - return userInteractionDisabled; + public ReadOnlyBooleanProperty userInteractionDisabledProperty() { + return unlockInProgress; } public boolean isUserInteractionDisabled() { - return userInteractionDisabled.get(); + return unlockInProgress.get(); } public ReadOnlyBooleanProperty unlockButtonDisabledProperty() { @@ -227,4 +205,6 @@ public class PassphraseEntryController implements FxController { public boolean isKeychainAccessAvailable() { return keychain.isSupported(); } + + } diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryModule.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryModule.java new file mode 100644 index 000000000..2c65d440b --- /dev/null +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryModule.java @@ -0,0 +1,28 @@ +package org.cryptomator.ui.keyloading.masterkeyfile; + +import dagger.Module; +import dagger.Provides; +import org.cryptomator.ui.common.DefaultSceneFactory; +import org.cryptomator.ui.common.FxmlFile; +import org.cryptomator.ui.common.FxmlLoaderFactory; + +import javafx.scene.Scene; +import java.util.ResourceBundle; +import java.util.concurrent.CompletableFuture; + +@Module +interface PassphraseEntryModule { + + @Provides + @PassphraseEntryScoped + static CompletableFuture provideResult() { + return new CompletableFuture<>(); + } + + @Provides + @PassphraseEntryScoped + static Scene provideUnlockScene(PassphraseEntryController controller, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) { + return FxmlLoaderFactory.forController(controller, sceneFactory, resourceBundle).createScene(FxmlFile.UNLOCK_ENTER_PASSWORD); + } + +} diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryResult.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryResult.java new file mode 100644 index 000000000..19057acca --- /dev/null +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryResult.java @@ -0,0 +1,8 @@ +package org.cryptomator.ui.keyloading.masterkeyfile; + +import org.cryptomator.common.Passphrase; + +// TODO: change to package-private, as soon as this works for Dagger -.- +public record PassphraseEntryResult(Passphrase passphrase, boolean savePassphrase) { + +} diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryScoped.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryScoped.java new file mode 100644 index 000000000..a077bcf81 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryScoped.java @@ -0,0 +1,13 @@ +package org.cryptomator.ui.keyloading.masterkeyfile; + +import javax.inject.Scope; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Scope +@Documented +@Retention(RetentionPolicy.RUNTIME) +@interface PassphraseEntryScoped { + +} diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/SelectMasterkeyFileController.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/SelectMasterkeyFileController.java deleted file mode 100644 index 39be2b36e..000000000 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/SelectMasterkeyFileController.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.cryptomator.ui.keyloading.masterkeyfile; - -import org.cryptomator.ui.common.FxController; -import org.cryptomator.ui.common.UserInteractionLock; -import org.cryptomator.ui.keyloading.KeyLoading; -import org.cryptomator.ui.keyloading.KeyLoadingScoped; -import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingModule.MasterkeyFileProvision; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import javafx.fxml.FXML; -import javafx.stage.FileChooser; -import javafx.stage.Stage; -import javafx.stage.WindowEvent; -import java.io.File; -import java.nio.file.Path; -import java.util.ResourceBundle; -import java.util.concurrent.atomic.AtomicReference; - -@KeyLoadingScoped -public class SelectMasterkeyFileController implements FxController { - - private static final Logger LOG = LoggerFactory.getLogger(SelectMasterkeyFileController.class); - - private final Stage window; - private final AtomicReference masterkeyPath; - private final UserInteractionLock masterkeyFileProvisionLock; - private final ResourceBundle resourceBundle; - - @Inject - public SelectMasterkeyFileController(@KeyLoading Stage window, AtomicReference masterkeyPath, UserInteractionLock masterkeyFileProvisionLock, ResourceBundle resourceBundle) { - this.window = window; - this.masterkeyPath = masterkeyPath; - this.masterkeyFileProvisionLock = masterkeyFileProvisionLock; - this.resourceBundle = resourceBundle; - this.window.setOnHiding(this::windowClosed); - } - - @FXML - public void cancel() { - window.close(); - } - - private void windowClosed(WindowEvent windowEvent) { - // if not already interacted, mark this workflow as cancelled: - if (masterkeyFileProvisionLock.awaitingInteraction().get()) { - LOG.debug("Unlock canceled by user."); - masterkeyFileProvisionLock.interacted(MasterkeyFileProvision.CANCELED); - } - } - - @FXML - public void proceed() { - LOG.trace("proceed()"); - FileChooser fileChooser = new FileChooser(); - fileChooser.setTitle(resourceBundle.getString("unlock.chooseMasterkey.filePickerTitle")); - fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Cryptomator Masterkey", "*.cryptomator")); - File masterkeyFile = fileChooser.showOpenDialog(window); - if (masterkeyFile != null) { - LOG.debug("Chose masterkey file: {}", masterkeyFile); - masterkeyPath.set(masterkeyFile.toPath()); - masterkeyFileProvisionLock.interacted(MasterkeyFileProvision.MASTERKEYFILE_PROVIDED); - } - } - -} diff --git a/src/main/java/org/cryptomator/ui/lock/LockForcedController.java b/src/main/java/org/cryptomator/ui/lock/LockForcedController.java index c3a452acc..15cf119be 100644 --- a/src/main/java/org/cryptomator/ui/lock/LockForcedController.java +++ b/src/main/java/org/cryptomator/ui/lock/LockForcedController.java @@ -2,56 +2,48 @@ package org.cryptomator.ui.lock; import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.common.FxController; -import org.cryptomator.ui.common.UserInteractionLock; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import javax.inject.Inject; import javafx.fxml.FXML; import javafx.stage.Stage; import javafx.stage.WindowEvent; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; @LockScoped public class LockForcedController implements FxController { - private static final Logger LOG = LoggerFactory.getLogger(LockForcedController.class); - private final Stage window; private final Vault vault; - private final UserInteractionLock forceLockDecisionLock; + private final AtomicReference> forceRetryDecision; @Inject - public LockForcedController(@LockWindow Stage window, @LockWindow Vault vault, UserInteractionLock forceLockDecisionLock) { + public LockForcedController(@LockWindow Stage window, @LockWindow Vault vault, AtomicReference> forceRetryDecision) { this.window = window; this.vault = vault; - this.forceLockDecisionLock = forceLockDecisionLock; + this.forceRetryDecision = forceRetryDecision; this.window.setOnHiding(this::windowClosed); } @FXML public void cancel() { - forceLockDecisionLock.interacted(LockModule.ForceLockDecision.CANCEL); window.close(); } @FXML public void retry() { - forceLockDecisionLock.interacted(LockModule.ForceLockDecision.RETRY); + forceRetryDecision.get().complete(false); window.close(); } @FXML public void force() { - forceLockDecisionLock.interacted(LockModule.ForceLockDecision.FORCE); + forceRetryDecision.get().complete(true); window.close(); } private void windowClosed(WindowEvent windowEvent) { - // if not already interacted, set the decision to CANCEL - if (forceLockDecisionLock.awaitingInteraction().get()) { - LOG.debug("Lock canceled in force-lock-phase by user."); - forceLockDecisionLock.interacted(LockModule.ForceLockDecision.CANCEL); - } + forceRetryDecision.get().cancel(true); } // ----- Getter & Setter ----- diff --git a/src/main/java/org/cryptomator/ui/lock/LockModule.java b/src/main/java/org/cryptomator/ui/lock/LockModule.java index d1eb5f189..ddee13dff 100644 --- a/src/main/java/org/cryptomator/ui/lock/LockModule.java +++ b/src/main/java/org/cryptomator/ui/lock/LockModule.java @@ -6,13 +6,12 @@ import dagger.Provides; import dagger.multibindings.IntoMap; import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.common.DefaultSceneFactory; -import org.cryptomator.ui.common.FxmlLoaderFactory; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxControllerKey; import org.cryptomator.ui.common.FxmlFile; +import org.cryptomator.ui.common.FxmlLoaderFactory; import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.StageFactory; -import org.cryptomator.ui.common.UserInteractionLock; import javax.inject.Named; import javax.inject.Provider; @@ -22,20 +21,16 @@ import javafx.stage.Stage; import java.util.Map; import java.util.Optional; import java.util.ResourceBundle; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; @Module abstract class LockModule { - enum ForceLockDecision { - CANCEL, - RETRY, - FORCE; - } - @Provides @LockScoped - static UserInteractionLock provideForceLockDecisionLock() { - return new UserInteractionLock<>(null); + static AtomicReference> provideForceRetryDecisionRef() { + return new AtomicReference<>(); } @Provides diff --git a/src/main/java/org/cryptomator/ui/lock/LockWorkflow.java b/src/main/java/org/cryptomator/ui/lock/LockWorkflow.java index 00b25c507..1e05ceb73 100644 --- a/src/main/java/org/cryptomator/ui/lock/LockWorkflow.java +++ b/src/main/java/org/cryptomator/ui/lock/LockWorkflow.java @@ -8,7 +8,6 @@ import org.cryptomator.common.vaults.Volume; import org.cryptomator.ui.common.ErrorComponent; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; -import org.cryptomator.ui.common.UserInteractionLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,6 +17,10 @@ import javafx.concurrent.Task; import javafx.scene.Scene; import javafx.stage.Stage; import javafx.stage.Window; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicReference; /** * The sequence of actions performed and checked during lock of a vault. @@ -34,43 +37,48 @@ public class LockWorkflow extends Task { private final Stage lockWindow; private final Vault vault; - private final UserInteractionLock forceLockDecisionLock; + private final AtomicReference> forceRetryDecision; private final Lazy lockForcedScene; private final Lazy lockFailedScene; private final ErrorComponent.Builder errorComponent; @Inject - public LockWorkflow(@LockWindow Stage lockWindow, @LockWindow Vault vault, UserInteractionLock forceLockDecisionLock, @FxmlScene(FxmlFile.LOCK_FORCED) Lazy lockForcedScene, @FxmlScene(FxmlFile.LOCK_FAILED) Lazy lockFailedScene, ErrorComponent.Builder errorComponent) { + public LockWorkflow(@LockWindow Stage lockWindow, @LockWindow Vault vault, AtomicReference> forceRetryDecision, @FxmlScene(FxmlFile.LOCK_FORCED) Lazy lockForcedScene, @FxmlScene(FxmlFile.LOCK_FAILED) Lazy lockFailedScene, ErrorComponent.Builder errorComponent) { this.lockWindow = lockWindow; this.vault = vault; - this.forceLockDecisionLock = forceLockDecisionLock; + this.forceRetryDecision = forceRetryDecision; this.lockForcedScene = lockForcedScene; this.lockFailedScene = lockFailedScene; this.errorComponent = errorComponent; } @Override - protected Void call() throws Volume.VolumeException, InterruptedException, LockNotCompletedException { + protected Void call() throws Volume.VolumeException, InterruptedException, LockNotCompletedException, ExecutionException { lock(false); return null; } - private void lock(boolean forced) throws InterruptedException { + private void lock(boolean forced) throws InterruptedException, ExecutionException { try { vault.lock(forced); } catch (Volume.VolumeException | LockNotCompletedException e) { LOG.info("Locking {} failed (forced: {}).", vault.getDisplayName(), forced, e); - var decision = askUserForAction(); - switch (decision) { - case RETRY -> lock(false); - case FORCE -> lock(true); - case CANCEL -> cancel(false); - } + retryOrCancel(); } } - private LockModule.ForceLockDecision askUserForAction() throws InterruptedException { - forceLockDecisionLock.reset(null); + private void retryOrCancel() throws ExecutionException, InterruptedException { + try { + boolean forced = askWhetherToUseTheForce().get(); + lock(forced); + } catch (CancellationException e) { + cancel(false); + } + } + + private CompletableFuture askWhetherToUseTheForce() { + var decision = new CompletableFuture(); + forceRetryDecision.set(decision); // show forcedLock dialogue ... Platform.runLater(() -> { lockWindow.setScene(lockForcedScene.get()); @@ -83,8 +91,7 @@ public class LockWorkflow extends Task { lockWindow.centerOnScreen(); } }); - // ... and wait for answer - return forceLockDecisionLock.awaitInteraction(); + return decision; } @Override diff --git a/src/main/java/org/cryptomator/ui/stats/VaultStatisticsController.java b/src/main/java/org/cryptomator/ui/stats/VaultStatisticsController.java index a3c430946..56729f1fe 100644 --- a/src/main/java/org/cryptomator/ui/stats/VaultStatisticsController.java +++ b/src/main/java/org/cryptomator/ui/stats/VaultStatisticsController.java @@ -127,10 +127,10 @@ public class VaultStatisticsController implements FxController { encryptedBytesWrite.getData().add(new Data<>(currentStep, encBytes)); // adjust ranges: - readChartXAxis.setLowerBound(currentStep - IO_SAMPLING_STEPS); + readChartXAxis.setLowerBound(currentStep - IO_SAMPLING_STEPS * 1.0); readChartXAxis.setUpperBound(currentStep); readChartYAxis.setUpperBound(allTimeMax); - writeChartXAxis.setLowerBound(currentStep - IO_SAMPLING_STEPS); + writeChartXAxis.setLowerBound(currentStep - IO_SAMPLING_STEPS * 1.0); writeChartXAxis.setUpperBound(currentStep); writeChartYAxis.setUpperBound(allTimeMax); } diff --git a/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java b/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java index 4ce3808c4..dd08d5dc0 100644 --- a/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java +++ b/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java @@ -91,6 +91,8 @@ class TrayMenuController { unlockItem.addActionListener(createActionListenerForVault(vault, this::unlockVault)); submenu.add(unlockItem); } else if (vault.isUnlocked()) { + submenu.setLabel("* ".concat(submenu.getLabel())); + MenuItem lockItem = new MenuItem(resourceBundle.getString("traymenu.vault.lock")); lockItem.addActionListener(createActionListenerForVault(vault, this::lockVault)); submenu.add(lockItem); diff --git a/src/main/java/org/cryptomator/ui/unlock/UnlockInvalidMountPointController.java b/src/main/java/org/cryptomator/ui/unlock/UnlockInvalidMountPointController.java index fd85db988..234bac65b 100644 --- a/src/main/java/org/cryptomator/ui/unlock/UnlockInvalidMountPointController.java +++ b/src/main/java/org/cryptomator/ui/unlock/UnlockInvalidMountPointController.java @@ -1,5 +1,6 @@ package org.cryptomator.ui.unlock; +import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.vaults.MountPointRequirement; import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.common.FxController; @@ -32,12 +33,24 @@ public class UnlockInvalidMountPointController implements FxController { return vault.getVaultSettings().getCustomMountPath().orElse("AUTO"); } - public boolean getMustExist() { - MountPointRequirement requirement = vault.getVolume().orElseThrow(() -> new IllegalStateException("Invalid Mountpoint without a Volume?!")).getMountPointRequirement(); - assert requirement != MountPointRequirement.NONE; //An invalid MountPoint with no required MountPoint doesn't seem sensible - assert requirement != MountPointRequirement.PARENT_OPT_MOUNT_POINT; //Not implemented anywhere (yet) - - return requirement == MountPointRequirement.EMPTY_MOUNT_POINT; + public boolean getNotExisting() { + return getMountPointRequirement() == MountPointRequirement.EMPTY_MOUNT_POINT; } -} + public boolean getExisting() { + return getMountPointRequirement() == MountPointRequirement.PARENT_NO_MOUNT_POINT; + } + + public boolean getDriveLetterOccupied() { + return getMountPointRequirement() == MountPointRequirement.UNUSED_ROOT_DIR; + } + + private MountPointRequirement getMountPointRequirement() { + var requirement = vault.getVolume().orElseThrow(() -> new IllegalStateException("Invalid Mountpoint without a Volume?!")).getMountPointRequirement(); + assert requirement != MountPointRequirement.NONE; //An invalid MountPoint with no required MountPoint doesn't seem sensible + assert requirement != MountPointRequirement.PARENT_OPT_MOUNT_POINT; //Not implemented anywhere (yet) + assert requirement != MountPointRequirement.UNUSED_ROOT_DIR || SystemUtils.IS_OS_WINDOWS; //Not implemented anywhere, but on Windows + + return requirement; + } +} \ No newline at end of file diff --git a/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java b/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java index 073258d80..6964c3c86 100644 --- a/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java +++ b/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java @@ -2,6 +2,7 @@ package org.cryptomator.ui.unlock; import com.google.common.base.Throwables; import dagger.Lazy; +import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.mountpoint.InvalidMountPointException; import org.cryptomator.common.vaults.MountPointRequirement; import org.cryptomator.common.vaults.Vault; @@ -79,9 +80,10 @@ public class UnlockWorkflow extends Task { } private void handleInvalidMountPoint(InvalidMountPointException impExc) { - MountPointRequirement requirement = vault.getVolume().orElseThrow(() -> new IllegalStateException("Invalid Mountpoint without a Volume?!", impExc)).getMountPointRequirement(); + var requirement = vault.getVolume().orElseThrow(() -> new IllegalStateException("Invalid Mountpoint without a Volume?!", impExc)).getMountPointRequirement(); assert requirement != MountPointRequirement.NONE; //An invalid MountPoint with no required MountPoint doesn't seem sensible assert requirement != MountPointRequirement.PARENT_OPT_MOUNT_POINT; //Not implemented anywhere (yet) + assert requirement != MountPointRequirement.UNUSED_ROOT_DIR || SystemUtils.IS_OS_WINDOWS; //Not implemented anywhere, but on Windows Throwable cause = impExc.getCause(); // TODO: apply https://openjdk.java.net/jeps/8213076 in future JDK versions @@ -93,7 +95,11 @@ public class UnlockWorkflow extends Task { } showInvalidMountPointScene(); } else if (cause instanceof FileAlreadyExistsException) { - LOG.error("Unlock failed. Mountpoint already exists: {}", cause.getMessage()); + if (requirement == MountPointRequirement.UNUSED_ROOT_DIR) { + LOG.error("Unlock failed. Drive Letter already in use: {}", cause.getMessage()); + } else { + LOG.error("Unlock failed. Mountpoint already exists: {}", cause.getMessage()); + } showInvalidMountPointScene(); } else if (cause instanceof DirectoryNotEmptyException) { LOG.error("Unlock failed. Mountpoint not an empty directory: {}", cause.getMessage()); diff --git a/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java b/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java index 8739be791..b7b01fdd6 100644 --- a/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java +++ b/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java @@ -11,9 +11,6 @@ import org.cryptomator.ui.common.FxController; import javax.inject.Inject; import javafx.beans.binding.Bindings; -import javafx.beans.binding.BooleanBinding; -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.StringProperty; import javafx.beans.value.ObservableValue; import javafx.fxml.FXML; @@ -27,23 +24,21 @@ import javafx.stage.DirectoryChooser; import javafx.stage.Stage; import javafx.util.StringConverter; import java.io.File; +import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.util.ResourceBundle; import java.util.Set; -/** - * TODO: if WebDav is selected on a windows system, custom mount directory is _not_ supported. This is currently not indicated/shown/etc in the ui - */ @VaultOptionsScoped public class MountOptionsController implements FxController { private final Stage window; private final Vault vault; - private final BooleanProperty osIsWindows = new SimpleBooleanProperty(SystemUtils.IS_OS_WINDOWS); - private final BooleanBinding webDavAndWindows; + private final VolumeImpl usedVolumeImpl; private final WindowsDriveLetters windowsDriveLetters; private final ResourceBundle resourceBundle; + public CheckBox readOnlyCheckbox; public CheckBox customMountFlagsCheckbox; public TextField mountFlags; @@ -53,20 +48,13 @@ public class MountOptionsController implements FxController { public RadioButton mountPointCustomDir; public ChoiceBox driveLetterSelection; - //FUSE + Windows -> Disable some (experimental) features for the user because they are unstable - //Use argument Dfuse.experimental="true" to override - private final BooleanBinding restrictToStableFuseOnWindows; - @Inject MountOptionsController(@VaultOptionsWindow Stage window, @VaultOptionsWindow Vault vault, Settings settings, WindowsDriveLetters windowsDriveLetters, ResourceBundle resourceBundle, Environment environment) { this.window = window; this.vault = vault; - this.webDavAndWindows = settings.preferredVolumeImpl().isEqualTo(VolumeImpl.WEBDAV).and(osIsWindows); + this.usedVolumeImpl = settings.preferredVolumeImpl().get(); this.windowsDriveLetters = windowsDriveLetters; this.resourceBundle = resourceBundle; - - BooleanBinding isFuseOnWindows = settings.preferredVolumeImpl().isEqualTo(VolumeImpl.FUSE).and(osIsWindows); - this.restrictToStableFuseOnWindows = isFuseOnWindows.and(new SimpleBooleanProperty(!environment.useExperimentalFuse())); //Is FUSE on Win and is NOT experimental fuse enabled } @FXML @@ -74,10 +62,11 @@ public class MountOptionsController implements FxController { // readonly: readOnlyCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().usesReadOnlyMode()); - if (getRestrictToStableFuseOnWindows()) { + //TODO: support this feature on Windows + if (usedVolumeImpl == VolumeImpl.FUSE && isOsWindows()) { readOnlyCheckbox.setSelected(false); // to prevent invalid states + readOnlyCheckbox.setDisable(true); } - readOnlyCheckbox.disableProperty().bind(customMountFlagsCheckbox.selectedProperty().or(restrictToStableFuseOnWindows)); // custom mount flags: mountFlags.disableProperty().bind(customMountFlagsCheckbox.selectedProperty().not()); @@ -95,9 +84,7 @@ public class MountOptionsController implements FxController { driveLetterSelection.setConverter(new WinDriveLetterLabelConverter(windowsDriveLetters, resourceBundle)); driveLetterSelection.setValue(vault.getVaultSettings().winDriveLetter().get()); - if (vault.getVaultSettings().useCustomMountPath().get() - && vault.getVaultSettings().getCustomMountPath().isPresent() - && !getRestrictToStableFuseOnWindows() /* to prevent invalid states */) { + if (vault.getVaultSettings().useCustomMountPath().get() && vault.getVaultSettings().getCustomMountPath().isPresent()) { mountPoint.selectToggle(mountPointCustomDir); } else if (!Strings.isNullOrEmpty(vault.getVaultSettings().winDriveLetter().get())) { mountPoint.selectToggle(mountPointWinDriveLetter); @@ -136,8 +123,11 @@ public class MountOptionsController implements FxController { DirectoryChooser directoryChooser = new DirectoryChooser(); directoryChooser.setTitle(resourceBundle.getString("vaultOptions.mount.mountPoint.directoryPickerTitle")); try { - var initialDir = vault.getVaultSettings().getCustomMountPath().orElse(System.getProperty("user.home")); - directoryChooser.setInitialDirectory(Path.of(initialDir).toFile()); + var initialDir = Path.of(vault.getVaultSettings().getCustomMountPath().orElse(System.getProperty("user.home"))); + + if(Files.exists(initialDir)) { + directoryChooser.setInitialDirectory(initialDir.toFile()); + } } catch (InvalidPathException e) { // no-op } @@ -188,32 +178,28 @@ public class MountOptionsController implements FxController { // Getter & Setter - public BooleanProperty osIsWindowsProperty() { - return osIsWindows; + public boolean isOsWindows() { + return SystemUtils.IS_OS_WINDOWS; } - public boolean getOsIsWindows() { - return osIsWindows.get(); + public boolean isCustomMountPointSupported() { + return !(usedVolumeImpl == VolumeImpl.WEBDAV && isOsWindows()); } - public BooleanBinding webDavAndWindowsProperty() { - return webDavAndWindows; - } - - public boolean isWebDavAndWindows() { - return webDavAndWindows.get(); + public boolean isReadOnlySupported() { + return !(usedVolumeImpl == VolumeImpl.FUSE && isOsWindows()); } public StringProperty customMountPathProperty() { return vault.getVaultSettings().customMountPath(); } + public boolean isCustomMountOptionsSupported() { + return usedVolumeImpl != VolumeImpl.WEBDAV; + } + public String getCustomMountPath() { return vault.getVaultSettings().customMountPath().get(); } - public Boolean getRestrictToStableFuseOnWindows() { - return restrictToStableFuseOnWindows.get(); - } - } diff --git a/src/main/resources/fxml/preferences_about.fxml b/src/main/resources/fxml/preferences_about.fxml index 287e91da3..cfa8ec010 100644 --- a/src/main/resources/fxml/preferences_about.fxml +++ b/src/main/resources/fxml/preferences_about.fxml @@ -22,7 +22,7 @@ - diff --git a/src/main/resources/fxml/unlock_invalid_mount_point.fxml b/src/main/resources/fxml/unlock_invalid_mount_point.fxml index 253ff5704..062981304 100644 --- a/src/main/resources/fxml/unlock_invalid_mount_point.fxml +++ b/src/main/resources/fxml/unlock_invalid_mount_point.fxml @@ -29,8 +29,9 @@ - - + + + diff --git a/src/main/resources/fxml/unlock_select_masterkeyfile.fxml b/src/main/resources/fxml/unlock_select_masterkeyfile.fxml index b6539f88f..d37289fca 100644 --- a/src/main/resources/fxml/unlock_select_masterkeyfile.fxml +++ b/src/main/resources/fxml/unlock_select_masterkeyfile.fxml @@ -11,7 +11,7 @@ - + diff --git a/src/main/resources/i18n/strings.properties b/src/main/resources/i18n/strings.properties index 7b17fa791..6648496b9 100644 --- a/src/main/resources/i18n/strings.properties +++ b/src/main/resources/i18n/strings.properties @@ -103,8 +103,10 @@ unlock.title=Unlock "%s" unlock.passwordPrompt=Enter password for "%s": unlock.savePassword=Remember Password unlock.unlockBtn=Unlock -## +## Select +unlock.chooseMasterkey.title=Select Masterkey of "%s" unlock.chooseMasterkey.prompt=Could not find the masterkey file for this vault at its expected location. Please choose the key file manually. +unlock.chooseMasterkey.chooseBtn=Choose… unlock.chooseMasterkey.filePickerTitle=Select Masterkey File ## Success unlock.success.message=Unlocked "%s" successfully! Your vault is now accessible via its virtual drive. @@ -115,6 +117,7 @@ unlock.error.heading=Unable to unlock vault ### Invalid Mount Point unlock.error.invalidMountPoint.notExisting=Mount point "%s" is not a directory, not empty or does not exist. unlock.error.invalidMountPoint.existing=Mount point "%s" already exists or parent folder is missing. +unlock.error.invalidMountPoint.driveLetterOccupied=Drive Letter "%s" is already in use. # Lock ## Force diff --git a/src/main/resources/i18n/strings_ar.properties b/src/main/resources/i18n/strings_ar.properties index a10658b48..740555d11 100644 --- a/src/main/resources/i18n/strings_ar.properties +++ b/src/main/resources/i18n/strings_ar.properties @@ -94,7 +94,8 @@ forgetPassword.confirmBtn=نسيت كلمة المرور unlock.passwordPrompt=‮أدخل كلمة السر لـ "‪%s‬": unlock.savePassword=تذكر كلمة المرور unlock.unlockBtn=افتح -## +## Select +unlock.chooseMasterkey.chooseBtn=اختر… unlock.chooseMasterkey.filePickerTitle=اختر ملف الـ Masterkey ## Success unlock.success.message=تم فتح "%s" بنجاح! يمكنك الآن الوصول لمخزنك عن طريق القرص الافتراضي الخاص به. diff --git a/src/main/resources/i18n/strings_bn.properties b/src/main/resources/i18n/strings_bn.properties new file mode 100644 index 000000000..12101cf31 --- /dev/null +++ b/src/main/resources/i18n/strings_bn.properties @@ -0,0 +1,143 @@ +# Locale Specific CSS files such as CJK, RTL,... + +# Generics +## Button +generic.button.apply=প্রয়োগ করুন +generic.button.back=পিছনে +generic.button.cancel=বাতিল করুন +generic.button.change=পরিবর্তন করুন +generic.button.close=বন্ধ করুন +generic.button.copy=কপি +generic.button.copied=কপি হয়েছে! +generic.button.done=সম্পন্ন হয়েছে +generic.button.next=পরবর্তী +generic.button.print=প্রিন্ট +## Error +generic.error.title=ত্রুটি %s +generic.error.instruction=ওহো! ক্রিপ্টোমেটর এটা যে হবে তা আশা করেনি. আপনি এই ত্রুটির সমাধানটি খুঁজে দেখুন. ত্রুটিটি সম্পর্কে যদি বিবরণ না পান, আপনি সেটি রিপোর্ট করতে পারেন. +generic.error.hyperlink.lookup=ত্রুটিটি খুঁজে দেখুন +generic.error.hyperlink.report=ত্রুটিটি রিপোর্ট করুন +generic.error.technicalDetails=বিস্তারিত: + +# Defaults +defaults.vault.vaultName=ভোল্ট + +# Tray Menu +traymenu.showMainWindow=দেখান +traymenu.lockAllVaults=সব লক করুন +traymenu.quitApplication=বের হোন +traymenu.vault.unlock=আনলক করুন +traymenu.vault.lock=লক করুন + +# Add Vault Wizard +addvaultwizard.title=ভোল্ট যুক্ত করুন +## Welcome +addvaultwizard.welcome.newButton=নতুন ভোল্ট তৈরি করুন +addvaultwizard.welcome.existingButton=বিদ্যমান কোনো ভোল্ট খুলুন +## New +### Name +addvaultwizard.new.nameInstruction=ভোল্ট এর একটি নাম দেন +addvaultwizard.new.namePrompt=ভোল্ট এর নাম +### Location +addvaultwizard.new.locationInstruction=ভোল্ট এর এনক্রিপ্টেড ফাইলগুলো ক্রিপ্টোমেটর কোথায় সংরক্ষণ করবে? +addvaultwizard.new.locationLabel=স্টোরেজ লোকেশন +addvaultwizard.new.locationPrompt=… +addvaultwizard.new.directoryPickerButton=নির্বাচন করুন… +addvaultwizard.new.directoryPickerTitle=ডিরেক্টরি নির্বাচন +addvaultwizard.new.locationDoesNotExist=নিদিষ্ট করা পথে একটি ডিরেক্টরি বিদ্যমান নয় অথবা প্রবেশ করা যাচ্ছে না +addvaultwizard.new.locationIsNotWritable=নিদিষ্ট করা পথে কোনো কিছু পরিবর্তন করার অনুমতি নেই +addvaultwizard.new.locationIsOk=আপনার ভোল্টের জন্য উপযুক্ত ঠিকানা +addvaultwizard.new.invalidName=ভোল্টের নাম অনুপযুক্ত. অনুগ্রহ করে ভোল্টের জন্য একটি সাধারণ নাম ব্যবহার করুন. +### Password +addvaultwizard.new.createVaultBtn=ভোল্ট তৈরি করুন +addvaultwizard.new.generateRecoveryKeyChoice=পাসওয়ার্ড ছাড়া আপনি আপনার তথ্যগুলো ব্যবহার করতে পারবেন না. তাই আপনি কি একটি পুনরুদ্ধার চাবি চান যদি আপনি পাসওয়ার্ড হারিয়ে ফেলেন? +addvaultwizard.new.generateRecoveryKeyChoice.yes=হ্যাঁ অবশ্যই, দুঃখিত হওয়ার চেয়ে নিরাপদ থাকাই ভালো +addvaultwizard.new.generateRecoveryKeyChoice.no=না ধন্যবাদ, আমি আমার পাসওয়ার্ড হারাবো না +### Information +addvault.new.readme.storageLocation.fileName=গুরুত্বপূর্ণ.rtf +addvault.new.readme.storageLocation.1=⚠️ ভোল্ট ফাইলস ⚠️ +addvault.new.readme.storageLocation.2=এটি হচ্ছে আপনার ভোল্টের স্টোরেজ ঠিকানা. +addvault.new.readme.storageLocation.3=যা করবেন না +addvault.new.readme.storageLocation.4=• কোন ফাইলে কোন ধরনের পরিবর্তন +addvault.new.readme.storageLocation.5=• এনক্রিপশনের জন্য কোন ফাইল এই ডিরেক্টরিতে পেশ করা. +## Existing +addvaultwizard.existing.chooseBtn=নির্বাচন করুন… +## Success + +# Remove Vault + +# Change Password + +# Forget Password + +# Unlock +unlock.unlockBtn=আনলক করুন +## Select +unlock.chooseMasterkey.chooseBtn=নির্বাচন করুন… +## Success +## Failure +### Invalid Mount Point + +# Lock +## Force +lock.forced.retryBtn=পুনরায় চেষ্টা করুন +## Failure + +# Migration +## Start +## Run +## Success +## Missing file system capabilities +## Impossible + +# Health Check +## Start +## Start Failure +## Check Selection +## Detail view +## Fix Application + +# Preferences +## General +## Volume +## Updates +## Contribution +#<-- Add entries for donations and code/translation/documentation contribution --> + +## About + +# Vault Statistics +## Read +## Write + +# Main Window +main.closeBtn.tooltip=বন্ধ করুন +## Drag 'n' Drop +## Vault List +main.vaultlist.contextMenu.lock=লক করুন +main.vaultlist.addVaultBtn=ভোল্ট যুক্ত করুন +## Vault Detail +### Welcome +### Locked +### Unlocked +main.vaultDetail.lockBtn=লক করুন +### Missing +### Needs Migration +### Error + +# Wrong File Alert + +# Vault Options +## General +vaultOptions.general.vaultName=ভোল্ট এর নাম + +## Mount +vaultOptions.mount.mountPoint.directoryPickerButton=নির্বাচন করুন… +## Master Key + + +# Recovery Key + +# New Password + +# Quit diff --git a/src/main/resources/i18n/strings_bs.properties b/src/main/resources/i18n/strings_bs.properties index e336ff9d3..0b8d45a40 100644 --- a/src/main/resources/i18n/strings_bs.properties +++ b/src/main/resources/i18n/strings_bs.properties @@ -94,7 +94,8 @@ forgetPassword.confirmBtn=Zaboravili ste šifru unlock.passwordPrompt=Unesite lozinku za "%s": unlock.savePassword=Zapamti šifru unlock.unlockBtn=Otključaj -## +## Select +unlock.chooseMasterkey.chooseBtn=Odaberi… unlock.chooseMasterkey.filePickerTitle=Odaberite Masterkey Datoteku ## Success unlock.success.message=Uspješno ste otključali "%s"! Vaš sef je sada dostupan putem svog virtualnog diska. diff --git a/src/main/resources/i18n/strings_ca.properties b/src/main/resources/i18n/strings_ca.properties index 520b7d073..afefc7b44 100644 --- a/src/main/resources/i18n/strings_ca.properties +++ b/src/main/resources/i18n/strings_ca.properties @@ -102,8 +102,9 @@ unlock.title=Desbloca "%s" unlock.passwordPrompt=Introduïu la contrasenya de "%s": unlock.savePassword=Recorda la contrasenya unlock.unlockBtn=Desbloqueja -## +## Select unlock.chooseMasterkey.prompt=No es pot trobar el fitxer de clau mestra per aquesta bòveda a la ubicació esperada. Escull el fixer manualment. +unlock.chooseMasterkey.chooseBtn=Trieu… unlock.chooseMasterkey.filePickerTitle=Seleccioneu el fitxer de Clau Mestra ## Success unlock.success.message=S'ha desblocat %s correctament! Podeu accedir a la vostra caixa forta a través de la unitat virtual. diff --git a/src/main/resources/i18n/strings_cs.properties b/src/main/resources/i18n/strings_cs.properties index 5bac326e1..34a7e185f 100644 --- a/src/main/resources/i18n/strings_cs.properties +++ b/src/main/resources/i18n/strings_cs.properties @@ -102,8 +102,10 @@ unlock.title=Odemknout "%s" unlock.passwordPrompt=Zadejte heslo pro "%s": unlock.savePassword=Zapamatovat heslo unlock.unlockBtn=Odemknout -## +## Select +unlock.chooseMasterkey.title=Vyberte soubor s hlavním klíčem "%s" unlock.chooseMasterkey.prompt=Nepodařilo se najít soubor hlavního klíče pro tento trezor v očekávaném umístění. Vyberte prosím soubor klíče ručně. +unlock.chooseMasterkey.chooseBtn=Vybrat... unlock.chooseMasterkey.filePickerTitle=Vyberte soubor s hlavním klíčem ## Success unlock.success.message=Trezor "%s" byl úspěšně odemčen a nyní je dostupný jako virtuální jednotka. @@ -114,6 +116,7 @@ unlock.error.heading=Nelze odemknout trezor ### Invalid Mount Point unlock.error.invalidMountPoint.notExisting=Připojovací bod %s není složkou, není prázdný nebo neexistuje. unlock.error.invalidMountPoint.existing=Připojovací bod %s již existuje nebo nadřazená složka chybí. +unlock.error.invalidMountPoint.driveLetterOccupied=Písmeno „%s“ už je používáno pro jiný disk. # Lock ## Force @@ -301,6 +304,7 @@ main.vaultDetail.missing.changeLocation=Změnit umístění trezoru… main.vaultDetail.migrateButton=Upgrade trezoru main.vaultDetail.migratePrompt=Váš trezor musí být aktualizován na nový formát, než k němu budete mít přístup ### Error +main.vaultDetail.error.info=Došlo k chybě při načítání trezoru z disku. main.vaultDetail.error.reload=Obnovit main.vaultDetail.error.windowTitle=Chyba při načítání trezoru diff --git a/src/main/resources/i18n/strings_de.properties b/src/main/resources/i18n/strings_de.properties index fa5551619..c7a4a6ea1 100644 --- a/src/main/resources/i18n/strings_de.properties +++ b/src/main/resources/i18n/strings_de.properties @@ -44,7 +44,7 @@ addvaultwizard.new.namePrompt=Tresorname addvaultwizard.new.locationInstruction=Wo soll Cryptomator die verschlüsselten Dateien deines Tresors ablegen? addvaultwizard.new.locationLabel=Speicherort addvaultwizard.new.locationPrompt=… -addvaultwizard.new.directoryPickerLabel=Eigener Ort +addvaultwizard.new.directoryPickerLabel=Benutzerdefinierter Ort addvaultwizard.new.directoryPickerButton=Durchsuchen … addvaultwizard.new.directoryPickerTitle=Verzeichnis auswählen addvaultwizard.new.fileAlreadyExists=Eine Datei oder ein Ordner mit diesem Namen ist bereits vorhanden @@ -72,14 +72,14 @@ addvault.new.readme.storageLocation.10=Falls Du Hilfe brauchst, lies die Dokumen addvault.new.readme.accessLocation.fileName=WILLKOMMEN.rtf addvault.new.readme.accessLocation.1=🔐️ VERSCHLÜSSELTES LAUFWERK 🔐️ 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.4=Diese Datei kannst du löschen. +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. ## Existing 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.filePickerTitle=Tresor Datei auswählen +addvaultwizard.existing.chooseBtn=Durchsuchen … +addvaultwizard.existing.filePickerTitle=Tresor-Datei auswählen ## Success -addvaultwizard.success.nextStepsInstructions=Tresor „%s“ hinzugefügt.\nUm auf Inhalte zuzugreifen oder welche hinzuzufügen, musst du den Tresor entsperren. Du kannst ihn aber auch zu jedem späteren Zeitpunkt entsperren. +addvaultwizard.success.nextStepsInstructions=Tresor „%s“ hinzugefügt.\nUm auf Inhalte zuzugreifen oder welche hinzuzufügen, musst Du den Tresor entsperren. Du kannst ihn aber auch zu jedem späteren Zeitpunkt entsperren. addvaultwizard.success.unlockNow=Jetzt entsperren # Remove Vault @@ -94,7 +94,7 @@ changepassword.finalConfirmation=Mir ist bewusst, dass ich bei Verlust meines Pa # Forget Password forgetPassword.title=Passwort vergessen -forgetPassword.information=Dies löscht das gespeicherte Passwort dieses Tresors aus dem Schlüsselbund deines Betriebssystems. +forgetPassword.information=Dies löscht das gespeicherte Passwort dieses Tresors aus dem Schlüsselbund Deines Betriebssystems. forgetPassword.confirmBtn=Passwort vergessen # Unlock @@ -102,18 +102,21 @@ unlock.title="%s" entsperren unlock.passwordPrompt=Gib das Passwort für „%s“ ein: unlock.savePassword=Passwort merken unlock.unlockBtn=Entsperren -## +## Select +unlock.chooseMasterkey.title=Masterkey von „%s“ auswählen unlock.chooseMasterkey.prompt=Die Masterkey-Datei dieses Tresors konnte nicht gefunden werden. Bitte wähle die Masterkey-Datei manuell aus. +unlock.chooseMasterkey.chooseBtn=Durchsuchen… unlock.chooseMasterkey.filePickerTitle=Masterkey-Datei auswählen ## Success -unlock.success.message=„%s“ erfolgreich entsperrt! Nun kannst du über das virtuelle Laufwerk auf deinen Tresor zugreifen. +unlock.success.message=„%s“ erfolgreich entsperrt! Nun kannst Du über das virtuelle Laufwerk auf Deinen Tresor zugreifen. unlock.success.rememberChoice=Auswahl speichern und nicht mehr anzeigen unlock.success.revealBtn=Laufwerk anzeigen ## Failure unlock.error.heading=Tresor konnte nicht entsperrt werden ### Invalid Mount Point -unlock.error.invalidMountPoint.notExisting=Einhängepunkt ist kein leeres Verzeichnis oder existiert nicht: %s +unlock.error.invalidMountPoint.notExisting=Einhängepunkt %s ist kein leeres Verzeichnis oder existiert nicht. unlock.error.invalidMountPoint.existing=Einhängepunkt/Ordner bereits vorhanden oder übergeordneter Ordner fehlt: %s. +unlock.error.invalidMountPoint.driveLetterOccupied=Laufwerksbuchstabe "%s" wird bereits verwendet. # Lock ## Force @@ -123,7 +126,7 @@ lock.forced.retryBtn=Wiederholen lock.forced.forceBtn=Sperren erzwingen ## Failure lock.fail.heading=Tresor konnte nicht gesperrt werden. -lock.fail.message=Der Tresor „%s“ konnte nicht gesperrt werden. Stelle sicher, dass du deine ungespeicherte Arbeit an anderer Stelle speicherst und wichtige Lese-/Schreibvorgänge abgeschlossen sind. Um den Tresor zu schließen, beende den Cryptomator-Prozess. +lock.fail.message=Der Tresor „%s“ konnte nicht gesperrt werden. Stelle sicher, dass Du Deine ungespeicherte Arbeit an anderer Stelle speicherst und wichtige Lese-/Schreibvorgänge abgeschlossen sind. Um den Tresor zu schließen, beende den Cryptomator-Prozess. # Migration migration.title=Tresor aktualisieren @@ -139,7 +142,7 @@ migration.success.nextStepsInstructions=„%s“ erfolgreich migriert.\nDu kanns migration.success.unlockNow=Jetzt entsperren ## Missing file system capabilities migration.error.missingFileSystemCapabilities.title=Nicht unterstütztes Dateisystem -migration.error.missingFileSystemCapabilities.description=Die Migration wurde nicht gestartet, da sich dein Tresor auf einem ungeeigneten Dateisystem befindet. +migration.error.missingFileSystemCapabilities.description=Die Migration wurde nicht gestartet, da sich Dein Tresor auf einem ungeeigneten Dateisystem befindet. migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=Das Dateisystem unterstützt keine langen Dateinamen. migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Das Dateisystem unterstützt keine langen Pfadnamen. migration.error.missingFileSystemCapabilities.reason.READ_ACCESS=Das Dateisystem lässt keine Lesevorgänge zu. @@ -147,15 +150,15 @@ migration.error.missingFileSystemCapabilities.reason.WRITE_ACCESS=Das Dateisyste ## Impossible migration.impossible.heading=Tresor kann nicht migriert werden migration.impossible.reason=Der Tresor kann nicht automatisch migriert werden, da sein Speicherort oder Zugangspunkt nicht kompatibel ist. -migration.impossible.moreInfo=Der Tresor kann auch jetzt noch mit einer älteren Version geöffnet werden. Eine Anleitung zum manuellen Migrieren eines Tresors findest du unter +migration.impossible.moreInfo=Der Tresor kann auch jetzt noch mit einer älteren Version geöffnet werden. Eine Anleitung zum manuellen Migrieren eines Tresors findest Du unter # Health Check ## Start -health.title=Integritätstest von "%s" -health.intro.header=Zustandsprüfung -health.intro.text=Der Zustandscheck ist eine Sammlung von Tests, um Probleme mit der internen Struktur deines Tresores zu finden und möglicherweise zu reparieren. Bitte bedenke: +health.title=Integritätsprüfung von "%s" +health.intro.header=Integritätsprüfung +health.intro.text=Die Integritätsprüfung ist eine Sammlung von Tests, um Probleme mit der internen Struktur deines Tresors zu finden und möglicherweise zu reparieren. Bitte bedenke: health.intro.remarkSync=Stelle sicher, dass alle Geräte vollständig synchronisiert sind. Dies löst die meisten Probleme. -health.intro.remarkFix=Nicht alle Probleme können gelöst werden. +health.intro.remarkFix=Nicht alle Probleme können behoben werden. health.intro.remarkBackup=Wenn Daten beschädigt sind, kann nur ein Backup helfen. health.intro.affirmation=Ich habe die obenstehende Information gelesen und verstanden ## Start Failure @@ -164,24 +167,24 @@ health.fail.ioError=Beim Lesezugriff auf die Konfigurationsdatei ist ein Fehler health.fail.parseError=Beim Parsen der Tresor-Konfiguration ist ein Fehler aufgetreten. health.fail.moreInfo=Weitere Informationen ## Check Selection -health.checkList.description=Markiere Prüfungen in der linken Liste oder benutze die Knöpfe darunter. +health.checkList.description=Markiere Prüfungen in der linken Liste oder benutze die Schaltflächen darunter. health.checkList.selectAllButton=Alle Prüfungen auswählen health.checkList.deselectAllButton=Alle Prüfungen abwählen health.check.runBatchBtn=Ausgewählte Prüfungen ausführen ## Detail view -health.check.detail.noSelectedCheck=Wähle für die Ergebnisse eine abgeschlossene Integritätsprüfung in der Liste links aus. +health.check.detail.noSelectedCheck=Wähle für die Ergebnisse eine abgeschlossene Intregritätsprüfung in der Liste links aus. health.check.detail.checkScheduled=Die Prüfung ist geplant. -health.check.detail.checkRunning=Prüfung läuft… +health.check.detail.checkRunning=Die Prüfung läuft derzeit … health.check.detail.checkSkipped=Die Prüfung wurde nicht zur Ausführung ausgewählt. health.check.detail.checkFinished=Die Prüfung wurde erfolgreich abgeschlossen. -health.check.detail.checkFinishedAndFound=Die Überprüfung wurde beendet. Bitte sichte die Ergebnisse. -health.check.detail.checkFailed=Die Prüfung wurde wegen eines Fehlers beendet. +health.check.detail.checkFinishedAndFound=Die Prüfung wurde beendet. Bitte überprüfe die Ergebnisse. +health.check.detail.checkFailed=Die Prüfung wurde wegen eines Fehlers abgebrochen. health.check.detail.checkCancelled=Die Prüfung wurde abgebrochen. health.check.exportBtn=Bericht exportieren ## Fix Application health.fix.fixBtn=Beheben health.fix.successTip=Fehlerbehebung erfolgreich -health.fix.failTip=Reparatur fehlgeschlagen, siehe Log für Details +health.fix.failTip=Reparatur fehlgeschlagen, siehe Protokoll für Details # Preferences preferences.title=Einstellungen @@ -216,8 +219,8 @@ preferences.updates.updateAvailable=Update auf Version %s verfügbar. ## Contribution preferences.contribute=Unterstütze uns preferences.contribute.registeredFor=Supporter-Zertifikat registriert für %s -preferences.contribute.noCertificate=Unterstütze Cryptomator und erhalte ein Supporter-Zertifikat. Es ist wie ein Lizenzschlüssel, aber für großartige Menschen, die freie Software verwenden. ;-) -preferences.contribute.getCertificate=Du hast noch keines? Erfahre, wie du es erhalten kannst. +preferences.contribute.noCertificate=Unterstütze Cryptomator und erhalte ein Supporter-Zertifikat. Es ist eine Art Lizenzschlüssel, aber für großartige Menschen, die freie Software verwenden. ;-) +preferences.contribute.getCertificate=Du hast noch keins? Erfahre, wie Du es erhalten kannst. preferences.contribute.promptText=Code des Supporter-Zertifikats hier einfügen #<-- Add entries for donations and code/translation/documentation contribution --> @@ -262,10 +265,10 @@ main.debugModeEnabled.tooltip=Diagnosemodus ist aktiviert main.donationKeyMissing.tooltip=Zieh bitte eine Spende in Betracht ## Drag 'n' Drop main.dropZone.dropVault=Diesen Tresor hinzufügen -main.dropZone.unknownDragboardContent=Falls du einen Tresor hinzufügen möchtest, zieh ihn in dieses Fenster +main.dropZone.unknownDragboardContent=Falls Du einen Tresor hinzufügen möchtest, zieh ihn in dieses Fenster ## Vault List main.vaultlist.emptyList.onboardingInstruction=Klicke hier, um einen Tresor hinzuzufügen -main.vaultlist.contextMenu.remove=Entfernen … +main.vaultlist.contextMenu.remove=Entfernen… main.vaultlist.contextMenu.lock=Sperren main.vaultlist.contextMenu.unlock=Entsperren … main.vaultlist.contextMenu.unlockNow=Jetzt entsperren @@ -274,7 +277,7 @@ main.vaultlist.contextMenu.reveal=Laufwerk anzeigen main.vaultlist.addVaultBtn=Tresor hinzufügen ## Vault Detail ### 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: ### Locked main.vaultDetail.lockedStatus=GESPERRT main.vaultDetail.unlockBtn=Entsperren … @@ -334,7 +337,7 @@ vaultOptions.mount.readonly=Schreibgeschützt vaultOptions.mount.customMountFlags=Benutzerdefinierte Einhänge-Optionen vaultOptions.mount.winDriveLetterOccupied=belegt vaultOptions.mount.mountPoint=Einhängepunkt -vaultOptions.mount.mountPoint.auto=Wähle automatisch einen geeigneten Ort aus +vaultOptions.mount.mountPoint.auto=Automatisch einen geeigneten Ort auswählen vaultOptions.mount.mountPoint.driveLetter=Laufwerksbuchstaben zuweisen vaultOptions.mount.mountPoint.custom=Eigener Pfad vaultOptions.mount.mountPoint.directoryPickerButton=Durchsuchen … diff --git a/src/main/resources/i18n/strings_el.properties b/src/main/resources/i18n/strings_el.properties index 756530597..2b4db00b1 100644 --- a/src/main/resources/i18n/strings_el.properties +++ b/src/main/resources/i18n/strings_el.properties @@ -102,8 +102,10 @@ unlock.title=Ξεκλειδώστε "%s" unlock.passwordPrompt=Εισάγετε τον κωδικό για "%s": unlock.savePassword=Απομνημόνευση κωδικού πρόσβασης unlock.unlockBtn=Ξεκλείδωμα -## +## Select +unlock.chooseMasterkey.title=Επιλέξτε το Masterkey του "%s" unlock.chooseMasterkey.prompt=Αδυναμία εύρεσης του αρχείου masterkey για αυτό το vault στην αναμενόμενη τοποθεσία. Παρακαλώ επιλέξτε το αρχείο χειροκίνητα. +unlock.chooseMasterkey.chooseBtn=Επιλογή… unlock.chooseMasterkey.filePickerTitle=Επιλέξτε το αρχείο Masterkey ## Success unlock.success.message=Ξεκλειδώθηκε "%s" επιτυχώς! Το vault σας είναι διαθέσιμο μέσω του εικονικού δίσκου του. @@ -114,6 +116,7 @@ unlock.error.heading=Αδυναμία ξεκλειδώματος vault ### Invalid Mount Point unlock.error.invalidMountPoint.notExisting=Το σημείο προσάρτησης δεν είναι κενός φάκελος ή δεν υπάρχει: %s unlock.error.invalidMountPoint.existing=Το σημείο/φάκελος προσάρτησης υπάρχει ήδη ή ο γονικός φάκελος λείπει: %s +unlock.error.invalidMountPoint.driveLetterOccupied=Το Γράμμα Δίσκου "%s" χρησιμοποιείται ήδη. # Lock ## Force diff --git a/src/main/resources/i18n/strings_es.properties b/src/main/resources/i18n/strings_es.properties index cb3d9d3e7..ddc7e79e3 100644 --- a/src/main/resources/i18n/strings_es.properties +++ b/src/main/resources/i18n/strings_es.properties @@ -68,7 +68,7 @@ addvault.new.readme.storageLocation.6=Cuando se quiere cifrar archivos y ver el addvault.new.readme.storageLocation.7=1. Agregar esta bóveda a Cryptomator. addvault.new.readme.storageLocation.8=2. Desbloquear la bóveda en Cryptomator. addvault.new.readme.storageLocation.9=3. Abrir el lugar de acceso haciendo clic en el botón "Revelar". -addvault.new.readme.storageLocation.10=Si se necesita ayuda, visitar la documentación: %s +addvault.new.readme.storageLocation.10=Si necesita ayuda, visite la documentación: %s addvault.new.readme.accessLocation.fileName=BIENVENIDA.rtf addvault.new.readme.accessLocation.1=🔐️ VOLUMEN CIFRADO 🔐️ addvault.new.readme.accessLocation.2=Este es el lugar de acceso de la bóveda. @@ -102,8 +102,10 @@ unlock.title=Desbloquear "%s" unlock.passwordPrompt=Ingresar contraseña para "%s": unlock.savePassword=Recordar contraseña unlock.unlockBtn=Desbloquear -## +## Select +unlock.chooseMasterkey.title=Seleccionar clave maestra de "%s" unlock.chooseMasterkey.prompt=No se pudo encontrar el archivo de la clave maestra de esta bóveda en la ubicación esperada. Por favor, elija manualmente el archivo de la clave. +unlock.chooseMasterkey.chooseBtn=Elegir… unlock.chooseMasterkey.filePickerTitle=Seleccione el archivo de la clave maestra ## Success unlock.success.message=¡Desbloqueo de "%s" exitoso! Su bóveda ahora es accesible a través de su unidad virtual. @@ -112,8 +114,9 @@ unlock.success.revealBtn=Revelar unidad ## Failure unlock.error.heading=No se puede desbloquear la bóveda ### Invalid Mount Point -unlock.error.invalidMountPoint.notExisting=El punto de montaje no es un directorio vacío o no existe: %s -unlock.error.invalidMountPoint.existing=El punto de montaje/carpeta ya existe o falta la carpeta padre: %s +unlock.error.invalidMountPoint.notExisting=El punto de montaje "%s"no es un directorio, no está vacío o no existe. +unlock.error.invalidMountPoint.existing=El punto de montaje "%s" ya existe o falta la carpeta padre. +unlock.error.invalidMountPoint.driveLetterOccupied=La letra de unidad "%s" ya está en uso. # Lock ## Force @@ -122,7 +125,7 @@ lock.forced.message=El bloqueo de "%s" fue bloqueado por operaciones pendientes lock.forced.retryBtn=Reintentar lock.forced.forceBtn=Forzar bloqueo ## Failure -lock.fail.heading=Falló al bloquear la bóveda. +lock.fail.heading=Error al bloquear la bóveda. lock.fail.message=No se pudo bloquear la bóveda "%s". Asegúrese de que el trabajo no guardado se ha guardado en otro lugar y las operaciones de lectura/escritura importantes han finalizado. Para cerrar la bóveda termine el proceso de Cryptomator. # Migration @@ -169,7 +172,7 @@ health.checkList.selectAllButton=Seleccionar todas las comprobaciones health.checkList.deselectAllButton=Deseleccionar todas las comprobaciones health.check.runBatchBtn=Ejecutar las comprobaciones seleccionadas ## Detail view -health.check.detail.noSelectedCheck=Para los resultados seleccione una comprobación del estado en la lista de la izquierda +health.check.detail.noSelectedCheck=Para los resultados seleccione una comprobación del estado finalizada en la lista de la izquierda. health.check.detail.checkScheduled=La comprobación está programada. health.check.detail.checkRunning=La comprobación se está ejecutando… health.check.detail.checkSkipped=No se ha seleccionado la comprobación para ejecutarse. @@ -217,7 +220,7 @@ preferences.updates.updateAvailable=Actualización a la versión %s disponible. preferences.contribute=Apóyenos preferences.contribute.registeredFor=Certificado de soporte registrado para %s preferences.contribute.noCertificate=Apoye a Cryptomator y reciba un certificado de seguidor. Es como una clave de licencia, pero para gente asombrosa usando software libre. ;-) -preferences.contribute.getCertificate=¿Aún no tiene una? Aprenda cómo puede obtenerlo. +preferences.contribute.getCertificate=¿Aún no tiene uno? Aprenda cómo puede obtenerlo. preferences.contribute.promptText=Pegue aquí el código de certificado de seguidor #<-- Add entries for donations and code/translation/documentation contribution --> @@ -244,7 +247,7 @@ stats.read.accessCount=Total leídos: %d stats.write.throughput.idle=Escritura: inactivo stats.write.throughput.kibs=Escritura: %.2f kiB/s stats.write.throughput.mibs=Escritura: %.2f MiB/s -stats.write.total.data.none=Datos escritos:- +stats.write.total.data.none=Datos escritos: - stats.write.total.data.kib=Datos escritos: %.1f kiB stats.write.total.data.mib=Datos escritos: %.1f MiB stats.write.total.data.gib=Datos escritos: %.1f GiB @@ -259,12 +262,12 @@ main.closeBtn.tooltip=Cerrar main.minimizeBtn.tooltip=Minimizar main.preferencesBtn.tooltip=Preferencias main.debugModeEnabled.tooltip=Modo de depuración activado -main.donationKeyMissing.tooltip=Por favor, considera donar +main.donationKeyMissing.tooltip=Por favor, considere donar ## Drag 'n' Drop main.dropZone.dropVault=Añadir esta bóveda main.dropZone.unknownDragboardContent=Si desea añadir una bóveda, arrástrela a esta ventana ## Vault List -main.vaultlist.emptyList.onboardingInstruction=Hacer clic aquí para añadir una bóveda +main.vaultlist.emptyList.onboardingInstruction=Haga clic aquí para añadir una bóveda main.vaultlist.contextMenu.remove=Eliminar… main.vaultlist.contextMenu.lock=Bloquear main.vaultlist.contextMenu.unlock=Desbloquear… @@ -274,9 +277,9 @@ main.vaultlist.contextMenu.reveal=Revelar unidad main.vaultlist.addVaultBtn=Añadir bóveda ## Vault Detail ### Welcome -main.vaultDetail.welcomeOnboarding=Gracias por elegir Cryptomator para proteger los archivos. En caso de necesitar ayuda, revisar nuestras guías: +main.vaultDetail.welcomeOnboarding=Gracias por elegir Cryptomator para proteger sus archivos. En caso de necesitar ayuda, revise nuestras guías: ### Locked -main.vaultDetail.lockedStatus=BLOQUEADO +main.vaultDetail.lockedStatus=BLOQUEADA main.vaultDetail.unlockBtn=Desbloquear… main.vaultDetail.unlockNowBtn=Desbloquear ahora main.vaultDetail.optionsBtn=Opciones de la bóveda @@ -286,7 +289,7 @@ main.vaultDetail.unlockedStatus=DESBLOQUEADO main.vaultDetail.accessLocation=El contenido de la bóveda es accesible aquí: main.vaultDetail.revealBtn=Revelar unidad main.vaultDetail.lockBtn=Bloquear -main.vaultDetail.bytesPerSecondRead=Leído: +main.vaultDetail.bytesPerSecondRead=Lectura: main.vaultDetail.bytesPerSecondWritten=Escritura: main.vaultDetail.throughput.idle=inactivo main.vaultDetail.throughput.kbps=%.1f kiB/s @@ -298,7 +301,7 @@ main.vaultDetail.missing.recheck=Volver a comprobar main.vaultDetail.missing.remove=Eliminar de la lista de bóveda… main.vaultDetail.missing.changeLocation=Cambiar ubicación de la bóveda… ### Needs Migration -main.vaultDetail.migrateButton=Mejorar bóveda +main.vaultDetail.migrateButton=Actualizar bóveda main.vaultDetail.migratePrompt=Su bóveda necesita ser actualizada a un formato nuevo antes de poder acceder a ella ### Error main.vaultDetail.error.info=Se produjo un error al cargar la bóveda del disco. @@ -306,13 +309,13 @@ main.vaultDetail.error.reload=Recargar main.vaultDetail.error.windowTitle=Error al cargar la bóveda # Wrong File Alert -wrongFileAlert.title=Cómo encriptar archivos -wrongFileAlert.header.title=¿Se intentan cifrar estos archivos? +wrongFileAlert.title=Cómo cifrar archivos +wrongFileAlert.header.title=¿Intenta cifrar estos archivos? wrongFileAlert.header.lead=Para este propósito, Cryptomator proporciona un volumen en su administrador de archivos del sistema. -wrongFileAlert.instruction.0=Para cifrar archivos, seguir estos pasos: -wrongFileAlert.instruction.1=1. Desbloquear la bóveda. -wrongFileAlert.instruction.2=2. Hacer clic en "Revelar" para abrir el volumen en el administrador de archivos. -wrongFileAlert.instruction.3=3. Añadir los archivos a este volumen. +wrongFileAlert.instruction.0=Para cifrar archivos, siga estos pasos: +wrongFileAlert.instruction.1=1. Desbloquee su bóveda. +wrongFileAlert.instruction.2=2. Haga clic en "Revelar" para abrir el volumen en el administrador de archivos. +wrongFileAlert.instruction.3=3. Añada los archivos a este volumen. wrongFileAlert.link=Para más ayuda, visite # Vault Options @@ -322,7 +325,7 @@ vaultOptions.general.vaultName=Nombre de la bóveda vaultOptions.general.autoLock.lockAfterTimePart1=Bloquear después de vaultOptions.general.autoLock.lockAfterTimePart2=minutos vaultOptions.general.unlockAfterStartup=Desbloquear bóveda al iniciar Cryptomator -vaultOptions.general.actionAfterUnlock=Después de desbloquear exitosamente +vaultOptions.general.actionAfterUnlock=Después de desbloquear con éxito vaultOptions.general.actionAfterUnlock.ignore=No hacer nada vaultOptions.general.actionAfterUnlock.reveal=Revelar unidad vaultOptions.general.actionAfterUnlock.ask=Preguntar @@ -362,7 +365,7 @@ newPassword.promptText=Ingrese una contraseña nueva newPassword.reenterPassword=Confirme la contraseña nueva newPassword.passwordsMatch=¡Las contraseñas coinciden! newPassword.passwordsDoNotMatch=Las contraseñas no coinciden -passwordStrength.messageLabel.tooShort=Usar al menos %d caracteres +passwordStrength.messageLabel.tooShort=Use al menos %d caracteres passwordStrength.messageLabel.0=Muy débil passwordStrength.messageLabel.1=Débil passwordStrength.messageLabel.2=Aceptable diff --git a/src/main/resources/i18n/strings_fil.properties b/src/main/resources/i18n/strings_fil.properties index 9f2a7767f..83c92ce90 100644 --- a/src/main/resources/i18n/strings_fil.properties +++ b/src/main/resources/i18n/strings_fil.properties @@ -61,7 +61,8 @@ addvaultwizard.existing.chooseBtn=Mamili… # Unlock unlock.unlockBtn=I-unlock -## +## Select +unlock.chooseMasterkey.chooseBtn=Mamili… ## Success ## Failure ### Invalid Mount Point diff --git a/src/main/resources/i18n/strings_fr.properties b/src/main/resources/i18n/strings_fr.properties index 6a6a4d90d..17372d6a3 100644 --- a/src/main/resources/i18n/strings_fr.properties +++ b/src/main/resources/i18n/strings_fr.properties @@ -63,7 +63,7 @@ addvault.new.readme.storageLocation.1=Fichiers de coffre-fort addvault.new.readme.storageLocation.2=Ceci est le chemin de votre coffre-fort. addvault.new.readme.storageLocation.3=NE PAS addvault.new.readme.storageLocation.4=Modifier n'importe quel fichier dans ce répertoire ou -addvault.new.readme.storageLocation.5=Collez n'importe quel fichier à chiffrer dans ce répertoire. +addvault.new.readme.storageLocation.5=• coller de fichier à chiffrer dans ce répertoire. addvault.new.readme.storageLocation.6=Si vous voulez chiffrer les fichiers et afficher le contenu du coffre, faites ce qui suit : addvault.new.readme.storageLocation.7=1. Ajouter ce coffre à Cryptomator. addvault.new.readme.storageLocation.8=2. Déverrouillez le coffre-fort dans Cryptomator. @@ -102,8 +102,10 @@ unlock.title=Déverrouiller %s unlock.passwordPrompt=Entrez le mot de passe pour “%s” : unlock.savePassword=Mémoriser le mot de passe unlock.unlockBtn=Déverrouiller -## +## Select +unlock.chooseMasterkey.title=Sélectionner la clé principale de "%s" unlock.chooseMasterkey.prompt=Impossible de trouver le fichier clef à l'adresse attendue pour ce coffre. Veuillez sélectionner le fichier clef manuellement. +unlock.chooseMasterkey.chooseBtn=Choisir... unlock.chooseMasterkey.filePickerTitle=Sélectionner le fichier clef ## Success unlock.success.message=“%s” déverrouillé ! Le contenu de votre coffre est maintenant accessible par son lecteur virtuel. @@ -114,6 +116,7 @@ unlock.error.heading=Impossible de déverrouiller le coffre ### Invalid Mount Point unlock.error.invalidMountPoint.notExisting=Le point de montage «%s» n'est pas un répertoire, n'est pas vide ou n'existe pas. unlock.error.invalidMountPoint.existing=Le point de montage/le répertoire existe déjà ou le répertoire parent est manquant: %s +unlock.error.invalidMountPoint.driveLetterOccupied=Le lecteur "%s" est en déjà utilisé. # Lock ## Force @@ -318,7 +321,7 @@ wrongFileAlert.link=Pour toute aide supplémentaire, visitez # Vault Options ## General vaultOptions.general=Général -vaultOptions.general.vaultName=Nom de voûte +vaultOptions.general.vaultName=Nom du coffre-fort vaultOptions.general.autoLock.lockAfterTimePart1=Verrouiler en cas d'inactivité pendant vaultOptions.general.autoLock.lockAfterTimePart2=minutes vaultOptions.general.unlockAfterStartup=Déverrouiller le coffre au démarrage diff --git a/src/main/resources/i18n/strings_gl.properties b/src/main/resources/i18n/strings_gl.properties new file mode 100644 index 000000000..88e7c506a --- /dev/null +++ b/src/main/resources/i18n/strings_gl.properties @@ -0,0 +1,89 @@ +# Locale Specific CSS files such as CJK, RTL,... + +# Generics +## Button +## Error + +# Defaults + +# Tray Menu + +# Add Vault Wizard +## Welcome +## New +### Name +### Location +### Password +### Information +## Existing +## Success + +# Remove Vault + +# Change Password + +# Forget Password + +# Unlock +## Select +## Success +## Failure +### Invalid Mount Point + +# Lock +## Force +lock.forced.retryBtn=Tentar de novo +## Failure + +# Migration +## Start +## Run +## Success +## Missing file system capabilities +## Impossible + +# Health Check +## Start +## Start Failure +## Check Selection +## Detail view +## Fix Application + +# Preferences +## General +## Volume +## Updates +## Contribution +#<-- Add entries for donations and code/translation/documentation contribution --> + +## About + +# Vault Statistics +## Read +## Write + +# Main Window +## Drag 'n' Drop +## Vault List +## Vault Detail +### Welcome +### Locked +### Unlocked +### Missing +### Needs Migration +### Error + +# Wrong File Alert + +# Vault Options +## General + +## Mount +## Master Key + + +# Recovery Key + +# New Password + +# Quit diff --git a/src/main/resources/i18n/strings_he.properties b/src/main/resources/i18n/strings_he.properties index 2a4d51e6c..e5086ac5e 100644 --- a/src/main/resources/i18n/strings_he.properties +++ b/src/main/resources/i18n/strings_he.properties @@ -32,7 +32,7 @@ traymenu.vault.lock=נעילה traymenu.vault.reveal=חשוף # Add Vault Wizard -addvaultwizard.title=הוסף כספת +addvaultwizard.title=יצירת כספת ## Welcome addvaultwizard.welcome.newButton=צור כספת חדשה addvaultwizard.welcome.existingButton=פתח כספת קיימת @@ -43,9 +43,10 @@ addvaultwizard.new.namePrompt=שם הכספת ### Location addvaultwizard.new.locationInstruction=היכן Cryptomator צריך לשמור את הקבצים המוצפנים של הכספת שלך? addvaultwizard.new.locationLabel=מיקום אחסון +addvaultwizard.new.locationPrompt=… addvaultwizard.new.directoryPickerLabel=מיקום מותאם אישית addvaultwizard.new.directoryPickerButton=בחר... -addvaultwizard.new.directoryPickerTitle=בחירת ספרייה +addvaultwizard.new.directoryPickerTitle=בחירת תיקייה addvaultwizard.new.fileAlreadyExists=שם הקובץ או שם התיקייה עם שם הכספת כבר קיים addvaultwizard.new.locationDoesNotExist=מחיצה בנתיב הנקוב לא קיימת או אין אפשרות לקבל אליה גישה addvaultwizard.new.locationIsNotWritable=אין הרשאת כתיבה בנתיב הנקוב @@ -74,32 +75,74 @@ addvault.new.readme.accessLocation.2=זהו מיקום גישה לכספת של addvault.new.readme.accessLocation.3=כל קובץ אשר יצורף לספרייה זו יעבור הצפנה באמצעות Cryptomator. את/ה תוכל/י לעבוד עליו כמו עם כל קבוץ/מחיצה רגילים. זהו מצב הצגה מפוענח של התוכן, הקבצים שלך נשארים מוצפנים על הדיסק הקשיח שלך בכל רגע. addvault.new.readme.accessLocation.4=תרגיש/י בנוח להסיר את הקובץ הזה. ## Existing +addvaultwizard.existing.instruction=בחר את קובץ "vault.cryptomator" של כספת קיימת. אם קיים קובץ בשם "masterkey.cryptomator" בלבד, בחר/י אותו במקום. addvaultwizard.existing.chooseBtn=בחר... +addvaultwizard.existing.filePickerTitle=בחר קובץ כספת ## Success +addvaultwizard.success.nextStepsInstructions=נוספה כספת "%s".\nהנך צריך/ה לבטל נעילת כספת זו בכדי לקבל גישה או להוסיף קבצים. לחילופין תוכל/י לבטל נעילה בכל נקודת זמן מאוחרת יותר. +addvaultwizard.success.unlockNow=בטל נעילה כעת # Remove Vault +removeVault.title=הסר כספת +removeVault.information=זה יגרום ל-Cryptomator לשכוח מהכספת הזו. תוכל/י להוסיף אותה שוב מאוחר יותר. קבצים מוצפנים לא ימחקו מהכונן שלך. +removeVault.confirmBtn=הסר כספת # Change Password +changepassword.title=שנה סיסמה +changepassword.enterOldPassword=הקש את הסיסמה הנוכחית ל-"%s" +changepassword.finalConfirmation=אני מבין/ה שלא אוכל לגשת למידע שלי במקרה ואשכח את הסיסמה שלי # Forget Password +forgetPassword.title=שכח סיסמה +forgetPassword.information=זה ימחק את הסיסמה השמורה של הכספת הזו ממחזיק מפתחות המערכת. +forgetPassword.confirmBtn=שכח סיסמה # Unlock +unlock.title=בטל/י נעילה ל-"%s" +unlock.passwordPrompt=הכנס/י סיסמה ל-"%s": +unlock.savePassword=זכור סיסמה unlock.unlockBtn=בטל נעילה -## +## Select +unlock.chooseMasterkey.title=בחר/י מפתח מאסטר של "%s" +unlock.chooseMasterkey.prompt=מפתח מאסטר של כספת זו לא נמצא במיקום הצפוי. בחר/י בבקשה את המפתח באופן ידני. +unlock.chooseMasterkey.chooseBtn=בחר... +unlock.chooseMasterkey.filePickerTitle=בחר/י קובץ מפתח מאסטר ## Success +unlock.success.message=הנעילה ל-"%s" בוטלה בהצלחה! הכספת שלך נגישה כעת דרך כונן ווירטואלי. +unlock.success.rememberChoice=זכור בחירה, אל תראה שוב +unlock.success.revealBtn=חשוף את הכונן ## Failure +unlock.error.heading=לא מתאפשר לבטל נעילת הכספת ### Invalid Mount Point # Lock ## Force +lock.forced.heading=הנעילה נכשלה +lock.forced.retryBtn=נסה שנית +lock.forced.forceBtn=הכרח נעילה ## Failure +lock.fail.heading=נעילת הכספת נשכלה. # Migration +migration.title=שדרג הכספת ## Start +migration.start.confirm=כן, הכספת שלי מסונכרנת במלואה ## Run +migration.run.enterPassword=הכנס/י סיסמה עבור "%s" +migration.run.startMigrationBtn=העבר הכספת +migration.run.progressHint=הדבר יקח זמן מה… ## Success +migration.success.nextStepsInstructions="%s" הועברה בהצלחה.\nהנך יכול/ה לפתוח את הכספת שלך. +migration.success.unlockNow=בטל נעילה כעת ## Missing file system capabilities +migration.error.missingFileSystemCapabilities.title=מערכת קבצים אינה נתמכת +migration.error.missingFileSystemCapabilities.description=העברה לא החלה, כיוון שהכספת שלך נמצאת במערכת קבצים לא מתאימה. +migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=מערכת קבצים אינה תומכת בשמות קבצים ארוכים. +migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=מערכת קבצים אינה תומכת בנתיבים ארוכים. +migration.error.missingFileSystemCapabilities.reason.READ_ACCESS=מערכת הקבצים אינה מאפשרת את קריאתה. +migration.error.missingFileSystemCapabilities.reason.WRITE_ACCESS=מערכת הקבצים אינה מאפשרת את הכתיבה אליה. ## Impossible +migration.impossible.heading=לא יכול להעביר את הכספת # Health Check ## Start @@ -128,14 +171,19 @@ main.preferencesBtn.tooltip=העדפות ## Drag 'n' Drop ## Vault List main.vaultlist.contextMenu.lock=נעילה -main.vaultlist.addVaultBtn=הוסף כספת +main.vaultlist.contextMenu.unlockNow=בטל נעילה כעת +main.vaultlist.contextMenu.reveal=חשוף את הכונן +main.vaultlist.addVaultBtn=יצירת כספת ## Vault Detail ### Welcome ### Locked +main.vaultDetail.unlockNowBtn=בטל נעילה כעת ### Unlocked +main.vaultDetail.revealBtn=חשוף את הכונן main.vaultDetail.lockBtn=נעילה ### Missing ### Needs Migration +main.vaultDetail.migrateButton=שדרג הכספת ### Error # Wrong File Alert @@ -143,10 +191,12 @@ main.vaultDetail.lockBtn=נעילה # Vault Options ## General vaultOptions.general.vaultName=שם הכספת +vaultOptions.general.actionAfterUnlock.reveal=חשוף את הכונן ## Mount vaultOptions.mount.mountPoint.directoryPickerButton=בחר... ## Master Key +vaultOptions.masterkey.changePasswordBtn=שנה סיסמה # Recovery Key diff --git a/src/main/resources/i18n/strings_hi.properties b/src/main/resources/i18n/strings_hi.properties index 26fe68bda..44988d255 100644 --- a/src/main/resources/i18n/strings_hi.properties +++ b/src/main/resources/i18n/strings_hi.properties @@ -58,6 +58,7 @@ addvault.new.readme.storageLocation.5=• • इस डायरेक्ट ## Existing addvaultwizard.existing.chooseBtn=चुनें… ## Success +addvaultwizard.success.unlockNow=अब अनलॉक करें # Remove Vault @@ -68,7 +69,8 @@ changepassword.title=पासवर्ड बदलें # Unlock unlock.unlockBtn=अनलॉक करें -## +## Select +unlock.chooseMasterkey.chooseBtn=चुनें… ## Success ## Failure ### Invalid Mount Point @@ -83,6 +85,7 @@ migration.title=वाउल्ट को अपग्रेड करें ## Start ## Run ## Success +migration.success.unlockNow=अब अनलॉक करें ## Missing file system capabilities ## Impossible @@ -114,10 +117,12 @@ main.preferencesBtn.tooltip=प्राथमिकताएं ## Drag 'n' Drop ## Vault List main.vaultlist.contextMenu.lock=लॉक करें +main.vaultlist.contextMenu.unlockNow=अब अनलॉक करें main.vaultlist.addVaultBtn=वाउल्ट डालें ## Vault Detail ### Welcome ### Locked +main.vaultDetail.unlockNowBtn=अब अनलॉक करें main.vaultDetail.optionsBtn=वॉल्ट के विकल्प ### Unlocked main.vaultDetail.accessLocation=आपके वॉल्ट की चीजें यहाँ एक्सेस कर सकतें हैं: diff --git a/src/main/resources/i18n/strings_hr.properties b/src/main/resources/i18n/strings_hr.properties index 1b7af9c4d..ca14a896f 100644 --- a/src/main/resources/i18n/strings_hr.properties +++ b/src/main/resources/i18n/strings_hr.properties @@ -25,7 +25,7 @@ # Forget Password # Unlock -## +## Select ## Success ## Failure ### Invalid Mount Point diff --git a/src/main/resources/i18n/strings_hu.properties b/src/main/resources/i18n/strings_hu.properties index ff18ab346..8ade80875 100644 --- a/src/main/resources/i18n/strings_hu.properties +++ b/src/main/resources/i18n/strings_hu.properties @@ -102,8 +102,9 @@ unlock.title="%s" feloldása unlock.passwordPrompt=Írja be a jelszavát a következő széfhez "%s": unlock.savePassword=Jelszó megjegyzése unlock.unlockBtn=Feloldás -## +## Select unlock.chooseMasterkey.prompt=Nem található a tároló kulcsfájlja a várt helyen. Kérjük válassza ki a kulcsfájlt manuálisan. +unlock.chooseMasterkey.chooseBtn=Választás… unlock.chooseMasterkey.filePickerTitle=Mesterkulcs fájl kiválasztása ## Success unlock.success.message="%s" sikreresen feloldásra került! Mostmár hozzáférhet a virtuális trezorhoz. diff --git a/src/main/resources/i18n/strings_id.properties b/src/main/resources/i18n/strings_id.properties index 8b4d9e8df..0b2bbbb1d 100644 --- a/src/main/resources/i18n/strings_id.properties +++ b/src/main/resources/i18n/strings_id.properties @@ -13,66 +13,66 @@ generic.button.done=Selesai generic.button.next=Lanjut generic.button.print=Cetak ## Error -generic.error.title=Kesalahan %s -generic.error.instruction=Ups! Cryptomator tidak mengharapkan ini terjadi. Anda dapat mencari solusi yang ada untuk kesalahan ini. Atau jika belum dilaporkan, jangan ragu untuk melakukannya. -generic.error.hyperlink.lookup=Cari kesalahan -generic.error.hyperlink.report=Laporkan kesalahan +generic.error.title=Error %s +generic.error.instruction=Ups! Cryptomator tidak menyangka hal ini terjadi. Anda dapat mencari solusi yang tersedia untuk error ini. Atau jika error ini belum pernah dilaporkan, tidak perlu sungkan untuk melaporkannya. +generic.error.hyperlink.lookup=Cari error berikut +generic.error.hyperlink.report=Laporkan error berikut generic.error.technicalDetails=Rincian: # Defaults -defaults.vault.vaultName=Brankas +defaults.vault.vaultName=Vault # Tray Menu traymenu.showMainWindow=Tampilkan traymenu.showPreferencesWindow=Preferensi -traymenu.lockAllVaults=Gembok Semua +traymenu.lockAllVaults=Kunci Semua traymenu.quitApplication=Keluar -traymenu.vault.unlock=Buka Gembok -traymenu.vault.lock=Gembok +traymenu.vault.unlock=Buka Kunci +traymenu.vault.lock=Kunci traymenu.vault.reveal=Perlihatkan # Add Vault Wizard -addvaultwizard.title=Tambah Brankas +addvaultwizard.title=Tambah Vault ## Welcome -addvaultwizard.welcome.newButton=Buat Brankas Baru -addvaultwizard.welcome.existingButton=Buka Brankas yg Sudah Ada +addvaultwizard.welcome.newButton=Buat Vault Baru +addvaultwizard.welcome.existingButton=Buka Vault yang Tersedia ## New ### Name -addvaultwizard.new.nameInstruction=Buat sebuah nama untuk brankas -addvaultwizard.new.namePrompt=Nama Brankas +addvaultwizard.new.nameInstruction=Beri nama untuk vault berikut +addvaultwizard.new.namePrompt=Nama Vault ### Location -addvaultwizard.new.locationInstruction=Dimana Cryptomator seharusnya menyimpan brankas berkas terenkripsi kamu? +addvaultwizard.new.locationInstruction=Dimana Cryptomator seharusnya menyimpan vault berisi file enkripsi Anda? addvaultwizard.new.locationLabel=Lokasi penyimpanan addvaultwizard.new.locationPrompt=… -addvaultwizard.new.directoryPickerLabel=Sesuaikan Lokasi +addvaultwizard.new.directoryPickerLabel=Lokasi khusus addvaultwizard.new.directoryPickerButton=Pilih… -addvaultwizard.new.directoryPickerTitle=Pilih Folder -addvaultwizard.new.fileAlreadyExists=Sudah ada file atau direktori dengan nama yang sama -addvaultwizard.new.locationDoesNotExist=Direktori pada path yang dipilih tidak ada atau tidak dapat diakses +addvaultwizard.new.directoryPickerTitle=Pilih Direktori +addvaultwizard.new.fileAlreadyExists=Sudah ada file atau direktori dengan nama vault tersebut +addvaultwizard.new.locationDoesNotExist=Direktori pada path yang dipilih tidak tersedia atau tidak dapat diakses addvaultwizard.new.locationIsNotWritable=Anda tidak memiliki hak akses untuk menulis pada path yang dipilih addvaultwizard.new.locationIsOk=Lokasi yang sesuai dengan vault Anda -addvaultwizard.new.invalidName=Nama brankas salah. Harap pilih nama folder yang umum. +addvaultwizard.new.invalidName=Nama vault tidak sesuai. Harap pilih nama direktori yang umum digunakan. ### Password -addvaultwizard.new.createVaultBtn=Buat Brankas -addvaultwizard.new.generateRecoveryKeyChoice=Kamu tidak dapat mengakses data tanpa kata sandi kamu. Apa kamu ingin sebuah kunci pemulihan buat jaga-jaga? -addvaultwizard.new.generateRecoveryKeyChoice.yes=Ya, sedia payung sebelum hujan -addvaultwizard.new.generateRecoveryKeyChoice.no=Tidak, terima kasih, Saya tidak akan kehilangan kata sandi saya +addvaultwizard.new.createVaultBtn=Buat Vault +addvaultwizard.new.generateRecoveryKeyChoice=Anda tidak dapat mengakses data tanpa kata sandi yang Anda miliki. Apa Anda ingin sebuah kunci pemulihan untuk berjaga-jaga jika seandainya Anda kehilangan kata sandi? +addvaultwizard.new.generateRecoveryKeyChoice.yes=Ya tolong, Lebih baik aman daripada menyesal +addvaultwizard.new.generateRecoveryKeyChoice.no=Tidak terima kasih, Saya tidak akan kehilangan kata sandi saya ### Information addvault.new.readme.storageLocation.fileName=PENTING.rtf -addvault.new.readme.storageLocation.1=⚠️ BERKAS BRANKAS ⚠️ -addvault.new.readme.storageLocation.2=Ini adalah lokasi penyimpanan brankas kamu. -addvault.new.readme.storageLocation.3=JANGAN -addvault.new.readme.storageLocation.4=• mengubah file dalam direktori ini, atau -addvault.new.readme.storageLocation.5=• menempelkan file untuk dienkripsi ke dalam direktori ini. -addvault.new.readme.storageLocation.6=Jika kamu ingin mengenkripsi berkas dan melihat isi brankas, lakukan hal berikut: -addvault.new.readme.storageLocation.7=1. Tambahkan brankas ini ke Cryptomator. -addvault.new.readme.storageLocation.8=2. Buka gembok brankas di Cryptomator. -addvault.new.readme.storageLocation.9=3. Buka akses lokasi dengan mengklik tombol "Perlihatkan". -addvault.new.readme.storageLocation.10=Jika kamu butuh bantuan, kunjungi dokumentasi: %s -addvault.new.readme.accessLocation.fileName=SELAMAT_DATANG.rtf -addvault.new.readme.accessLocation.1=🔐️ ISI TERENKRIPSI 🔐️ -addvault.new.readme.accessLocation.2=Ini adalah lokasi akses brankas kamu. -addvault.new.readme.accessLocation.3=File yang ditambahkan ke volume ini akan dienkripsi oleh Cryptomator. Anda dapat mempergunakan isi vault seperti dalam folder lain. Saat ini Anda sedang mengakses tampilan versi dekripsi, file Anda selalu terenkripsi di dalam cakram keras Anda. +addvault.new.readme.storageLocation.1=⚠️ FILE VAULT ⚠️ +addvault.new.readme.storageLocation.2=Ini adalah lokasi penyimpanan vault Anda. +addvault.new.readme.storageLocation.3=DILARANG +addvault.new.readme.storageLocation.4=• mengubah file apapun di direktori ini, atau +addvault.new.readme.storageLocation.5=• menyalin file untuk dienkripsi ke dalam direktori ini. +addvault.new.readme.storageLocation.6=Jika Anda ingin mengenkripsi file dan melihat isi konten dari vault, lakukan hal berikut: +addvault.new.readme.storageLocation.7=1. Tambahkan vault ini ke Cryptomator. +addvault.new.readme.storageLocation.8=2. Buka vault melalui Cryptomator. +addvault.new.readme.storageLocation.9=3. Buka lokasi akses dengan mengklik tombol "Perlihatkan". +addvault.new.readme.storageLocation.10=Jika Anda butuh bantuan, kunjungi dokumentasi: %s +addvault.new.readme.accessLocation.fileName=SELAMAT DATANG.rtf +addvault.new.readme.accessLocation.1=🔐️ VOLUME TERENKRIPSI 🔐️ +addvault.new.readme.accessLocation.2=Ini adalah lokasi akses dari vault Anda. +addvault.new.readme.accessLocation.3=Seluruh file yang ditambahkan ke volume ini akan dienkripsi oleh Cryptomator. Anda dapat mempergunakan isi vault seperti dalam folder/drive lain pada umumnya. Tampilan yang saat ini Anda lihat adalah tampilan dalam bentuk terdekripsi, yang sebenarnya terjadi adalah file-file Anda selalu dalam kondisi terenkripsi di dalam hard drive Anda. addvault.new.readme.accessLocation.4=Anda dapat menghapus file ini. ## Existing addvaultwizard.existing.instruction=Pilih file "vault.cryptomator" Anda dari vault yang ada. Jika hanya ada file bernama "masterkey.cryptomator", pilih file tersebut. @@ -84,46 +84,49 @@ addvaultwizard.success.unlockNow=Buka Kunci Sekarang # Remove Vault removeVault.title=Hapus Vault -removeVault.information=Cryptomator hanya akan melupakan vault ini. Anda dapat menambahkan vault ini lagi nantinya. File yang telah dienkripsi tidak akan dihapus dari cakram keras Anda. +removeVault.information=Tindakan ini hanya akan membuat Cryptomator melupakan vault ini. Anda dapat menambahkan vault ini lagi nanti. File yang telah dienkripsi tidak akan dihapus dari hard drive Anda. removeVault.confirmBtn=Hapus Vault # Change Password changepassword.title=Ubah Kata Sandi -changepassword.enterOldPassword=Masukkan kata sandi untuk "%s" saat ini +changepassword.enterOldPassword=Masukkan kata sandi "%s" saat ini changepassword.finalConfirmation=Saya mengerti bahwa saya tidak akan dapat mengakses data saya apabila saya lupa kata sandi saya # Forget Password forgetPassword.title=Lupa Kata Sandi -forgetPassword.information=Kata sandi vault yang tersimpan akan dihapus dari keychain. +forgetPassword.information=Tindakan ini akan menghapus kata sandi vault berikut yang tersimpan dari sistem keychain Anda. forgetPassword.confirmBtn=Lupa Kata Sandi # Unlock -unlock.title=Membuka "%s" -unlock.passwordPrompt=Masukkan kata sandi untuk "%s": -unlock.savePassword=Simpan Kata Sandi -unlock.unlockBtn=Buka Gembok -## +unlock.title=Buka Kunci "%s" +unlock.passwordPrompt=Masukkan kata sandi "%s": +unlock.savePassword=Ingat Kata Sandi +unlock.unlockBtn=Buka Kunci +## Select +unlock.chooseMasterkey.title=Pilih Masterkey of "%s" unlock.chooseMasterkey.prompt=Tidak dapat menemukan file masterkey untuk vault ini pada lokasi yang dicari. Mohon pilih file kunci secara manual. -unlock.chooseMasterkey.filePickerTitle=Pilih File Kunci Induk +unlock.chooseMasterkey.chooseBtn=Pilih… +unlock.chooseMasterkey.filePickerTitle=Pilih File Masterkey ## Success -unlock.success.message="%s" telat terbuka! Vault Anda sekarang dapat diakses melalui drive virtual. +unlock.success.message="%s" berhasil dibuka! Vault Anda sekarang dapat diakses melalui drive virtual. unlock.success.rememberChoice=Ingat pilihan saya, jangan perlihatkan lagi -unlock.success.revealBtn=Buka Drive +unlock.success.revealBtn=Tampilkan Drive ## Failure unlock.error.heading=Tidak dapat membuka vault ### Invalid Mount Point -unlock.error.invalidMountPoint.notExisting=Poin mount "%s" bukan direktori, dan tidak kosong atau tidak ada. -unlock.error.invalidMountPoint.existing=Poin mount "%s" sudah ada atau folder induknya tidak ada. +unlock.error.invalidMountPoint.notExisting=Mount point "%s" bukanlah sebuah direktori, tidak sedang kosong, atau bahkan tidak ada sama sekali. +unlock.error.invalidMountPoint.existing=Mount point "%s" sudah ada atau folder induknya tidak ditemukan. +unlock.error.invalidMountPoint.driveLetterOccupied=Drive Letter "%s" sedang digunakan. # Lock ## Force lock.forced.heading=Gagal mengunci -lock.forced.message=Penguncian "%s" terblokir oleh operasi yang sedang berjalan atau file yang masih terbuka. Anda dapat mengunci paksa vault ini, namun ada kemungkinan mengganggu I/O akan menghilangkan data yang belum disimpan. +lock.forced.message=Penguncian "%s" terblokir oleh operasi yang sedang berjalan atau file yang masih terbuka. Anda dapat mengunci paksa vault ini, namun ada kemungkinan mengganggu I/O yang berakibat kehilangan data yang belum disimpan. lock.forced.retryBtn=Coba lagi -lock.forced.forceBtn=Kunci Paksa +lock.forced.forceBtn=Paksa Kunci ## Failure lock.fail.heading=Gagal mengunci vault. -lock.fail.message=Vault "%s" tidak dapat dikunci. Pastikan bahwa file yang belum tersimpan telah disimpan di lokasi lain, dan operasi Baca/Tulis yang penting telah selesai. Untuk menutup vault ini, matikan proses Cryptomator. +lock.fail.message=Vault "%s" tidak dapat dikunci. Pastikan bahwa file yang belum tersimpan telah disimpan di lokasi lain, dan operasi Baca/Tulis telah selesai dilakukan. Untuk menutup vault ini, matikan proses Cryptomator. # Migration migration.title=Tingkatkan Vault @@ -146,17 +149,17 @@ migration.error.missingFileSystemCapabilities.reason.READ_ACCESS=Sistem file tid migration.error.missingFileSystemCapabilities.reason.WRITE_ACCESS=Sistem file tidak mengizinkan untuk ditulis. ## Impossible migration.impossible.heading=Tidak dapat memindahkan vault -migration.impossible.reason=Vault tidak dapat dipindahkan secara otomatis karena lokasi penyimpanan atau poin akses tidak sesuai. -migration.impossible.moreInfo=Vault dapat dibuka dengan program versi lebih lama. Apabila Anda ingin memindahkan vault secara manual, silahkan buka +migration.impossible.reason=Vault tidak dapat dipindahkan secara otomatis karena lokasi penyimpanan atau access point tidak sesuai. +migration.impossible.moreInfo=Vault masih bisa dibuka dengan aplikasi versi terdahulu. Untuk panduan memindahkan vault secara manual, silahkan buka # Health Check ## Start health.title=Pemeriksaan Kesehatan "%s" health.intro.header=Pemeriksaan Kesehatan -health.intro.text=Pemeriksaan Kesehatan adalah kumpulan pemeriksaan untuk mendeteksi dan mungkin memperbaiki masalah dalam struktur internal brankas Anda. Harap diingat: -health.intro.remarkSync=Pastikan semua perangkat disinkronkan sepenuhnya, ini menyelesaikan sebagian besar masalah. +health.intro.text=Pemeriksaan Kesehatan adalah kumpulan pemeriksaan untuk mendeteksi dan memperbaiki masalah di dalam struktur internal vault Anda. Perlu diperhatikan: +health.intro.remarkSync=Pastikan semua perangkat tersinkronisasi sepenuhnya, sebagian besar masalah teratasi dengan cara ini. health.intro.remarkFix=Tidak semua masalah bisa diperbaiki. -health.intro.remarkBackup=Jika data rusak, hanya cadangan yang dapat membantu. +health.intro.remarkBackup=Jika data rusak, hanya cadangan yang dapat memulihkannya. health.intro.affirmation=Saya telah membaca dan memahami informasi di atas ## Start Failure health.fail.header=Kesalahan saat memuat Konfigurasi Vault @@ -167,14 +170,14 @@ health.fail.moreInfo=Info Selengkapnya health.checkList.description=Centang daftar di bagian kiri atau gunakan tombol di bawah. health.checkList.selectAllButton=Centang Semua health.checkList.deselectAllButton=Jangan Centang Semua -health.check.runBatchBtn=Cek Jalankan yang dipilih +health.check.runBatchBtn=Jalankan Centang yang Terpilih ## Detail view -health.check.detail.noSelectedCheck=Untuk hasil, pilih pemeriksaan kesehatan yang sudah selesai di sebelah kiri. -health.check.detail.checkScheduled=Pemeriksaan dijadwalkan. +health.check.detail.noSelectedCheck=Pilih pemeriksaan kesehatan yang sudah selesai di sebelah kiri untuk melihat hasilnya. +health.check.detail.checkScheduled=Pemeriksaan telah terjadwal. health.check.detail.checkRunning=Saat ini pemeriksaan sedang berjalan… -health.check.detail.checkSkipped=Pemeriksaan tidak dipilih untuk dijalankan. -health.check.detail.checkFinished=Pemeriksaan berhasil diselesaikan. -health.check.detail.checkFinishedAndFound=Pemeriksaan selesai berjalan. Harap tinjau hasilnya. +health.check.detail.checkSkipped=Pemeriksaan tidak dajalankan karena tidak dipilih. +health.check.detail.checkFinished=Pemeriksaan berhasil dilakukan. +health.check.detail.checkFinishedAndFound=Pemeriksaan selesai. Silahkan tinjau hasilnya. health.check.detail.checkFailed=Pemeriksaan terhenti karena terjadi kesalahan. health.check.detail.checkCancelled=Pemeriksaan dibatalkan. health.check.exportBtn=Ekspor Laporan @@ -187,17 +190,17 @@ health.fix.failTip=Perbaikan gagal, lihat log untuk detailnya preferences.title=Preferensi ## General preferences.general=Umum -preferences.general.theme=Lihat dan rasakan +preferences.general.theme=Tampilan & Suasana preferences.general.theme.automatic=Otomatis preferences.general.theme.light=Terang preferences.general.theme.dark=Gelap preferences.general.unlockThemes=Buka mode gelap -preferences.general.showMinimizeButton=Perkecil tampilan tombol -preferences.general.showTrayIcon=\nTampilkan ikon baki (memerlukan mulai ulang) -preferences.general.startHidden=Sembunyikan halaman saat memulai Cryptomator +preferences.general.showMinimizeButton=Tampilkan tombol perkecil +preferences.general.showTrayIcon=Tampilkan tray icon (diperlukan muat ulang) +preferences.general.startHidden=Sembunyikan jendela saat memulai Cryptomator preferences.general.debugLogging=Aktifkan pencatatan debug -preferences.general.debugDirectory=Buka file log -preferences.general.autoStart=Luncurkan Cryptomator pada awal sistem +preferences.general.debugDirectory=Perlihatkan file log +preferences.general.autoStart=Jalankan Cryptomator saat sistem dimulai preferences.general.keychainBackend=Simpan kata sandi dengan preferences.general.interfaceOrientation=Orientasi Antarmuka preferences.general.interfaceOrientation.ltr=Kiri ke kanan @@ -205,20 +208,20 @@ preferences.general.interfaceOrientation.rtl=Kanan ke kiri ## Volume preferences.volume=Drive Virtual preferences.volume.type=Jenis Volume -preferences.volume.webdav.port=Kabel WebDAV +preferences.volume.webdav.port=Port WebDAV preferences.volume.webdav.scheme=Skema WebDAV ## Updates preferences.updates=Pembaharuan -preferences.updates.currentVersion=Versi Sekarang: %s -preferences.updates.autoUpdateCheck=Periksa untuk update otomatis +preferences.updates.currentVersion=Versi Saat Ini: %s +preferences.updates.autoUpdateCheck=Otomatis periksa update preferences.updates.checkNowBtn=Periksa Sekarang preferences.updates.updateAvailable=Pembaharuan ke versi %s tersedia. ## Contribution preferences.contribute=Dukung Kami -preferences.contribute.registeredFor=Sertifikat pendukung terdaftar untuk %s -preferences.contribute.noCertificate=Dukung Cryptomator dan terima sertifikat pendukung. Ini seperti kunci lisensi tetapi untuk orang-orang hebat yang menggunakan perangkat lunak gratis. ;-) +preferences.contribute.registeredFor=Sertifikat supporter terdaftar atas nama %s +preferences.contribute.noCertificate=Dukung Cryptomator dan terima sebuah sertifikat supporter. Sertifikat ini layaknya kunci lisensi, tetapi hanya untuk orang-orang hebat yang menggunakan aplikasi versi gratis. ;-) preferences.contribute.getCertificate=Belum punya? Pelajari bagaimana Anda bisa mendapatkannya. -preferences.contribute.promptText=Tempel kode sertifikat pendukung di sini +preferences.contribute.promptText=Tempel kode sertifikat supporter di sini #<-- Add entries for donations and code/translation/documentation contribution --> ## About @@ -228,22 +231,22 @@ preferences.about=Tentang stats.title=Statistik untuk %s stats.cacheHitRate=Tingkat Hit Cache ## Read -stats.read.throughput.idle=Read: idle -stats.read.throughput.kibs=Read: %.2f kiB/s -stats.read.throughput.mibs=Read: %.2f MiB/s -stats.read.total.data.none=Data read: - -stats.read.total.data.kib=Data read: %.1f kiB -stats.read.total.data.mib=Data read: %.1f MiB -stats.read.total.data.gib=Data read: %.1f GiB -stats.decr.total.data.none=Data decrypted: - -stats.decr.total.data.kib=Data decrypted: %.1f kiB -stats.decr.total.data.mib=Data decrypted: %.1f MiB -stats.decr.total.data.gib=Data decrypted: %.1f GiB -stats.read.accessCount=Total reads: %d +stats.read.throughput.idle=Baca: diam +stats.read.throughput.kibs=Baca: %.2f kiB/detik +stats.read.throughput.mibs=Baca: %.2f MiB/detik +stats.read.total.data.none=Data dibaca: - +stats.read.total.data.kib=Data dibaca: %.1f kiB +stats.read.total.data.mib=Data dibaca: %.1f MiB +stats.read.total.data.gib=Data dibaca: %.1f GiB +stats.decr.total.data.none=Data terdekripsi: - +stats.decr.total.data.kib=Data terdekripsi: %.1f kiB +stats.decr.total.data.mib=Data terdeksripsi: %.1f MiB +stats.decr.total.data.gib=Data terdeksripsi: %.1f GiB +stats.read.accessCount=Total dibaca: %d ## Write -stats.write.throughput.idle=Write: idle -stats.write.throughput.kibs=Write: %.2f kiB/s -stats.write.throughput.mibs=Write: %.2f MiB/s +stats.write.throughput.idle=Tulis: diam +stats.write.throughput.kibs=Tulis: %.2f kiB/detik +stats.write.throughput.mibs=Tulis: %.2f MiB/detik stats.write.total.data.none=Data tertulis: - stats.write.total.data.kib=Data tertulis: %.1f kiB stats.write.total.data.mib=Data tertulis: %.1f MiB @@ -252,22 +255,22 @@ stats.encr.total.data.none=Data terenkripsi: - stats.encr.total.data.kib=Data terenkripsi: %.1f kiB stats.encr.total.data.mib=Data terenkripsi: %.1f MiB stats.encr.total.data.gib=Data terenkripsi: %.1f GiB -stats.write.accessCount=Total menulis: %d +stats.write.accessCount=Total ditulis: %d # Main Window main.closeBtn.tooltip=Tutup -main.minimizeBtn.tooltip=Minimalkan +main.minimizeBtn.tooltip=Perkecil main.preferencesBtn.tooltip=Preferensi main.debugModeEnabled.tooltip=Mode Debug diaktifkan -main.donationKeyMissing.tooltip=Pertimbangjan untuk donasi +main.donationKeyMissing.tooltip=Tolong pertimbangkan untuk melakukan donasi ## Drag 'n' Drop main.dropZone.dropVault=Tambah vault ini main.dropZone.unknownDragboardContent=Jika Anda ingin menambahkan vault, seret ke jendela ini ## Vault List -main.vaultlist.emptyList.onboardingInstruction=Klik untuk tambah vault +main.vaultlist.emptyList.onboardingInstruction=Klik di sini untuk menambahkan vault main.vaultlist.contextMenu.remove=Hapus… -main.vaultlist.contextMenu.lock=Gembok -main.vaultlist.contextMenu.unlock=Membuka… +main.vaultlist.contextMenu.lock=Kunci +main.vaultlist.contextMenu.unlock=Buka Kunci… main.vaultlist.contextMenu.unlockNow=Buka Kunci Sekarang main.vaultlist.contextMenu.vaultoptions=Tampilkan Opsi Vault main.vaultlist.contextMenu.reveal=Buka Drive @@ -277,7 +280,7 @@ main.vaultlist.addVaultBtn=Tambah Brankas main.vaultDetail.welcomeOnboarding=Terima kasih telah memilih Cryptomator untuk melindungi file Anda. Jika Anda memerlukan bantuan, lihat panduan awal kami: ### Locked main.vaultDetail.lockedStatus=TERKUNCI -main.vaultDetail.unlockBtn=Membuka… +main.vaultDetail.unlockBtn=Buka Kunci… main.vaultDetail.unlockNowBtn=Buka Kunci Sekarang main.vaultDetail.optionsBtn=Opsi Vault main.vaultDetail.passwordSavedInKeychain=Kata Sandi tersimpan @@ -332,10 +335,10 @@ vaultOptions.general.startHealthCheckBtn=Mulai Pemeriksaan Kesehatan vaultOptions.mount=Pemasangan vaultOptions.mount.readonly=Read-Only vaultOptions.mount.customMountFlags=Custom Mount Flags -vaultOptions.mount.winDriveLetterOccupied=terisi +vaultOptions.mount.winDriveLetterOccupied=terpakai vaultOptions.mount.mountPoint=Titik Pasang vaultOptions.mount.mountPoint.auto=Secara otomatis memilih lokasi yang sesuai -vaultOptions.mount.mountPoint.driveLetter=Gunakan huruf drive yang ditetapkan +vaultOptions.mount.mountPoint.driveLetter=Gunakan drive letter yang sudah ditetapkan vaultOptions.mount.mountPoint.custom=Path khusus vaultOptions.mount.mountPoint.directoryPickerButton=Pilih… vaultOptions.mount.mountPoint.directoryPickerTitle=Pilih direktori kosong @@ -370,5 +373,5 @@ passwordStrength.messageLabel.3=Kuat passwordStrength.messageLabel.4=Sangat kuat # Quit -quit.prompt=Keluar aplikasi? Terdapat brankas yg belum digembok. -quit.lockAndQuit=Gembok dan Keluar +quit.prompt=Keluar aplikasi? Masih ada vault yg belum dikunci. +quit.lockAndQuit=Kunci dan Keluar diff --git a/src/main/resources/i18n/strings_it.properties b/src/main/resources/i18n/strings_it.properties index 51adc6e18..c2aebe911 100644 --- a/src/main/resources/i18n/strings_it.properties +++ b/src/main/resources/i18n/strings_it.properties @@ -102,8 +102,10 @@ unlock.title=Sblocca "%s" unlock.passwordPrompt=Inserisci la password per "%s": unlock.savePassword=Ricorda la Password unlock.unlockBtn=Sblocca -## +## Select +unlock.chooseMasterkey.title=Seleziona Masterkey di "%s" unlock.chooseMasterkey.prompt=Impossibile trovare il file Masterkey per questa cassaforte alla sua posizione prevista. Sei pregato di sceglierlo manualmente. +unlock.chooseMasterkey.chooseBtn=Scegli… unlock.chooseMasterkey.filePickerTitle=Seleziona il File Masterkey ## Success unlock.success.message="%s" sbloccato correttamente! La tua cassaforte è ora accessibile tramite la sua unità virtuale. @@ -114,11 +116,14 @@ unlock.error.heading=Impossibile sbloccare la cassaforte ### Invalid Mount Point unlock.error.invalidMountPoint.notExisting=Il punto di montaggio "%s" non è una cartella, non è vuoto o non esiste. unlock.error.invalidMountPoint.existing=Il punto di montaggio "%s" esiste già o la cartella madre è mancante. +unlock.error.invalidMountPoint.driveLetterOccupied=La lettera di unità "%s" è già in uso. # Lock ## Force +lock.forced.heading=Blocco non riuscito lock.forced.message=Il bloccaggio di "%s" è stato impedito dalle operazioni in sospeso o dai file aperti. Puoi forzare il blocco di questa cassaforte, tuttavia, interrompere I/O potrebbe risultare nella perdita dei dati non salvati. lock.forced.retryBtn=Riprova +lock.forced.forceBtn=Forza Blocco ## Failure lock.fail.heading=Blocco della cassaforte fallito. lock.fail.message=Impossibile bloccare la cassaforte "%s". Assicurati che il lavoro non salvato sia salvato altrove e che le importanti operazioni di Lettura/Scrittura siano terminate. Per chiudere la cassaforte, termina il processo di Cryptomator. diff --git a/src/main/resources/i18n/strings_ja.properties b/src/main/resources/i18n/strings_ja.properties index dfda53a95..944d05701 100644 --- a/src/main/resources/i18n/strings_ja.properties +++ b/src/main/resources/i18n/strings_ja.properties @@ -35,7 +35,7 @@ traymenu.vault.reveal=表示 addvaultwizard.title=金庫を追加 ## Welcome addvaultwizard.welcome.newButton=新しい金庫を作成 -addvaultwizard.welcome.existingButton=すでにある金庫を開く +addvaultwizard.welcome.existingButton=既存の金庫を開く ## New ### Name addvaultwizard.new.nameInstruction=金庫の名前を入力してください @@ -51,7 +51,7 @@ addvaultwizard.new.fileAlreadyExists=金庫名と同じ名前のファイルま addvaultwizard.new.locationDoesNotExist=指定されたパスのディレクトリが存在しないかアクセスできません addvaultwizard.new.locationIsNotWritable=指定されたパスに書き込み権限がありません addvaultwizard.new.locationIsOk=金庫に適した場所 -addvaultwizard.new.invalidName=無効な金庫の名前です。一般的なディレクトリの名前を検討してください。 +addvaultwizard.new.invalidName=使用できない金庫の名前です。一般的なディレクトリの名前にしてください。 ### Password addvaultwizard.new.createVaultBtn=金庫を作成 addvaultwizard.new.generateRecoveryKeyChoice=データにアクセスするにはパスワードが必須です。パスワードを紛失したときのためにリカバリーキーは必要ですか? @@ -102,18 +102,21 @@ unlock.title="%s" を解錠 unlock.passwordPrompt="%s" のパスワードを入力してください: unlock.savePassword=パスワードを記憶させる unlock.unlockBtn=解錠 -## +## Select +unlock.chooseMasterkey.title="%s" の Masterkey ファイルを選択 unlock.chooseMasterkey.prompt=必要な場所にこの金庫の masterkey ファイルがありませんでした。ファイルを手動で選択してください。 +unlock.chooseMasterkey.chooseBtn=選択... unlock.chooseMasterkey.filePickerTitle=Masterkey ファイルを選択 ## Success unlock.success.message="%s" の解錠に成功しました! 仮想ドライブから金庫にアクセス可能です。 unlock.success.rememberChoice=選択を記憶させて、再度表示しない -unlock.success.revealBtn=ドライブを表示 +unlock.success.revealBtn=金庫内を表示 ## Failure unlock.error.heading=金庫の解錠に失敗 ### Invalid Mount Point unlock.error.invalidMountPoint.notExisting=マウントポイントが空のディレクトリか存在していません: %s unlock.error.invalidMountPoint.existing=マウント ポイント "%s" が既に存在するか、親フォルダーがありません。 +unlock.error.invalidMountPoint.driveLetterOccupied=ドライブレター「%s」は既に使用されています。 # Lock ## Force @@ -187,21 +190,21 @@ health.fix.failTip=修正に失敗しました。詳細はログを参照して preferences.title=設定 ## General preferences.general=基本設定 -preferences.general.theme=外見 & 操作性 +preferences.general.theme=外見と操作性 preferences.general.theme.automatic=自動 preferences.general.theme.light=ライト preferences.general.theme.dark=ダーク -preferences.general.unlockThemes=ダークモードの解錠 +preferences.general.unlockThemes=ダークモードを解除 preferences.general.showMinimizeButton=最小化ボタンを表示 preferences.general.showTrayIcon=トレイアイコンを表示 (再起動が必要) preferences.general.startHidden=Cryptomator を開始したときウィンドウを隠す preferences.general.debugLogging=ログを有効にする -preferences.general.debugDirectory=ログ ファイルを表示 +preferences.general.debugDirectory=ログファイルを表示 preferences.general.autoStart=システム開始時にCryptomatorを起動する preferences.general.keychainBackend=次を利用してパスワードを保存する preferences.general.interfaceOrientation=インターフェイスの向き -preferences.general.interfaceOrientation.ltr=左から右 -preferences.general.interfaceOrientation.rtl=右から左 +preferences.general.interfaceOrientation.ltr=左横書き +preferences.general.interfaceOrientation.rtl=右横書き ## Volume preferences.volume=仮想ドライブ preferences.volume.type=マウント方法 @@ -214,11 +217,11 @@ preferences.updates.autoUpdateCheck=自動的に更新を確認する preferences.updates.checkNowBtn=今すぐ確認 preferences.updates.updateAvailable=利用可能なバージョン %s に更新します。 ## Contribution -preferences.contribute=サポートする +preferences.contribute=支援する preferences.contribute.registeredFor=サポーター証明書が %s に登録されました -preferences.contribute.noCertificate=Cryptomator を支援し、サポーター証明書を受け取りましょう。ライセンスキーに似ていますがフリーソフトを使う寄付者向けのキーです。 ;-) +preferences.contribute.noCertificate=Cryptomator を支援し、サポーター証明書を受け取りましょう。ライセンスキーに似ていますが自由ソフトウェアを使う寄付者向けのキーです。 ;-) preferences.contribute.getCertificate=まだ証明書を手に入れていませんか? 詳細はこちらから確認できます。 -preferences.contribute.promptText=サポーター証明書をここに張り付けてください +preferences.contribute.promptText=サポーター証明書をここに貼り付け #<-- Add entries for donations and code/translation/documentation contribution --> ## About @@ -274,7 +277,7 @@ main.vaultlist.contextMenu.reveal=ドライブを表示 main.vaultlist.addVaultBtn=金庫を追加 ## Vault Detail ### Welcome -main.vaultDetail.welcomeOnboarding=ファイル保護するために Cryptomator を選んでいただきありがとうございます。ヘルプが必要であれば、スタートガイドをご覧ください: +main.vaultDetail.welcomeOnboarding=ファイルを保護するために Cryptomator を選んでいただきありがとうございます。ヘルプが必要であれば、スタートガイドをご覧ください: ### Locked main.vaultDetail.lockedStatus=施錠済み main.vaultDetail.unlockBtn=解錠... @@ -293,7 +296,7 @@ main.vaultDetail.throughput.kbps=%.1f kiB/s main.vaultDetail.throughput.mbps=%.1f MiB/s main.vaultDetail.stats=金庫の統計情報 ### Missing -main.vaultDetail.missing.info=Cryptomator はこのパスの金庫を見つけることができませんでした。 +main.vaultDetail.missing.info=Cryptomator はこの場所に金庫を見つけることができませんでした。 main.vaultDetail.missing.recheck=再確認 main.vaultDetail.missing.remove=金庫のリストから削除... main.vaultDetail.missing.changeLocation=金庫の場所を変更... @@ -343,7 +346,7 @@ vaultOptions.mount.mountPoint.directoryPickerTitle=空のディレクトリを vaultOptions.masterkey=パスワード vaultOptions.masterkey.changePasswordBtn=パスワードの変更 vaultOptions.masterkey.forgetSavedPasswordBtn=保存したパスワードを削除する -vaultOptions.masterkey.recoveryKeyExplanation=回復キーはパスワードを忘れてしまった場合でも、金庫へのアクセスを回復する唯一の手段です。 +vaultOptions.masterkey.recoveryKeyExplanation=回復キーはパスワードを忘れてしまった場合に、金庫へのアクセスを回復する唯一の手段です。 vaultOptions.masterkey.showRecoveryKeyBtn=回復キーを表示 vaultOptions.masterkey.recoverPasswordBtn=パスワードの回復 @@ -351,8 +354,8 @@ vaultOptions.masterkey.recoverPasswordBtn=パスワードの回復 # Recovery Key recoveryKey.title=回復キー recoveryKey.enterPassword.prompt="%s" の回復キーを表示するためのパスワードを入力してください: -recoveryKey.display.message="%s" へのアクセス権限を復元するリカバリーキー: -recoveryKey.display.StorageHints=とても安全な場所に保存してください、例えば:\n • パスワード管理ソフトに保存\n • USB フラッシュドライブに保存\n • 紙に印刷 +recoveryKey.display.message="%s" へのアクセス権限を復元する回復キー: +recoveryKey.display.StorageHints=十分に安全な場所に保存してください。例えば:\n • パスワード管理ソフトに保存\n • USB フラッシュドライブに保存\n • 紙に印刷 recoveryKey.recover.prompt="%s" の回復キーを入力してください: recoveryKey.recover.validKey=有効な回復キー recoveryKey.printout.heading=Cryptomator 回復キー\n"%s"\n diff --git a/src/main/resources/i18n/strings_ko.properties b/src/main/resources/i18n/strings_ko.properties index 63b1d9111..fd89b1e99 100644 --- a/src/main/resources/i18n/strings_ko.properties +++ b/src/main/resources/i18n/strings_ko.properties @@ -98,8 +98,9 @@ unlock.title="%s" 잠금 해제 unlock.passwordPrompt="%s"의 비밀번호를 입력하십시요. unlock.savePassword=비밀번호 기억 unlock.unlockBtn=잠금해제 -## +## Select unlock.chooseMasterkey.prompt=추정되는 위치에서 이 Vault의 마스터 키를 찾지 못했습니다. 마스터 키 위치를 수동으로 선택하여 주십시요. +unlock.chooseMasterkey.chooseBtn=선택 unlock.chooseMasterkey.filePickerTitle=마스터키 파일 선택 ## Success unlock.success.message="%s"이(가) 성공적으로 잠금해제되었습니다. 이제 이 Vault를 가상드라이브로 접근할 수 있습니다. diff --git a/src/main/resources/i18n/strings_lv.properties b/src/main/resources/i18n/strings_lv.properties index 3025ea909..a08a92433 100644 --- a/src/main/resources/i18n/strings_lv.properties +++ b/src/main/resources/i18n/strings_lv.properties @@ -88,7 +88,8 @@ forgetPassword.confirmBtn=Aizmirst paroli # Unlock unlock.passwordPrompt=Ievadiet "%s" paroli: unlock.unlockBtn=Atslēgt -## +## Select +unlock.chooseMasterkey.chooseBtn=Izvēlies... unlock.chooseMasterkey.filePickerTitle=Atlasīt galveno atslēgas datni ## Success unlock.success.revealBtn=Atklāt disku diff --git a/src/main/resources/i18n/strings_mk.properties b/src/main/resources/i18n/strings_mk.properties index 1b7af9c4d..ca14a896f 100644 --- a/src/main/resources/i18n/strings_mk.properties +++ b/src/main/resources/i18n/strings_mk.properties @@ -25,7 +25,7 @@ # Forget Password # Unlock -## +## Select ## Success ## Failure ### Invalid Mount Point diff --git a/src/main/resources/i18n/strings_nb.properties b/src/main/resources/i18n/strings_nb.properties index 9a3fb5fe5..150733868 100644 --- a/src/main/resources/i18n/strings_nb.properties +++ b/src/main/resources/i18n/strings_nb.properties @@ -13,6 +13,11 @@ generic.button.done=Ferdig generic.button.next=Neste generic.button.print=Skriv ut ## Error +generic.error.title=Feilkode %s +generic.error.instruction=Oops! Cryptomator forventet ikke at dette skulle skje. Du kan slå opp mulige løsninger for denne feilen. Hvis feilen ikke er rapportert så kan du gjerne gjøre det. +generic.error.hyperlink.lookup=Slå opp denne feilen +generic.error.hyperlink.report=Rapporter denne feilen +generic.error.technicalDetails=Detaljer: # Defaults defaults.vault.vaultName=Hvelv @@ -33,7 +38,7 @@ addvaultwizard.welcome.newButton=Lag et nytt hvelv addvaultwizard.welcome.existingButton=Åpne et eksisterende hvelv ## New ### Name -addvaultwizard.new.nameInstruction=Velg et navn på hvelvet +addvaultwizard.new.nameInstruction=Navngi hvelvet addvaultwizard.new.namePrompt=Navn på hvelvet ### Location addvaultwizard.new.locationInstruction=Hvor skal Cryptomator lagre de krypterte filene dine? @@ -43,6 +48,8 @@ addvaultwizard.new.directoryPickerLabel=Tilpasset lagringssted addvaultwizard.new.directoryPickerButton=Velg… addvaultwizard.new.directoryPickerTitle=Velg mappe addvaultwizard.new.fileAlreadyExists=En fil eller mappe med det hvelvnavnet finnes allerede +addvaultwizard.new.locationDoesNotExist=En mappe i den angitte stien finnes ikke eller kan ikke nås +addvaultwizard.new.locationIsNotWritable=Ingen skrivetilgang på den angitte stien addvaultwizard.new.locationIsOk=Egnet sted for hvelvet ditt addvaultwizard.new.invalidName=Ugyldig navn på hvelvet. Vennligst vurder et vanlig mappenavn. ### Password @@ -55,13 +62,13 @@ addvault.new.readme.storageLocation.fileName=VIKTIG.rtf addvault.new.readme.storageLocation.1=⚠️ HVELVFILER ⚠️ addvault.new.readme.storageLocation.2=Dette er hvelvets lagringssted. addvault.new.readme.storageLocation.3=IKKE -addvault.new.readme.storageLocation.4=• endre hvilke filer som helst i denne mappen eller -addvault.new.readme.storageLocation.5=• i denne mappen limer du inn alle filer du ønsker skal krypteres. +addvault.new.readme.storageLocation.4=• endre noen filer i denne mappen eller +addvault.new.readme.storageLocation.5=• lim inn filer du ønsker skal krypteres. addvault.new.readme.storageLocation.6=Hvis du vil kryptere filer og se innholdet i hvelvet, gjør du følgende: addvault.new.readme.storageLocation.7=1. Legg til dette hvelvet i Cryptomator. addvault.new.readme.storageLocation.8=2. Lås opp hvelvet i Cryptomator. addvault.new.readme.storageLocation.9=3. Åpne tilgangspunktet ved å klikke på "Gjør synlig" -knappen. -addvault.new.readme.storageLocation.10=Hvis du trenger hjelp, kan du gå til dokumentasjonen: %s +addvault.new.readme.storageLocation.10=Hvis du trenger hjelp, kan du se på dokumentasjonen: %s addvault.new.readme.accessLocation.fileName=VELKOMMEN.rtf addvault.new.readme.accessLocation.1=🔐️ KRYPTERT VOLUM 🔐️ addvault.new.readme.accessLocation.2=Dette er hvelvets lagringssted. @@ -69,6 +76,7 @@ addvault.new.readme.accessLocation.3=Alle filer som er lagt til i dette volumet, addvault.new.readme.accessLocation.4=Denne filen kan fjernes hvis ønskelig. ## Existing addvaultwizard.existing.chooseBtn=Velg… +addvaultwizard.existing.filePickerTitle=Velg hvelvfil ## Success addvaultwizard.success.nextStepsInstructions=Lagt til hvelvet "%s".\nDu må låse opp dette hvelvet for å få tilgang til eller legge til innhold. Alternativt kan du låse det opp på et hvilket som helst senere tidspunkt. addvaultwizard.success.unlockNow=Lås opp nå @@ -93,7 +101,8 @@ unlock.title=Lås opp "%s" unlock.passwordPrompt=Skriv inn passordet for "%s": unlock.savePassword=Husk passord unlock.unlockBtn=Lås opp -## +## Select +unlock.chooseMasterkey.chooseBtn=Velg… unlock.chooseMasterkey.filePickerTitle=Velg hovednøkkelfil ## Success unlock.success.rememberChoice=Husk valget - ikke vis dette igjen @@ -106,8 +115,10 @@ unlock.error.invalidMountPoint.existing=Monteringspunktet "%s" finnes enten alle # Lock ## Force +lock.forced.heading=Låsing mislyktes lock.forced.message=Låsing "%s" ble blokkert av ventende operasjoner eller åpne filer. Du kan tvinge låsing av dette hvelvet, men avbrytelse av I/O kan føre til tap av ulagrede data. lock.forced.retryBtn=Prøv igjen +lock.forced.forceBtn=Tving låsing ## Failure lock.fail.heading=Låsing av hvelvet mislyktes. lock.fail.message=Hvelvet "%s" kunne ikke låses. Forsikre deg om at ulagrede arbeider lagres andre steder, og at viktige lese/skrive-operasjoner er fullført. For å lukke hvelvet må du avbryte Cryptomatorprosessen. @@ -119,22 +130,22 @@ migration.start.prompt=Hvelvet ditt "%s" må oppdateres til et nyere format. Fø migration.start.confirm=Ja, hvelvet mitt er fullstendig synkronisert ## Run migration.run.enterPassword=Skriv inn passordet for "%s" -migration.run.startMigrationBtn=Overfør hvelv +migration.run.startMigrationBtn=Oppgrader hvelv migration.run.progressHint=Dette kan ta litt tid… ## Success -migration.success.nextStepsInstructions=Vellykket overføring av "%s".\nDu kan nå låse opp hvelvet ditt. +migration.success.nextStepsInstructions=Vellykket oppgradering av "%s".\nDu kan nå låse opp hvelvet ditt. migration.success.unlockNow=Lås opp nå ## Missing file system capabilities migration.error.missingFileSystemCapabilities.title=Filsystemet er ikke støttet -migration.error.missingFileSystemCapabilities.description=Overføringen ble ikke startet fordi hvelvet ditt ligger i et mangelfullt filsystem. +migration.error.missingFileSystemCapabilities.description=Oppgraderingen ble ikke startet fordi hvelvet ditt ligger i et mangelfullt filsystem. migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=Filsystemet støtter ikke lange filnavn. migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Filsystemet støtter ikke lange søkestier. migration.error.missingFileSystemCapabilities.reason.READ_ACCESS=Filsystemet tillater ikke lesing. migration.error.missingFileSystemCapabilities.reason.WRITE_ACCESS=Filsystemet tillater ikke å bli skrevet på. ## Impossible -migration.impossible.heading=Kunne ikke overføre hvelvet +migration.impossible.heading=Kunne ikke oppgradere hvelvet migration.impossible.reason=Hvelvet kan ikke overføres automatisk fordi lagringsstedet eller tilgangspunkt ikke er kompatibelt. -migration.impossible.moreInfo=Hvelvet kan fortsatt åpnes hvis du bruker en eldre versjon. For instruksjoner om hvordan man overfører et hvelv, besøk +migration.impossible.moreInfo=Hvelvet kan fortsatt åpnes hvis du bruker en eldre versjon. For veiledning om hvordan man oppgraderer et hvelv, besøk # Health Check ## Start @@ -143,6 +154,7 @@ health.intro.remarkFix=Ikke alle problemer kan løses. health.fail.moreInfo=Mer informasjon ## Check Selection ## Detail view +health.check.exportBtn=Eksporter rapport ## Fix Application health.fix.fixBtn=Reparer health.fix.successTip=Vellykket reparering @@ -175,7 +187,7 @@ preferences.volume.webdav.scheme=WebDAV-ordning ## Updates preferences.updates=Oppdateringer preferences.updates.currentVersion=Gjeldende versjon: %s -preferences.updates.autoUpdateCheck=Se etter oppdateringer automatisk +preferences.updates.autoUpdateCheck=Se automatisk etter oppdateringer preferences.updates.checkNowBtn=Sjekk nå preferences.updates.updateAvailable=Oppdatering til versjon %s er tilgjengelig. ## Contribution @@ -220,7 +232,7 @@ main.closeBtn.tooltip=Lukk main.minimizeBtn.tooltip=Minimer main.preferencesBtn.tooltip=Innstillinger main.debugModeEnabled.tooltip=Feilsøkingsmodus er aktivert -main.donationKeyMissing.tooltip=Vennligst vurder å donere +main.donationKeyMissing.tooltip=Gjerne vurdér en donasjon ## Drag 'n' Drop main.dropZone.dropVault=Legg til dette hvelvet main.dropZone.unknownDragboardContent=Hvis du vil legge til et hvelv, kan du dra det til dette vinduet diff --git a/src/main/resources/i18n/strings_nl.properties b/src/main/resources/i18n/strings_nl.properties index 0f95c1619..72d7400f4 100644 --- a/src/main/resources/i18n/strings_nl.properties +++ b/src/main/resources/i18n/strings_nl.properties @@ -102,8 +102,10 @@ unlock.title=Ontgrendel "%s" unlock.passwordPrompt=Voer wachtwoord voor "%s" in: unlock.savePassword=Wachtwoord Onthouden unlock.unlockBtn=Ontgrendel -## +## Select +unlock.chooseMasterkey.title=Selecteer Masterkey van "%s" unlock.chooseMasterkey.prompt=Kon het sleutelbestand voor deze kluis niet vinden op de gewenste locatie. Kies het sleutelbestand handmatig. +unlock.chooseMasterkey.chooseBtn=Kies… unlock.chooseMasterkey.filePickerTitle=Selecteer het Masterkey-bestand ## Success unlock.success.message="%s" is met succes ontgrendeld! Uw kluis is nu toegankelijk via zijn virtuele schijf. @@ -114,6 +116,7 @@ unlock.error.heading=Kan kluis niet ontgrendelen ### Invalid Mount Point unlock.error.invalidMountPoint.notExisting=Koppelpunt "%s" is geen map, is niet leeg of bestaat niet. unlock.error.invalidMountPoint.existing=Koppelpunt "%s" bestaat reeds of de bovenliggende map ontbreekt. +unlock.error.invalidMountPoint.driveLetterOccupied=Schijf "%s" is al in gebruik. # Lock ## Force diff --git a/src/main/resources/i18n/strings_nn.properties b/src/main/resources/i18n/strings_nn.properties index f1ae4c1a2..f37e4e92e 100644 --- a/src/main/resources/i18n/strings_nn.properties +++ b/src/main/resources/i18n/strings_nn.properties @@ -88,7 +88,8 @@ forgetPassword.confirmBtn=Gløym passord # Unlock unlock.passwordPrompt=Skriv inn passordet for "%s": unlock.unlockBtn=Låse opp -## +## Select +unlock.chooseMasterkey.chooseBtn=Vel… unlock.chooseMasterkey.filePickerTitle=Vel hovudnøkkelfil ## Success unlock.success.rememberChoice=Hugs valet - ikkje vis dette igjen diff --git a/src/main/resources/i18n/strings_pa.properties b/src/main/resources/i18n/strings_pa.properties index 394ab7355..1c7400930 100644 --- a/src/main/resources/i18n/strings_pa.properties +++ b/src/main/resources/i18n/strings_pa.properties @@ -90,7 +90,8 @@ forgetPassword.confirmBtn=ਪਾਸਵਰਡ ਭੁੱਲ ਗਏ unlock.passwordPrompt="%s" ਲਈ ਪਾਸਵਰਡ ਦਿਓ: unlock.savePassword=ਪਾਸਵਰਡ ਯਾਦ ਰੱਖੋ unlock.unlockBtn=ਅਣ-ਲਾਕ ਕਰੋ -## +## Select +unlock.chooseMasterkey.chooseBtn=…ਚੁਣੋ unlock.chooseMasterkey.filePickerTitle=ਮਾਸਟਰ-ਕੁੰਜੀ ਫਾਇਲ ਚੁਣੋ ## Success unlock.success.rememberChoice=ਚੋਣਾਂ ਯਾਦ ਰੱਖੋ, ਇਹ ਮੁੜ ਕੇ ਨਾ ਵੇਖਾਓ diff --git a/src/main/resources/i18n/strings_pl.properties b/src/main/resources/i18n/strings_pl.properties index e823d166f..8d6eafbec 100644 --- a/src/main/resources/i18n/strings_pl.properties +++ b/src/main/resources/i18n/strings_pl.properties @@ -102,8 +102,10 @@ unlock.title=Odblokuj "%s" unlock.passwordPrompt=Wprowadź hasło dla "%s": unlock.savePassword=Zapamiętaj hasło unlock.unlockBtn=Odblokuj -## +## Select +unlock.chooseMasterkey.title=Wybierz Masterkey z "%s" unlock.chooseMasterkey.prompt=Brak pliku Masterkey dla tego sejfu w oczekiwanej lokalizacji. Proszę wybrać plik klucza ręcznie. +unlock.chooseMasterkey.chooseBtn=Wybierz… unlock.chooseMasterkey.filePickerTitle=Wybierz plik Masterkey ## Success unlock.success.message=Odblokowano "%s" pomyślnie! Twój sejf jest teraz dostępny za pomocą dysku wirtualnego. @@ -114,6 +116,7 @@ unlock.error.heading=Nie można odblokować sejfu ### Invalid Mount Point unlock.error.invalidMountPoint.notExisting=Punkt montowania nie jest pustym katalogiem lub nie istnieje: %s unlock.error.invalidMountPoint.existing=Punkt montowania już istnieje lub brakuje katalogu nadrzędnego: %s +unlock.error.invalidMountPoint.driveLetterOccupied=Litera dysku "%s" jest już w użyciu. # Lock ## Force diff --git a/src/main/resources/i18n/strings_pt.properties b/src/main/resources/i18n/strings_pt.properties index 5f60e7025..5436a0b67 100644 --- a/src/main/resources/i18n/strings_pt.properties +++ b/src/main/resources/i18n/strings_pt.properties @@ -95,12 +95,15 @@ forgetPassword.confirmBtn=Esqueci a Senha # Unlock unlock.passwordPrompt=Insira a senha para "%s": unlock.unlockBtn=Destrancar -## +## Select +unlock.chooseMasterkey.title=Selecione a Masterkey de "%s" +unlock.chooseMasterkey.chooseBtn=Escolher… unlock.chooseMasterkey.filePickerTitle=Selecionar ficheiro MasterKey ## Success unlock.success.rememberChoice=Lembrar escolha, não mostrar isto novamente ## Failure ### Invalid Mount Point +unlock.error.invalidMountPoint.driveLetterOccupied=A letra de unidade "%s" já está sendo usada. # Lock ## Force diff --git a/src/main/resources/i18n/strings_pt_BR.properties b/src/main/resources/i18n/strings_pt_BR.properties index bd19fa5bf..350bc5e39 100644 --- a/src/main/resources/i18n/strings_pt_BR.properties +++ b/src/main/resources/i18n/strings_pt_BR.properties @@ -102,8 +102,10 @@ unlock.title=Desbloquear "%s" unlock.passwordPrompt=Digite a senha para "%s": unlock.savePassword=Lembrar Senha unlock.unlockBtn=Desbloquear -## +## Select +unlock.chooseMasterkey.title=Selecione o Masterkey de "%s" unlock.chooseMasterkey.prompt=Não foi possível encontrar o arquivo Masterkey deste cofre no local esperado. Por favor selecione o arquivo chave manualmente. +unlock.chooseMasterkey.chooseBtn=Escolher… unlock.chooseMasterkey.filePickerTitle=Selecionar Arquivo Masterkey ## Success unlock.success.message="%s" desbloqueado com êxito! Seu cofre agora está acessível na unidade virtual. @@ -114,6 +116,7 @@ unlock.error.heading=Não foi possível desbloquear o cofre ### Invalid Mount Point unlock.error.invalidMountPoint.notExisting=O ponto de montagem não é um diretório vazio ou não existe: %s unlock.error.invalidMountPoint.existing=Ponto de montagem/pasta já existe ou a pasta pai está faltando: %s +unlock.error.invalidMountPoint.driveLetterOccupied=Letra de unidade "%s" já está em uso. # Lock ## Force diff --git a/src/main/resources/i18n/strings_ro.properties b/src/main/resources/i18n/strings_ro.properties index 2663460b0..d319b87d1 100644 --- a/src/main/resources/i18n/strings_ro.properties +++ b/src/main/resources/i18n/strings_ro.properties @@ -95,8 +95,9 @@ unlock.title=Deblocare "%s" unlock.passwordPrompt=Introduceți parola pentru "%s": unlock.savePassword=Memorează parola unlock.unlockBtn=Deblocați -## +## Select unlock.chooseMasterkey.prompt=Nu s-a putut găsi fișierul masterkey pentru acest seif la locația așteptată. Vă rugăm să alegeți manual fișierul cheie. +unlock.chooseMasterkey.chooseBtn=Alege… unlock.chooseMasterkey.filePickerTitle=Selectaţi fişierul Masterkey ## Success unlock.success.message=Deblocat "%s" cu succes! Seiful dvs. este acum accesibil prin unitatea sa virtuală. diff --git a/src/main/resources/i18n/strings_ru.properties b/src/main/resources/i18n/strings_ru.properties index 22b529478..992e96f5f 100644 --- a/src/main/resources/i18n/strings_ru.properties +++ b/src/main/resources/i18n/strings_ru.properties @@ -14,7 +14,7 @@ generic.button.next=Далее generic.button.print=Печать ## Error generic.error.title=Ошибка: %s -generic.error.instruction=Ой! Криптоматор не ожидал, что так произойдет. Вы можете поискать существующие решения этой ошибки. Или если об этом еще не сообщалось, не стесняйтесь сделать это. +generic.error.instruction=Произошла непредвиденная ситуация. Попробуйте найти уже имеющиеся решения этой ошибки. Если об этой ошибке ещё не сообщали, то сделайте это. generic.error.hyperlink.lookup=Искать ошибку generic.error.hyperlink.report=Сообщить об ошибке generic.error.technicalDetails=Подробности: @@ -95,15 +95,17 @@ changepassword.finalConfirmation=Я понимаю, что не смогу по # Forget Password forgetPassword.title=Не помню пароль forgetPassword.information=Сохранённый пароль от этого хранилища будет удалён из вашей связки ключей. -forgetPassword.confirmBtn=Забыть сохранённый пароль +forgetPassword.confirmBtn=Забыть пароль # Unlock unlock.title=Разблокировать "%s" unlock.passwordPrompt=Введите пароль для "%s" unlock.savePassword=Запомнить пароль unlock.unlockBtn=Разблокировать -## +## Select +unlock.chooseMasterkey.title=Выберите файл MasterKey для "%s" unlock.chooseMasterkey.prompt=Не удалось найти файл MasterKey для этого хранилища в ожидаемом месте. Выберите ключевой файл вручную. +unlock.chooseMasterkey.chooseBtn=Выбрать… unlock.chooseMasterkey.filePickerTitle=Выберите файл MasterKey ## Success unlock.success.message=Разблокировка "%s" успешно выполнена! Доступ в хранилище открыт через его виртуальный диск. @@ -114,6 +116,7 @@ unlock.error.heading=Невозможно разблокировать хран ### Invalid Mount Point unlock.error.invalidMountPoint.notExisting=Точка монтирования %s - не папка, не пуста или не существует. unlock.error.invalidMountPoint.existing=Точка монтирования %s уже существует, либо отсутствует родительская папка. +unlock.error.invalidMountPoint.driveLetterOccupied=Буква диска "%s" уже используется. # Lock ## Force diff --git a/src/main/resources/i18n/strings_sk.properties b/src/main/resources/i18n/strings_sk.properties index ac52f4a22..f948d4f0e 100644 --- a/src/main/resources/i18n/strings_sk.properties +++ b/src/main/resources/i18n/strings_sk.properties @@ -102,8 +102,10 @@ unlock.title=Odomknúť "%s" unlock.passwordPrompt=Zadajte heslo pre "%s": unlock.savePassword=Odomknúť.uložiťHeslo unlock.unlockBtn=Odomknúť -## +## Select +unlock.chooseMasterkey.title=Vyberte súbor s hlavným kľúčom "%s" unlock.chooseMasterkey.prompt=Nemožno nájsť hlavný kľúčový súbor pre tento trezor na jeho očakávanom mieste. Prosím zvoľte kľúčový súbor manuálne. +unlock.chooseMasterkey.chooseBtn=Vybrať… unlock.chooseMasterkey.filePickerTitle=Zvoľte hlavný kľúčový súbor ## Success unlock.success.message=Odomknutie "%s" úspešné! Váš trezor je už prístupný cez jeho virtuálny disk. @@ -114,6 +116,7 @@ unlock.error.heading=Nie je možné odomknúť trezor ### Invalid Mount Point unlock.error.invalidMountPoint.notExisting=Bod pripojenia "%s" nie je adresár, nie je prázdny alebo neexistuje. unlock.error.invalidMountPoint.existing=Bod pripojenia "%s" už existuje alebo chýba nadradený adresár. +unlock.error.invalidMountPoint.driveLetterOccupied=Písmeno disku %s sa už používa. # Lock ## Force diff --git a/src/main/resources/i18n/strings_sr.properties b/src/main/resources/i18n/strings_sr.properties index f01d76a67..fad56439c 100644 --- a/src/main/resources/i18n/strings_sr.properties +++ b/src/main/resources/i18n/strings_sr.properties @@ -94,7 +94,8 @@ forgetPassword.confirmBtn=Заборави лозинку unlock.passwordPrompt=Унесите лозинку за "%s": unlock.savePassword=Запамти лозинку unlock.unlockBtn=Откључај -## +## Select +unlock.chooseMasterkey.chooseBtn=Изабери… unlock.chooseMasterkey.filePickerTitle=Изабери "Masterkey" датотеку ## Success unlock.success.message=Успешно сте откључали "%s"! Ваш сеф је сада доступан путем свог виртуелног диска. diff --git a/src/main/resources/i18n/strings_sr_Latn.properties b/src/main/resources/i18n/strings_sr_Latn.properties index 83b9161a9..5cf642d42 100644 --- a/src/main/resources/i18n/strings_sr_Latn.properties +++ b/src/main/resources/i18n/strings_sr_Latn.properties @@ -94,7 +94,8 @@ forgetPassword.confirmBtn=Zaboravi lozinku unlock.passwordPrompt=Unesite lozinku za "%s": unlock.savePassword=Zapamti lozinku unlock.unlockBtn=Otključaj -## +## Select +unlock.chooseMasterkey.chooseBtn=Izaberi… unlock.chooseMasterkey.filePickerTitle=Izaberi "Masterkey" datoteku ## Success unlock.success.message=Uspešno ste otključali "%s"! Vaš sef je sada dostupan putem ovog virtuelnog diska. diff --git a/src/main/resources/i18n/strings_sv.properties b/src/main/resources/i18n/strings_sv.properties index 547a8b8fa..3c872c488 100644 --- a/src/main/resources/i18n/strings_sv.properties +++ b/src/main/resources/i18n/strings_sv.properties @@ -102,8 +102,10 @@ unlock.title=Lås upp "%s" unlock.passwordPrompt=Ange lösenord för "%s": unlock.savePassword=Kom ihåg lösenord unlock.unlockBtn=Lås upp -## +## Select +unlock.chooseMasterkey.title=Välj Masterkey för "%s" unlock.chooseMasterkey.prompt=Kunde inte hitta Masterkey-filen för detta valv på förväntad plats. Välj filen manuellt. +unlock.chooseMasterkey.chooseBtn=Välj… unlock.chooseMasterkey.filePickerTitle=Välj Masterkey-fil ## Success unlock.success.message="%s" upplåst! Ditt valv är nu åtkomligt från den virtuella enheten. @@ -114,11 +116,14 @@ unlock.error.heading=Kan inte låsa upp valvet ### Invalid Mount Point unlock.error.invalidMountPoint.notExisting=Monteringspunkten "%s" saknas eller är inte tom. unlock.error.invalidMountPoint.existing=Monteringspunkten "%s" finns redan eller så saknas överordnad mapp. +unlock.error.invalidMountPoint.driveLetterOccupied=Enhetsbokstav "%s" används redan. # Lock ## Force +lock.forced.heading=Kunde inte låsa lock.forced.message=Låsning av "%s" förhindras av pågående operationer eller öppna filer. Du kan tvinga låsning av detta valv, men det kan resultera i förlust av osparade data. lock.forced.retryBtn=Försök igen +lock.forced.forceBtn=Tvinga låsning ## Failure lock.fail.heading=Låsning av valv misslyckades. lock.fail.message=Valvet "%s" kunde inte låsas. Se till att osparat arbete sparas någon annanstans och viktiga läs- och skrivfunktioner är klara. För att stänga valvet, avsluta Cryptomator-processen. diff --git a/src/main/resources/i18n/strings_ta.properties b/src/main/resources/i18n/strings_ta.properties index 6b5ea4bc9..c6a85fe76 100644 --- a/src/main/resources/i18n/strings_ta.properties +++ b/src/main/resources/i18n/strings_ta.properties @@ -25,7 +25,7 @@ # Forget Password # Unlock -## +## Select ## Success ## Failure ### Invalid Mount Point diff --git a/src/main/resources/i18n/strings_te.properties b/src/main/resources/i18n/strings_te.properties index e2ce80d76..8dc7b6511 100644 --- a/src/main/resources/i18n/strings_te.properties +++ b/src/main/resources/i18n/strings_te.properties @@ -25,7 +25,7 @@ # Forget Password # Unlock -## +## Select ## Success ## Failure ### Invalid Mount Point diff --git a/src/main/resources/i18n/strings_th.properties b/src/main/resources/i18n/strings_th.properties index 1b7af9c4d..9240aa470 100644 --- a/src/main/resources/i18n/strings_th.properties +++ b/src/main/resources/i18n/strings_th.properties @@ -2,20 +2,78 @@ # Generics ## Button +generic.button.apply=นำไปใช้ +generic.button.back=ย้อนกลับ +generic.button.cancel=ยกเลิก +generic.button.change=เปลี่ยน +generic.button.close=ปิด +generic.button.copy=คัดลอก +generic.button.copied=คัดลอกแล้ว! +generic.button.done=เสร็จสิ้น +generic.button.next=ถัดไป +generic.button.print=พิมพ์ ## Error +generic.error.title=ข้อผิดพลาด: %s +generic.error.instruction=ไม่นะ! Cryptomator ไม่ได้ต้องการให้สิ่งนี้เกิดขึ้น คุณสามารถค้นหาข้อผิดพลาดนี้ได้ หากค้นหาไม่เจอ โปรดรายงาน +generic.error.hyperlink.lookup=ค้นหาข้อผิดพลาดนี้ +generic.error.hyperlink.report=รายงานข้อผิดพลาดนี้ +generic.error.technicalDetails=ราย​ละเอียด: # Defaults +defaults.vault.vaultName=Vault # Tray Menu +traymenu.showMainWindow=แสดง +traymenu.showPreferencesWindow=การตั้งค่า +traymenu.lockAllVaults=ล็อกทั้งหมด +traymenu.quitApplication=ออก +traymenu.vault.unlock=ปลดล็อก +traymenu.vault.lock=ล็อก +traymenu.vault.reveal=เปิดเผย # Add Vault Wizard +addvaultwizard.title=เพิ่ม Vault ## Welcome +addvaultwizard.welcome.newButton=สร้าง Vault ใหม่ +addvaultwizard.welcome.existingButton=เปิด Vault ## New ### Name +addvaultwizard.new.nameInstruction=เลือกชื่อให้ Vault +addvaultwizard.new.namePrompt=ชื่อ Vault ### Location +addvaultwizard.new.locationInstruction=Cryptomator ควรเก็บไฟล์ของคุณไว้ที่ไหน? +addvaultwizard.new.locationLabel=ที่จัดเก็บข้อมูล +addvaultwizard.new.locationPrompt=… +addvaultwizard.new.directoryPickerLabel=ตำแหน่งที่กำหนดเอง +addvaultwizard.new.directoryPickerButton=เลือก... +addvaultwizard.new.directoryPickerTitle=เลือกไดเรกทอรี +addvaultwizard.new.fileAlreadyExists=คำเตือน: มีชื่อแฟ้มหรือไดเรกทอรีนี้อยู่แล้ว +addvaultwizard.new.locationDoesNotExist=ไม่มีไดเรกทอรีในตำแหน่งที่คุณเลือก หรือไม่สามารถเข้าถึงได้ +addvaultwizard.new.locationIsNotWritable=ไม่มีสิทธิ์ในการเขียนตำแหน่งที่คุณเลือก +addvaultwizard.new.locationIsOk=ตำแหน่งนี้เหมาะสมสำหรับ Vault ของคุณ +addvaultwizard.new.invalidName=ไม่สามารถใช้ชื่อนี้ได้ ### Password +addvaultwizard.new.createVaultBtn=สร้าง Vault +addvaultwizard.new.generateRecoveryKeyChoice=หากคุณลืมรหัสผ่าน คุณจะไม่สามารถเข้าถึงข้อมูลเหล่านี้ได้ คุณต้องการที่จะสร้างรหัสกู้คืนหรือไม่? +addvaultwizard.new.generateRecoveryKeyChoice.yes=สร้างเลย ปลอดภัยเอาไว้ดีกว่า +addvaultwizard.new.generateRecoveryKeyChoice.no=ไม่สร้าง ฉันจะไม่ลืมรหัสผ่าน ### Information +addvault.new.readme.storageLocation.fileName=โปรดอ่านข้อความนี้.rtf +addvault.new.readme.storageLocation.1=⚠️ ไฟล์ Vault ⚠️ +addvault.new.readme.storageLocation.2=นี่คือตำแหน่งจัดเก็บ Vault ของคุณ +addvault.new.readme.storageLocation.3=ห้าม +addvault.new.readme.storageLocation.4=• แก้ไขไฟล์ในนี้ +addvault.new.readme.storageLocation.5=• หรือวางไฟล์ในนี้ +addvault.new.readme.storageLocation.6=หากคุณต้องการที่จะเก็บไฟล์หรือดูไฟล์ใน Vault นี้ โปรด: +addvault.new.readme.storageLocation.7=1. เพิ่ม Vault นี้ไปใน Cryptomator +addvault.new.readme.storageLocation.8=2. ปลดล็อก Vault +addvault.new.readme.storageLocation.9=3. เปิดตำแหน่งเข้าถึง โดยการกดปุ่ม "เปิดเผย" +addvault.new.readme.storageLocation.10=หากคุณต้องการความช่วยเหลือ โปรดอ่านเอกสารอ้างอิง: %s +addvault.new.readme.accessLocation.fileName=ยินดีต้อนรับ.rtf +addvault.new.readme.accessLocation.1=🔐️ พื้นที่นี้เข้ารหัสแล้ว 🔐️ +addvault.new.readme.accessLocation.2=นี่คือตำแหน่งจัดเก็บ Vault ของคุณ ## Existing +addvaultwizard.existing.chooseBtn=เลือก... ## Success # Remove Vault @@ -25,7 +83,9 @@ # Forget Password # Unlock -## +unlock.unlockBtn=ปลดล็อก +## Select +unlock.chooseMasterkey.chooseBtn=เลือก... ## Success ## Failure ### Invalid Mount Point @@ -49,6 +109,7 @@ ## Fix Application # Preferences +preferences.title=การตั้งค่า ## General ## Volume ## Updates @@ -62,12 +123,17 @@ ## Write # Main Window +main.closeBtn.tooltip=ปิด +main.preferencesBtn.tooltip=การตั้งค่า ## Drag 'n' Drop ## Vault List +main.vaultlist.contextMenu.lock=ล็อก +main.vaultlist.addVaultBtn=เพิ่ม Vault ## Vault Detail ### Welcome ### Locked ### Unlocked +main.vaultDetail.lockBtn=ล็อก ### Missing ### Needs Migration ### Error @@ -76,8 +142,10 @@ # Vault Options ## General +vaultOptions.general.vaultName=ชื่อ Vault ## Mount +vaultOptions.mount.mountPoint.directoryPickerButton=เลือก... ## Master Key diff --git a/src/main/resources/i18n/strings_tr.properties b/src/main/resources/i18n/strings_tr.properties index ecb4fff5f..d0a16c69b 100644 --- a/src/main/resources/i18n/strings_tr.properties +++ b/src/main/resources/i18n/strings_tr.properties @@ -102,8 +102,10 @@ unlock.title="%s" kilidini aç unlock.passwordPrompt="%s" için şifre girin: unlock.savePassword=Şifreyi Hatırla unlock.unlockBtn=Kilidi Aç -## +## Select +unlock.chooseMasterkey.title="%s" ait Ana anahtarı seçin unlock.chooseMasterkey.prompt=Bu kasa için masterkey dosyası beklenen konumda bulunamadı. Lütfen anahtar dosyasını manuel olarak seçin. +unlock.chooseMasterkey.chooseBtn=Seç… unlock.chooseMasterkey.filePickerTitle=Masterkey Dosyasını Seç ## Success unlock.success.message="%s" 'nin kilidi başarıyla açıldı! Kasanız şimdi sanal sürücüsü ile erişilebilir durumda. @@ -114,6 +116,7 @@ unlock.error.heading=Kasanın kilidi açılamıyor ### Invalid Mount Point unlock.error.invalidMountPoint.notExisting=Bağlantı noktası boş bir dizin değil veya mevcut değil: %s unlock.error.invalidMountPoint.existing=Bağlama noktası / klasör zaten var veya ana klasör eksik: %s +unlock.error.invalidMountPoint.driveLetterOccupied="%s" sürücü adı zaten kullanılıyor. # Lock ## Force diff --git a/src/main/resources/i18n/strings_uk.properties b/src/main/resources/i18n/strings_uk.properties index ff6e674e8..101eb5763 100644 --- a/src/main/resources/i18n/strings_uk.properties +++ b/src/main/resources/i18n/strings_uk.properties @@ -62,7 +62,8 @@ addvaultwizard.existing.chooseBtn=Обрати… # Unlock unlock.unlockBtn=Розблокувати -## +## Select +unlock.chooseMasterkey.chooseBtn=Обрати… ## Success ## Failure ### Invalid Mount Point diff --git a/src/main/resources/i18n/strings_zh.properties b/src/main/resources/i18n/strings_zh.properties index fbd2fa861..0b32a9665 100644 --- a/src/main/resources/i18n/strings_zh.properties +++ b/src/main/resources/i18n/strings_zh.properties @@ -55,7 +55,7 @@ addvaultwizard.new.invalidName=无效的保险库名称,请考虑一个常规 ### Password addvaultwizard.new.createVaultBtn=创建保险库 addvaultwizard.new.generateRecoveryKeyChoice=如果没有密码,您将无法访问您的数据。您想要一个恢复密钥来以防您丢失密码吗? -addvaultwizard.new.generateRecoveryKeyChoice.yes=是的请,有备无患 +addvaultwizard.new.generateRecoveryKeyChoice.yes=好的,有备无患 addvaultwizard.new.generateRecoveryKeyChoice.no=不,谢谢。我不会丢失密码的 ### Information addvault.new.readme.storageLocation.fileName=重要.rtf @@ -102,8 +102,10 @@ unlock.title=解锁 "%s" unlock.passwordPrompt=输入 "%s" 的密码 unlock.savePassword=记住密码 unlock.unlockBtn=解锁 -## +## Select +unlock.chooseMasterkey.title=选择 %s 的 Masterkey 文件 unlock.chooseMasterkey.prompt=在指定路径找不到该保险库的 masterkey 文件,请手动选择密钥文件 +unlock.chooseMasterkey.chooseBtn=选择... unlock.chooseMasterkey.filePickerTitle=选择 Masterkey 文件 ## Success unlock.success.message=已成功解锁 "%s"! 您现在可以通过其虚拟驱动器访问它 @@ -114,6 +116,7 @@ unlock.error.heading=无法解锁保险库 ### Invalid Mount Point unlock.error.invalidMountPoint.notExisting=挂载点 "%s" 不是目录、非空或不存在 unlock.error.invalidMountPoint.existing=挂载点 "%s" 已存在或缺少父文件夹 +unlock.error.invalidMountPoint.driveLetterOccupied=驱动器号 %s 已被占用。 # Lock ## Force @@ -215,10 +218,10 @@ preferences.updates.checkNowBtn=立即检查 preferences.updates.updateAvailable=可更新到版本 %s ## Contribution preferences.contribute=支持我们 -preferences.contribute.registeredFor=已为 %s 注册支持者证书 +preferences.contribute.registeredFor=已注册署名 %s 的赞助者证书 preferences.contribute.noCertificate=支持 Cryptomator 并获得一份支持者证书。它类似于许可证密钥,特别之处是提供给使用免费软件的牛人的 ;-) preferences.contribute.getCertificate=还没有该证书吗?了解您如何获取 -preferences.contribute.promptText=请在此粘贴支持者证书代码 +preferences.contribute.promptText=在这里粘贴赞助者证书码 #<-- Add entries for donations and code/translation/documentation contribution --> ## About diff --git a/src/main/resources/i18n/strings_zh_HK.properties b/src/main/resources/i18n/strings_zh_HK.properties new file mode 100644 index 000000000..ca14a896f --- /dev/null +++ b/src/main/resources/i18n/strings_zh_HK.properties @@ -0,0 +1,88 @@ +# Locale Specific CSS files such as CJK, RTL,... + +# Generics +## Button +## Error + +# Defaults + +# Tray Menu + +# Add Vault Wizard +## Welcome +## New +### Name +### Location +### Password +### Information +## Existing +## Success + +# Remove Vault + +# Change Password + +# Forget Password + +# Unlock +## Select +## Success +## Failure +### Invalid Mount Point + +# Lock +## Force +## Failure + +# Migration +## Start +## Run +## Success +## Missing file system capabilities +## Impossible + +# Health Check +## Start +## Start Failure +## Check Selection +## Detail view +## Fix Application + +# Preferences +## General +## Volume +## Updates +## Contribution +#<-- Add entries for donations and code/translation/documentation contribution --> + +## About + +# Vault Statistics +## Read +## Write + +# Main Window +## Drag 'n' Drop +## Vault List +## Vault Detail +### Welcome +### Locked +### Unlocked +### Missing +### Needs Migration +### Error + +# Wrong File Alert + +# Vault Options +## General + +## Mount +## Master Key + + +# Recovery Key + +# New Password + +# Quit diff --git a/src/main/resources/i18n/strings_zh_TW.properties b/src/main/resources/i18n/strings_zh_TW.properties index cf77ffbbe..7b488f003 100644 --- a/src/main/resources/i18n/strings_zh_TW.properties +++ b/src/main/resources/i18n/strings_zh_TW.properties @@ -102,8 +102,10 @@ unlock.title=解鎖 %s unlock.passwordPrompt=輸入 "%s" 的密碼: unlock.savePassword=記住密碼 unlock.unlockBtn=解鎖 -## +## Select +unlock.chooseMasterkey.title=选择“%s"的主密钥 unlock.chooseMasterkey.prompt=無法在其預期位置找到此保管庫的主密鑰文件。請手動選擇密鑰文件。 +unlock.chooseMasterkey.chooseBtn=選取 unlock.chooseMasterkey.filePickerTitle=選擇主金鑰檔案 ## Success unlock.success.message=成功解鎖 "%s"!您現在可以存存取您的加密檔案庫。 diff --git a/src/main/resources/license/THIRD-PARTY.txt b/src/main/resources/license/THIRD-PARTY.txt index 32680971b..839562428 100644 --- a/src/main/resources/license/THIRD-PARTY.txt +++ b/src/main/resources/license/THIRD-PARTY.txt @@ -17,23 +17,23 @@ Cryptomator uses 40 third-party dependencies under the following licenses: - jnr-a64asm (com.github.jnr:jnr-a64asm:1.0.0 - http://nexus.sonatype.org/oss-repository-hosting.html/jnr-a64asm) - jnr-constants (com.github.jnr:jnr-constants:0.10.2 - http://github.com/jnr/jnr-constants) - jnr-ffi (com.github.jnr:jnr-ffi:2.2.7 - http://github.com/jnr/jnr-ffi) - - Dagger (com.google.dagger:dagger:2.40.3 - https://github.com/google/dagger) + - Dagger (com.google.dagger:dagger:2.41 - https://github.com/google/dagger) - Guava InternalFutureFailureAccess and InternalFutures (com.google.guava:failureaccess:1.0.1 - https://github.com/google/guava/failureaccess) - - Guava: Google Core Libraries for Java (com.google.guava:guava:31.0-jre - https://github.com/google/guava) + - Guava: Google Core Libraries for Java (com.google.guava:guava:31.1-jre - https://github.com/google/guava) - Apache Commons CLI (commons-cli:commons-cli:1.4 - http://commons.apache.org/proper/commons-cli/) - javax.inject (javax.inject:javax.inject:1 - http://code.google.com/p/atinject/) - Apache Commons Lang (org.apache.commons:commons-lang3:3.12.0 - https://commons.apache.org/proper/commons-lang/) - Apache HttpCore (org.apache.httpcomponents:httpcore:4.4.14 - http://hc.apache.org/httpcomponents-core-ga) - Jackrabbit WebDAV Library (org.apache.jackrabbit:jackrabbit-webdav:2.21.5 - http://jackrabbit.apache.org/jackrabbit-webdav/) - - Jetty :: Http Utility (org.eclipse.jetty:jetty-http:10.0.6 - https://eclipse.org/jetty/jetty-http) - - Jetty :: IO Utility (org.eclipse.jetty:jetty-io:10.0.6 - https://eclipse.org/jetty/jetty-io) - - Jetty :: Security (org.eclipse.jetty:jetty-security:10.0.6 - https://eclipse.org/jetty/jetty-security) - - Jetty :: Server Core (org.eclipse.jetty:jetty-server:10.0.6 - https://eclipse.org/jetty/jetty-server) - - Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:10.0.6 - https://eclipse.org/jetty/jetty-servlet) - - Jetty :: Utilities (org.eclipse.jetty:jetty-util:10.0.6 - https://eclipse.org/jetty/jetty-util) + - Jetty :: Http Utility (org.eclipse.jetty:jetty-http:10.0.8 - https://eclipse.org/jetty/jetty-http) + - Jetty :: IO Utility (org.eclipse.jetty:jetty-io:10.0.8 - https://eclipse.org/jetty/jetty-io) + - Jetty :: Security (org.eclipse.jetty:jetty-security:10.0.8 - https://eclipse.org/jetty/jetty-security) + - Jetty :: Server Core (org.eclipse.jetty:jetty-server:10.0.8 - https://eclipse.org/jetty/jetty-server) + - Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:10.0.8 - https://eclipse.org/jetty/jetty-servlet) + - Jetty :: Utilities (org.eclipse.jetty:jetty-util:10.0.8 - https://eclipse.org/jetty/jetty-util) - Jetty :: Servlet API and Schemas for JPMS and OSGi (org.eclipse.jetty.toolchain:jetty-servlet-api:4.0.6 - https://eclipse.org/jetty/jetty-servlet-api) Apache-2.0: - - Gson (com.google.code.gson:gson:2.8.9 - https://github.com/google/gson/gson) + - Gson (com.google.code.gson:gson:2.9.0 - https://github.com/google/gson/gson) - Java Native Access (net.java.dev.jna:jna:5.9.0 - https://github.com/java-native-access/jna) - Java Native Access Platform (net.java.dev.jna:jna-platform:5.9.0 - https://github.com/java-native-access/jna) BSD-3-Clause: @@ -45,38 +45,38 @@ Cryptomator uses 40 third-party dependencies under the following licenses: Eclipse Public License - Version 1.0: - Jetty :: Servlet API and Schemas for JPMS and OSGi (org.eclipse.jetty.toolchain:jetty-servlet-api:4.0.6 - https://eclipse.org/jetty/jetty-servlet-api) Eclipse Public License - Version 2.0: - - Jetty :: Http Utility (org.eclipse.jetty:jetty-http:10.0.6 - https://eclipse.org/jetty/jetty-http) - - Jetty :: IO Utility (org.eclipse.jetty:jetty-io:10.0.6 - https://eclipse.org/jetty/jetty-io) - - Jetty :: Security (org.eclipse.jetty:jetty-security:10.0.6 - https://eclipse.org/jetty/jetty-security) - - Jetty :: Server Core (org.eclipse.jetty:jetty-server:10.0.6 - https://eclipse.org/jetty/jetty-server) - - Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:10.0.6 - https://eclipse.org/jetty/jetty-servlet) - - Jetty :: Utilities (org.eclipse.jetty:jetty-util:10.0.6 - https://eclipse.org/jetty/jetty-util) + - Jetty :: Http Utility (org.eclipse.jetty:jetty-http:10.0.8 - https://eclipse.org/jetty/jetty-http) + - Jetty :: IO Utility (org.eclipse.jetty:jetty-io:10.0.8 - https://eclipse.org/jetty/jetty-io) + - Jetty :: Security (org.eclipse.jetty:jetty-security:10.0.8 - https://eclipse.org/jetty/jetty-security) + - Jetty :: Server Core (org.eclipse.jetty:jetty-server:10.0.8 - https://eclipse.org/jetty/jetty-server) + - Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:10.0.8 - https://eclipse.org/jetty/jetty-servlet) + - Jetty :: Utilities (org.eclipse.jetty:jetty-util:10.0.8 - https://eclipse.org/jetty/jetty-util) Eclipse Public License - v 1.0: - - Logback Classic Module (ch.qos.logback:logback-classic:1.2.8 - http://logback.qos.ch/logback-classic) - - Logback Core Module (ch.qos.logback:logback-core:1.2.8 - http://logback.qos.ch/logback-core) + - Logback Classic Module (ch.qos.logback:logback-classic:1.2.11 - http://logback.qos.ch/logback-classic) + - Logback Core Module (ch.qos.logback:logback-core:1.2.11 - http://logback.qos.ch/logback-core) Eclipse Public License - v 2.0: - jnr-posix (com.github.jnr:jnr-posix:3.1.10 - http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix) GNU Lesser General Public License: - - Logback Classic Module (ch.qos.logback:logback-classic:1.2.8 - http://logback.qos.ch/logback-classic) - - Logback Core Module (ch.qos.logback:logback-core:1.2.8 - http://logback.qos.ch/logback-core) + - Logback Classic Module (ch.qos.logback:logback-classic:1.2.11 - http://logback.qos.ch/logback-classic) + - Logback Core Module (ch.qos.logback:logback-core:1.2.11 - http://logback.qos.ch/logback-core) GPLv2: - jnr-posix (com.github.jnr:jnr-posix:3.1.10 - http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix) GPLv2+CE: - - javafx-base (org.openjfx:javafx-base:17.0.1 - https://openjdk.java.net/projects/openjfx/javafx-base/) - - javafx-controls (org.openjfx:javafx-controls:17.0.1 - https://openjdk.java.net/projects/openjfx/javafx-controls/) - - javafx-fxml (org.openjfx:javafx-fxml:17.0.1 - https://openjdk.java.net/projects/openjfx/javafx-fxml/) - - javafx-graphics (org.openjfx:javafx-graphics:17.0.1 - https://openjdk.java.net/projects/openjfx/javafx-graphics/) + - javafx-base (org.openjfx:javafx-base:18 - https://openjdk.java.net/projects/openjfx/javafx-base/) + - javafx-controls (org.openjfx:javafx-controls:18 - https://openjdk.java.net/projects/openjfx/javafx-controls/) + - javafx-fxml (org.openjfx:javafx-fxml:18 - https://openjdk.java.net/projects/openjfx/javafx-fxml/) + - javafx-graphics (org.openjfx:javafx-graphics:18 - https://openjdk.java.net/projects/openjfx/javafx-graphics/) LGPL 2.1: - jnr-posix (com.github.jnr:jnr-posix:3.1.10 - http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix) LGPL-2.1-or-later: - Java Native Access (net.java.dev.jna:jna:5.9.0 - https://github.com/java-native-access/jna) - Java Native Access Platform (net.java.dev.jna:jna-platform:5.9.0 - https://github.com/java-native-access/jna) MIT License: - - java jwt (com.auth0:java-jwt:3.18.2 - https://github.com/auth0/java-jwt) + - java jwt (com.auth0:java-jwt:3.19.0 - https://github.com/auth0/java-jwt) - jnr-x86asm (com.github.jnr:jnr-x86asm:1.0.2 - http://github.com/jnr/jnr-x86asm) - jnr-fuse (com.github.serceman:jnr-fuse:0.5.7 - https://github.com/SerCeMan/jnr-fuse) - zxcvbn4j (com.nulab-inc:zxcvbn:1.5.2 - https://github.com/nulab/zxcvbn4j) - - SLF4J API Module (org.slf4j:slf4j-api:1.7.32 - http://www.slf4j.org) + - SLF4J API Module (org.slf4j:slf4j-api:1.7.36 - http://www.slf4j.org) The BSD 2-Clause License: - EasyBind (com.tobiasdiez:easybind:2.2 - https://github.com/tobiasdiez/EasyBind) diff --git a/src/test/java/org/cryptomator/common/PassphraseTest.java b/src/test/java/org/cryptomator/common/PassphraseTest.java new file mode 100644 index 000000000..02f640e94 --- /dev/null +++ b/src/test/java/org/cryptomator/common/PassphraseTest.java @@ -0,0 +1,129 @@ +package org.cryptomator.common; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; + +public class PassphraseTest { + + @ParameterizedTest + @CsvSource(value = { + "-1, 0", + "0, -1", + "0, 10", + "10, 0", + "10, 10" + }) + public void testInvalidConstructorArgs(int offset, int length) { + char[] data = "test".toCharArray(); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> { + new Passphrase(data, offset, length); + }); + } + + @ParameterizedTest + @CsvSource(value = { + "0, 4", + "0, 0", + "0, 1", + "1, 1", + "2, 2" + }) + public void testValidConstructorArgs(int offset, int length) { + char[] data = "test".toCharArray(); + var pw = new Passphrase(data, offset, length); + Assertions.assertEquals(length, pw.length()); + Assertions.assertEquals("test".substring(offset, offset + length), pw.toString()); + } + + @Nested + public class InstanceMethods { + + private Passphrase pw1; + private Passphrase pw2; + + @BeforeEach + public void setup() { + char[] foo = "test test".toCharArray(); + pw1 = new Passphrase(foo, 5, 4); + pw2 = Passphrase.copyOf("test"); + } + + @Test + public void testToString() { + Assertions.assertEquals("test", pw1.toString()); + Assertions.assertEquals("test", pw2.toString()); + } + + @Test + public void testEquals() { + Assertions.assertEquals(pw1, pw2); + } + + @Test + public void testHashcode() { + Assertions.assertEquals(pw1.hashCode(), pw2.hashCode()); + } + + @Test + public void testLength() { + Assertions.assertEquals(4, pw1.length()); + Assertions.assertEquals(4, pw2.length()); + } + + @Test + public void testCharAt() { + Assertions.assertEquals('s', pw1.charAt(2)); + } + + @ParameterizedTest + @ValueSource(ints = {-1, 4, 5}) + public void testInvalidCharAt(int idx) { + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> pw1.charAt(idx)); + } + + @ParameterizedTest + @ValueSource(ints = {0, 1, 2, 3}) + public void testValidCharAt(int idx) { + Assertions.assertEquals("test".charAt(idx), pw1.charAt(idx)); + } + + @ParameterizedTest + @CsvSource(value = { + "-1, 0", + "0, -1", + "-1, -1", + "0, 5", + "3, 2" + }) + public void testInvalidSubSequence(int start, int end) { + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> pw1.subSequence(start, end)); + } + + @ParameterizedTest + @CsvSource(value = { + "0, 4", + "1, 4", + "0, 2", + "2, 4", + "4, 4", + }) + public void testValidSubSequence(int start, int end) { + Assertions.assertEquals("test".substring(start, end), pw1.subSequence(start, end).toString()); + } + + @Test + public void testDestroy() { + pw2.destroy(); + Assertions.assertFalse(pw1.isDestroyed()); + Assertions.assertTrue(pw2.isDestroyed()); + Assertions.assertNotEquals(pw1, pw2); + } + + } + +} \ No newline at end of file diff --git a/src/test/java/org/cryptomator/common/keychain/KeychainManagerTest.java b/src/test/java/org/cryptomator/common/keychain/KeychainManagerTest.java index e82e67e2d..aa8b6e1f3 100644 --- a/src/test/java/org/cryptomator/common/keychain/KeychainManagerTest.java +++ b/src/test/java/org/cryptomator/common/keychain/KeychainManagerTest.java @@ -2,7 +2,9 @@ package org.cryptomator.common.keychain; import org.cryptomator.integrations.keychain.KeychainAccessException; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; @@ -32,7 +34,8 @@ public class KeychainManagerTest { public static void startup() throws InterruptedException { CountDownLatch latch = new CountDownLatch(1); Platform.startup(latch::countDown); - latch.await(5, TimeUnit.SECONDS); + var javafxStarted = latch.await(5, TimeUnit.SECONDS); + Assumptions.assumeTrue(javafxStarted); } @Test diff --git a/src/test/java/org/cryptomator/common/mountpoint/CustomMountPointChooserTest.java b/src/test/java/org/cryptomator/common/mountpoint/CustomMountPointChooserTest.java new file mode 100644 index 000000000..da2e0fde0 --- /dev/null +++ b/src/test/java/org/cryptomator/common/mountpoint/CustomMountPointChooserTest.java @@ -0,0 +1,187 @@ +package org.cryptomator.common.mountpoint; + +import org.cryptomator.common.Environment; +import org.cryptomator.common.settings.VaultSettings; +import org.cryptomator.common.vaults.MountPointRequirement; +import org.cryptomator.common.vaults.Volume; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class CustomMountPointChooserTest { + + //--- Mocks --- + VaultSettings vaultSettings; + Environment environment; + Volume volume; + + CustomMountPointChooser customMpc; + + + @BeforeEach + public void init() { + this.volume = Mockito.mock(Volume.class); + this.vaultSettings = Mockito.mock(VaultSettings.class); + this.environment = Mockito.mock(Environment.class); + this.customMpc = new CustomMountPointChooser(vaultSettings, environment); + } + + @Nested + public class WinfspPreperations { + + @Test + @DisplayName("Hideaway name for PARENT_NO_MOUNTPOINT is not the same as mountpoint") + public void testGetHideaway() { + //prepare + Path mntPoint = Path.of("/foo/bar"); + //execute + var hideaway = customMpc.getHideaway(mntPoint); + //eval + Assertions.assertNotEquals(hideaway.getFileName(), mntPoint.getFileName()); + Assertions.assertEquals(hideaway.getParent(), mntPoint.getParent()); + Assertions.assertTrue(hideaway.getFileName().toString().contains(mntPoint.getFileName().toString())); + } + + @Test + @DisplayName("PARENT_NO_MOUNTPOINT preparations succeeds, if only mountpoint is present") + public void testPrepareParentNoMountpointOnlyMountpoint(@TempDir Path tmpDir) throws IOException { + //prepare + var mntPoint = tmpDir.resolve("mntPoint"); + Files.createDirectory(mntPoint); + + //execute + Assertions.assertDoesNotThrow(() -> customMpc.prepareParentNoMountPoint(mntPoint)); + + //evaluate + Assertions.assertTrue(Files.notExists(mntPoint)); + + Path hideaway = customMpc.getHideaway(mntPoint); + Assertions.assertTrue(Files.exists(hideaway)); + + if(OS.WINDOWS.isCurrentOs()) { + Assertions.assertTrue((Boolean) Files.getAttribute(hideaway, "dos:hidden")); + } + } + + @Test + @DisplayName("PARENT_NO_MOUNTPOINT preparations fail, if only non-empty mountpoint is present") + public void testPrepareParentNoMountpointOnlyNonEmptyMountpoint(@TempDir Path tmpDir) throws IOException { + //prepare + var mntPoint = tmpDir.resolve("mntPoint"); + Files.createDirectory(mntPoint); + Files.createFile(mntPoint.resolve("foo")); + + //execute + Assertions.assertThrows(InvalidMountPointException.class, () -> customMpc.prepareParentNoMountPoint(mntPoint)); + + //evaluate + Assertions.assertTrue(Files.exists(mntPoint.resolve("foo"))); + } + + @Test + @DisplayName("PARENT_NO_MOUNTPOINT preparation succeeds, if for any reason only hideaway dir is present") + public void testPrepareParentNoMountpointOnlyHideaway(@TempDir Path tmpDir) throws IOException { + //prepare + var mntPoint = tmpDir.resolve("mntPoint"); + var hideaway = customMpc.getHideaway(mntPoint); + Files.createDirectory(hideaway); //we explicitly do not set the file attributes here + + //execute + Assertions.assertDoesNotThrow(() -> customMpc.prepareParentNoMountPoint(mntPoint)); + + //evaluate + Assertions.assertTrue(Files.exists(hideaway)); + + if(OS.WINDOWS.isCurrentOs()) { + Assertions.assertTrue((Boolean) Files.getAttribute(hideaway, "dos:hidden")); + } + } + + @Test + @DisplayName("PARENT_NO_MOUNTPOINT preparation fails, if mountpoint and hideaway dirs are present") + public void testPrepareParentNoMountpointMountPointAndHideaway(@TempDir Path tmpDir) throws IOException { + //prepare + var mntPoint = tmpDir.resolve("mntPoint"); + var hideaway = customMpc.getHideaway(mntPoint); + Files.createDirectory(hideaway); //we explicitly do not set the file attributes here + Files.createDirectory(mntPoint); + + //execute + Assertions.assertThrows(InvalidMountPointException.class, () -> customMpc.prepareParentNoMountPoint(mntPoint)); + + //evaluate + Assertions.assertTrue(Files.exists(hideaway)); + Assertions.assertTrue(Files.exists(mntPoint)); + + if(OS.WINDOWS.isCurrentOs()) { + Assertions.assertFalse((Boolean) Files.getAttribute(hideaway, "dos:hidden")); + } + } + + @Test + @DisplayName("PARENT_NO_MOUNTPOINT preparation fails, if neither mountpoint nor hideaway dir is present") + public void testPrepareParentNoMountpointNothing(@TempDir Path tmpDir) { + //prepare + var mntPoint = tmpDir.resolve("mntPoint"); + var hideaway = customMpc.getHideaway(mntPoint); + + //execute + Assertions.assertThrows(InvalidMountPointException.class, () -> customMpc.prepareParentNoMountPoint(mntPoint)); + + //evaluate + Assertions.assertTrue(Files.notExists(hideaway)); + Assertions.assertTrue(Files.notExists(mntPoint)); + } + + @Test + @DisplayName("Normal Cleanup for PARENT_NO_MOUNTPOINT") + public void testCleanupSuccess(@TempDir Path tmpDir) throws IOException { + //prepare + var mntPoint = tmpDir.resolve("mntPoint"); + var hideaway = customMpc.getHideaway(mntPoint); + + Files.createDirectory(hideaway); + Mockito.when(volume.getMountPointRequirement()).thenReturn(MountPointRequirement.PARENT_NO_MOUNT_POINT); + + //execute + Assertions.assertDoesNotThrow(() -> customMpc.cleanup(volume, mntPoint)); + + //evaluate + Assertions.assertTrue(Files.exists(mntPoint)); + Assertions.assertTrue(Files.notExists(hideaway)); + + if(OS.WINDOWS.isCurrentOs()) { + Assertions.assertFalse((Boolean) Files.getAttribute(mntPoint, "dos:hidden")); + } + } + + @Test + @DisplayName("On IOException cleanup for PARENT_NO_MOUNTPOINT exits normally") + public void testCleanupIOFailure(@TempDir Path tmpDir) throws IOException { + //prepare + var mntPoint = tmpDir.resolve("mntPoint"); + var hideaway = customMpc.getHideaway(mntPoint); + + Files.createDirectory(hideaway); + Mockito.when(volume.getMountPointRequirement()).thenReturn(MountPointRequirement.PARENT_NO_MOUNT_POINT); + try (MockedStatic filesMock = Mockito.mockStatic(Files.class)) { + filesMock.when(() -> Files.move(Mockito.any(), Mockito.any(), Mockito.any())).thenThrow(new IOException("error")); + //execute + Assertions.assertDoesNotThrow(() -> customMpc.cleanup(volume, mntPoint)); + } + } + + } + + +} diff --git a/src/test/java/org/cryptomator/logging/LaunchBasedTriggeringPolicyTest.java b/src/test/java/org/cryptomator/logging/LaunchBasedTriggeringPolicyTest.java index 16385d949..0449bd5e2 100644 --- a/src/test/java/org/cryptomator/logging/LaunchBasedTriggeringPolicyTest.java +++ b/src/test/java/org/cryptomator/logging/LaunchBasedTriggeringPolicyTest.java @@ -31,8 +31,8 @@ public class LaunchBasedTriggeringPolicyTest { triggered = policy.isTriggeringEvent(activeFile, event); Assertions.assertFalse(triggered); - Mockito.verifyZeroInteractions(activeFile); - Mockito.verifyZeroInteractions(event); + Mockito.verifyNoInteractions(activeFile); + Mockito.verifyNoInteractions(event); } } diff --git a/src/test/java/org/cryptomator/ui/controls/SecurePasswordFieldTest.java b/src/test/java/org/cryptomator/ui/controls/SecurePasswordFieldTest.java index 865966049..bfe31816e 100644 --- a/src/test/java/org/cryptomator/ui/controls/SecurePasswordFieldTest.java +++ b/src/test/java/org/cryptomator/ui/controls/SecurePasswordFieldTest.java @@ -1,5 +1,6 @@ package org.cryptomator.ui.controls; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeAll; @@ -8,7 +9,6 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import javafx.application.Platform; -import java.awt.GraphicsEnvironment; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -18,13 +18,10 @@ public class SecurePasswordFieldTest { @BeforeAll public static void initJavaFx() throws InterruptedException { - Assumptions.assumeFalse(GraphicsEnvironment.isHeadless()); - final CountDownLatch latch = new CountDownLatch(1); + CountDownLatch latch = new CountDownLatch(1); Platform.startup(latch::countDown); - - if (!latch.await(5L, TimeUnit.SECONDS)) { - throw new ExceptionInInitializerError(); - } + var javafxStarted = latch.await(5, TimeUnit.SECONDS); + Assumptions.assumeTrue(javafxStarted); } @Nested diff --git a/src/test/java/org/cryptomator/ui/recoverykey/RecoveryKeyFactoryTest.java b/src/test/java/org/cryptomator/ui/recoverykey/RecoveryKeyFactoryTest.java index 0df21f6c0..c9061451e 100644 --- a/src/test/java/org/cryptomator/ui/recoverykey/RecoveryKeyFactoryTest.java +++ b/src/test/java/org/cryptomator/ui/recoverykey/RecoveryKeyFactoryTest.java @@ -11,13 +11,12 @@ import org.mockito.Mockito; import java.io.IOException; import java.nio.file.Path; -import java.security.SecureRandom; public class RecoveryKeyFactoryTest { - private WordEncoder wordEncoder = new WordEncoder(); - private MasterkeyFileAccess masterkeyFileAccess = Mockito.mock(MasterkeyFileAccess.class); - private RecoveryKeyFactory inTest = new RecoveryKeyFactory(wordEncoder, masterkeyFileAccess); + private final WordEncoder wordEncoder = new WordEncoder(); + private final MasterkeyFileAccess masterkeyFileAccess = Mockito.mock(MasterkeyFileAccess.class); + private final RecoveryKeyFactory inTest = new RecoveryKeyFactory(wordEncoder, masterkeyFileAccess); @Test @DisplayName("createRecoveryKey() creates 44 words") diff --git a/suppression.xml b/suppression.xml index c747f92a7..ccd1a1cdf 100644 --- a/suppression.xml +++ b/suppression.xml @@ -25,4 +25,23 @@ org.eclipse.jetty.toolchain:jetty-servlet-api:4.0.6 .* + + + + ^org\.cryptomator:.*$ + cpe:/a:cryptomator:cryptomator + CVE-2022-25366 + + + + + ^commons\-cli:commons\-cli:.*$ + cpe:/a:apache:james + + cpe:/a:spirit-project:spirit + \ No newline at end of file