Merge branch 'develop' into feature/integrations-api-1.1.0

# Conflicts:
#	pom.xml
#	src/main/java/org/cryptomator/ui/launcher/UiLauncherModule.java
#	src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java
#	src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java
#	src/main/resources/license/THIRD-PARTY.txt
This commit is contained in:
Sebastian Stenzel
2022-04-03 12:53:08 +02:00
121 changed files with 1783 additions and 1189 deletions
+7
View File
@@ -4,6 +4,10 @@ on:
release:
types: [published]
workflow_dispatch:
inputs:
version:
description: 'Version'
required: false
env:
JAVA_VERSION: 17
@@ -28,6 +32,9 @@ jobs:
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
SEM_VER_STR=${GITHUB_REF##*/}
mvn versions:set -DnewVersion=${SEM_VER_STR}
elif [[ "${{ github.event.inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
SEM_VER_STR="${{ github.event.inputs.version }}"
mvn versions:set -DnewVersion=${SEM_VER_STR}
else
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
fi
+11 -6
View File
@@ -43,13 +43,15 @@ jobs:
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)
- name: Sign source tarball with key 615D449FE6E6A235
if: startsWith(github.ref, 'refs/tags/')
run: |
git archive --prefix="cryptomator-${{ github.ref_name }}/" -o "cryptomator-${{ github.ref_name }}.tar.gz" ${{ github.ref }}
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-*.tar.gz
env:
CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
continue-on-error: true
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- name: Draft a release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v1
@@ -58,6 +60,9 @@ jobs:
discussion_category_name: releases
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
generate_release_notes: true
files: |
cryptomator-*.tar.gz.asc
fail_on_unmatched_files: true
body: |-
:construction: Work in Progress
+6
View File
@@ -10,6 +10,9 @@ on:
required: true
default: false
type: boolean
version:
description: 'Version'
required: false
env:
JAVA_VERSION: 17
@@ -38,6 +41,9 @@ jobs:
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
SEM_VER_STR=${GITHUB_REF##*/}
mvn versions:set -DnewVersion=${SEM_VER_STR}
elif [[ "${{ github.event.inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
SEM_VER_STR="${{ github.event.inputs.version }}"
mvn versions:set -DnewVersion=${SEM_VER_STR}
else
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
fi
+7
View File
@@ -4,6 +4,10 @@ on:
release:
types: [published]
workflow_dispatch:
inputs:
version:
description: 'Version'
required: false
env:
JAVA_VERSION: 17
@@ -28,6 +32,9 @@ jobs:
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
SEM_VER_STR=${GITHUB_REF##*/}
mvn versions:set -DnewVersion=${SEM_VER_STR}
elif [[ "${{ github.event.inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
SEM_VER_STR="${{ github.event.inputs.version }}"
mvn versions:set -DnewVersion=${SEM_VER_STR}
else
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
fi
+1 -1
View File
@@ -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
run: xvfb-run mvn -B clean install jacoco:report -Pcoverage,dependency-check
+54 -6
View File
@@ -4,6 +4,10 @@ on:
release:
types: [published]
workflow_dispatch:
inputs:
version:
description: 'Version'
required: false
env:
JAVA_VERSION: 17
@@ -33,6 +37,9 @@ jobs:
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
SEM_VER_STR=${GITHUB_REF##*/}
mvn versions:set -DnewVersion=${SEM_VER_STR}
elif [[ "${{ github.event.inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
SEM_VER_STR="${{ github.event.inputs.version }}"
mvn versions:set -DnewVersion=${SEM_VER_STR}
else
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
fi
@@ -80,6 +87,7 @@ jobs:
--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=\"~/AppData/Roaming/Cryptomator\""
--java-options "-Dcryptomator.pluginDir=\"~/AppData/Roaming/Cryptomator/Plugins\""
@@ -217,11 +225,11 @@ jobs:
run: >
"${WIX}/bin/light.exe" -b dist/win/ dist/win/bundle/bundleWithWinfsp.wixobj
-ext WixBalExtension
-out installer/unsigned/Cryptomator.exe
-out installer/unsigned/Cryptomator-Installer.exe
- name: Detach burn engine in preparation to sign
run: >
"${WIX}/bin/insignia.exe"
-ib installer/unsigned/Cryptomator.exe
-ib installer/unsigned/Cryptomator-Installer.exe
-o tmp/engine.exe
- name: Codesign burn engine
uses: skymatic/code-sign-action@v1
@@ -235,8 +243,8 @@ jobs:
- name: Reattach signed burn engine to installer
run : >
"${WIX}/bin/insignia.exe"
-ab tmp/engine.exe installer/unsigned/Cryptomator.exe
-o installer/Cryptomator.exe
-ab tmp/engine.exe installer/unsigned/Cryptomator-Installer.exe
-o installer/Cryptomator-Installer.exe
- name: Codesign EXE
uses: skymatic/code-sign-action@v1
with:
@@ -247,7 +255,7 @@ jobs:
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
run: mv installer/Cryptomator-Installer.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
@@ -271,4 +279,44 @@ jobs:
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
files: |
Cryptomator-*.exe
Cryptomator-*.asc
Cryptomator-*.asc
allowlist:
name: Anti Virus Allowlisting
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
needs: [build-msi, build-exe]
steps:
- name: Download .msi
uses: actions/download-artifact@v2
with:
name: msi
path: msi
- name: Download .exe
uses: actions/download-artifact@v2
with:
name: exe
path: exe
- name: Collect files
run: |
mkdir files
cp msi/*.msi files
cp exe/*.exe files
- name: Upload to Kaspersky
uses: SamKirkland/FTP-Deploy-Action@4.3.0
with:
protocol: ftps
server: allowlist.kaspersky-labs.com
port: 990
username: ${{ secrets.ALLOWLIST_KASPERSKY_USERNAME }}
password: ${{ secrets.ALLOWLIST_KASPERSKY_PASSWORD }}
local-dir: files/
- name: Upload to Avast
uses: SamKirkland/FTP-Deploy-Action@4.3.0
with:
protocol: ftp
server: whitelisting.avast.com
port: 21
username: ${{ secrets.ALLOWLIST_AVAST_USERNAME }}
password: ${{ secrets.ALLOWLIST_AVAST_PASSWORD }}
local-dir: files/
+1 -1
View File
@@ -2,7 +2,7 @@
<configuration default="false" name="Cryptomator Windows Dev" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath=&quot;~/AppData/Roaming/Cryptomator-Dev/settings.json&quot; -Dcryptomator.ipcSocketPath=&quot;~/AppData/Roaming/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;~/AppData/Roaming/Cryptomator-Dev&quot; -Dcryptomator.pluginDir=&quot;~/AppData/Roaming/Cryptomator-Dev/Plugins&quot; -Dcryptomator.keychainPath=&quot;~/AppData/Roaming/Cryptomator-Dev/keychain.json&quot; -Dcryptomator.mountPointsDir=&quot;~/Cryptomator-Dev&quot; -Dfuse.experimental=&quot;true&quot; -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m" />
<option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath=&quot;~/AppData/Roaming/Cryptomator-Dev/settings.json&quot; -Dcryptomator.ipcSocketPath=&quot;~/AppData/Roaming/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;~/AppData/Roaming/Cryptomator-Dev&quot; -Dcryptomator.pluginDir=&quot;~/AppData/Roaming/Cryptomator-Dev/Plugins&quot; -Dcryptomator.keychainPath=&quot;~/AppData/Roaming/Cryptomator-Dev/keychain.json&quot; -Dcryptomator.mountPointsDir=&quot;~/Cryptomator-Dev&quot; -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m" />
<method v="2">
<option name="Make" enabled="true" />
</method>
+1 -1
View File
@@ -2,7 +2,7 @@
[![Build](https://github.com/cryptomator/cryptomator/workflows/Build/badge.svg)](https://github.com/cryptomator/cryptomator/actions?query=workflow%3ABuild)
[![Known Vulnerabilities](https://snyk.io/test/github/cryptomator/cryptomator/badge.svg)](https://snyk.io/test/github/cryptomator/cryptomator)
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/2a0adf3cec6a4143b91035d3924178f1)](https://www.codacy.com/gh/cryptomator/cryptomator/dashboard)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=cryptomator_cryptomator&metric=alert_status)](https://sonarcloud.io/dashboard?id=cryptomator_cryptomator)
[![Twitter](https://img.shields.io/badge/twitter-@Cryptomator-blue.svg?style=flat)](http://twitter.com/Cryptomator)
[![Crowdin](https://badges.crowdin.net/cryptomator/localized.svg)](https://translate.cryptomator.org/)
[![Latest Release](https://img.shields.io/github/release/cryptomator/cryptomator.svg)](https://github.com/cryptomator/cryptomator/releases/latest)
+1 -1
View File
@@ -7,5 +7,5 @@ Terminal=false
Type=Application
Categories=Utility;Security;FileTools;
StartupNotify=true
StartupWMClass=org.cryptomator.launcher.Cryptomator
StartupWMClass=org.cryptomator.launcher.Cryptomator$MainApp
MimeType=application/vnd.cryptomator.encrypted;application/vnd.cryptomator.vault;
@@ -66,6 +66,7 @@
</content_rating>
<releases>
<release date="2022-03-30" version="1.6.8"/>
<release date="2021-12-16" version="1.6.5"/>
</releases>
</component>
+1 -1
View File
@@ -140,4 +140,4 @@ Copy-Item ".\installer\Cryptomator-*.msi" -Destination ".\bundle\resources\Crypt
-dAboutUrl="$aboutUrl" `
-dHelpUrl="$helpUrl" `
-dUpdateUrl="$updateUrl"
& "$env:WIX\bin\light.exe" -b . .\bundle\BundlewithWinfsp.wixobj -ext WixBalExtension -out installer\CryptomatorBundle.exe
& "$env:WIX\bin\light.exe" -b . .\bundle\BundlewithWinfsp.wixobj -ext WixBalExtension -out installer\Cryptomator-Installer.exe
+25 -2
View File
@@ -13,7 +13,6 @@
<DialogRef Id="BrowseDlg" />
<DialogRef Id="DiskCostDlg" />
<DialogRef Id="ErrorDlg" />
<DialogRef Id="FatalError" />
<DialogRef Id="FilesInUse" />
<DialogRef Id="MsiRMFilesInUse" />
<DialogRef Id="PrepareDlg" />
@@ -24,8 +23,9 @@
<Publish Dialog="BrowseDlg" Control="OK" Event="DoAction" Value="WixUIValidatePath" Order="3">1</Publish>
<Publish Dialog="BrowseDlg" Control="OK" Event="SpawnDialog" Value="InvalidDirDlg" Order="4"><![CDATA[NOT WIXUI_DONTVALIDATEPATH AND WIXUI_INSTALLDIR_VALID<>"1"]]></Publish>
<!-- custom end dialog -->
<!-- custom end dialogs -->
<Publish Dialog="MyExitDialog" Control="Finish" Event="EndDialog" Value="Return" Order="999">1</Publish>
<Publish Dialog="MyFatalErrorDlg" Control="Finish" Event="EndDialog" Value="Return" Order="998">1</Publish>
<Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="LicenseAgreementDlg">NOT Installed</Publish>
<Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg">Installed AND PATCH</Publish>
@@ -75,11 +75,34 @@
</Control>
</Dialog>
<!-- copy pasta from https://github.com/wixtoolset/wix3/blob/develop/src/ext/UIExtension/wixlib/FatalError.wxs with adjustments-->
<Dialog Id="MyFatalErrorDlg" Width="370" Height="270" Title="!(loc.FatalError_Title)">
<Control Id="Finish" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Cancel="yes" Text="!(loc.WixUIFinish)">
<Publish Event="EndDialog" Value="Exit">1</Publish>
</Control>
<Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Disabled="yes" Text="!(loc.WixUICancel)" />
<Control Id="Bitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="234" TabSkip="no" Text="!(loc.FatalErrorBitmap)" />
<Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Disabled="yes" Text="!(loc.WixUIBack)" />
<Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
<Control Id="Title" Type="Text" X="135" Y="20" Width="220" Height="60" Transparent="yes" NoPrefix="yes" Text="!(loc.FatalErrorTitle)" />
<Control Id="Description" Type="Text" X="135" Y="70" Width="220" Height="80" Transparent="yes" NoPrefix="yes" Text="!(loc.FatalErrorDescription1) !(loc.FatalErrorDescription2)" />
<Control Id="DescriptionReason1" Type="Text" X="135" Y="160" Width="220" Height="20" Transparent="yes" NoPrefix="yes" Hidden="yes" >
<Text>Reason:</Text>
<Condition Action="show">FOUNDRUNNINGAPP</Condition>
</Control>
<Control Id="DescriptionReason2" Type="Text" X="135" Y="170" Width="220" Height="40" Transparent="yes" NoPrefix="yes" Hidden="yes" >
<Text>Cryptomator was still running during installation.</Text>
<Condition Action="show">FOUNDRUNNINGAPP</Condition>
</Control>
</Dialog>
<InstallUISequence>
<Show Dialog="MyExitDialog" Overridable="yes" OnExit="success"/>
<Show Dialog="MyFatalErrorDlg" Overridable="yes" OnExit="error"/>
</InstallUISequence>
<AdminUISequence>
<Show Dialog="MyExitDialog" Overridable="yes" OnExit="success"/>
<Show Dialog="MyFatalErrorDlg" Overridable="yes" OnExit="error"/>
</AdminUISequence>
</UI>
+20 -1
View File
@@ -127,6 +127,20 @@
<!-- WebDAV patches -->
<CustomAction Id="PatchWebDAV" Impersonate="no" ExeCommand="[INSTALLDIR]patchWebDAV.bat" Directory="INSTALLDIR" Execute="deferred" Return="asyncWait" />
<!-- Running App detection and exit -->
<Property Id="FOUNDRUNNINGAPP" Admin="yes"/>
<util:CloseApplication
Id="CloseCryptomator"
Target="cryptomator.exe"
CloseMessage="no"
RebootPrompt="no"
PromptToContinue="yes"
Description="A running instance of Cryptomator is found. Please close it to continue."
Property="FOUNDRUNNINGAPP"
>
</util:CloseApplication>
<CustomAction Id="FailOnRunningApp" Impersonate="no" ExeCommand="[SystemFolder]\cmd.exe /c &quot;exit 1&quot;" Directory="INSTALLDIR" Execute="immediate" Return="check" />
<?ifdef JpIcon ?>
<Property Id="ARPPRODUCTICON" Value="JpARPPRODUCTICON"/>
<Icon Id="JpARPPRODUCTICON" SourceFile="$(var.JpIcon)"/>
@@ -155,7 +169,12 @@
<?ifndef JpAllowDowngrades ?>
<Custom Action="JpDisallowDowngrade" After="FindRelatedProducts">JP_DOWNGRADABLE_FOUND</Custom>
<?endif?>
<RemoveExistingProducts Before="CostInitialize"/>
<!-- Check and fail if Cryptomator is running -->
<Custom Action="WixCloseApplications" Before="InstallValidate"></Custom>
<Custom Action="FailOnRunningApp" After="WixCloseApplications" >FOUNDRUNNINGAPP</Custom>
<RemoveExistingProducts After="InstallValidate"/>
<Custom Action="PatchWebDAV" After="InstallFiles">NOT Installed OR REINSTALL</Custom>
</InstallExecuteSequence>
+12 -4
View File
@@ -27,7 +27,7 @@
<nonModularGroupIds>com.github.serceman,com.github.jnr,org.ow2.asm,net.java.dev.jna,org.apache.jackrabbit,org.apache.httpcomponents,de.swiesend,org.purejava,com.github.hypfvieh</nonModularGroupIds>
<!-- cryptomator dependencies -->
<cryptomator.cryptofs.version>2.3.1</cryptomator.cryptofs.version>
<cryptomator.cryptofs.version>2.4.0</cryptomator.cryptofs.version>
<cryptomator.integrations.version>1.1.0-beta2</cryptomator.integrations.version>
<cryptomator.integrations.win.version>1.1.0-beta1</cryptomator.integrations.win.version>
<cryptomator.integrations.mac.version>1.1.0-beta1</cryptomator.integrations.mac.version>
@@ -39,7 +39,7 @@
<!-- 3rd party dependencies -->
<javafx.version>18</javafx.version>
<commons-lang3.version>3.12.0</commons-lang3.version>
<jwt.version>3.19.0</jwt.version>
<jwt.version>3.19.1</jwt.version>
<easybind.version>2.2</easybind.version>
<guava.version>31.1-jre</guava.version>
<dagger.version>2.41</dagger.version>
@@ -53,8 +53,9 @@
<mockito.version>4.4.0</mockito.version>
<hamcrest.version>2.2</hamcrest.version>
<!-- build plugin dependencies -->
<dependency-check.version>7.0.0</dependency-check.version>
<!-- build-time dependencies -->
<jetbrains.annotations.version>23.0.0</jetbrains.annotations.version>
<dependency-check.version>7.0.2</dependency-check.version>
<jacoco.version>0.8.7</jacoco.version>
</properties>
@@ -224,6 +225,13 @@
<version>1.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>${jetbrains.annotations.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
+4
View File
@@ -2,6 +2,8 @@ import org.cryptomator.integrations.tray.TrayMenuController;
import org.cryptomator.ui.traymenu.AwtTrayMenuController;
module org.cryptomator.desktop {
requires static org.jetbrains.annotations;
requires org.cryptomator.cryptofs;
requires org.cryptomator.frontend.dokany;
requires org.cryptomator.frontend.fuse;
@@ -31,6 +33,8 @@ module org.cryptomator.desktop {
opens org.cryptomator.common.settings to com.google.gson;
opens org.cryptomator.launcher to javafx.graphics;
opens org.cryptomator.common to javafx.fxml;
opens org.cryptomator.common.vaults to javafx.fxml;
opens org.cryptomator.ui.addvaultwizard to javafx.fxml;
@@ -89,11 +89,6 @@ public class Environment {
return Boolean.getBoolean("cryptomator.showTrayIcon");
}
@Deprecated // TODO: remove as soon as custom mount path works properly on Win+Fuse
public boolean useExperimentalFuse() {
return Boolean.getBoolean("fuse.experimental");
}
private int getInt(String propertyName, int defaultValue) {
String value = System.getProperty(propertyName);
try {
@@ -19,6 +19,7 @@ import java.nio.file.NoSuchFileException;
import java.nio.file.NotDirectoryException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Optional;
class CustomMountPointChooser implements MountPointChooser {
@@ -29,17 +30,15 @@ class CustomMountPointChooser implements MountPointChooser {
private static final Logger LOG = LoggerFactory.getLogger(CustomMountPointChooser.class);
private final VaultSettings vaultSettings;
private final Environment environment;
@Inject
public CustomMountPointChooser(VaultSettings vaultSettings, Environment environment) {
public CustomMountPointChooser(VaultSettings vaultSettings) {
this.vaultSettings = vaultSettings;
this.environment = environment;
}
@Override
public boolean isApplicable(Volume caller) {
return caller.getImplementationType() != VolumeImpl.FUSE || !SystemUtils.IS_OS_WINDOWS || environment.useExperimentalFuse();
return caller.getImplementationType() != VolumeImpl.WEBDAV;
}
@Override
@@ -9,6 +9,6 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface DefaultMountFlags {
@interface DefaultMountFlags {
}
@@ -0,0 +1,13 @@
package org.cryptomator.launcher;
import java.nio.file.Path;
import java.util.Collection;
public record AppLaunchEvent(AppLaunchEvent.EventType type, Collection<Path> pathsToOpen) {
public enum EventType {
REVEAL_APP,
OPEN_FILE
}
}
@@ -13,17 +13,15 @@ import org.cryptomator.common.ShutdownHook;
import org.cryptomator.ipc.IpcCommunicator;
import org.cryptomator.logging.DebugMode;
import org.cryptomator.logging.LoggerConfiguration;
import org.cryptomator.ui.launcher.UiLauncher;
import org.cryptomator.ui.fxapp.FxApplicationComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.IOException;
import javafx.application.Application;
import javafx.stage.Stage;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
@Singleton
@@ -38,19 +36,15 @@ public class Cryptomator {
private final DebugMode debugMode;
private final Environment env;
private final Lazy<IpcMessageHandler> ipcMessageHandler;
private final CountDownLatch shutdownLatch;
private final ShutdownHook shutdownHook;
private final Lazy<UiLauncher> uiLauncher;
@Inject
Cryptomator(LoggerConfiguration logConfig, DebugMode debugMode, Environment env, Lazy<IpcMessageHandler> ipcMessageHandler, @Named("shutdownLatch") CountDownLatch shutdownLatch, ShutdownHook shutdownHook, Lazy<UiLauncher> uiLauncher) {
Cryptomator(LoggerConfiguration logConfig, DebugMode debugMode, Environment env, Lazy<IpcMessageHandler> ipcMessageHandler, ShutdownHook shutdownHook) {
this.logConfig = logConfig;
this.debugMode = debugMode;
this.env = env;
this.ipcMessageHandler = ipcMessageHandler;
this.shutdownLatch = shutdownLatch;
this.shutdownHook = shutdownHook;
this.uiLauncher = uiLauncher;
}
public static void main(String[] args) {
@@ -79,7 +73,7 @@ public class Cryptomator {
communicator.sendHandleLaunchargs(List.of(args));
communicator.sendRevealRunningApp();
LOG.info("Found running application instance. Shutting down...");
return 2;
return 0;
} else {
shutdownHook.runOnShutdown(communicator::closeUnchecked);
var executor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("IPC-%d").build());
@@ -96,21 +90,38 @@ public class Cryptomator {
}
/**
* Launches the JavaFX application and waits until shutdown is requested.
* Launches the JavaFX application, blocking the main thread until shuts down.
*
* @return Nonzero exit code in case of an error.
* @implNote This method blocks until {@link #shutdownLatch} reached zero.
*/
private int runGuiApplication() {
try {
uiLauncher.get().launch();
shutdownLatch.await();
Application.launch(MainApp.class);
LOG.info("UI shut down");
return 0;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (Exception e) {
LOG.error("Terminating due to error", e);
return 1;
}
}
public static class MainApp extends Application {
@Override
public void start(Stage primaryStage) {
LOG.info("JavaFX application started.");
FxApplicationComponent component = CRYPTOMATOR_COMPONENT.fxAppComponentBuilder() //
.fxApplication(this) //
.primaryStage(primaryStage) //
.build();
component.application().start();
}
@Override
public void stop() {
LOG.info("JavaFX application stopped.");
}
}
}
@@ -3,14 +3,16 @@ package org.cryptomator.launcher;
import dagger.Component;
import org.cryptomator.common.CommonsModule;
import org.cryptomator.logging.LoggerModule;
import org.cryptomator.ui.launcher.UiLauncherModule;
import org.cryptomator.ui.fxapp.FxApplicationComponent;
import javax.inject.Singleton;
@Singleton
@Component(modules = {CryptomatorModule.class, CommonsModule.class, LoggerModule.class, UiLauncherModule.class})
@Component(modules = {CryptomatorModule.class, CommonsModule.class, LoggerModule.class})
public interface CryptomatorComponent {
Cryptomator application();
FxApplicationComponent.Builder fxAppComponentBuilder();
}
@@ -2,20 +2,50 @@ package org.cryptomator.launcher;
import dagger.Module;
import dagger.Provides;
import org.cryptomator.integrations.autostart.AutoStartProvider;
import org.cryptomator.integrations.tray.TrayIntegrationProvider;
import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
import org.cryptomator.ui.fxapp.FxApplicationComponent;
import javax.inject.Named;
import javax.inject.Singleton;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.ResourceBundle;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
@Module
@Module(subcomponents = {FxApplicationComponent.class})
class CryptomatorModule {
@Provides
@Singleton
@Named("shutdownLatch")
static CountDownLatch provideShutdownLatch() {
return new CountDownLatch(1);
static ResourceBundle provideLocalization() {
return ResourceBundle.getBundle("i18n.strings");
}
@Provides
@Singleton
@Named("launchEventQueue")
static BlockingQueue<AppLaunchEvent> provideFileOpenRequests() {
return new ArrayBlockingQueue<>(10);
}
@Provides
@Singleton
static Optional<UiAppearanceProvider> provideAppearanceProvider() {
return UiAppearanceProvider.get();
}
@Provides
@Singleton
static Optional<AutoStartProvider> provideAutostartProvider() {
return AutoStartProvider.get();
}
@Provides
@Singleton
static Optional<TrayIntegrationProvider> provideTrayIntegrationProvider() {
return TrayIntegrationProvider.get();
}
}
@@ -6,7 +6,6 @@
*******************************************************************************/
package org.cryptomator.launcher;
import org.cryptomator.ui.launcher.AppLaunchEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -20,12 +19,10 @@ import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.stream.Collectors;
@Singleton
class FileOpenRequestHandler {
@@ -1,7 +1,6 @@
package org.cryptomator.launcher;
import org.cryptomator.ipc.IpcMessageListener;
import org.cryptomator.ui.launcher.AppLaunchEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -14,7 +14,7 @@ import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.NewPasswordController;
import org.cryptomator.ui.common.PasswordStrengthUtil;
import org.cryptomator.ui.common.StageFactory;
import org.cryptomator.ui.mainwindow.MainWindow;
import org.cryptomator.ui.fxapp.PrimaryStage;
import org.cryptomator.ui.recoverykey.RecoveryKeyDisplayController;
import javax.inject.Named;
@@ -43,12 +43,12 @@ public abstract class AddVaultModule {
@Provides
@AddVaultWizardWindow
@AddVaultWizardScoped
static Stage provideStage(StageFactory factory, @MainWindow Stage owner, ResourceBundle resourceBundle) {
static Stage provideStage(StageFactory factory, @PrimaryStage Stage primaryStage, ResourceBundle resourceBundle) {
Stage stage = factory.create();
stage.setTitle(resourceBundle.getString("addvaultwizard.title"));
stage.setResizable(false);
stage.initModality(Modality.WINDOW_MODAL);
stage.initOwner(owner);
stage.initOwner(primaryStage);
return stage;
}
@@ -2,25 +2,24 @@ package org.cryptomator.ui.addvaultwizard;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.FxApplication;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import javax.inject.Inject;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.fxml.FXML;
import javafx.stage.Stage;
import java.util.Optional;
@AddVaultWizardScoped
public class AddVaultSuccessController implements FxController {
private final FxApplication fxApplication;
private final FxApplicationWindows appWindows;
private final Stage window;
private final ReadOnlyObjectProperty<Vault> vault;
@Inject
AddVaultSuccessController(FxApplication fxApplication, @AddVaultWizardWindow Stage window, @AddVaultWizardWindow ObjectProperty<Vault> vault) {
this.fxApplication = fxApplication;
AddVaultSuccessController(FxApplicationWindows appWindows, @AddVaultWizardWindow Stage window, @AddVaultWizardWindow ObjectProperty<Vault> vault) {
this.appWindows = appWindows;
this.window = window;
this.vault = vault;
}
@@ -28,7 +27,7 @@ public class AddVaultSuccessController implements FxController {
@FXML
public void unlockAndClose() {
close();
fxApplication.startUnlockWorkflow(vault.get(), Optional.of(window));
appWindows.startUnlockWorkflow(vault.get(), window);
}
@FXML
@@ -4,10 +4,10 @@ import dagger.Lazy;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -20,9 +20,6 @@ import javafx.stage.FileChooser;
import javafx.stage.Stage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.ResourceBundle;
@@ -34,7 +31,7 @@ public class ChooseExistingVaultController implements FxController {
private final Stage window;
private final Lazy<Scene> welcomeScene;
private final Lazy<Scene> successScene;
private final ErrorComponent.Builder errorComponent;
private final FxApplicationWindows appWindows;
private final ObjectProperty<Path> vaultPath;
private final ObjectProperty<Vault> vault;
private final VaultListManager vaultListManager;
@@ -43,11 +40,11 @@ public class ChooseExistingVaultController implements FxController {
private Image screenshot;
@Inject
ChooseExistingVaultController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy<Scene> welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, ErrorComponent.Builder errorComponent, ObjectProperty<Path> vaultPath, @AddVaultWizardWindow ObjectProperty<Vault> vault, VaultListManager vaultListManager, ResourceBundle resourceBundle) {
ChooseExistingVaultController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy<Scene> welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, FxApplicationWindows appWindows, ObjectProperty<Path> vaultPath, @AddVaultWizardWindow ObjectProperty<Vault> vault, VaultListManager vaultListManager, ResourceBundle resourceBundle) {
this.window = window;
this.welcomeScene = welcomeScene;
this.successScene = successScene;
this.errorComponent = errorComponent;
this.appWindows = appWindows;
this.vaultPath = vaultPath;
this.vault = vault;
this.vaultListManager = vaultListManager;
@@ -82,7 +79,7 @@ public class ChooseExistingVaultController implements FxController {
window.setScene(successScene.get());
} catch (IOException e) {
LOG.error("Failed to open existing vault.", e);
errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
appWindows.showErrorWindow(e, window, window.getScene());
}
}
}
@@ -10,12 +10,12 @@ import org.cryptomator.cryptolib.api.CryptorProvider;
import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.api.MasterkeyLoader;
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.NewPasswordController;
import org.cryptomator.ui.common.Tasks;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy;
import org.cryptomator.ui.recoverykey.RecoveryKeyFactory;
import org.slf4j.Logger;
@@ -60,7 +60,7 @@ public class CreateNewVaultPasswordController implements FxController {
private final Lazy<Scene> chooseLocationScene;
private final Lazy<Scene> recoveryKeyScene;
private final Lazy<Scene> successScene;
private final ErrorComponent.Builder errorComponent;
private final FxApplicationWindows appWindows;
private final ExecutorService executor;
private final RecoveryKeyFactory recoveryKeyFactory;
private final StringProperty vaultNameProperty;
@@ -83,12 +83,12 @@ public class CreateNewVaultPasswordController implements FxController {
public NewPasswordController newPasswordSceneController;
@Inject
CreateNewVaultPasswordController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy<Scene> chooseLocationScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY) Lazy<Scene> recoveryKeyScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, ErrorComponent.Builder errorComponent, ExecutorService executor, RecoveryKeyFactory recoveryKeyFactory, @Named("vaultName") StringProperty vaultName, ObjectProperty<Path> vaultPath, @AddVaultWizardWindow ObjectProperty<Vault> vault, @Named("recoveryKey") StringProperty recoveryKey, VaultListManager vaultListManager, ResourceBundle resourceBundle, ReadmeGenerator readmeGenerator, SecureRandom csprng, MasterkeyFileAccess masterkeyFileAccess) {
CreateNewVaultPasswordController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy<Scene> chooseLocationScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY) Lazy<Scene> recoveryKeyScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, FxApplicationWindows appWindows, ExecutorService executor, RecoveryKeyFactory recoveryKeyFactory, @Named("vaultName") StringProperty vaultName, ObjectProperty<Path> vaultPath, @AddVaultWizardWindow ObjectProperty<Vault> vault, @Named("recoveryKey") StringProperty recoveryKey, VaultListManager vaultListManager, ResourceBundle resourceBundle, ReadmeGenerator readmeGenerator, SecureRandom csprng, MasterkeyFileAccess masterkeyFileAccess) {
this.window = window;
this.chooseLocationScene = chooseLocationScene;
this.recoveryKeyScene = recoveryKeyScene;
this.successScene = successScene;
this.errorComponent = errorComponent;
this.appWindows = appWindows;
this.executor = executor;
this.recoveryKeyFactory = recoveryKeyFactory;
this.vaultNameProperty = vaultName;
@@ -127,7 +127,7 @@ public class CreateNewVaultPasswordController implements FxController {
Files.createDirectory(pathToVault);
} catch (IOException e) {
LOG.error("Failed to create vault directory.", e);
errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
appWindows.showErrorWindow(e, window, window.getScene());
return;
}
@@ -152,7 +152,7 @@ public class CreateNewVaultPasswordController implements FxController {
window.setScene(recoveryKeyScene.get());
}).onError(IOException.class, e -> {
LOG.error("Failed to initialize vault.", e);
errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
appWindows.showErrorWindow(e, window, window.getScene());
}).andFinally(() -> {
processing.set(false);
}).runOnce(executor);
@@ -168,7 +168,7 @@ public class CreateNewVaultPasswordController implements FxController {
window.setScene(successScene.get());
}).onError(IOException.class, e -> {
LOG.error("Failed to initialize vault.", e);
errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
appWindows.showErrorWindow(e, window, window.getScene());
}).andFinally(() -> {
processing.set(false);
}).runOnce(executor);
@@ -8,10 +8,10 @@ import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
import org.cryptomator.integrations.keychain.KeychainAccessException;
import org.cryptomator.ui.common.Animations;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.NewPasswordController;
import org.cryptomator.ui.controls.NiceSecurePasswordField;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -26,7 +26,6 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.security.SecureRandom;
import static org.cryptomator.common.Constants.MASTERKEY_BACKUP_SUFFIX;
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
@@ -38,9 +37,8 @@ public class ChangePasswordController implements FxController {
private final Stage window;
private final Vault vault;
private final ErrorComponent.Builder errorComponent;
private final FxApplicationWindows appWindows;
private final KeychainManager keychain;
private final SecureRandom csprng;
private final MasterkeyFileAccess masterkeyFileAccess;
public NiceSecurePasswordField oldPasswordField;
@@ -49,12 +47,11 @@ public class ChangePasswordController implements FxController {
public NewPasswordController newPasswordController;
@Inject
public ChangePasswordController(@ChangePasswordWindow Stage window, @ChangePasswordWindow Vault vault, ErrorComponent.Builder errorComponent, KeychainManager keychain, SecureRandom csprng, MasterkeyFileAccess masterkeyFileAccess) {
public ChangePasswordController(@ChangePasswordWindow Stage window, @ChangePasswordWindow Vault vault, FxApplicationWindows appWindows, KeychainManager keychain, MasterkeyFileAccess masterkeyFileAccess) {
this.window = window;
this.vault = vault;
this.errorComponent = errorComponent;
this.appWindows = appWindows;
this.keychain = keychain;
this.csprng = csprng;
this.masterkeyFileAccess = masterkeyFileAccess;
}
@@ -95,7 +92,7 @@ public class ChangePasswordController implements FxController {
oldPasswordField.requestFocus();
} catch (IOException | CryptoException e) {
LOG.error("Password change failed. Unable to perform operation.", e);
errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
appWindows.showErrorWindow(e, window, window.getScene());
}
}
@@ -4,7 +4,6 @@ import dagger.BindsInstance;
import dagger.Subcomponent;
import org.cryptomator.common.Nullable;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.stage.Stage;
@@ -16,34 +15,17 @@ public interface ErrorComponent {
@FxmlScene(FxmlFile.ERROR)
Scene scene();
default void showErrorScene() {
if (Platform.isFxApplicationThread()) {
show();
} else {
Platform.runLater(this::show);
}
}
private void show() {
default Stage show() {
Stage stage = window();
stage.setScene(scene());
stage.show();
return stage;
}
@Subcomponent.Builder
interface Builder {
@BindsInstance
Builder cause(Throwable cause);
@BindsInstance
Builder window(Stage window);
@BindsInstance
Builder returnToScene(@Nullable Scene previousScene);
ErrorComponent build();
@Subcomponent.Factory
interface Factory {
ErrorComponent create(@BindsInstance Throwable cause, @BindsInstance Stage window, @BindsInstance @Nullable Scene previousScene);
}
}
@@ -1,23 +1,24 @@
package org.cryptomator.ui.common;
import org.cryptomator.ui.fxapp.FxApplicationScoped;
import javax.inject.Inject;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import java.util.function.Consumer;
@FxApplicationScoped
public class StageFactory {
private final Consumer<Stage> initializer;
public StageFactory(Consumer<Stage> initializer) {
@Inject
public StageFactory(StageInitializer initializer) {
this.initializer = initializer;
}
public Stage create() {
return create(StageStyle.DECORATED);
}
public Stage create(StageStyle stageStyle) {
Stage stage = new Stage(stageStyle);
Stage stage = new Stage(StageStyle.DECORATED);
initializer.accept(stage);
return stage;
}
@@ -0,0 +1,32 @@
package org.cryptomator.ui.common;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.ui.fxapp.FxApplicationScoped;
import javax.inject.Inject;
import javafx.scene.image.Image;
import javafx.stage.Stage;
import java.util.List;
import java.util.function.Consumer;
/**
* Performs common setup for all stages
*/
@FxApplicationScoped
public class StageInitializer implements Consumer<Stage> {
private final List<Image> windowIcons;
@Inject
public StageInitializer() {
this.windowIcons = SystemUtils.IS_OS_MAC ? List.of() : List.of( //
new Image(StageInitializer.class.getResource("/img/window_icon_32.png").toString()), //
new Image(StageInitializer.class.getResource("/img/window_icon_512.png").toString()) //
);
}
@Override
public void accept(Stage stage) {
stage.getIcons().setAll(windowIcons);
}
}
@@ -10,6 +10,7 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.concurrent.Task;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
@@ -53,7 +54,9 @@ public class VaultService {
*
* @param vault The vault to lock
* @param forced Whether to attempt a forced lock
* @deprecated use {@link org.cryptomator.ui.fxapp.FxApplicationWindows#startLockWorkflow(Vault, Stage)}
*/
@Deprecated
public void lock(Vault vault, boolean forced) {
executorService.execute(createLockTask(vault, forced));
}
@@ -90,7 +93,7 @@ public class VaultService {
* @return Meta-Task that waits until all vaults are locked or fails after the first failure of a subtask
*/
public Task<Collection<Vault>> createLockAllTask(Collection<Vault> vaults, boolean forced) {
List<Task<Vault>> lockTasks = vaults.stream().map(v -> new LockVaultTask(v, forced)).collect(Collectors.toUnmodifiableList());
List<Task<Vault>> lockTasks = vaults.stream().<Task<Vault>>map(v -> new LockVaultTask(v, forced)).toList();
lockTasks.forEach(executorService::execute);
Task<Collection<Vault>> task = new WaitForTasksTask(lockTasks);
String vaultNames = vaults.stream().map(Vault::getDisplayName).collect(Collectors.joining(", "));
@@ -1,14 +1,14 @@
package org.cryptomator.ui.launcher;
package org.cryptomator.ui.fxapp;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.ui.fxapp.FxApplication;
import org.cryptomator.launcher.AppLaunchEvent;
import org.cryptomator.ui.common.VaultService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import javafx.application.Platform;
import java.io.IOException;
import java.nio.file.Path;
@@ -17,22 +17,25 @@ import java.util.concurrent.ExecutorService;
import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_EXT;
@Singleton
// TODO: use message bus
@FxApplicationScoped
class AppLaunchEventHandler {
private static final Logger LOG = LoggerFactory.getLogger(AppLaunchEventHandler.class);
private final BlockingQueue<AppLaunchEvent> launchEventQueue;
private final ExecutorService executorService;
private final FxApplicationStarter fxApplicationStarter;
private final FxApplicationWindows appWindows;
private final VaultListManager vaultListManager;
private final VaultService vaultService;
@Inject
public AppLaunchEventHandler(@Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue, ExecutorService executorService, FxApplicationStarter fxApplicationStarter, VaultListManager vaultListManager) {
public AppLaunchEventHandler(@Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue, ExecutorService executorService, FxApplicationWindows appWindows, VaultListManager vaultListManager, VaultService vaultService) {
this.launchEventQueue = launchEventQueue;
this.executorService = executorService;
this.fxApplicationStarter = fxApplicationStarter;
this.appWindows = appWindows;
this.vaultListManager = vaultListManager;
this.vaultService = vaultService;
}
public void startHandlingLaunchEvents() {
@@ -52,14 +55,12 @@ class AppLaunchEventHandler {
}
private void handleLaunchEvent(AppLaunchEvent event) {
switch (event.getType()) {
case REVEAL_APP -> fxApplicationStarter.get().thenAccept(FxApplication::showMainWindow);
case OPEN_FILE -> fxApplicationStarter.get().thenRun(() -> {
Platform.runLater(() -> {
event.getPathsToOpen().forEach(this::addOrRevealVault);
});
switch (event.type()) {
case REVEAL_APP -> appWindows.showMainWindow();
case OPEN_FILE -> Platform.runLater(() -> {
event.pathsToOpen().forEach(this::addOrRevealVault);
});
default -> LOG.warn("Unsupported event type: {}", event.getType());
default -> LOG.warn("Unsupported event type: {}", event.type());
}
}
@@ -75,7 +76,7 @@ class AppLaunchEventHandler {
}
if (v.isUnlocked()) {
fxApplicationStarter.get().thenAccept(app -> app.getVaultService().reveal(v));
vaultService.reveal(v);
}
LOG.debug("Added vault {}", potentialVaultPath);
} catch (IOException e) {
@@ -0,0 +1,26 @@
package org.cryptomator.ui.fxapp;
import org.cryptomator.common.vaults.Vault;
import javax.inject.Inject;
import javafx.collections.ObservableList;
@FxApplicationScoped
public class AutoUnlocker {
private final ObservableList<Vault> vaults;
private final FxApplicationWindows appWindows;
@Inject
public AutoUnlocker(ObservableList<Vault> vaults, FxApplicationWindows appWindows) {
this.vaults = vaults;
this.appWindows = appWindows;
}
public void unlock() {
vaults.stream().filter(Vault::isLocked).filter(v -> v.getVaultSettings().unlockAfterStartup().get()).forEach(v -> {
appWindows.startUnlockWorkflow(v, null);
});
}
}
@@ -0,0 +1,20 @@
package org.cryptomator.ui.fxapp;
import javafx.application.Platform;
import java.awt.desktop.QuitResponse;
record ExitingQuitResponse(QuitResponse delegate) implements QuitResponse {
@Override
public void performQuit() {
Platform.exit();
// TODO wait a moment for javafx to terminate?
delegate.performQuit();
}
@Override
public void cancelQuit() {
delegate.cancelQuit();
}
}
@@ -1,213 +1,70 @@
package org.cryptomator.ui.fxapp;
import dagger.Lazy;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.stage.Stage;
import javafx.stage.Window;
import org.cryptomator.common.LicenseHolder;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.UiTheme;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.integrations.tray.TrayIntegrationProvider;
import org.cryptomator.integrations.uiappearance.Theme;
import org.cryptomator.integrations.uiappearance.UiAppearanceException;
import org.cryptomator.integrations.uiappearance.UiAppearanceListener;
import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.VaultService;
import org.cryptomator.ui.lock.LockComponent;
import org.cryptomator.ui.mainwindow.MainWindowComponent;
import org.cryptomator.ui.preferences.PreferencesComponent;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.cryptomator.ui.quit.QuitComponent;
import org.cryptomator.ui.unlock.UnlockComponent;
import org.cryptomator.ui.traymenu.TrayMenuComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Provider;
import java.awt.desktop.QuitResponse;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javafx.application.Platform;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import java.awt.SystemTray;
@FxApplicationScoped
public class FxApplication extends Application {
public class FxApplication {
private static final Logger LOG = LoggerFactory.getLogger(FxApplication.class);
private final Settings settings;
private final Lazy<MainWindowComponent> mainWindow;
private final Lazy<PreferencesComponent> preferencesWindow;
private final Lazy<QuitComponent> quitWindow;
private final Provider<UnlockComponent.Builder> unlockWorkflowBuilderProvider;
private final Provider<LockComponent.Builder> lockWorkflowBuilderProvider;
private final ErrorComponent.Builder errorWindowBuilder;
private final Optional<TrayIntegrationProvider> trayIntegration;
private final Optional<UiAppearanceProvider> appearanceProvider;
private final VaultService vaultService;
private final LicenseHolder licenseHolder;
private final ObservableList<Window> visibleWindows;
private final BooleanBinding hasVisibleWindows;
private final UiAppearanceListener systemInterfaceThemeListener = this::systemInterfaceThemeChanged;
private final AppLaunchEventHandler launchEventHandler;
private final Lazy<TrayMenuComponent> trayMenu;
private final FxApplicationWindows appWindows;
private final FxApplicationStyle applicationStyle;
private final FxApplicationTerminator applicationTerminator;
private final AutoUnlocker autoUnlocker;
@Inject
FxApplication(Settings settings, Lazy<MainWindowComponent> mainWindow, Lazy<PreferencesComponent> preferencesWindow, Provider<UnlockComponent.Builder> unlockWorkflowBuilderProvider, Provider<LockComponent.Builder> lockWorkflowBuilderProvider, Lazy<QuitComponent> quitWindow, ErrorComponent.Builder errorWindowBuilder, Optional<TrayIntegrationProvider> trayIntegration, Optional<UiAppearanceProvider> appearanceProvider, VaultService vaultService, LicenseHolder licenseHolder) {
FxApplication(Settings settings, AppLaunchEventHandler launchEventHandler, Lazy<TrayMenuComponent> trayMenu, FxApplicationWindows appWindows, FxApplicationStyle applicationStyle, FxApplicationTerminator applicationTerminator, AutoUnlocker autoUnlocker) {
this.settings = settings;
this.mainWindow = mainWindow;
this.preferencesWindow = preferencesWindow;
this.unlockWorkflowBuilderProvider = unlockWorkflowBuilderProvider;
this.lockWorkflowBuilderProvider = lockWorkflowBuilderProvider;
this.quitWindow = quitWindow;
this.errorWindowBuilder = errorWindowBuilder;
this.trayIntegration = trayIntegration;
this.appearanceProvider = appearanceProvider;
this.vaultService = vaultService;
this.licenseHolder = licenseHolder;
this.visibleWindows = Stage.getWindows().filtered(Window::isShowing);
this.hasVisibleWindows = Bindings.isNotEmpty(visibleWindows);
this.launchEventHandler = launchEventHandler;
this.trayMenu = trayMenu;
this.appWindows = appWindows;
this.applicationStyle = applicationStyle;
this.applicationTerminator = applicationTerminator;
this.autoUnlocker = autoUnlocker;
}
public void start() {
LOG.trace("FxApplication.start()");
Platform.setImplicitExit(false);
applicationStyle.initialize();
appWindows.initialize();
applicationTerminator.initialize();
hasVisibleWindows.addListener(this::hasVisibleStagesChanged);
settings.theme().addListener(this::appThemeChanged);
loadSelectedStyleSheet(settings.theme().get());
}
@Override
public void start(Stage stage) {
throw new UnsupportedOperationException("Use start() instead.");
}
private void hasVisibleStagesChanged(@SuppressWarnings("unused") ObservableValue<? extends Boolean> observableValue, @SuppressWarnings("unused") boolean oldValue, boolean newValue) {
LOG.debug("has visible stages: {}", newValue);
if (newValue) {
trayIntegration.ifPresent(TrayIntegrationProvider::restoredFromTray);
// init system tray
final boolean hasTrayIcon;
if (SystemTray.isSupported() && settings.showTrayIcon().get()) {
trayMenu.get().initializeTrayIcon();
Platform.setImplicitExit(false); // don't quit when closing all windows
hasTrayIcon = true;
} else {
trayIntegration.ifPresent(TrayIntegrationProvider::minimizedToTray);
hasTrayIcon = false;
}
}
public void showPreferencesWindow(SelectedPreferencesTab selectedTab) {
Platform.runLater(() -> {
preferencesWindow.get().showPreferencesWindow(selectedTab);
LOG.debug("Showing Preferences");
});
}
public CompletionStage<Stage> showMainWindow() {
CompletableFuture<Stage> future = new CompletableFuture<>();
Platform.runLater(() -> {
var win = mainWindow.get().showMainWindow();
LOG.debug("Showing MainWindow");
future.complete(win);
});
return future;
}
public void startUnlockWorkflow(Vault vault, Optional<Stage> owner) {
Platform.runLater(() -> {
if (vault.stateProperty().transition(VaultState.Value.LOCKED, VaultState.Value.PROCESSING)) {
unlockWorkflowBuilderProvider.get().vault(vault).owner(owner).build().startUnlockWorkflow();
LOG.debug("Start unlock workflow for {}", vault.getDisplayName());
} else {
showMainWindow().thenAccept(mainWindow -> errorWindowBuilder.window(mainWindow).cause(new IllegalStateException("Unable to unlock vault in non-locked state.")));
// show main window
appWindows.showMainWindow().thenAccept(stage -> {
if (settings.startHidden().get()) {
if (hasTrayIcon) {
stage.hide();
} else {
stage.setIconified(true);
}
}
});
}
public void startLockWorkflow(Vault vault, Optional<Stage> owner) {
Platform.runLater(() -> {
if (vault.stateProperty().transition(VaultState.Value.UNLOCKED, VaultState.Value.PROCESSING)) {
lockWorkflowBuilderProvider.get().vault(vault).owner(owner).build().startLockWorkflow();
LOG.debug("Start lock workflow for {}", vault.getDisplayName());
} else {
showMainWindow().thenAccept(mainWindow -> errorWindowBuilder.window(mainWindow).cause(new IllegalStateException("Unable to lock vault in non-unlocked state.")));
}
});
}
public void showQuitWindow(QuitResponse response) {
Platform.runLater(() -> {
quitWindow.get().showQuitWindow(response);
LOG.debug("Showing QuitWindow");
});
}
public VaultService getVaultService() {
return vaultService;
}
private void appThemeChanged(@SuppressWarnings("unused") ObservableValue<? extends UiTheme> observable, @SuppressWarnings("unused") UiTheme oldValue, UiTheme newValue) {
if (appearanceProvider.isPresent() && oldValue == UiTheme.AUTOMATIC && newValue != UiTheme.AUTOMATIC) {
try {
appearanceProvider.get().removeListener(systemInterfaceThemeListener);
} catch (UiAppearanceException e) {
LOG.error("Failed to disable automatic theme switching.");
}
}
loadSelectedStyleSheet(newValue);
}
private void loadSelectedStyleSheet(UiTheme desiredTheme) {
UiTheme theme = licenseHolder.isValidLicense() ? desiredTheme : UiTheme.LIGHT;
switch (theme) {
case LIGHT -> applyLightTheme();
case DARK -> applyDarkTheme();
case AUTOMATIC -> {
appearanceProvider.ifPresent(appearanceProvider -> {
try {
appearanceProvider.addListener(systemInterfaceThemeListener);
} catch (UiAppearanceException e) {
LOG.error("Failed to enable automatic theme switching.");
}
});
applySystemTheme();
}
}
}
private void systemInterfaceThemeChanged(Theme theme) {
switch (theme) {
case LIGHT -> applyLightTheme();
case DARK -> applyDarkTheme();
}
}
private void applySystemTheme() {
if (appearanceProvider.isPresent()) {
systemInterfaceThemeChanged(appearanceProvider.get().getSystemTheme());
} else {
LOG.warn("No UiAppearanceProvider present, assuming LIGHT theme...");
applyLightTheme();
}
}
private void applyLightTheme() {
Application.setUserAgentStylesheet(getClass().getResource("/css/light_theme.css").toString());
appearanceProvider.ifPresent(appearanceProvider -> {
appearanceProvider.adjustToTheme(Theme.LIGHT);
});
}
private void applyDarkTheme() {
Application.setUserAgentStylesheet(getClass().getResource("/css/dark_theme.css").toString());
appearanceProvider.ifPresent(appearanceProvider -> {
appearanceProvider.adjustToTheme(Theme.DARK);
});
launchEventHandler.startHandlingLaunchEvents();
autoUnlocker.unlock();
}
}
@@ -5,8 +5,12 @@
*******************************************************************************/
package org.cryptomator.ui.fxapp;
import dagger.BindsInstance;
import dagger.Subcomponent;
import javafx.application.Application;
import javafx.stage.Stage;
@FxApplicationScoped
@Subcomponent(modules = FxApplicationModule.class)
public interface FxApplicationComponent {
@@ -16,6 +20,12 @@ public interface FxApplicationComponent {
@Subcomponent.Builder
interface Builder {
@BindsInstance
Builder fxApplication(Application application);
@BindsInstance
Builder primaryStage(@PrimaryStage Stage primaryStage);
FxApplicationComponent build();
}
@@ -5,7 +5,6 @@
*******************************************************************************/
package org.cryptomator.ui.fxapp;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import org.apache.commons.lang3.SystemUtils;
@@ -15,67 +14,46 @@ import org.cryptomator.ui.lock.LockComponent;
import org.cryptomator.ui.mainwindow.MainWindowComponent;
import org.cryptomator.ui.preferences.PreferencesComponent;
import org.cryptomator.ui.quit.QuitComponent;
import org.cryptomator.ui.traymenu.TrayMenuComponent;
import org.cryptomator.ui.unlock.UnlockComponent;
import javax.inject.Named;
import javafx.application.Application;
import javafx.collections.ObservableSet;
import javafx.scene.image.Image;
import javafx.stage.Stage;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.util.Collections;
import java.util.List;
@Module(includes = {UpdateCheckerModule.class}, subcomponents = {MainWindowComponent.class, PreferencesComponent.class, UnlockComponent.class, LockComponent.class, QuitComponent.class, ErrorComponent.class})
@Module(includes = {UpdateCheckerModule.class}, subcomponents = {TrayMenuComponent.class, MainWindowComponent.class, PreferencesComponent.class, UnlockComponent.class, LockComponent.class, QuitComponent.class, ErrorComponent.class})
abstract class FxApplicationModule {
@Provides
@Named("windowIcons")
@FxApplicationScoped
static List<Image> provideWindowIcons() {
if (SystemUtils.IS_OS_MAC) {
return Collections.emptyList();
}
try {
return List.of( //
createImageFromResource("/img/window_icon_32.png"), //
createImageFromResource("/img/window_icon_512.png") //
);
} catch (IOException e) {
throw new UncheckedIOException("Failed to load embedded resource.", e);
}
}
@Provides
@FxApplicationScoped
static StageFactory provideStageFactory(@Named("windowIcons") List<Image> windowIcons) {
return new StageFactory(stage -> {
stage.getIcons().addAll(windowIcons);
});
}
private static Image createImageFromResource(String resourceName) throws IOException {
try (InputStream in = FxApplicationModule.class.getResourceAsStream(resourceName)) {
return new Image(in);
}
}
@Binds
abstract Application bindApplication(FxApplication application);
@Provides
@FxApplicationScoped
static TrayMenuComponent provideTrayMenuComponent(TrayMenuComponent.Builder builder) {
return builder.build();
}
@Provides
@FxApplicationScoped
static MainWindowComponent provideMainWindowComponent(MainWindowComponent.Builder builder) {
return builder.build();
}
@Provides
@FxApplicationScoped
static PreferencesComponent providePreferencesComponent(PreferencesComponent.Builder builder) {
return builder.build();
}
@Provides
@FxApplicationScoped
static QuitComponent provideQuitComponent(QuitComponent.Builder builder) {
return builder.build();
}
@@ -0,0 +1,94 @@
package org.cryptomator.ui.fxapp;
import org.cryptomator.common.LicenseHolder;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.UiTheme;
import org.cryptomator.integrations.uiappearance.Theme;
import org.cryptomator.integrations.uiappearance.UiAppearanceException;
import org.cryptomator.integrations.uiappearance.UiAppearanceListener;
import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.application.Application;
import javafx.beans.value.ObservableValue;
import java.util.Optional;
@FxApplicationScoped
public class FxApplicationStyle {
private static final Logger LOG = LoggerFactory.getLogger(FxApplicationStyle.class);
private final Settings settings;
private final Optional<UiAppearanceProvider> appearanceProvider;
private final LicenseHolder licenseHolder;
private final UiAppearanceListener systemInterfaceThemeListener = this::systemInterfaceThemeChanged;
@Inject
public FxApplicationStyle(Settings settings, Optional<UiAppearanceProvider> appearanceProvider, LicenseHolder licenseHolder){
this.settings = settings;
this.appearanceProvider = appearanceProvider;
this.licenseHolder = licenseHolder;
}
public void initialize() {
settings.theme().addListener(this::appThemeChanged);
loadSelectedStyleSheet(settings.theme().get());
}
private void appThemeChanged(@SuppressWarnings("unused") ObservableValue<? extends UiTheme> observable, @SuppressWarnings("unused") UiTheme oldValue, UiTheme newValue) {
if (appearanceProvider.isPresent() && oldValue == UiTheme.AUTOMATIC && newValue != UiTheme.AUTOMATIC) {
try {
appearanceProvider.get().removeListener(systemInterfaceThemeListener);
} catch (UiAppearanceException e) {
LOG.error("Failed to disable automatic theme switching.");
}
}
loadSelectedStyleSheet(newValue);
}
private void loadSelectedStyleSheet(UiTheme desiredTheme) {
UiTheme theme = licenseHolder.isValidLicense() ? desiredTheme : UiTheme.LIGHT;
switch (theme) {
case LIGHT -> applyLightTheme();
case DARK -> applyDarkTheme();
case AUTOMATIC -> {
appearanceProvider.ifPresent(provider -> {
try {
provider.addListener(systemInterfaceThemeListener);
} catch (UiAppearanceException e) {
LOG.error("Failed to enable automatic theme switching.");
}
});
applySystemTheme();
}
}
}
private void systemInterfaceThemeChanged(Theme theme) {
switch (theme) {
case LIGHT -> applyLightTheme();
case DARK -> applyDarkTheme();
}
}
private void applySystemTheme() {
if (appearanceProvider.isPresent()) {
systemInterfaceThemeChanged(appearanceProvider.get().getSystemTheme());
} else {
LOG.warn("No UiAppearanceProvider present, assuming LIGHT theme...");
applyLightTheme();
}
}
private void applyLightTheme() {
Application.setUserAgentStylesheet(getClass().getResource("/css/light_theme.css").toString());
appearanceProvider.ifPresent(provider -> provider.adjustToTheme(Theme.LIGHT));
}
private void applyDarkTheme() {
Application.setUserAgentStylesheet(getClass().getResource("/css/dark_theme.css").toString());
appearanceProvider.ifPresent(provider -> provider.adjustToTheme(Theme.DARK));
}
}
@@ -0,0 +1,134 @@
package org.cryptomator.ui.fxapp;
import com.google.common.base.Preconditions;
import org.cryptomator.common.ShutdownHook;
import org.cryptomator.common.vaults.LockNotCompletedException;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.common.vaults.Volume;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.beans.Observable;
import javafx.collections.ObservableList;
import java.awt.Desktop;
import java.awt.desktop.QuitResponse;
import java.awt.desktop.QuitStrategy;
import java.util.EnumSet;
import java.util.EventObject;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.cryptomator.common.vaults.VaultState.Value.*;
@FxApplicationScoped
public class FxApplicationTerminator {
private static final Set<VaultState.Value> STATES_ALLOWING_TERMINATION = EnumSet.of(LOCKED, NEEDS_MIGRATION, MISSING, ERROR);
private static final Logger LOG = LoggerFactory.getLogger(FxApplicationTerminator.class);
private final ObservableList<Vault> vaults;
private final ShutdownHook shutdownHook;
private final FxApplicationWindows appWindows;
private final AtomicBoolean allowQuitWithoutPrompt = new AtomicBoolean();
@Inject
public FxApplicationTerminator(ObservableList<Vault> vaults, ShutdownHook shutdownHook, FxApplicationWindows appWindows) {
this.vaults = vaults;
this.shutdownHook = shutdownHook;
this.appWindows = appWindows;
}
public void initialize() {
Preconditions.checkState(Desktop.isDesktopSupported(), "java.awt.Desktop not supported");
Desktop desktop = Desktop.getDesktop();
// register quit handler
if (desktop.isSupported(Desktop.Action.APP_QUIT_HANDLER)) {
desktop.setQuitHandler(this::handleQuitRequest);
}
// set quit strategy (cmd+q would call `System.exit(0)` otherwise)
if (desktop.isSupported(Desktop.Action.APP_QUIT_STRATEGY)) {
desktop.setQuitStrategy(QuitStrategy.CLOSE_ALL_WINDOWS);
}
// allow sudden termination?
vaultListChanged(vaults);
vaults.addListener(this::vaultListChanged);
shutdownHook.runOnShutdown(this::forceUnmountRemainingVaults);
}
/**
* Gracefully terminates the application.
*/
public void terminate() {
handleQuitRequest(null, new NoopQuitResponse());
}
private void vaultListChanged(@SuppressWarnings("unused") Observable observable) {
boolean allowSuddenTermination = vaults.stream().map(Vault::getState).allMatch(STATES_ALLOWING_TERMINATION::contains);
boolean stateChanged = allowQuitWithoutPrompt.compareAndSet(!allowSuddenTermination, allowSuddenTermination);
Desktop desktop = Desktop.getDesktop();
if (stateChanged && desktop.isSupported(Desktop.Action.APP_SUDDEN_TERMINATION)) {
if (allowSuddenTermination) {
LOG.debug("Enabling sudden termination");
desktop.enableSuddenTermination();
} else {
LOG.debug("Disabling sudden termination");
desktop.disableSuddenTermination();
}
}
}
/**
* Asks the app to quit. If confirmed, the JavaFX application will exit before giving a {@code response}.
*
* @param e ignored
* @param response a quit response that will be {@link ExitingQuitResponse decorated in order to exit the JavaFX application}.
*/
private void handleQuitRequest(@SuppressWarnings("unused") @Nullable EventObject e, QuitResponse response) {
var exitingResponse = new ExitingQuitResponse(response);
if (allowQuitWithoutPrompt.get()) {
exitingResponse.performQuit();
} else {
appWindows.showQuitWindow(exitingResponse);
}
}
private void forceUnmountRemainingVaults() {
for (Vault vault : vaults) {
if (vault.isUnlocked()) {
try {
vault.lock(true);
} catch (Volume.VolumeException e) {
LOG.error("Failed to unmount vault " + vault.getPath(), e);
} catch (LockNotCompletedException e) {
LOG.error("Failed to lock vault " + vault.getPath(), e);
}
}
}
}
/**
* A dummy QuitResponse that ignores the response.
*
* To be used with {@link #handleQuitRequest(EventObject, QuitResponse)} if the invoking method is not interested in the response.
*/
private static class NoopQuitResponse implements QuitResponse {
@Override
public void performQuit() {
// no-op
}
@Override
public void cancelQuit() {
// no-op
}
}
}
@@ -0,0 +1,155 @@
package org.cryptomator.ui.fxapp;
import com.google.common.base.Preconditions;
import dagger.Lazy;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.integrations.tray.TrayIntegrationProvider;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.lock.LockComponent;
import org.cryptomator.ui.mainwindow.MainWindowComponent;
import org.cryptomator.ui.preferences.PreferencesComponent;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.cryptomator.ui.quit.QuitComponent;
import org.cryptomator.ui.unlock.UnlockComponent;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.application.Platform;
import javafx.collections.ListChangeListener;
import javafx.collections.transformation.FilteredList;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.stage.Window;
import java.awt.Desktop;
import java.awt.desktop.AppReopenedListener;
import java.awt.desktop.QuitResponse;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutorService;
@FxApplicationScoped
public class FxApplicationWindows {
private static final Logger LOG = LoggerFactory.getLogger(FxApplicationWindows.class);
private final Stage primaryStage;
private final Optional<TrayIntegrationProvider> trayIntegration;
private final Lazy<MainWindowComponent> mainWindow;
private final Lazy<PreferencesComponent> preferencesWindow;
private final Lazy<QuitComponent> quitWindow;
private final UnlockComponent.Factory unlockWorkflowFactory;
private final LockComponent.Factory lockWorkflowFactory;
private final ErrorComponent.Factory errorWindowFactory;
private final ExecutorService executor;
private final FilteredList<Window> visibleWindows;
@Inject
public FxApplicationWindows(@PrimaryStage Stage primaryStage, Optional<TrayIntegrationProvider> trayIntegration, Lazy<MainWindowComponent> mainWindow, Lazy<PreferencesComponent> preferencesWindow, Lazy<QuitComponent> quitWindow, UnlockComponent.Factory unlockWorkflowFactory, LockComponent.Factory lockWorkflowFactory, ErrorComponent.Factory errorWindowFactory, ExecutorService executor) {
this.primaryStage = primaryStage;
this.trayIntegration = trayIntegration;
this.mainWindow = mainWindow;
this.preferencesWindow = preferencesWindow;
this.quitWindow = quitWindow;
this.unlockWorkflowFactory = unlockWorkflowFactory;
this.lockWorkflowFactory = lockWorkflowFactory;
this.errorWindowFactory = errorWindowFactory;
this.executor = executor;
this.visibleWindows = Window.getWindows().filtered(Window::isShowing);
}
public void initialize() {
Preconditions.checkState(Desktop.isDesktopSupported(), "java.awt.Desktop not supported");
Desktop desktop = Desktop.getDesktop();
// register preferences shortcut
if (desktop.isSupported(Desktop.Action.APP_PREFERENCES)) {
desktop.setPreferencesHandler(evt -> showPreferencesWindow(SelectedPreferencesTab.ANY));
}
// register preferences shortcut
if (desktop.isSupported(Desktop.Action.APP_ABOUT)) {
desktop.setAboutHandler(evt -> showPreferencesWindow(SelectedPreferencesTab.ABOUT));
}
// register app reopen listener
if (desktop.isSupported(Desktop.Action.APP_EVENT_REOPENED)) {
desktop.addAppEventListener((AppReopenedListener) e -> showMainWindow());
}
// observe visible windows
if (trayIntegration.isPresent()) {
visibleWindows.addListener(this::visibleWindowsChanged);
}
}
private void visibleWindowsChanged(ListChangeListener.Change<? extends Window> change) {
int visibleWindows = change.getList().size();
LOG.debug("visible windows: {}", visibleWindows);
if (visibleWindows > 0) {
trayIntegration.ifPresent(TrayIntegrationProvider::restoredFromTray);
} else {
trayIntegration.ifPresent(TrayIntegrationProvider::minimizedToTray);
}
}
public CompletionStage<Stage> showMainWindow() {
return CompletableFuture.supplyAsync(mainWindow.get()::showMainWindow, Platform::runLater).whenComplete(this::reportErrors);
}
public CompletionStage<Stage> showPreferencesWindow(SelectedPreferencesTab selectedTab) {
return CompletableFuture.supplyAsync(() -> preferencesWindow.get().showPreferencesWindow(selectedTab), Platform::runLater).whenComplete(this::reportErrors);
}
public CompletionStage<Stage> showQuitWindow(QuitResponse response) {
return CompletableFuture.supplyAsync(() -> quitWindow.get().showQuitWindow(response), Platform::runLater).whenComplete(this::reportErrors);
}
public CompletionStage<Void> startUnlockWorkflow(Vault vault, @Nullable Stage owner) {
return CompletableFuture.supplyAsync(() -> {
Preconditions.checkState(vault.stateProperty().transition(VaultState.Value.LOCKED, VaultState.Value.PROCESSING), "Vault not locked.");
LOG.debug("Start unlock workflow for {}", vault.getDisplayName());
return unlockWorkflowFactory.create(vault, owner).unlockWorkflow();
}, Platform::runLater) //
.thenCompose(unlockWorkflow -> CompletableFuture.runAsync(unlockWorkflow, executor)) //
.exceptionally(e -> {
showErrorWindow(e, owner == null ? primaryStage : owner, null);
return null;
});
}
public CompletionStage<Void> startLockWorkflow(Vault vault, @Nullable Stage owner) {
return CompletableFuture.supplyAsync(() -> {
Preconditions.checkState(vault.stateProperty().transition(VaultState.Value.UNLOCKED, VaultState.Value.PROCESSING), "Vault not unlocked.");
LOG.debug("Start lock workflow for {}", vault.getDisplayName());
return lockWorkflowFactory.create(vault, owner).lockWorkflow();
}, Platform::runLater) //
.thenCompose(lockWorkflow -> CompletableFuture.runAsync(lockWorkflow, executor)) //
.exceptionally(e -> {
showErrorWindow(e, owner == null ? primaryStage : owner, null);
return null;
});
}
/**
* Displays the generic error scene in the given window.
*
* @param cause The exception to show
* @param window What window to display the scene in
* @param previousScene To what scene to return to when pressing "back". Back button will be hidden, if <code>null</code>
* @return A
*/
public CompletionStage<Stage> showErrorWindow(Throwable cause, Stage window, @Nullable Scene previousScene) {
return CompletableFuture.supplyAsync(() -> errorWindowFactory.create(cause, window, previousScene).show(), Platform::runLater).whenComplete(this::reportErrors);
}
private void reportErrors(@Nullable Stage stage, @Nullable Throwable error) {
if (error != null) {
LOG.error("Failed to display stage", error);
}
}
}
@@ -0,0 +1,14 @@
package org.cryptomator.ui.fxapp;
import javax.inject.Qualifier;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface PrimaryStage {
}
@@ -1,9 +1,8 @@
package org.cryptomator.ui.health;
import com.google.common.base.Preconditions;
import dagger.Lazy;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -15,9 +14,7 @@ import javafx.beans.property.ObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ListView;
import javafx.scene.control.SelectionMode;
import javafx.stage.Stage;
@@ -37,7 +34,7 @@ public class CheckListController implements FxController {
private final ObjectProperty<Check> selectedCheck;
private final BooleanBinding mainRunStarted; //TODO: rerunning not considered for now
private final BooleanBinding somethingsRunning;
private final Lazy<ErrorComponent.Builder> errorComponentBuilder;
private final FxApplicationWindows appWindows;
private final IntegerBinding chosenTaskCount;
private final BooleanBinding anyCheckSelected;
private final CheckListCellFactory listCellFactory;
@@ -46,7 +43,7 @@ public class CheckListController implements FxController {
public ListView<Check> checksListView;
@Inject
public CheckListController(@HealthCheckWindow Stage window, List<Check> checks, CheckExecutor checkExecutor, ReportWriter reportWriteTask, ObjectProperty<Check> selectedCheck, Lazy<ErrorComponent.Builder> errorComponentBuilder, CheckListCellFactory listCellFactory) {
public CheckListController(@HealthCheckWindow Stage window, List<Check> checks, CheckExecutor checkExecutor, ReportWriter reportWriteTask, ObjectProperty<Check> selectedCheck, FxApplicationWindows appWindows, CheckListCellFactory listCellFactory) {
this.window = window;
this.checks = FXCollections.observableList(checks, Check::observables);
this.checkExecutor = checkExecutor;
@@ -54,7 +51,7 @@ public class CheckListController implements FxController {
this.chosenChecks = this.checks.filtered(Check::isChosenForExecution);
this.reportWriter = reportWriteTask;
this.selectedCheck = selectedCheck;
this.errorComponentBuilder = errorComponentBuilder;
this.appWindows = appWindows;
this.chosenTaskCount = Bindings.size(this.chosenChecks);
this.mainRunStarted = Bindings.isEmpty(this.checks.filtered(c -> c.getState() == Check.CheckState.RUNNABLE));
this.somethingsRunning = Bindings.isNotEmpty(this.checks.filtered(c -> c.getState() == Check.CheckState.SCHEDULED || c.getState() == Check.CheckState.RUNNING));
@@ -104,7 +101,7 @@ public class CheckListController implements FxController {
reportWriter.writeReport(chosenChecks);
} catch (IOException e) {
LOG.error("Failed to write health check report.", e);
errorComponentBuilder.get().cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
appWindows.showErrorWindow(e, window, window.getScene());
}
}
@@ -7,10 +7,10 @@ import org.cryptomator.cryptofs.VaultConfig;
import org.cryptomator.cryptofs.VaultConfigLoadException;
import org.cryptomator.cryptofs.VaultKeyInvalidException;
import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
import org.cryptomator.ui.unlock.UnlockCancelledException;
import org.slf4j.Logger;
@@ -40,10 +40,10 @@ public class StartController implements FxController {
private final AtomicReference<Masterkey> masterkeyRef;
private final AtomicReference<VaultConfig> vaultConfigRef;
private final Lazy<Scene> checkScene;
private final Lazy<ErrorComponent.Builder> errorComponent;
private final FxApplicationWindows appWindows;
@Inject
public StartController(@HealthCheckWindow Stage window, @HealthCheckWindow Vault vault, @HealthCheckWindow KeyLoadingStrategy keyLoadingStrategy, ExecutorService executor, AtomicReference<Masterkey> masterkeyRef, AtomicReference<VaultConfig> vaultConfigRef, @FxmlScene(FxmlFile.HEALTH_CHECK_LIST) Lazy<Scene> checkScene, Lazy<ErrorComponent.Builder> errorComponent, @Named("unlockWindow") Stage unlockWindow) {
public StartController(@HealthCheckWindow Stage window, @HealthCheckWindow Vault vault, @HealthCheckWindow KeyLoadingStrategy keyLoadingStrategy, ExecutorService executor, AtomicReference<Masterkey> masterkeyRef, AtomicReference<VaultConfig> vaultConfigRef, @FxmlScene(FxmlFile.HEALTH_CHECK_LIST) Lazy<Scene> checkScene, FxApplicationWindows appWindows, @Named("unlockWindow") Stage unlockWindow) {
this.window = window;
this.unlockWindow = unlockWindow;
this.vaultConfig = vault.getVaultConfigCache();
@@ -52,7 +52,7 @@ public class StartController implements FxController {
this.masterkeyRef = masterkeyRef;
this.vaultConfigRef = vaultConfigRef;
this.checkScene = checkScene;
this.errorComponent = errorComponent;
this.appWindows = appWindows;
}
@FXML
@@ -106,10 +106,10 @@ public class StartController implements FxController {
// ok
} else if (e instanceof VaultKeyInvalidException) {
LOG.error("Invalid key"); //TODO: specific error screen
errorComponent.get().window(window).cause(e).build().showErrorScene();
appWindows.showErrorWindow(e, window, null);
} else {
LOG.error("Failed to load key.", e);
errorComponent.get().window(window).cause(e).build().showErrorScene();
appWindows.showErrorWindow(e, window, null);
}
}
@@ -129,8 +129,11 @@ public class PassphraseEntryController implements FxController {
}
private void windowClosed(WindowEvent windowEvent) {
LOG.debug("Unlock canceled by user.");
result.cancel(true);
if(!result.isDone()) {
result.cancel(true);
LOG.debug("Unlock canceled by user.");
}
}
@FXML
@@ -1,28 +0,0 @@
package org.cryptomator.ui.launcher;
import java.nio.file.Path;
import java.util.Collection;
public class AppLaunchEvent {
private final EventType type;
private final Collection<Path> pathsToOpen;
public enum EventType {
REVEAL_APP,
OPEN_FILE
}
public AppLaunchEvent(EventType type, Collection<Path> pathsToOpen) {
this.type = type;
this.pathsToOpen = pathsToOpen;
}
public EventType getType() {
return type;
}
public Collection<Path> getPathsToOpen() {
return pathsToOpen;
}
}
@@ -1,146 +0,0 @@
package org.cryptomator.ui.launcher;
import org.cryptomator.common.ShutdownHook;
import org.cryptomator.common.vaults.LockNotCompletedException;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.common.vaults.Volume;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.collections.ObservableList;
import java.awt.Desktop;
import java.awt.EventQueue;
import java.awt.desktop.AboutEvent;
import java.awt.desktop.QuitResponse;
import java.awt.desktop.QuitStrategy;
import java.util.EnumSet;
import java.util.EventObject;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.cryptomator.common.vaults.VaultState.Value.*;
@Singleton
public class AppLifecycleListener {
private static final Logger LOG = LoggerFactory.getLogger(AppLifecycleListener.class);
public static final Set<VaultState.Value> STATES_ALLOWING_TERMINATION = EnumSet.of(LOCKED, NEEDS_MIGRATION, MISSING, ERROR);
private final FxApplicationStarter fxApplicationStarter;
private final CountDownLatch shutdownLatch;
private final ObservableList<Vault> vaults;
private final AtomicBoolean allowQuitWithoutPrompt;
@Inject
AppLifecycleListener(FxApplicationStarter fxApplicationStarter, @Named("shutdownLatch") CountDownLatch shutdownLatch, ShutdownHook shutdownHook, ObservableList<Vault> vaults) {
this.fxApplicationStarter = fxApplicationStarter;
this.shutdownLatch = shutdownLatch;
this.vaults = vaults;
this.allowQuitWithoutPrompt = new AtomicBoolean(true);
vaults.addListener(this::vaultListChanged);
// register preferences shortcut
if (Desktop.getDesktop().isSupported(Desktop.Action.APP_PREFERENCES)) {
Desktop.getDesktop().setPreferencesHandler(this::showPreferencesWindow);
}
// register preferences shortcut
if (Desktop.getDesktop().isSupported(Desktop.Action.APP_ABOUT)) {
Desktop.getDesktop().setAboutHandler(this::showAboutWindow);
}
// register quit handler
if (Desktop.getDesktop().isSupported(Desktop.Action.APP_QUIT_HANDLER)) {
Desktop.getDesktop().setQuitHandler(this::handleQuitRequest);
}
// set quit strategy (cmd+q would call `System.exit(0)` otherwise)
if (Desktop.getDesktop().isSupported(Desktop.Action.APP_QUIT_STRATEGY)) {
Desktop.getDesktop().setQuitStrategy(QuitStrategy.CLOSE_ALL_WINDOWS);
}
shutdownHook.runOnShutdown(this::forceUnmountRemainingVaults);
}
/**
* Gracefully terminates the application.
*/
public void quit() {
handleQuitRequest(null, new QuitResponse() {
@Override
public void performQuit() {
// no-op
}
@Override
public void cancelQuit() {
// no-op
}
});
}
private void handleQuitRequest(@SuppressWarnings("unused") EventObject e, QuitResponse response) {
QuitResponse decoratedQuitResponse = decorateQuitResponse(response);
if (allowQuitWithoutPrompt.get()) {
decoratedQuitResponse.performQuit();
} else {
fxApplicationStarter.get().thenAccept(app -> app.showQuitWindow(decoratedQuitResponse));
}
}
private QuitResponse decorateQuitResponse(QuitResponse originalQuitResponse) {
return new QuitResponse() {
@Override
public void performQuit() {
Platform.exit(); // will be no-op, if JavaFX never started.
shutdownLatch.countDown(); // main thread is waiting for this latch
originalQuitResponse.performQuit();
}
@Override
public void cancelQuit() {
originalQuitResponse.cancelQuit();
}
};
}
private void vaultListChanged(@SuppressWarnings("unused") Observable observable) {
assert Platform.isFxApplicationThread();
boolean allVaultsAllowTermination = vaults.stream().map(Vault::getState).allMatch(STATES_ALLOWING_TERMINATION::contains);
boolean suddenTerminationChanged = allowQuitWithoutPrompt.compareAndSet(!allVaultsAllowTermination, allVaultsAllowTermination);
if (suddenTerminationChanged) {
LOG.debug("Allow quitting without prompt: {}", allVaultsAllowTermination);
}
}
private void showPreferencesWindow(@SuppressWarnings("unused") EventObject actionEvent) {
fxApplicationStarter.get().thenAccept(app -> app.showPreferencesWindow(SelectedPreferencesTab.ANY));
}
private void showAboutWindow(@SuppressWarnings("unused") AboutEvent aboutEvent) {
fxApplicationStarter.get().thenAccept(app -> app.showPreferencesWindow(SelectedPreferencesTab.ABOUT));
}
private void forceUnmountRemainingVaults() {
for (Vault vault : vaults) {
if (vault.isUnlocked()) {
try {
vault.lock(true);
} catch (Volume.VolumeException e) {
LOG.error("Failed to unmount vault " + vault.getPath(), e);
} catch (LockNotCompletedException e) {
LOG.error("Failed to lock vault " + vault.getPath(), e);
}
}
}
}
}
@@ -1,54 +0,0 @@
package org.cryptomator.ui.launcher;
import dagger.Lazy;
import org.cryptomator.ui.fxapp.FxApplication;
import org.cryptomator.ui.fxapp.FxApplicationComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import javafx.application.Platform;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
@Singleton
public class FxApplicationStarter {
private static final Logger LOG = LoggerFactory.getLogger(FxApplicationStarter.class);
private final Lazy<FxApplicationComponent> fxAppComponent;
private final ExecutorService executor;
private final AtomicBoolean started;
private final CompletableFuture<FxApplication> future;
@Inject
public FxApplicationStarter(Lazy<FxApplicationComponent> fxAppComponent, ExecutorService executor) {
this.fxAppComponent = fxAppComponent;
this.executor = executor;
this.started = new AtomicBoolean();
this.future = new CompletableFuture<>();
}
public CompletionStage<FxApplication> get() {
if (!started.getAndSet(true)) {
start();
}
return future;
}
private void start() {
executor.submit(() -> {
LOG.debug("Starting JavaFX runtime...");
Platform.startup(() -> {
assert Platform.isFxApplicationThread();
LOG.info("JavaFX Runtime started.");
FxApplication app = fxAppComponent.get().application();
app.start();
future.complete(app);
});
});
}
}
@@ -1,90 +0,0 @@
package org.cryptomator.ui.launcher;
import dagger.Lazy;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.integrations.tray.TrayIntegrationProvider;
import org.cryptomator.ui.fxapp.FxApplication;
import org.cryptomator.ui.traymenu.TrayMenuComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import javafx.collections.ObservableList;
import java.awt.Desktop;
import java.awt.SystemTray;
import java.awt.desktop.AppReopenedListener;
import java.util.Collection;
import java.util.Optional;
@Singleton
public class UiLauncher {
private static final Logger LOG = LoggerFactory.getLogger(UiLauncher.class);
private final Settings settings;
private final ObservableList<Vault> vaults;
private final Lazy<TrayMenuComponent> trayMenu;
private final FxApplicationStarter fxApplicationStarter;
private final AppLaunchEventHandler launchEventHandler;
private final Optional<TrayIntegrationProvider> trayIntegration;
@Inject
public UiLauncher(Settings settings, ObservableList<Vault> vaults, Lazy<TrayMenuComponent> trayMenu, FxApplicationStarter fxApplicationStarter, AppLaunchEventHandler launchEventHandler, Optional<TrayIntegrationProvider> trayIntegration) {
this.settings = settings;
this.vaults = vaults;
this.trayMenu = trayMenu;
this.fxApplicationStarter = fxApplicationStarter;
this.launchEventHandler = launchEventHandler;
this.trayIntegration = trayIntegration;
}
public void launch() {
boolean hidden = settings.startHidden().get();
if (SystemTray.isSupported() && settings.showTrayIcon().get()) {
trayMenu.get().initializeTrayIcon();
launch(true, hidden);
} else {
launch(false, hidden);
}
}
private void launch(boolean withTrayIcon, boolean hidden) {
// start hidden, minimized or normal?
if (withTrayIcon && hidden) {
LOG.debug("Hiding application...");
trayIntegration.ifPresent(TrayIntegrationProvider::minimizedToTray);
} else if (!withTrayIcon && hidden) {
LOG.debug("Minimizing application...");
showMainWindowAsync(true);
} else {
LOG.debug("Showing application...");
showMainWindowAsync(false);
}
// register app reopen listener
Desktop.getDesktop().addAppEventListener((AppReopenedListener) e -> showMainWindowAsync(false));
// auto unlock
Collection<Vault> vaultsToAutoUnlock = vaults.filtered(this::shouldAttemptAutoUnlock);
if (!vaultsToAutoUnlock.isEmpty()) {
fxApplicationStarter.get().thenAccept(app -> {
for (Vault vault : vaultsToAutoUnlock) {
app.startUnlockWorkflow(vault, Optional.empty());
}
});
}
launchEventHandler.startHandlingLaunchEvents();
}
private boolean shouldAttemptAutoUnlock(Vault vault) {
return vault.isLocked() && vault.getVaultSettings().unlockAfterStartup().get();
}
private void showMainWindowAsync(boolean minimize) {
fxApplicationStarter.get().thenCompose(FxApplication::showMainWindow).thenAccept(win -> win.setIconified(minimize));
}
}
@@ -1,66 +0,0 @@
package org.cryptomator.ui.launcher;
import dagger.Module;
import dagger.Provides;
import org.cryptomator.common.PluginClassLoader;
import org.cryptomator.integrations.autostart.AutoStartProvider;
import org.cryptomator.integrations.tray.TrayIntegrationProvider;
import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
import org.cryptomator.ui.fxapp.FxApplicationComponent;
import org.cryptomator.ui.traymenu.TrayMenuComponent;
import javax.inject.Named;
import javax.inject.Singleton;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.ServiceLoader;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
@Module(subcomponents = {TrayMenuComponent.class, FxApplicationComponent.class})
public abstract class UiLauncherModule {
@Provides
@Singleton
static TrayMenuComponent provideTrayMenuComponent(TrayMenuComponent.Builder builder) {
return builder.build();
}
@Provides
@Singleton
static FxApplicationComponent provideFxApplicationComponent(FxApplicationComponent.Builder builder) {
return builder.build();
}
@Provides
@Singleton
static Optional<UiAppearanceProvider> provideAppearanceProvider() {
return UiAppearanceProvider.get();
}
@Provides
@Singleton
static Optional<AutoStartProvider> provideAutostartProvider() {
return AutoStartProvider.get();
}
@Provides
@Singleton
static Optional<TrayIntegrationProvider> provideTrayIntegrationProvider() {
return TrayIntegrationProvider.get();
}
@Provides
@Singleton
static ResourceBundle provideLocalization() {
return ResourceBundle.getBundle("i18n.strings");
}
@Provides
@Singleton
@Named("launchEventQueue")
static BlockingQueue<AppLaunchEvent> provideFileOpenRequests() {
return new ArrayBlockingQueue<>(10);
}
}
@@ -2,11 +2,11 @@ package org.cryptomator.ui.lock;
import dagger.BindsInstance;
import dagger.Subcomponent;
import org.cryptomator.common.Nullable;
import org.cryptomator.common.vaults.Vault;
import javax.inject.Named;
import javafx.stage.Stage;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
@@ -25,15 +25,9 @@ public interface LockComponent {
return workflow;
}
@Subcomponent.Builder
interface Builder {
@BindsInstance
LockComponent.Builder vault(@LockWindow Vault vault);
@BindsInstance
LockComponent.Builder owner(@Named("lockWindowOwner") Optional<Stage> owner);
LockComponent build();
@Subcomponent.Factory
interface Factory {
LockComponent create(@BindsInstance @LockWindow Vault vault, @BindsInstance @Named("lockWindowOwner") @Nullable Stage owner);
}
}
@@ -12,6 +12,7 @@ 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.jetbrains.annotations.Nullable;
import javax.inject.Named;
import javax.inject.Provider;
@@ -19,7 +20,6 @@ import javafx.scene.Scene;
import javafx.stage.Modality;
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;
@@ -43,12 +43,12 @@ abstract class LockModule {
@Provides
@LockWindow
@LockScoped
static Stage provideWindow(StageFactory factory, @LockWindow Vault vault, @Named("lockWindowOwner") Optional<Stage> owner) {
static Stage provideWindow(StageFactory factory, @LockWindow Vault vault, @Nullable @Named("lockWindowOwner") Stage owner) {
Stage stage = factory.create();
stage.setTitle(vault.getDisplayName());
stage.setResizable(false);
if (owner.isPresent()) {
stage.initOwner(owner.get());
if (owner != null) {
stage.initOwner(owner);
stage.initModality(Modality.WINDOW_MODAL);
} else {
stage.initModality(Modality.APPLICATION_MODAL);
@@ -5,9 +5,9 @@ import org.cryptomator.common.vaults.LockNotCompletedException;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
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.fxapp.FxApplicationWindows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -40,16 +40,16 @@ public class LockWorkflow extends Task<Void> {
private final AtomicReference<CompletableFuture<Boolean>> forceRetryDecision;
private final Lazy<Scene> lockForcedScene;
private final Lazy<Scene> lockFailedScene;
private final ErrorComponent.Builder errorComponent;
private final FxApplicationWindows appWindows;
@Inject
public LockWorkflow(@LockWindow Stage lockWindow, @LockWindow Vault vault, AtomicReference<CompletableFuture<Boolean>> forceRetryDecision, @FxmlScene(FxmlFile.LOCK_FORCED) Lazy<Scene> lockForcedScene, @FxmlScene(FxmlFile.LOCK_FAILED) Lazy<Scene> lockFailedScene, ErrorComponent.Builder errorComponent) {
public LockWorkflow(@LockWindow Stage lockWindow, @LockWindow Vault vault, AtomicReference<CompletableFuture<Boolean>> forceRetryDecision, @FxmlScene(FxmlFile.LOCK_FORCED) Lazy<Scene> lockForcedScene, @FxmlScene(FxmlFile.LOCK_FAILED) Lazy<Scene> lockFailedScene, FxApplicationWindows appWindows) {
this.lockWindow = lockWindow;
this.vault = vault;
this.forceRetryDecision = forceRetryDecision;
this.lockForcedScene = lockForcedScene;
this.lockFailedScene = lockFailedScene;
this.errorComponent = errorComponent;
this.appWindows = appWindows;
}
@Override
@@ -109,7 +109,7 @@ public class LockWorkflow extends Task<Void> {
lockWindow.setScene(lockFailedScene.get());
lockWindow.show();
} else {
errorComponent.cause(throwable).window(lockWindow).build().showErrorScene();
appWindows.showErrorWindow(throwable, lockWindow, null);
}
}
@@ -9,6 +9,6 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface MainWindow {
@interface MainWindow {
}
@@ -13,6 +13,8 @@ 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.StageInitializer;
import org.cryptomator.ui.fxapp.PrimaryStage;
import org.cryptomator.ui.health.HealthCheckComponent;
import org.cryptomator.ui.migration.MigrationComponent;
import org.cryptomator.ui.removevault.RemoveVaultComponent;
@@ -34,6 +36,18 @@ import java.util.ResourceBundle;
@Module(subcomponents = {AddVaultWizardComponent.class, HealthCheckComponent.class, MigrationComponent.class, RemoveVaultComponent.class, VaultOptionsComponent.class, VaultStatisticsComponent.class, WrongFileAlertComponent.class, ErrorComponent.class})
abstract class MainWindowModule {
@Provides
@MainWindow
@MainWindowScoped
static Stage provideMainWindow(@PrimaryStage Stage stage, StageInitializer initializer) {
initializer.accept(stage);
stage.setTitle("Cryptomator");
stage.initStyle(StageStyle.UNDECORATED);
stage.setMinWidth(650);
stage.setMinHeight(440);
return stage;
}
@Provides
@MainWindowScoped
static ObjectProperty<Vault> provideSelectedVault() {
@@ -47,22 +61,11 @@ abstract class MainWindowModule {
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
}
@Provides
@MainWindow
@MainWindowScoped
static Stage provideStage(StageFactory factory) {
Stage stage = factory.create(StageStyle.UNDECORATED);
stage.setMinWidth(650);
stage.setMinHeight(440);
stage.setTitle("Cryptomator");
return stage;
}
@Provides
@MainWindowScoped
@Named("errorWindow")
static Stage provideErrorStage(@MainWindow Stage window, StageFactory factory, ResourceBundle resourceBundle) {
Stage stage = factory.create(StageStyle.DECORATED);
Stage stage = factory.create();
stage.setTitle(resourceBundle.getString("main.vaultDetail.error.windowTitle"));
stage.initModality(Modality.APPLICATION_MODAL);
stage.initOwner(window);
@@ -3,9 +3,9 @@ package org.cryptomator.ui.mainwindow;
import org.cryptomator.common.LicenseHolder;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.FxApplication;
import org.cryptomator.ui.fxapp.FxApplicationTerminator;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.fxapp.UpdateChecker;
import org.cryptomator.ui.launcher.AppLifecycleListener;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.cryptomator.ui.traymenu.TrayMenuComponent;
import org.slf4j.Logger;
@@ -25,9 +25,9 @@ public class MainWindowTitleController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(MainWindowTitleController.class);
private final AppLifecycleListener appLifecycle;
private final Stage window;
private final FxApplication application;
private final FxApplicationTerminator terminator;
private final FxApplicationWindows appWindows;
private final boolean trayMenuInitialized;
private final UpdateChecker updateChecker;
private final BooleanBinding updateAvailable;
@@ -40,10 +40,10 @@ public class MainWindowTitleController implements FxController {
private double yOffset;
@Inject
MainWindowTitleController(AppLifecycleListener appLifecycle, @MainWindow Stage window, FxApplication application, TrayMenuComponent trayMenu, UpdateChecker updateChecker, LicenseHolder licenseHolder, Settings settings) {
this.appLifecycle = appLifecycle;
MainWindowTitleController(@MainWindow Stage window, FxApplicationTerminator terminator, FxApplicationWindows appWindows, TrayMenuComponent trayMenu, UpdateChecker updateChecker, LicenseHolder licenseHolder, Settings settings) {
this.window = window;
this.application = application;
this.terminator = terminator;
this.appWindows = appWindows;
this.trayMenuInitialized = trayMenu.isInitialized();
this.updateChecker = updateChecker;
this.updateAvailable = updateChecker.latestVersionProperty().isNotNull();
@@ -96,7 +96,7 @@ public class MainWindowTitleController implements FxController {
if (trayMenuInitialized) {
window.close();
} else {
appLifecycle.quit();
terminator.terminate();
}
}
@@ -107,17 +107,17 @@ public class MainWindowTitleController implements FxController {
@FXML
public void showPreferences() {
application.showPreferencesWindow(SelectedPreferencesTab.ANY);
appWindows.showPreferencesWindow(SelectedPreferencesTab.ANY);
}
@FXML
public void showGeneralPreferences() {
application.showPreferencesWindow(SelectedPreferencesTab.GENERAL);
appWindows.showPreferencesWindow(SelectedPreferencesTab.GENERAL);
}
@FXML
public void showDonationKeyPreferences() {
application.showPreferencesWindow(SelectedPreferencesTab.CONTRIBUTE);
appWindows.showPreferencesWindow(SelectedPreferencesTab.CONTRIBUTE);
}
/* Getter/Setter */
@@ -1,7 +1,6 @@
package org.cryptomator.ui.mainwindow;
import com.tobiasdiez.easybind.EasyBind;
import com.tobiasdiez.easybind.Subscription;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.ui.common.Animations;
@@ -9,9 +8,9 @@ import org.cryptomator.ui.common.AutoAnimator;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.controls.FontAwesome5Icon;
import org.cryptomator.ui.controls.FontAwesome5IconView;
import org.cryptomator.ui.fxapp.FxApplication;
import javax.inject.Inject;
import javafx.application.Application;
import javafx.beans.binding.Binding;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.ObjectProperty;
@@ -22,7 +21,7 @@ import javafx.fxml.FXML;
public class VaultDetailController implements FxController {
private final ReadOnlyObjectProperty<Vault> vault;
private final FxApplication application;
private final Application application;
private final Binding<FontAwesome5Icon> glyph;
private final BooleanBinding anyVaultSelected;
@@ -33,7 +32,7 @@ public class VaultDetailController implements FxController {
@Inject
VaultDetailController(ObjectProperty<Vault> vault, FxApplication application) {
VaultDetailController(ObjectProperty<Vault> vault, Application application) {
this.vault = vault;
this.application = application;
this.glyph = EasyBind.select(vault) //
@@ -4,8 +4,7 @@ import com.tobiasdiez.easybind.EasyBind;
import org.cryptomator.common.keychain.KeychainManager;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.FxApplication;
import org.cryptomator.ui.health.HealthCheckComponent;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab;
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
@@ -14,25 +13,23 @@ import javafx.beans.binding.BooleanExpression;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.stage.Stage;
import java.util.Optional;
@MainWindowScoped
public class VaultDetailLockedController implements FxController {
private final ReadOnlyObjectProperty<Vault> vault;
private final FxApplication application;
private final FxApplicationWindows appWindows;
private final VaultOptionsComponent.Builder vaultOptionsWindow;
private final KeychainManager keychain;
private final Stage mainWindow;
private final BooleanExpression passwordSaved;
@Inject
VaultDetailLockedController(ObjectProperty<Vault> vault, FxApplication application, VaultOptionsComponent.Builder vaultOptionsWindow, KeychainManager keychain, @MainWindow Stage mainWindow) {
VaultDetailLockedController(ObjectProperty<Vault> vault, FxApplicationWindows appWindows, VaultOptionsComponent.Builder vaultOptionsWindow, KeychainManager keychain, @MainWindow Stage mainWindow) {
this.vault = vault;
this.application = application;
this.appWindows = appWindows;
this.vaultOptionsWindow = vaultOptionsWindow;
this.keychain = keychain;
this.mainWindow = mainWindow;
@@ -45,7 +42,7 @@ public class VaultDetailLockedController implements FxController {
@FXML
public void unlock() {
application.startUnlockWorkflow(vault.get(), Optional.of(mainWindow));
appWindows.startUnlockWorkflow(vault.get(), mainWindow);
}
@FXML
@@ -1,15 +1,13 @@
package org.cryptomator.ui.mainwindow;
import com.tobiasdiez.easybind.EasyBind;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.removevault.RemoveVaultComponent;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.beans.binding.Binding;
import javafx.beans.property.ObjectProperty;
import javafx.fxml.FXML;
import javafx.stage.Stage;
@@ -18,21 +16,21 @@ import javafx.stage.Stage;
public class VaultDetailUnknownErrorController implements FxController {
private final ObjectProperty<Vault> vault;
private final ErrorComponent.Builder errorComponentBuilder;
private final FxApplicationWindows appWindows;
private final Stage errorWindow;
private final RemoveVaultComponent.Builder removeVault;
@Inject
public VaultDetailUnknownErrorController(ObjectProperty<Vault> vault, ErrorComponent.Builder errorComponentBuilder, @Named("errorWindow") Stage errorWindow, RemoveVaultComponent.Builder removeVault) {
public VaultDetailUnknownErrorController(ObjectProperty<Vault> vault, FxApplicationWindows appWindows, @Named("errorWindow") Stage errorWindow, RemoveVaultComponent.Builder removeVault) {
this.vault = vault;
this.errorComponentBuilder = errorComponentBuilder;
this.appWindows = appWindows;
this.errorWindow = errorWindow;
this.removeVault = removeVault;
}
@FXML
public void showError() {
errorComponentBuilder.window(errorWindow).cause(vault.get().getLastKnownException()).build().showErrorScene();
appWindows.showErrorWindow(vault.get().getLastKnownException(), errorWindow, null);
}
@FXML
@@ -6,7 +6,7 @@ import com.google.common.cache.LoadingCache;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.VaultService;
import org.cryptomator.ui.fxapp.FxApplication;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.stats.VaultStatisticsComponent;
import javax.inject.Inject;
@@ -14,22 +14,21 @@ import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.fxml.FXML;
import javafx.stage.Stage;
import java.util.Optional;
@MainWindowScoped
public class VaultDetailUnlockedController implements FxController {
private final ReadOnlyObjectProperty<Vault> vault;
private final FxApplication application;
private final FxApplicationWindows appWindows;
private final VaultService vaultService;
private final Stage mainWindow;
private final LoadingCache<Vault, VaultStatisticsComponent> vaultStats;
private final VaultStatisticsComponent.Builder vaultStatsBuilder;
@Inject
public VaultDetailUnlockedController(ObjectProperty<Vault> vault, FxApplication application, VaultService vaultService, VaultStatisticsComponent.Builder vaultStatsBuilder, @MainWindow Stage mainWindow) {
public VaultDetailUnlockedController(ObjectProperty<Vault> vault, FxApplicationWindows appWindows, VaultService vaultService, VaultStatisticsComponent.Builder vaultStatsBuilder, @MainWindow Stage mainWindow) {
this.vault = vault;
this.application = application;
this.appWindows = appWindows;
this.vaultService = vaultService;
this.mainWindow = mainWindow;
this.vaultStats = CacheBuilder.newBuilder().weakValues().build(CacheLoader.from(this::buildVaultStats));
@@ -47,7 +46,7 @@ public class VaultDetailUnlockedController implements FxController {
@FXML
public void lock() {
application.startLockWorkflow(vault.get(), Optional.of(mainWindow));
appWindows.startLockWorkflow(vault.get(), mainWindow);
}
@FXML
@@ -7,7 +7,8 @@ import org.cryptomator.common.keychain.KeychainManager;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.FxApplication;
import org.cryptomator.ui.common.VaultService;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.removevault.RemoveVaultComponent;
import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab;
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
@@ -18,7 +19,6 @@ import javafx.beans.property.ObjectProperty;
import javafx.fxml.FXML;
import javafx.stage.Stage;
import java.util.EnumSet;
import java.util.Optional;
import static org.cryptomator.common.vaults.VaultState.Value.*;
@@ -27,7 +27,8 @@ public class VaultListContextMenuController implements FxController {
private final ObservableOptionalValue<Vault> selectedVault;
private final Stage mainWindow;
private final FxApplication application;
private final FxApplicationWindows appWindows;
private final VaultService vaultService;
private final KeychainManager keychain;
private final RemoveVaultComponent.Builder removeVault;
private final VaultOptionsComponent.Builder vaultOptionsWindow;
@@ -38,10 +39,11 @@ public class VaultListContextMenuController implements FxController {
private final Binding<Boolean> selectedVaultLockable;
@Inject
VaultListContextMenuController(ObjectProperty<Vault> selectedVault, @MainWindow Stage mainWindow, FxApplication application, KeychainManager keychain, RemoveVaultComponent.Builder removeVault, VaultOptionsComponent.Builder vaultOptionsWindow) {
VaultListContextMenuController(ObjectProperty<Vault> selectedVault, @MainWindow Stage mainWindow, FxApplicationWindows appWindows, VaultService vaultService, KeychainManager keychain, RemoveVaultComponent.Builder removeVault, VaultOptionsComponent.Builder vaultOptionsWindow) {
this.selectedVault = EasyBind.wrapNullable(selectedVault);
this.mainWindow = mainWindow;
this.application = application;
this.appWindows = appWindows;
this.vaultService = vaultService;
this.keychain = keychain;
this.removeVault = removeVault;
this.vaultOptionsWindow = vaultOptionsWindow;
@@ -74,22 +76,20 @@ public class VaultListContextMenuController implements FxController {
@FXML
public void didClickUnlockVault() {
selectedVault.ifValuePresent(v -> {
application.startUnlockWorkflow(v, Optional.of(mainWindow));
appWindows.startUnlockWorkflow(v, mainWindow);
});
}
@FXML
public void didClickLockVault() {
selectedVault.ifValuePresent(v -> {
application.startLockWorkflow(v, Optional.of(mainWindow));
appWindows.startLockWorkflow(v, mainWindow);
});
}
@FXML
public void didClickRevealVault() {
selectedVault.ifValuePresent(v -> {
application.getVaultService().reveal(v);
});
selectedVault.ifValuePresent(vaultService::reveal);
}
// Getter and Setter
@@ -2,9 +2,9 @@ package org.cryptomator.ui.migration;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.FxApplication;
import javax.inject.Inject;
import javafx.application.Application;
import javafx.fxml.FXML;
import javafx.stage.Stage;
@@ -12,13 +12,13 @@ public class MigrationImpossibleController implements FxController {
private static final String HELP_URI = "https://docs.cryptomator.org/en/1.5/help/manual-migration/";
private final FxApplication fxApplication;
private final Application application;
private final Stage window;
private final Vault vault;
@Inject
MigrationImpossibleController(FxApplication fxApplication, @MigrationWindow Stage window, @MigrationWindow Vault vault) {
this.fxApplication = fxApplication;
MigrationImpossibleController(Application application, @MigrationWindow Stage window, @MigrationWindow Vault vault) {
this.application = application;
this.window = window;
this.vault = vault;
}
@@ -30,7 +30,7 @@ public class MigrationImpossibleController implements FxController {
@FXML
public void getMigrationHelp() {
fxApplication.getHostServices().showDocument(HELP_URI);
application.getHostServices().showDocument(HELP_URI);
}
/* Getter/Setters */
@@ -6,13 +6,13 @@ import dagger.Provides;
import dagger.multibindings.IntoMap;
import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
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.mainwindow.MainWindow;
import org.cryptomator.ui.fxapp.PrimaryStage;
import javax.inject.Named;
import javax.inject.Provider;
@@ -37,7 +37,7 @@ abstract class MigrationModule {
@Provides
@MigrationWindow
@MigrationScoped
static Stage provideStage(StageFactory factory, @MainWindow Stage owner, ResourceBundle resourceBundle) {
static Stage provideStage(StageFactory factory, @PrimaryStage Stage owner, ResourceBundle resourceBundle) {
Stage stage = factory.create();
stage.setTitle(resourceBundle.getString("migration.title"));
stage.setResizable(false);
@@ -12,12 +12,12 @@ import org.cryptomator.cryptofs.migration.api.MigrationProgressListener;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.integrations.keychain.KeychainAccessException;
import org.cryptomator.ui.common.Animations;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.Tasks;
import org.cryptomator.ui.controls.NiceSecurePasswordField;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -58,7 +58,7 @@ public class MigrationRunController implements FxController {
private final ScheduledExecutorService scheduler;
private final KeychainManager keychain;
private final ObjectProperty<FileSystemCapabilityChecker.Capability> missingCapability;
private final ErrorComponent.Builder errorComponent;
private final FxApplicationWindows appWindows;
private final Lazy<Scene> startScene;
private final Lazy<Scene> successScene;
private final Lazy<Scene> impossibleScene;
@@ -73,14 +73,14 @@ public class MigrationRunController implements FxController {
public NiceSecurePasswordField passwordField;
@Inject
public MigrationRunController(@MigrationWindow Stage window, @MigrationWindow Vault vault, ExecutorService executor, ScheduledExecutorService scheduler, KeychainManager keychain, @Named("capabilityErrorCause") ObjectProperty<FileSystemCapabilityChecker.Capability> missingCapability, @FxmlScene(FxmlFile.MIGRATION_START) Lazy<Scene> startScene, @FxmlScene(FxmlFile.MIGRATION_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.MIGRATION_CAPABILITY_ERROR) Lazy<Scene> capabilityErrorScene, @FxmlScene(FxmlFile.MIGRATION_IMPOSSIBLE) Lazy<Scene> impossibleScene, ErrorComponent.Builder errorComponent) {
public MigrationRunController(@MigrationWindow Stage window, @MigrationWindow Vault vault, ExecutorService executor, ScheduledExecutorService scheduler, KeychainManager keychain, @Named("capabilityErrorCause") ObjectProperty<FileSystemCapabilityChecker.Capability> missingCapability, @FxmlScene(FxmlFile.MIGRATION_START) Lazy<Scene> startScene, @FxmlScene(FxmlFile.MIGRATION_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.MIGRATION_CAPABILITY_ERROR) Lazy<Scene> capabilityErrorScene, @FxmlScene(FxmlFile.MIGRATION_IMPOSSIBLE) Lazy<Scene> impossibleScene, FxApplicationWindows appWindows) {
this.window = window;
this.vault = vault;
this.executor = executor;
this.scheduler = scheduler;
this.keychain = keychain;
this.missingCapability = missingCapability;
this.errorComponent = errorComponent;
this.appWindows = appWindows;
this.startScene = startScene;
this.successScene = successScene;
this.migrateButtonContentDisplay = Bindings.createObjectBinding(this::getMigrateButtonContentDisplay, vault.stateProperty());
@@ -146,12 +146,12 @@ public class MigrationRunController implements FxController {
}).onError(FileNameTooLongException.class, e -> {
LOG.error("Migration failed because the underlying file system does not support long filenames.", e);
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.NEEDS_MIGRATION);
errorComponent.cause(e).window(window).returnToScene(startScene.get()).build().showErrorScene();
appWindows.showErrorWindow(e, window, startScene.get());
window.setScene(impossibleScene.get());
}).onError(Exception.class, e -> { // including RuntimeExceptions
LOG.error("Migration failed for technical reasons.", e);
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.NEEDS_MIGRATION);
errorComponent.cause(e).window(window).returnToScene(startScene.get()).build().showErrorScene();
appWindows.showErrorWindow(e, window, startScene.get());
}).andFinally(() -> {
passwordField.setDisable(false);
progressSyncTask.cancel(true);
@@ -2,25 +2,24 @@ package org.cryptomator.ui.migration;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.FxApplication;
import org.cryptomator.ui.mainwindow.MainWindow;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.fxapp.PrimaryStage;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.stage.Stage;
import java.util.Optional;
@MigrationScoped
public class MigrationSuccessController implements FxController {
private final FxApplication fxApplication;
private final FxApplicationWindows appWindows;
private final Stage window;
private final Vault vault;
private final Stage mainWindow;
@Inject
MigrationSuccessController(FxApplication fxApplication, @MigrationWindow Stage window, @MigrationWindow Vault vault, @MainWindow Stage mainWindow) {
this.fxApplication = fxApplication;
MigrationSuccessController(FxApplicationWindows appWindows, @MigrationWindow Stage window, @MigrationWindow Vault vault, @PrimaryStage Stage mainWindow) {
this.appWindows = appWindows;
this.window = window;
this.vault = vault;
this.mainWindow = mainWindow;
@@ -29,7 +28,7 @@ public class MigrationSuccessController implements FxController {
@FXML
public void unlockAndClose() {
close();
fxApplication.startUnlockWorkflow(vault, Optional.of(mainWindow));
appWindows.startUnlockWorkflow(vault, mainWindow);
}
@FXML
@@ -7,8 +7,8 @@ import org.cryptomator.common.settings.UiTheme;
import org.cryptomator.integrations.autostart.AutoStartProvider;
import org.cryptomator.integrations.autostart.ToggleAutoStartFailedException;
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.traymenu.TrayMenuComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -30,7 +30,6 @@ import javafx.util.StringConverter;
import java.util.List;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.Set;
@PreferencesScoped
public class GeneralPreferencesController implements FxController {
@@ -48,7 +47,7 @@ public class GeneralPreferencesController implements FxController {
private final Application application;
private final Environment environment;
private final List<KeychainAccessProvider> keychainAccessProviders;
private final ErrorComponent.Builder errorComponent;
private final FxApplicationWindows appWindows;
public ChoiceBox<UiTheme> themeChoiceBox;
public ChoiceBox<KeychainAccessProvider> keychainBackendChoiceBox;
public CheckBox showMinimizeButtonCheckbox;
@@ -60,9 +59,8 @@ public class GeneralPreferencesController implements FxController {
public RadioButton nodeOrientationLtr;
public RadioButton nodeOrientationRtl;
@Inject
GeneralPreferencesController(@PreferencesWindow Stage window, Settings settings, TrayMenuComponent trayMenu, Optional<AutoStartProvider> autoStartProvider, List<KeychainAccessProvider> keychainAccessProviders, ObjectProperty<SelectedPreferencesTab> selectedTabProperty, LicenseHolder licenseHolder, ResourceBundle resourceBundle, Application application, Environment environment, ErrorComponent.Builder errorComponent) {
GeneralPreferencesController(@PreferencesWindow Stage window, Settings settings, TrayMenuComponent trayMenu, Optional<AutoStartProvider> autoStartProvider, List<KeychainAccessProvider> keychainAccessProviders, ObjectProperty<SelectedPreferencesTab> selectedTabProperty, LicenseHolder licenseHolder, ResourceBundle resourceBundle, Application application, Environment environment, FxApplicationWindows appWindows) {
this.window = window;
this.settings = settings;
this.trayMenuInitialized = trayMenu.isInitialized();
@@ -74,7 +72,7 @@ public class GeneralPreferencesController implements FxController {
this.resourceBundle = resourceBundle;
this.application = application;
this.environment = environment;
this.errorComponent = errorComponent;
this.appWindows = appWindows;
}
@FXML
@@ -143,7 +141,7 @@ public class GeneralPreferencesController implements FxController {
} catch (ToggleAutoStartFailedException e) {
autoStartCheckbox.setSelected(!enableAutoStart); // restore previous state
LOG.error("Failed to toggle autostart.", e);
errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
appWindows.showErrorWindow(e, window, window.getScene());
}
});
}
@@ -5,11 +5,11 @@ import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.ui.common.Animations;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.controls.NiceSecurePasswordField;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -33,18 +33,18 @@ public class RecoveryKeyCreationController implements FxController {
private final ExecutorService executor;
private final RecoveryKeyFactory recoveryKeyFactory;
private final StringProperty recoveryKeyProperty;
private final ErrorComponent.Builder errorComponent;
private final FxApplicationWindows appWindows;
public NiceSecurePasswordField passwordField;
@Inject
public RecoveryKeyCreationController(@RecoveryKeyWindow Stage window, @FxmlScene(FxmlFile.RECOVERYKEY_SUCCESS) Lazy<Scene> successScene, @RecoveryKeyWindow Vault vault, RecoveryKeyFactory recoveryKeyFactory, ExecutorService executor, @RecoveryKeyWindow StringProperty recoveryKey, ErrorComponent.Builder errorComponent) {
public RecoveryKeyCreationController(@RecoveryKeyWindow Stage window, @FxmlScene(FxmlFile.RECOVERYKEY_SUCCESS) Lazy<Scene> successScene, @RecoveryKeyWindow Vault vault, RecoveryKeyFactory recoveryKeyFactory, ExecutorService executor, @RecoveryKeyWindow StringProperty recoveryKey, FxApplicationWindows appWindows) {
this.window = window;
this.successScene = successScene;
this.vault = vault;
this.executor = executor;
this.recoveryKeyFactory = recoveryKeyFactory;
this.recoveryKeyProperty = recoveryKey;
this.errorComponent = errorComponent;
this.appWindows = appWindows;
}
@FXML
@@ -63,7 +63,7 @@ public class RecoveryKeyCreationController implements FxController {
Animations.createShakeWindowAnimation(window).play();
} else {
LOG.error("Creation of recovery key failed.", task.getException());
errorComponent.cause(task.getException()).window(window).returnToScene(window.getScene()).build().showErrorScene();
appWindows.showErrorWindow(task.getException(), window, window.getScene());
}
});
executor.submit(task);
@@ -2,11 +2,11 @@ package org.cryptomator.ui.recoverykey;
import dagger.Lazy;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.NewPasswordController;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -31,19 +31,19 @@ public class RecoveryKeyResetPasswordController implements FxController {
private final ExecutorService executor;
private final StringProperty recoveryKey;
private final Lazy<Scene> recoverScene;
private final ErrorComponent.Builder errorComponent;
private final FxApplicationWindows appWindows;
public NewPasswordController newPasswordController;
@Inject
public RecoveryKeyResetPasswordController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, RecoveryKeyFactory recoveryKeyFactory, ExecutorService executor, @RecoveryKeyWindow StringProperty recoveryKey, @FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy<Scene> recoverScene, ErrorComponent.Builder errorComponent) {
public RecoveryKeyResetPasswordController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, RecoveryKeyFactory recoveryKeyFactory, ExecutorService executor, @RecoveryKeyWindow StringProperty recoveryKey, @FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy<Scene> recoverScene, FxApplicationWindows appWindows) {
this.window = window;
this.vault = vault;
this.recoveryKeyFactory = recoveryKeyFactory;
this.executor = executor;
this.recoveryKey = recoveryKey;
this.recoverScene = recoverScene;
this.errorComponent = errorComponent;
this.appWindows = appWindows;
}
@FXML
@@ -64,7 +64,7 @@ public class RecoveryKeyResetPasswordController implements FxController {
});
task.setOnFailed(event -> {
LOG.error("Resetting password failed.", task.getException());
errorComponent.cause(task.getException()).window(window).returnToScene(recoverScene.get()).build().showErrorScene();
appWindows.showErrorWindow(task.getException(), window, recoverScene.get());
});
executor.submit(task);
}
@@ -5,13 +5,13 @@ import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
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.mainwindow.MainWindow;
import org.cryptomator.ui.fxapp.PrimaryStage;
import javax.inject.Provider;
import javafx.scene.Scene;
@@ -33,12 +33,12 @@ abstract class RemoveVaultModule {
@Provides
@RemoveVaultWindow
@RemoveVaultScoped
static Stage provideStage(StageFactory factory, @MainWindow Stage owner, ResourceBundle resourceBundle) {
static Stage provideStage(StageFactory factory, @PrimaryStage Stage primaryStage, ResourceBundle resourceBundle) {
Stage stage = factory.create();
stage.setTitle(resourceBundle.getString("removeVault.title"));
stage.setResizable(false);
stage.initModality(Modality.WINDOW_MODAL);
stage.initOwner(owner);
stage.initOwner(primaryStage);
return stage;
}
@@ -8,9 +8,9 @@ import org.cryptomator.integrations.tray.SeparatorItem;
import org.cryptomator.integrations.tray.SubMenuItem;
import org.cryptomator.integrations.tray.TrayMenuController;
import org.cryptomator.integrations.tray.TrayMenuItem;
import org.cryptomator.ui.fxapp.FxApplication;
import org.cryptomator.ui.launcher.AppLifecycleListener;
import org.cryptomator.ui.launcher.FxApplicationStarter;
import org.cryptomator.ui.common.VaultService;
import org.cryptomator.ui.fxapp.FxApplicationTerminator;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import javax.inject.Inject;
@@ -23,7 +23,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.function.Consumer;
@TrayMenuScoped
public class TrayMenuBuilder {
@@ -32,18 +31,20 @@ public class TrayMenuBuilder {
private static final String TRAY_ICON = "/img/tray_icon.png";
private final ResourceBundle resourceBundle;
private final AppLifecycleListener appLifecycle;
private final FxApplicationStarter fxApplicationStarter;
private final VaultService vaultService;
private final FxApplicationWindows appWindows;
private final FxApplicationTerminator appTerminator;
private final ObservableList<Vault> vaults;
private final TrayMenuController trayMenu;
private volatile boolean initialized;
@Inject
TrayMenuBuilder(ResourceBundle resourceBundle, AppLifecycleListener appLifecycle, FxApplicationStarter fxApplicationStarter, ObservableList<Vault> vaults, Optional<TrayMenuController> trayMenu) {
TrayMenuBuilder(ResourceBundle resourceBundle, VaultService vaultService, FxApplicationWindows appWindows, FxApplicationTerminator appTerminator, ObservableList<Vault> vaults, Optional<TrayMenuController> trayMenu) {
this.resourceBundle = resourceBundle;
this.appLifecycle = appLifecycle;
this.fxApplicationStarter = fxApplicationStarter;
this.vaultService = vaultService;
this.appWindows = appWindows;
this.appTerminator = appTerminator;
this.vaults = vaults;
this.trayMenu = trayMenu.orElse(null);
}
@@ -96,14 +97,13 @@ public class TrayMenuBuilder {
private List<TrayMenuItem> buildSubmenu(Vault vault) {
if (vault.isLocked()) {
return List.of(
new ActionItem(resourceBundle.getString("traymenu.vault.unlock"), () -> this.unlockVault(vault))
return List.of( //
new ActionItem(resourceBundle.getString("traymenu.vault.unlock"), () -> this.unlockVault(vault)) //
);
} else if (vault.isUnlocked()) {
return List.of(
new ActionItem(resourceBundle.getString("traymenu.vault.lock"), () -> this.lockVault(vault)),
new ActionItem(resourceBundle.getString("traymenu.vault.reveal"), () -> this.revealVault(vault))
return List.of( //
new ActionItem(resourceBundle.getString("traymenu.vault.lock"), () -> this.lockVault(vault)), //
new ActionItem(resourceBundle.getString("traymenu.vault.reveal"), () -> this.revealVault(vault)) //
);
} else {
return List.of();
@@ -113,35 +113,31 @@ public class TrayMenuBuilder {
/* action listeners: */
private void quitApplication() {
appLifecycle.quit();
appTerminator.terminate();
}
private void unlockVault(Vault vault) {
showMainAppAndThen(app -> app.startUnlockWorkflow(vault, Optional.empty()));
appWindows.startUnlockWorkflow(vault, null);
}
private void lockVault(Vault vault) {
showMainAppAndThen(app -> app.startLockWorkflow(vault, Optional.empty()));
appWindows.startLockWorkflow(vault, null);
}
private void lockAllVaults() {
showMainAppAndThen(app -> app.getVaultService().lockAll(vaults.filtered(Vault::isUnlocked), false));
vaultService.lockAll(vaults.filtered(Vault::isUnlocked), false);
}
private void revealVault(Vault vault) {
showMainAppAndThen(app -> app.getVaultService().reveal(vault));
vaultService.reveal(vault);
}
void showMainWindow() {
showMainAppAndThen(FxApplication::showMainWindow);
appWindows.showMainWindow();
}
private void showPreferencesWindow() {
showMainAppAndThen(app -> app.showPreferencesWindow(SelectedPreferencesTab.ANY));
}
private void showMainAppAndThen(Consumer<FxApplication> action) {
fxApplicationStarter.get().thenAccept(action);
appWindows.showPreferencesWindow(SelectedPreferencesTab.ANY);
}
}
@@ -7,11 +7,11 @@ package org.cryptomator.ui.unlock;
import dagger.BindsInstance;
import dagger.Subcomponent;
import org.cryptomator.common.Nullable;
import org.cryptomator.common.vaults.Vault;
import javax.inject.Named;
import javafx.stage.Stage;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
@@ -29,16 +29,9 @@ public interface UnlockComponent {
return workflow;
}
@Subcomponent.Builder
interface Builder {
@BindsInstance
Builder vault(@UnlockWindow Vault vault);
@BindsInstance
Builder owner(@Named("unlockWindowOwner") Optional<Stage> owner);
UnlockComponent build();
@Subcomponent.Factory
interface Factory {
UnlockComponent create(@BindsInstance @UnlockWindow Vault vault, @BindsInstance @Named("unlockWindowOwner") @Nullable Stage owner);
}
}
@@ -14,6 +14,7 @@ import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.StageFactory;
import org.cryptomator.ui.keyloading.KeyLoadingComponent;
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
import org.jetbrains.annotations.Nullable;
import javax.inject.Named;
import javax.inject.Provider;
@@ -21,7 +22,6 @@ import javafx.scene.Scene;
import javafx.stage.Modality;
import javafx.stage.Stage;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
@Module(subcomponents = {KeyLoadingComponent.class})
@@ -37,12 +37,12 @@ abstract class UnlockModule {
@Provides
@UnlockWindow
@UnlockScoped
static Stage provideStage(StageFactory factory, @UnlockWindow Vault vault, @Named("unlockWindowOwner") Optional<Stage> owner) {
static Stage provideStage(StageFactory factory, @UnlockWindow Vault vault, @Nullable @Named("unlockWindowOwner") Stage owner) {
Stage stage = factory.create();
stage.setTitle(vault.getDisplayName());
stage.setResizable(false);
if (owner.isPresent()) {
stage.initOwner(owner.get());
if (owner != null) {
stage.initOwner(owner);
stage.initModality(Modality.WINDOW_MODAL);
} else {
stage.initModality(Modality.APPLICATION_MODAL);
@@ -9,10 +9,10 @@ import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.common.vaults.Volume.VolumeException;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.VaultService;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -42,17 +42,17 @@ public class UnlockWorkflow extends Task<Boolean> {
private final VaultService vaultService;
private final Lazy<Scene> successScene;
private final Lazy<Scene> invalidMountPointScene;
private final ErrorComponent.Builder errorComponent;
private final FxApplicationWindows appWindows;
private final KeyLoadingStrategy keyLoadingStrategy;
@Inject
UnlockWorkflow(@UnlockWindow Stage window, @UnlockWindow Vault vault, VaultService vaultService, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy<Scene> invalidMountPointScene, ErrorComponent.Builder errorComponent, @UnlockWindow KeyLoadingStrategy keyLoadingStrategy) {
UnlockWorkflow(@UnlockWindow Stage window, @UnlockWindow Vault vault, VaultService vaultService, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy<Scene> invalidMountPointScene, FxApplicationWindows appWindows, @UnlockWindow KeyLoadingStrategy keyLoadingStrategy) {
this.window = window;
this.vault = vault;
this.vaultService = vaultService;
this.successScene = successScene;
this.invalidMountPointScene = invalidMountPointScene;
this.errorComponent = errorComponent;
this.appWindows = appWindows;
this.keyLoadingStrategy = keyLoadingStrategy;
}
@@ -118,7 +118,7 @@ public class UnlockWorkflow extends Task<Boolean> {
private void handleGenericError(Throwable e) {
LOG.error("Unlock failed for technical reasons.", e);
errorComponent.cause(e).window(window).build().showErrorScene();
appWindows.showErrorWindow(e, window, null);
}
@Override
@@ -13,7 +13,7 @@ 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.mainwindow.MainWindow;
import org.cryptomator.ui.fxapp.PrimaryStage;
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
import javax.inject.Provider;
@@ -44,14 +44,14 @@ abstract class VaultOptionsModule {
@Provides
@VaultOptionsWindow
@VaultOptionsScoped
static Stage provideStage(StageFactory factory, @MainWindow Stage owner, @VaultOptionsWindow Vault vault) {
static Stage provideStage(StageFactory factory, @PrimaryStage Stage primaryStage, @VaultOptionsWindow Vault vault) {
Stage stage = factory.create();
stage.setTitle(vault.getDisplayName());
stage.setResizable(true);
stage.setMinWidth(400);
stage.setMinHeight(300);
stage.initModality(Modality.WINDOW_MODAL);
stage.initOwner(owner);
stage.initOwner(primaryStage);
return stage;
}
@@ -5,13 +5,13 @@ import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
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.mainwindow.MainWindow;
import org.cryptomator.ui.fxapp.PrimaryStage;
import javax.inject.Provider;
import javafx.scene.Scene;
@@ -33,11 +33,11 @@ abstract class WrongFileAlertModule {
@Provides
@WrongFileAlertWindow
@WrongFileAlertScoped
static Stage provideStage(StageFactory factory, @MainWindow Stage mainWindow, ResourceBundle resourceBundle) {
static Stage provideStage(StageFactory factory, @PrimaryStage Stage primaryStage, ResourceBundle resourceBundle) {
Stage stage = factory.create();
stage.setTitle(resourceBundle.getString("wrongFileAlert.title"));
stage.setResizable(false);
stage.initOwner(mainWindow);
stage.initOwner(primaryStage);
stage.initModality(Modality.WINDOW_MODAL);
return stage;
}
@@ -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" بنجاح! يمكنك الآن الوصول لمخزنك عن طريق القرص الافتراضي الخاص به.
@@ -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
@@ -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.
@@ -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.
@@ -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
+40 -37
View File
@@ -35,7 +35,7 @@ traymenu.vault.reveal=Anzeigen
addvaultwizard.title=Tresor hinzufügen
## Welcome
addvaultwizard.welcome.newButton=Neuen Tresor erstellen
addvaultwizard.welcome.existingButton=Existierenden Tresor öffnen
addvaultwizard.welcome.existingButton=Bestehenden Tresor öffnen
## New
### Name
addvaultwizard.new.nameInstruction=Wähle einen Namen für den Tresor
@@ -44,10 +44,10 @@ 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
addvaultwizard.new.fileAlreadyExists=Eine Datei oder ein Ordner dieses Namens besteht bereits
addvaultwizard.new.locationDoesNotExist=Ein Ordner im angegebenen Pfad existiert nicht oder kann nicht geöffnet werden
addvaultwizard.new.locationIsNotWritable=Kein Schreibzugriff auf den angegebenen Pfad
addvaultwizard.new.locationIsOk=Geeigneter Ort für deinen Tresor
@@ -59,7 +59,7 @@ addvaultwizard.new.generateRecoveryKeyChoice.yes=Ja bitte, sicher ist sicher
addvaultwizard.new.generateRecoveryKeyChoice.no=Nein danke, ich werde mein Passwort nicht verlieren
### Information
addvault.new.readme.storageLocation.fileName=WICHTIG.rtf
addvault.new.readme.storageLocation.1=⚠️ TRESOR-DATEIEN ⚠️
addvault.new.readme.storageLocation.1=⚠️ TRESORDATEIEN ⚠️
addvault.new.readme.storageLocation.2=Dies ist der Speicherort deines Tresors.
addvault.new.readme.storageLocation.3=NICHT
addvault.new.readme.storageLocation.4=• Dateien in diesem Verzeichnis ändern oder
@@ -68,16 +68,16 @@ addvault.new.readme.storageLocation.6=Falls du Dateien verschlüsseln und den In
addvault.new.readme.storageLocation.7=1. Füge diesen Tresor zu Cryptomator hinzu.
addvault.new.readme.storageLocation.8=2. Entsperre den Tresor in Cryptomator.
addvault.new.readme.storageLocation.9=3. Öffne den Zugangsort durch Klicken auf die Schaltfläche „Anzeigen“.
addvault.new.readme.storageLocation.10=Falls Du Hilfe brauchst, lies die Dokumentation: %s
addvault.new.readme.storageLocation.10=Falls du Hilfe brauchst, lies die Dokumentation: %s
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.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.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=Tresordatei 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.unlockNow=Jetzt entsperren
@@ -98,22 +98,25 @@ forgetPassword.information=Dies löscht das gespeicherte Passwort dieses Tresors
forgetPassword.confirmBtn=Passwort vergessen
# Unlock
unlock.title="%s" entsperren
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! Du kannst nun ü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.existing=Einhängepunkt/Ordner bereits vorhanden oder übergeordneter Ordner fehlt: %s.
unlock.error.invalidMountPoint.notExisting=Einhängepunkt „%s“ ist kein leeres Verzeichnis oder existiert nicht.
unlock.error.invalidMountPoint.existing=Einhängepunkt „%s“ besteht bereits oder übergeordneter Ordner fehlt.
unlock.error.invalidMountPoint.driveLetterOccupied=Laufwerksbuchstabe „%s“ wird bereits verwendet.
# Lock
## Force
@@ -151,11 +154,11 @@ migration.impossible.moreInfo=Der Tresor kann auch jetzt noch mit einer älteren
# 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,7 +219,7 @@ 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.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 keines? 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 -->
@@ -228,9 +231,9 @@ preferences.about=Über
stats.title=Statistiken für %s
stats.cacheHitRate=Cache-Trefferrate
## Read
stats.read.throughput.idle=Lesen: Leerlauf
stats.read.throughput.kibs=Lesen: %.2f kiB/s
stats.read.throughput.mibs=Lesen: %.2f MiB/s
stats.read.throughput.idle=Leserate: inaktiv
stats.read.throughput.kibs=Leserate: %.2f kiB/s
stats.read.throughput.mibs=Leserate: %.2f MiB/s
stats.read.total.data.none=Gelesen: -
stats.read.total.data.kib=Gelesen: %.1f kiB
stats.read.total.data.mib=Gelesen: %.1f MiB
@@ -241,9 +244,9 @@ stats.decr.total.data.mib=Entschlüsselt: %.1f MiB
stats.decr.total.data.gib=Entschlüsselt: %.1f GiB
stats.read.accessCount=Lesezugriffe: %d
## Write
stats.write.throughput.idle=Schreiben: Leerlauf
stats.write.throughput.kibs=Schreiben: %.2f kiB/s
stats.write.throughput.mibs=Schreiben: %.2f MiB/s
stats.write.throughput.idle=Schreibrate: inaktiv
stats.write.throughput.kibs=Schreibrate: %.2f kiB/s
stats.write.throughput.mibs=Schreibrate: %.2f MiB/s
stats.write.total.data.none=Geschrieben: -
stats.write.total.data.kib=Geschrieben: %.1f kiB
stats.write.total.data.mib=Geschrieben: %.1f MiB
@@ -286,9 +289,9 @@ main.vaultDetail.unlockedStatus=ENTSPERRT
main.vaultDetail.accessLocation=Dein Tresorinhalt ist hier erreichbar:
main.vaultDetail.revealBtn=Laufwerk anzeigen
main.vaultDetail.lockBtn=Sperren
main.vaultDetail.bytesPerSecondRead=Gelesen:
main.vaultDetail.bytesPerSecondWritten=Schreiben:
main.vaultDetail.throughput.idle=Leerlauf
main.vaultDetail.bytesPerSecondRead=Leserate:
main.vaultDetail.bytesPerSecondWritten=Schreibrate:
main.vaultDetail.throughput.idle=inaktiv
main.vaultDetail.throughput.kbps=%.1f kiB/s
main.vaultDetail.throughput.mbps=%.1f MiB/s
main.vaultDetail.stats=Tresorstatistik
@@ -302,7 +305,7 @@ main.vaultDetail.migrateButton=Tresor aktualisieren
main.vaultDetail.migratePrompt=Dein Tresor muss auf ein neues Format aktualisiert werden, bevor du auf ihn zugreifen kannst
### Error
main.vaultDetail.error.info=Beim Laden des Tresors von der Festplatte ist ein Fehler aufgetreten.
main.vaultDetail.error.reload=Erneut laden
main.vaultDetail.error.reload=Neu laden
main.vaultDetail.error.windowTitle=Fehler beim Laden des Tresors
# Wrong File Alert
@@ -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 …
@@ -343,7 +346,7 @@ vaultOptions.mount.mountPoint.directoryPickerTitle=Wähle ein leeres Verzeichnis
vaultOptions.masterkey=Passwort
vaultOptions.masterkey.changePasswordBtn=Passwort ändern
vaultOptions.masterkey.forgetSavedPasswordBtn=Gespeichertes Passwort vergessen
vaultOptions.masterkey.recoveryKeyExplanation=Dein Wiederherstellungsschlüssel gehört nur dir! Dieser wird benötigt, um deinen Tresor wiederherzustellen, für den Fall das du dein Passwort verloren hast.
vaultOptions.masterkey.recoveryKeyExplanation=Ein Wiederherstellungsschlüssel ist bei Passwortverlust deine einzige Möglichkeit, den Zugriff auf einen Tresor wiederherzustellen.
vaultOptions.masterkey.showRecoveryKeyBtn=Wiederherstellungsschlüssel anzeigen
vaultOptions.masterkey.recoverPasswordBtn=Passwort wiederherstellen
@@ -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
+25 -22
View File
@@ -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
@@ -61,7 +61,8 @@ addvaultwizard.existing.chooseBtn=Mamili…
# Unlock
unlock.unlockBtn=I-unlock
##
## Select
unlock.chooseMasterkey.chooseBtn=Mamili…
## Success
## Failure
### Invalid Mount Point
@@ -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
@@ -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
+54 -4
View File
@@ -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
@@ -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=आपके वॉल्ट की चीजें यहाँ एक्सेस कर सकतें हैं:
@@ -25,7 +25,7 @@
# Forget Password
# Unlock
##
## Select
## Success
## Failure
### Invalid Mount Point
@@ -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.
+105 -102
View File
@@ -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
@@ -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.
+20 -17
View File
@@ -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
@@ -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를 가상드라이브로 접근할 수 있습니다.
@@ -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
@@ -25,7 +25,7 @@
# Forget Password
# Unlock
##
## Select
## Success
## Failure
### Invalid Mount Point
+24 -12
View File
@@ -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
@@ -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
@@ -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

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