Compare commits

..
Author SHA1 Message Date
JaniruTEC 9601263645 Added an additional layer of abstraction to error reports
Fixed var names
Added abstract base class for controllers
Added base interface for error components
Added base interface for error component builders
Generified scene providers
Made GenericError classes and InvalidMountPointException classes depend on those base interfaces/classes
2021-04-11 02:54:21 +02:00
JaniruTEC 415cb78d87 Renamed errorComponent vars to genericErrorBuilder 2021-04-11 02:25:16 +02:00
JaniruTEC 4f43463649 Renamed files/constants used by the generic error report 2021-04-11 02:25:15 +02:00
JaniruTEC dc1f313082 Moved error report related files to new package 2021-04-11 01:55:35 +02:00
96 changed files with 746 additions and 1086 deletions
-10
View File
@@ -92,16 +92,6 @@ jobs:
* [ ] add Linux appimage, zsync file and signature file
* [ ] add Windows installer and signature file
* [ ] add MacOs disk image and signature file
## What's new
## Bugfixes
## Misc
---
:scroll: A complete list of closed issues is available [here](LINK)
draft: true
prerelease: false
- name: Upload buildkit-linux.zip to GitHub Releases
+1 -1
View File
@@ -48,7 +48,7 @@ Download native binaries of Cryptomator on [cryptomator.org](https://cryptomator
## Features
- Works with Dropbox, Google Drive, OneDrive, MEGA, pCloud, ownCloud, Nextcloud and any other cloud storage service which synchronizes with a local directory
- Works with Dropbox, Google Drive, OneDrive, ownCloud, Nextcloud and any other cloud storage service which synchronizes with a local directory
- Open Source means: No backdoors, control is better than trust
- Client-side: No accounts, no data shared with any online service
- Totally transparent: Just work on the virtual drive as if it were a USB flash drive
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.5.16</version>
<version>1.6.0-SNAPSHOT</version>
</parent>
<artifactId>buildkit</artifactId>
<packaging>pom</packaging>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.5.16</version>
<version>1.6.0-SNAPSHOT</version>
</parent>
<artifactId>commons</artifactId>
<name>Cryptomator Commons</name>
@@ -5,15 +5,15 @@ import org.cryptomator.common.mountpoint.MountPointChooser;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.common.settings.VolumeImpl;
import org.cryptomator.cryptofs.CryptoFileSystem;
import org.cryptomator.frontend.dokany.DokanyMountFailedException;
import org.cryptomator.frontend.dokany.Mount;
import org.cryptomator.frontend.dokany.MountFactory;
import org.cryptomator.frontend.dokany.MountFailedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import java.util.function.Consumer;
import java.util.concurrent.ExecutorService;
public class DokanyVolume extends AbstractVolume {
@@ -22,13 +22,15 @@ public class DokanyVolume extends AbstractVolume {
private static final String FS_TYPE_NAME = "CryptomatorFS";
private final VaultSettings vaultSettings;
private final MountFactory mountFactory;
private Mount mount;
@Inject
public DokanyVolume(VaultSettings vaultSettings, @Named("orderedMountPointChoosers") Iterable<MountPointChooser> choosers) {
public DokanyVolume(VaultSettings vaultSettings, ExecutorService executorService, @Named("orderedMountPointChoosers") Iterable<MountPointChooser> choosers) {
super(choosers);
this.vaultSettings = vaultSettings;
this.mountFactory = new MountFactory(executorService);
}
@Override
@@ -37,11 +39,11 @@ public class DokanyVolume extends AbstractVolume {
}
@Override
public void mount(CryptoFileSystem fs, String mountFlags, Consumer<Throwable> onExitAction) throws InvalidMountPointException, VolumeException {
public void mount(CryptoFileSystem fs, String mountFlags) throws InvalidMountPointException, VolumeException {
this.mountPoint = determineMountPoint();
try {
this.mount = MountFactory.mount(fs.getPath("/"), mountPoint, vaultSettings.mountName().get(), FS_TYPE_NAME, mountFlags.strip(), onExitAction);
} catch (DokanyMountFailedException e) {
this.mount = mountFactory.mount(fs.getPath("/"), mountPoint, vaultSettings.mountName().get(), FS_TYPE_NAME, mountFlags.strip());
} catch (MountFailedException e) {
if (vaultSettings.getCustomMountPath().isPresent()) {
LOG.warn("Failed to mount vault into {}. Is this directory currently accessed by another process (e.g. Windows Explorer)?", mountPoint);
}
@@ -6,8 +6,8 @@ import org.cryptomator.common.mountpoint.InvalidMountPointException;
import org.cryptomator.common.mountpoint.MountPointChooser;
import org.cryptomator.common.settings.VolumeImpl;
import org.cryptomator.cryptofs.CryptoFileSystem;
import org.cryptomator.frontend.fuse.mount.CommandFailedException;
import org.cryptomator.frontend.fuse.mount.EnvironmentVariables;
import org.cryptomator.frontend.fuse.mount.FuseMountException;
import org.cryptomator.frontend.fuse.mount.FuseMountFactory;
import org.cryptomator.frontend.fuse.mount.FuseNotSupportedException;
import org.cryptomator.frontend.fuse.mount.Mount;
@@ -20,7 +20,6 @@ import javax.inject.Named;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.regex.Pattern;
public class FuseVolume extends AbstractVolume {
@@ -36,12 +35,13 @@ public class FuseVolume extends AbstractVolume {
}
@Override
public void mount(CryptoFileSystem fs, String mountFlags, Consumer<Throwable> onExitAction) throws InvalidMountPointException, VolumeException {
public void mount(CryptoFileSystem fs, String mountFlags) throws InvalidMountPointException, VolumeException {
this.mountPoint = determineMountPoint();
mount(fs.getPath("/"), mountFlags, onExitAction);
mount(fs.getPath("/"), mountFlags);
}
private void mount(Path root, String mountFlags, Consumer<Throwable> onExitAction) throws VolumeException {
private void mount(Path root, String mountFlags) throws VolumeException {
try {
Mounter mounter = FuseMountFactory.getMounter();
EnvironmentVariables envVars = EnvironmentVariables.create() //
@@ -49,8 +49,8 @@ public class FuseVolume extends AbstractVolume {
.withMountPoint(mountPoint) //
.withFileNameTranscoder(mounter.defaultFileNameTranscoder()) //
.build();
this.mount = mounter.mount(root, envVars, onExitAction);
} catch ( FuseMountException | FuseNotSupportedException e) {
this.mount = mounter.mount(root, envVars);
} catch (CommandFailedException | FuseNotSupportedException e) {
throw new VolumeException("Unable to mount Filesystem", e);
}
}
@@ -91,7 +91,8 @@ public class FuseVolume extends AbstractVolume {
public synchronized void unmountForced() throws VolumeException {
try {
mount.unmountForced();
} catch (FuseMountException e) {
mount.close();
} catch (CommandFailedException e) {
throw new VolumeException(e);
}
cleanupMountPoint();
@@ -101,7 +102,8 @@ public class FuseVolume extends AbstractVolume {
public synchronized void unmount() throws VolumeException {
try {
mount.unmount();
} catch (FuseMountException e) {
mount.close();
} catch (CommandFailedException e) {
throw new VolumeException(e);
}
cleanupMountPoint();
@@ -1,12 +0,0 @@
package org.cryptomator.common.vaults;
public class LockNotCompletedException extends Exception {
public LockNotCompletedException(String reason) {
super(reason);
}
public LockNotCompletedException(Throwable cause) {
super(cause);
}
}
@@ -42,7 +42,6 @@ import java.util.EnumSet;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
@@ -57,7 +56,7 @@ public class Vault {
private final Provider<Volume> volumeProvider;
private final StringBinding defaultMountFlags;
private final AtomicReference<CryptoFileSystem> cryptoFileSystem;
private final VaultState state;
private final ObjectProperty<VaultState> state;
private final ObjectProperty<Exception> lastKnownException;
private final VaultStats stats;
private final StringBinding displayName;
@@ -75,7 +74,7 @@ public class Vault {
private volatile Volume volume;
@Inject
Vault(VaultSettings vaultSettings, Provider<Volume> volumeProvider, @DefaultMountFlags StringBinding defaultMountFlags, AtomicReference<CryptoFileSystem> cryptoFileSystem, VaultState state, @Named("lastKnownException") ObjectProperty<Exception> lastKnownException, VaultStats stats) {
Vault(VaultSettings vaultSettings, Provider<Volume> volumeProvider, @DefaultMountFlags StringBinding defaultMountFlags, AtomicReference<CryptoFileSystem> cryptoFileSystem, ObjectProperty<VaultState> state, @Named("lastKnownException") ObjectProperty<Exception> lastKnownException, VaultStats stats) {
this.vaultSettings = vaultSettings;
this.volumeProvider = volumeProvider;
this.defaultMountFlags = defaultMountFlags;
@@ -105,27 +104,19 @@ public class Vault {
if (vaultSettings.usesReadOnlyMode().get()) {
flags.add(FileSystemFlags.READONLY);
}
int usedFilenameLengthLimit;
var fileSystemCapabilityChecker = new FileSystemCapabilityChecker();
if (flags.contains(FileSystemFlags.READONLY)) {
usedFilenameLengthLimit = Constants.MAX_CIPHERTEXT_NAME_LENGTH;
} else if (vaultSettings.filenameLengthLimit().get() == -1) {
if (!flags.contains(FileSystemFlags.READONLY) && vaultSettings.filenameLengthLimit().get() == -1) {
LOG.debug("Determining file name length limitations...");
usedFilenameLengthLimit = fileSystemCapabilityChecker.determineSupportedFileNameLength(getPath());
vaultSettings.filenameLengthLimit().set(usedFilenameLengthLimit);
LOG.info("Storing file name length limit of {}", usedFilenameLengthLimit);
} else {
usedFilenameLengthLimit = vaultSettings.filenameLengthLimit().get();
int limit = new FileSystemCapabilityChecker().determineSupportedFileNameLength(getPath());
vaultSettings.filenameLengthLimit().set(limit);
LOG.info("Storing file name length limit of {}", limit);
}
assert usedFilenameLengthLimit > 0;
assert vaultSettings.filenameLengthLimit().get() > 0;
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties() //
.withPassphrase(passphrase) //
.withFlags(flags) //
.withMasterkeyFilename(MASTERKEY_FILENAME) //
.withMaxPathLength(vaultSettings.filenameLengthLimit().get() + Constants.MAX_ADDITIONAL_PATH_LENGTH) //
.withMaxNameLength(usedFilenameLengthLimit) //
.withMaxNameLength(vaultSettings.filenameLengthLimit().get()) //
.build();
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
}
@@ -148,7 +139,7 @@ public class Vault {
cryptoFileSystem.set(fs);
try {
volume = volumeProvider.get();
volume.mount(fs, getEffectiveMountFlags(), this::lockOnVolumeExit);
volume.mount(fs, getEffectiveMountFlags());
} catch (Exception e) {
destroyCryptoFileSystem();
throw e;
@@ -158,33 +149,13 @@ public class Vault {
}
}
private void lockOnVolumeExit(Throwable t) {
LOG.info("Unmounted vault '{}'", getDisplayName());
destroyCryptoFileSystem();
state.set(VaultState.Value.LOCKED);
if (t != null) {
LOG.warn("Unexpected unmount and lock of vault " + getDisplayName(), t);
}
}
public synchronized void lock(boolean forced) throws VolumeException, LockNotCompletedException {
//initiate unmount
public synchronized void lock(boolean forced) throws VolumeException {
if (forced && volume.supportsForcedUnmount()) {
volume.unmountForced();
} else {
volume.unmount();
}
//wait for lockOnVolumeExit to be executed
try {
boolean locked = state.awaitState(VaultState.Value.LOCKED, 3000, TimeUnit.MILLISECONDS);
if (!locked) {
throw new LockNotCompletedException("Locking of vault " + this.getDisplayName() + " still in progress.");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new LockNotCompletedException(e);
}
destroyCryptoFileSystem();
}
public void reveal(Volume.Revealer vaultRevealer) throws VolumeException {
@@ -195,12 +166,16 @@ public class Vault {
// Observable Properties
// *******************************************************************************
public VaultState stateProperty() {
public ObjectProperty<VaultState> stateProperty() {
return state;
}
public VaultState.Value getState() {
return state.getValue();
public VaultState getState() {
return state.get();
}
public void setState(VaultState value) {
state.setValue(value);
}
public ObjectProperty<Exception> lastKnownExceptionProperty() {
@@ -220,7 +195,7 @@ public class Vault {
}
public boolean isLocked() {
return state.get() == VaultState.Value.LOCKED;
return state.get() == VaultState.LOCKED;
}
public BooleanBinding processingProperty() {
@@ -228,7 +203,7 @@ public class Vault {
}
public boolean isProcessing() {
return state.get() == VaultState.Value.PROCESSING;
return state.get() == VaultState.PROCESSING;
}
public BooleanBinding unlockedProperty() {
@@ -236,7 +211,7 @@ public class Vault {
}
public boolean isUnlocked() {
return state.get() == VaultState.Value.UNLOCKED;
return state.get() == VaultState.UNLOCKED;
}
public BooleanBinding missingProperty() {
@@ -244,7 +219,7 @@ public class Vault {
}
public boolean isMissing() {
return state.get() == VaultState.Value.MISSING;
return state.get() == VaultState.MISSING;
}
public BooleanBinding needsMigrationProperty() {
@@ -252,7 +227,7 @@ public class Vault {
}
public boolean isNeedsMigration() {
return state.get() == VaultState.Value.NEEDS_MIGRATION;
return state.get() == VaultState.NEEDS_MIGRATION;
}
public BooleanBinding unknownErrorProperty() {
@@ -260,7 +235,7 @@ public class Vault {
}
public boolean isUnknownError() {
return state.get() == VaultState.Value.ERROR;
return state.get() == VaultState.ERROR;
}
public StringBinding displayNameProperty() {
@@ -276,7 +251,7 @@ public class Vault {
}
public String getAccessPoint() {
if (state.getValue() == VaultState.Value.UNLOCKED) {
if (state.get() == VaultState.UNLOCKED) {
assert volume != null;
return volume.getMountPoint().orElse(Path.of("")).toString();
} else {
@@ -26,7 +26,7 @@ public interface VaultComponent {
Builder vaultSettings(VaultSettings vaultSettings);
@BindsInstance
Builder initialVaultState(VaultState.Value vaultState);
Builder initialVaultState(VaultState vaultState);
@BindsInstance
Builder initialErrorCause(@Nullable @Named("lastKnownException") Exception initialErrorCause);
@@ -25,6 +25,7 @@ import java.nio.file.Path;
import java.util.Collection;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.stream.Collectors;
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
@@ -93,43 +94,42 @@ public class VaultListManager {
private Vault create(VaultSettings vaultSettings) {
VaultComponent.Builder compBuilder = vaultComponentBuilder.vaultSettings(vaultSettings);
try {
VaultState.Value vaultState = determineVaultState(vaultSettings.path().get());
VaultState vaultState = determineVaultState(vaultSettings.path().get());
compBuilder.initialVaultState(vaultState);
} catch (IOException e) {
LOG.warn("Failed to determine vault state for " + vaultSettings.path().get(), e);
compBuilder.initialVaultState(VaultState.Value.ERROR);
compBuilder.initialVaultState(VaultState.ERROR);
compBuilder.initialErrorCause(e);
}
return compBuilder.build().vault();
}
public static VaultState.Value redetermineVaultState(Vault vault) {
VaultState state = vault.stateProperty();
VaultState.Value previousState = state.getValue();
public static VaultState redetermineVaultState(Vault vault) {
VaultState previousState = vault.getState();
return switch (previousState) {
case LOCKED, NEEDS_MIGRATION, MISSING -> {
try {
VaultState.Value determinedState = determineVaultState(vault.getPath());
state.set(determinedState);
VaultState determinedState = determineVaultState(vault.getPath());
vault.setState(determinedState);
yield determinedState;
} catch (IOException e) {
LOG.warn("Failed to determine vault state for " + vault.getPath(), e);
state.set(VaultState.Value.ERROR);
vault.setState(VaultState.ERROR);
vault.setLastKnownException(e);
yield VaultState.Value.ERROR;
yield VaultState.ERROR;
}
}
case ERROR, UNLOCKED, PROCESSING -> previousState;
};
}
private static VaultState.Value determineVaultState(Path pathToVault) throws IOException {
private static VaultState determineVaultState(Path pathToVault) throws IOException {
if (!CryptoFileSystemProvider.containsVault(pathToVault, MASTERKEY_FILENAME)) {
return VaultState.Value.MISSING;
return VaultState.MISSING;
} else if (Migrators.get().needsMigration(pathToVault, MASTERKEY_FILENAME)) {
return VaultState.Value.NEEDS_MIGRATION;
return VaultState.NEEDS_MIGRATION;
} else {
return VaultState.Value.LOCKED;
return VaultState.LOCKED;
}
}
@@ -40,6 +40,12 @@ public class VaultModule {
return new AtomicReference<>();
}
@Provides
@PerVault
public ObjectProperty<VaultState> provideVaultState(VaultState initialState) {
return new SimpleObjectProperty<>(initialState);
}
@Provides
@Named("lastKnownException")
@PerVault
@@ -1,141 +1,34 @@
package org.cryptomator.common.vaults;
import com.google.common.base.Preconditions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.application.Platform;
import javafx.beans.value.ObservableObjectValue;
import javafx.beans.value.ObservableValueBase;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@PerVault
public class VaultState extends ObservableValueBase<VaultState.Value> implements ObservableObjectValue<VaultState.Value> {
private static final Logger LOG = LoggerFactory.getLogger(VaultState.class);
public enum Value {
/**
* No vault found at the provided path
*/
MISSING,
/**
* Vault requires migration to a newer vault format
*/
NEEDS_MIGRATION,
/**
* Vault ready to be unlocked
*/
LOCKED,
/**
* Vault in transition between two other states
*/
PROCESSING,
/**
* Vault is unlocked
*/
UNLOCKED,
/**
* Unknown state due to preceeding unrecoverable exceptions.
*/
ERROR;
}
private final AtomicReference<Value> value;
private final Lock lock = new ReentrantLock();
private final Condition valueChanged = lock.newCondition();
@Inject
public VaultState(VaultState.Value initialValue) {
this.value = new AtomicReference<>(initialValue);
}
@Override
public Value get() {
return getValue();
}
@Override
public Value getValue() {
return value.get();
}
public enum VaultState {
/**
* No vault found at the provided path
*/
MISSING,
/**
* Transitions from <code>fromState</code> to <code>toState</code>.
*
* @param fromState Previous state
* @param toState New state
* @return <code>true</code> if successful
* Vault requires migration to a newer vault format
*/
public boolean transition(Value fromState, Value toState) {
Preconditions.checkArgument(fromState != toState, "fromState must be different than toState");
boolean success = value.compareAndSet(fromState, toState);
if (success) {
fireValueChangedEvent();
} else {
LOG.debug("Failed transiting into state {}: Expected state was not{}.", fromState, toState);
}
return success;
}
public void set(Value newState) {
var oldState = value.getAndSet(newState);
if (oldState != newState) {
fireValueChangedEvent();
}
}
NEEDS_MIGRATION,
/**
* Waits for the specified time, until the desired state is reached.
*
* @param desiredState what state to wait for
* @param time the maximum time to wait
* @param unit the time unit of the {@code time} argument
* @return {@code false} if the waiting time detectably elapsed before reaching {@code desiredState}
* @throws InterruptedException if the current thread is interrupted
* Vault ready to be unlocked
*/
public boolean awaitState(Value desiredState, long time, TimeUnit unit) throws InterruptedException {
lock.lock();
try {
long remaining = TimeUnit.NANOSECONDS.convert(time, unit);
while (value.get() != desiredState) {
if (remaining <= 0L) {
return false;
}
remaining = valueChanged.awaitNanos(remaining);
}
return true;
} finally {
lock.unlock();
}
}
LOCKED,
private void signal() {
lock.lock();
try {
valueChanged.signalAll();
} finally {
lock.unlock();
}
}
/**
* Vault in transition between two other states
*/
PROCESSING,
/**
* Vault is unlocked
*/
UNLOCKED,
/**
* Unknown state due to preceeding unrecoverable exceptions.
*/
ERROR;
@Override
protected void fireValueChangedEvent() {
signal();
if (Platform.isFxApplicationThread()) {
super.fireValueChangedEvent();
} else {
Platform.runLater(super::fireValueChangedEvent);
}
}
}
@@ -26,7 +26,7 @@ public class VaultStats {
private static final Logger LOG = LoggerFactory.getLogger(VaultStats.class);
private final AtomicReference<CryptoFileSystem> fs;
private final VaultState state;
private final ObjectProperty<VaultState> state;
private final ScheduledService<Optional<CryptoFileSystemStats>> updateService;
private final LongProperty bytesPerSecondRead = new SimpleLongProperty();
private final LongProperty bytesPerSecondWritten = new SimpleLongProperty();
@@ -41,7 +41,7 @@ public class VaultStats {
private final LongProperty filesWritten = new SimpleLongProperty();
@Inject
VaultStats(AtomicReference<CryptoFileSystem> fs, VaultState state, ExecutorService executor) {
VaultStats(AtomicReference<CryptoFileSystem> fs, ObjectProperty<VaultState> state, ExecutorService executor) {
this.fs = fs;
this.state = state;
this.updateService = new UpdateStatsService();
@@ -52,13 +52,13 @@ public class VaultStats {
}
private void vaultStateChanged(@SuppressWarnings("unused") Observable observable) {
if (VaultState.Value.UNLOCKED == state.get()) {
if (VaultState.UNLOCKED.equals(state.get())) {
assert fs.get() != null;
LOG.debug("start recording stats");
Platform.runLater(() -> updateService.restart());
updateService.restart();
} else {
LOG.debug("stop recording stats");
Platform.runLater(() -> updateService.cancel());
updateService.cancel();
}
}
@@ -7,8 +7,6 @@ import org.cryptomator.cryptofs.CryptoFileSystem;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Optional;
import java.util.concurrent.CompletionStage;
import java.util.function.Consumer;
import java.util.stream.Stream;
/**
@@ -34,7 +32,7 @@ public interface Volume {
* @param fs
* @throws IOException
*/
void mount(CryptoFileSystem fs, String mountFlags, Consumer<Throwable> onExitAction) throws IOException, VolumeException, InvalidMountPointException;
void mount(CryptoFileSystem fs, String mountFlags) throws IOException, VolumeException, InvalidMountPointException;
/**
* Reveals the mounted volume.
@@ -17,7 +17,6 @@ import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.file.Path;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class WebDavVolume implements Volume {
@@ -32,7 +31,6 @@ public class WebDavVolume implements Volume {
private WebDavServer server;
private WebDavServletController servlet;
private Mounter.Mount mount;
private Consumer<Throwable> onExitAction;
@Inject
public WebDavVolume(Provider<WebDavServer> serverProvider, VaultSettings vaultSettings, Settings settings, WindowsDriveLetters windowsDriveLetters) {
@@ -43,13 +41,12 @@ public class WebDavVolume implements Volume {
}
@Override
public void mount(CryptoFileSystem fs, String mountFlags, Consumer<Throwable> onExitAction) throws VolumeException {
public void mount(CryptoFileSystem fs, String mountFlags) throws VolumeException {
startServlet(fs);
mountServlet();
this.onExitAction = onExitAction;
}
private void startServlet(CryptoFileSystem fs) {
private void startServlet(CryptoFileSystem fs){
if (server == null) {
server = serverProvider.get();
}
@@ -69,7 +66,7 @@ public class WebDavVolume implements Volume {
//on windows, prevent an automatic drive letter selection in the upstream library. Either we choose already a specifc one or there is no free.
Supplier<String> driveLetterSupplier;
if (System.getProperty("os.name").toLowerCase().contains("windows") && vaultSettings.winDriveLetter().isEmpty().get()) {
if(System.getProperty("os.name").toLowerCase().contains("windows") && vaultSettings.winDriveLetter().isEmpty().get()) {
driveLetterSupplier = () -> windowsDriveLetters.getAvailableDriveLetter().orElse(null);
} else {
driveLetterSupplier = () -> vaultSettings.winDriveLetter().get();
@@ -104,7 +101,6 @@ public class WebDavVolume implements Volume {
throw new VolumeException(e);
}
cleanup();
onExitAction.accept(null);
}
@Override
@@ -115,7 +111,6 @@ public class WebDavVolume implements Volume {
throw new VolumeException(e);
}
cleanup();
onExitAction.accept(null);
}
@Override
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.5.16</version>
<version>1.6.0-SNAPSHOT</version>
</parent>
<artifactId>launcher</artifactId>
<name>Cryptomator Launcher</name>
+4 -4
View File
@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.5.16</version>
<version>1.6.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Cryptomator</name>
@@ -30,12 +30,12 @@
<cryptomator.integrations.win.version>1.0.0-beta2</cryptomator.integrations.win.version>
<cryptomator.integrations.mac.version>1.0.0-beta2</cryptomator.integrations.mac.version>
<cryptomator.integrations.linux.version>1.0.0-beta1</cryptomator.integrations.linux.version>
<cryptomator.fuse.version>1.3.1</cryptomator.fuse.version>
<cryptomator.dokany.version>1.3.1</cryptomator.dokany.version>
<cryptomator.fuse.version>1.3.0</cryptomator.fuse.version>
<cryptomator.dokany.version>1.2.4</cryptomator.dokany.version>
<cryptomator.webdav.version>1.2.0</cryptomator.webdav.version>
<!-- 3rd party dependencies -->
<javafx.version>16</javafx.version>
<javafx.version>15</javafx.version>
<commons-lang3.version>3.11</commons-lang3.version>
<jwt.version>3.13.0</jwt.version>
<easybind.version>2.1.0</easybind.version>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.5.16</version>
<version>1.6.0-SNAPSHOT</version>
</parent>
<artifactId>ui</artifactId>
<name>Cryptomator GUI</name>
@@ -4,7 +4,7 @@ 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.error.GenericErrorComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
@@ -34,7 +34,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 GenericErrorComponent.Builder genericErrorBuilder;
private final ObjectProperty<Path> vaultPath;
private final ObjectProperty<Vault> vault;
private final VaultListManager vaultListManager;
@@ -43,11 +43,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, GenericErrorComponent.Builder genericErrorBuilder, ObjectProperty<Path> vaultPath, @AddVaultWizardWindow ObjectProperty<Vault> vault, VaultListManager vaultListManager, ResourceBundle resourceBundle) {
this.window = window;
this.welcomeScene = welcomeScene;
this.successScene = successScene;
this.errorComponent = errorComponent;
this.genericErrorBuilder = genericErrorBuilder;
this.vaultPath = vaultPath;
this.vault = vault;
this.vaultListManager = vaultListManager;
@@ -83,7 +83,7 @@ public class ChooseExistingVaultController implements FxController {
window.setScene(successScene.get());
} catch (NoSuchFileException e) {
LOG.error("Failed to open existing vault.", e);
errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
genericErrorBuilder.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
}
}
}
@@ -1,10 +1,10 @@
package org.cryptomator.ui.addvaultwizard;
import dagger.Lazy;
import org.cryptomator.ui.error.GenericErrorComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.controls.FontAwesome5IconView;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -15,21 +15,21 @@ import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.stage.DirectoryChooser;
import javafx.stage.Stage;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ResourceBundle;
@@ -43,74 +43,55 @@ public class CreateNewVaultLocationController implements FxController {
private final Stage window;
private final Lazy<Scene> chooseNameScene;
private final Lazy<Scene> choosePasswordScene;
private final GenericErrorComponent.Builder genericErrorBuilder;
private final LocationPresets locationPresets;
private final ObjectProperty<Path> vaultPath;
private final StringProperty vaultName;
private final ResourceBundle resourceBundle;
private final BooleanBinding validVaultPath;
private final BooleanProperty usePresetPath;
private final StringProperty statusText;
private final ObjectProperty<Node> statusGraphic;
private final StringProperty warningText;
private Path customVaultPath = DEFAULT_CUSTOM_VAULT_PATH;
//FXML
public ToggleGroup predefinedLocationToggler;
public RadioButton iclouddriveRadioButton;
public RadioButton dropboxRadioButton;
public RadioButton gdriveRadioButton;
public RadioButton onedriveRadioButton;
public RadioButton megaRadioButton;
public RadioButton pcloudRadioButton;
public RadioButton customRadioButton;
public Label vaultPathStatus;
public FontAwesome5IconView goodLocation;
public FontAwesome5IconView badLocation;
@Inject
CreateNewVaultLocationController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_NAME) Lazy<Scene> chooseNameScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_PASSWORD) Lazy<Scene> choosePasswordScene, LocationPresets locationPresets, ObjectProperty<Path> vaultPath, @Named("vaultName") StringProperty vaultName, ResourceBundle resourceBundle) {
CreateNewVaultLocationController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_NAME) Lazy<Scene> chooseNameScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_PASSWORD) Lazy<Scene> choosePasswordScene, GenericErrorComponent.Builder genericErrorBuilder, LocationPresets locationPresets, ObjectProperty<Path> vaultPath, @Named("vaultName") StringProperty vaultName, ResourceBundle resourceBundle) {
this.window = window;
this.chooseNameScene = chooseNameScene;
this.choosePasswordScene = choosePasswordScene;
this.genericErrorBuilder = genericErrorBuilder;
this.locationPresets = locationPresets;
this.vaultPath = vaultPath;
this.vaultName = vaultName;
this.resourceBundle = resourceBundle;
this.validVaultPath = Bindings.createBooleanBinding(this::validateVaultPathAndSetStatus, this.vaultPath);
this.validVaultPath = Bindings.createBooleanBinding(this::isValidVaultPath, vaultPath);
this.usePresetPath = new SimpleBooleanProperty();
this.statusText = new SimpleStringProperty();
this.statusGraphic = new SimpleObjectProperty<>();
this.warningText = new SimpleStringProperty();
}
private boolean validateVaultPathAndSetStatus() {
final Path p = vaultPath.get();
if (p == null) {
statusText.set("Error: Path is NULL.");
statusGraphic.set(badLocation);
return false;
} else if (!Files.exists(p.getParent())) {
statusText.set(resourceBundle.getString("addvaultwizard.new.locationDoesNotExist"));
statusGraphic.set(badLocation);
return false;
} else if (!Files.isWritable(p.getParent())) {
statusText.set(resourceBundle.getString("addvaultwizard.new.locationIsNotWritable"));
statusGraphic.set(badLocation);
return false;
} else if (!Files.notExists(p)) {
statusText.set(resourceBundle.getString("addvaultwizard.new.fileAlreadyExists"));
statusGraphic.set(badLocation);
return false;
} else {
statusText.set(resourceBundle.getString("addvaultwizard.new.locationIsOk"));
statusGraphic.set(goodLocation);
return true;
}
private boolean isValidVaultPath() {
return vaultPath.get() != null && Files.notExists(vaultPath.get());
}
@FXML
public void initialize() {
predefinedLocationToggler.selectedToggleProperty().addListener(this::togglePredefinedLocation);
usePresetPath.bind(predefinedLocationToggler.selectedToggleProperty().isNotEqualTo(customRadioButton));
vaultPath.addListener(this::vaultPathDidChange);
}
private void vaultPathDidChange(@SuppressWarnings("unused") ObservableValue<? extends Path> observable, @SuppressWarnings("unused") Path oldValue, Path newValue) {
if (!Files.notExists(newValue)) {
warningText.set(resourceBundle.getString("addvaultwizard.new.fileAlreadyExists"));
} else {
warningText.set(null);
}
}
private void togglePredefinedLocation(@SuppressWarnings("unused") ObservableValue<? extends Toggle> observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) {
@@ -122,10 +103,6 @@ public class CreateNewVaultLocationController implements FxController {
vaultPath.set(locationPresets.getGdriveLocation().resolve(vaultName.get()));
} else if (onedriveRadioButton.equals(newValue)) {
vaultPath.set(locationPresets.getOnedriveLocation().resolve(vaultName.get()));
} else if (megaRadioButton.equals(newValue)) {
vaultPath.set(locationPresets.getMegaLocation().resolve(vaultName.get()));
} else if (pcloudRadioButton.equals(newValue)) {
vaultPath.set(locationPresets.getPcloudLocation().resolve(vaultName.get()));
} else if (customRadioButton.equals(newValue)) {
vaultPath.set(customVaultPath.resolve(vaultName.get()));
}
@@ -138,10 +115,21 @@ public class CreateNewVaultLocationController implements FxController {
@FXML
public void next() {
if (validateVaultPathAndSetStatus()) {
try {
// check if we have write access AND the vaultPath doesn't already exist:
assert Files.isDirectory(vaultPath.get().getParent());
Path createdDir = Files.createDirectory(vaultPath.get());
Files.delete(createdDir); // assert: dir exists and is empty
window.setScene(choosePasswordScene.get());
} else {
validVaultPath.invalidate();
} catch (FileAlreadyExistsException e) {
LOG.warn("Can not use already existing vault path {}", vaultPath.get());
warningText.set(resourceBundle.getString("addvaultwizard.new.fileAlreadyExists"));
} catch (NoSuchFileException e) {
LOG.warn("At least one path component does not exist of path {}", vaultPath.get());
warningText.set(resourceBundle.getString("addvaultwizard.new.locationDoesNotExist"));
} catch (IOException e) {
LOG.error("Failed to create and delete directory at chosen vault path.", e);
genericErrorBuilder.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
}
}
@@ -191,27 +179,19 @@ public class CreateNewVaultLocationController implements FxController {
return usePresetPath.get();
}
public BooleanBinding anyRadioButtonSelectedProperty() {
return predefinedLocationToggler.selectedToggleProperty().isNotNull();
public StringProperty warningTextProperty() {
return warningText;
}
public boolean isAnyRadioButtonSelected() {
return anyRadioButtonSelectedProperty().get();
public String getWarningText() {
return warningText.get();
}
public StringProperty statusTextProperty() {
return statusText;
public BooleanBinding showWarningProperty() {
return warningText.isNotEmpty();
}
public String getStatusText() {
return statusText.get();
}
public ObjectProperty<Node> statusGraphicProperty() {
return statusGraphic;
}
public Node getStatusGraphic() {
return statusGraphic.get();
public boolean isShowWarning() {
return showWarningProperty().get();
}
}
@@ -5,7 +5,7 @@ import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.error.GenericErrorComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
@@ -53,7 +53,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 GenericErrorComponent.Builder genericErrorBuilder;
private final ExecutorService executor;
private final RecoveryKeyFactory recoveryKeyFactory;
private final StringProperty vaultNameProperty;
@@ -73,12 +73,12 @@ public class CreateNewVaultPasswordController implements FxController {
public Toggle skipRecoveryKey;
@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, @Named("newPassword") ObjectProperty<CharSequence> password, ReadmeGenerator readmeGenerator) {
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, GenericErrorComponent.Builder genericErrorBuilder, ExecutorService executor, RecoveryKeyFactory recoveryKeyFactory, @Named("vaultName") StringProperty vaultName, ObjectProperty<Path> vaultPath, @AddVaultWizardWindow ObjectProperty<Vault> vault, @Named("recoveryKey") StringProperty recoveryKey, VaultListManager vaultListManager, ResourceBundle resourceBundle, @Named("newPassword") ObjectProperty<CharSequence> password, ReadmeGenerator readmeGenerator) {
this.window = window;
this.chooseLocationScene = chooseLocationScene;
this.recoveryKeyScene = recoveryKeyScene;
this.successScene = successScene;
this.errorComponent = errorComponent;
this.genericErrorBuilder = genericErrorBuilder;
this.executor = executor;
this.recoveryKeyFactory = recoveryKeyFactory;
this.vaultNameProperty = vaultName;
@@ -113,7 +113,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();
genericErrorBuilder.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
return;
}
@@ -138,7 +138,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();
genericErrorBuilder.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
}).andFinally(() -> {
processing.set(false);
}).runOnce(executor);
@@ -154,7 +154,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();
genericErrorBuilder.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
}).andFinally(() -> {
processing.set(false);
}).runOnce(executor);
@@ -16,21 +16,15 @@ public class LocationPresets {
private static final String[] DROPBOX_LOCATIONS = {"~/Dropbox"};
private static final String[] GDRIVE_LOCATIONS = {"~/Google Drive"};
private static final String[] ONEDRIVE_LOCATIONS = {"~/OneDrive"};
private static final String[] MEGA_LOCATIONS = {"~/MEGA"};
private static final String[] PCLOUD_LOCATIONS = {"~/pCloudDrive"};
private final ReadOnlyObjectProperty<Path> iclouddriveLocation;
private final ReadOnlyObjectProperty<Path> dropboxLocation;
private final ReadOnlyObjectProperty<Path> gdriveLocation;
private final ReadOnlyObjectProperty<Path> onedriveLocation;
private final ReadOnlyObjectProperty<Path> megaLocation;
private final ReadOnlyObjectProperty<Path> pcloudLocation;
private final BooleanBinding foundIclouddrive;
private final BooleanBinding foundDropbox;
private final BooleanBinding foundGdrive;
private final BooleanBinding foundOnedrive;
private final BooleanBinding foundMega;
private final BooleanBinding foundPcloud;
@Inject
public LocationPresets() {
@@ -38,14 +32,10 @@ public class LocationPresets {
this.dropboxLocation = new SimpleObjectProperty<>(existingWritablePath(DROPBOX_LOCATIONS));
this.gdriveLocation = new SimpleObjectProperty<>(existingWritablePath(GDRIVE_LOCATIONS));
this.onedriveLocation = new SimpleObjectProperty<>(existingWritablePath(ONEDRIVE_LOCATIONS));
this.megaLocation = new SimpleObjectProperty<>(existingWritablePath(MEGA_LOCATIONS));
this.pcloudLocation = new SimpleObjectProperty<>(existingWritablePath(PCLOUD_LOCATIONS));
this.foundIclouddrive = iclouddriveLocation.isNotNull();
this.foundDropbox = dropboxLocation.isNotNull();
this.foundGdrive = gdriveLocation.isNotNull();
this.foundOnedrive = onedriveLocation.isNotNull();
this.foundMega = megaLocation.isNotNull();
this.foundPcloud = pcloudLocation.isNotNull();
}
private static Path existingWritablePath(String... candidates) {
@@ -132,36 +122,4 @@ public class LocationPresets {
return foundOnedrive.get();
}
public ReadOnlyObjectProperty<Path> megaLocationProperty() {
return megaLocation;
}
public Path getMegaLocation() {
return megaLocation.get();
}
public BooleanBinding foundMegaProperty() {
return foundMega;
}
public boolean isFoundMega() {
return foundMega.get();
}
public ReadOnlyObjectProperty<Path> pcloudLocationProperty() {
return pcloudLocation;
}
public Path getPcloudLocation() {
return pcloudLocation.get();
}
public BooleanBinding foundPcloudProperty() {
return foundPcloud;
}
public boolean isFoundPcloud() {
return foundPcloud.get();
}
}
@@ -6,7 +6,7 @@ import org.cryptomator.cryptofs.CryptoFileSystemProvider;
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.error.GenericErrorComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.controls.NiceSecurePasswordField;
import org.slf4j.Logger;
@@ -23,7 +23,6 @@ import javafx.scene.control.CheckBox;
import javafx.stage.Stage;
import java.io.IOException;
import java.nio.CharBuffer;
import java.util.Optional;
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
@@ -35,7 +34,7 @@ public class ChangePasswordController implements FxController {
private final Stage window;
private final Vault vault;
private final ObjectProperty<CharSequence> newPassword;
private final ErrorComponent.Builder errorComponent;
private final GenericErrorComponent.Builder genericErrorBuilder;
private final KeychainManager keychain;
public NiceSecurePasswordField oldPasswordField;
@@ -43,11 +42,11 @@ public class ChangePasswordController implements FxController {
public Button finishButton;
@Inject
public ChangePasswordController(@ChangePasswordWindow Stage window, @ChangePasswordWindow Vault vault, @Named("newPassword") ObjectProperty<CharSequence> newPassword, ErrorComponent.Builder errorComponent, KeychainManager keychain) {
public ChangePasswordController(@ChangePasswordWindow Stage window, @ChangePasswordWindow Vault vault, @Named("newPassword") ObjectProperty<CharSequence> newPassword, GenericErrorComponent.Builder genericErrorBuilder, KeychainManager keychain) {
this.window = window;
this.vault = vault;
this.newPassword = newPassword;
this.errorComponent = errorComponent;
this.genericErrorBuilder = genericErrorBuilder;
this.keychain = keychain;
}
@@ -73,7 +72,7 @@ public class ChangePasswordController implements FxController {
updatePasswordInSystemkeychain();
} catch (IOException e) {
LOG.error("IO error occured during password change. Unable to perform operation.", e);
errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
genericErrorBuilder.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
} catch (InvalidPassphraseException e) {
Animations.createShakeWindowAnimation(window).play();
oldPasswordField.selectAll();
@@ -1,49 +0,0 @@
package org.cryptomator.ui.common;
import dagger.BindsInstance;
import dagger.Subcomponent;
import javax.annotation.Nullable;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.stage.Stage;
@Subcomponent(modules = {ErrorModule.class})
public interface ErrorComponent {
Stage window();
@FxmlScene(FxmlFile.ERROR)
Scene scene();
default void showErrorScene() {
if (Platform.isFxApplicationThread()) {
show();
} else {
Platform.runLater(this::show);
}
}
private void show() {
Stage stage = window();
stage.setScene(scene());
stage.show();
}
@Subcomponent.Builder
interface Builder {
@BindsInstance
Builder cause(Throwable cause);
@BindsInstance
Builder window(Stage window);
@BindsInstance
Builder returnToScene(@Nullable Scene previousScene);
ErrorComponent build();
}
}
@@ -1,44 +0,0 @@
package org.cryptomator.ui.common;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class ErrorController implements FxController {
private final String stackTrace;
private final Scene previousScene;
private final Stage window;
@Inject
ErrorController(@Named("stackTrace") String stackTrace, @Nullable Scene previousScene, Stage window) {
this.stackTrace = stackTrace;
this.previousScene = previousScene;
this.window = window;
}
@FXML
public void back() {
if (previousScene != null) {
window.setScene(previousScene);
}
}
@FXML
public void close() {
window.close();
}
/* Getter/Setter */
public boolean isPreviousScenePresent() {
return previousScene != null;
}
public String getStackTrace() {
return stackTrace;
}
}
@@ -9,8 +9,8 @@ public enum FxmlFile {
ADDVAULT_SUCCESS("/fxml/addvault_success.fxml"), //
ADDVAULT_WELCOME("/fxml/addvault_welcome.fxml"), //
CHANGEPASSWORD("/fxml/changepassword.fxml"), //
ERROR("/fxml/error.fxml"), //
FORGET_PASSWORD("/fxml/forget_password.fxml"), //
GENERIC_ERROR("/fxml/generic_error.fxml"), //
LOCK_FORCED("/fxml/lock_forced.fxml"), //
LOCK_FAILED("/fxml/lock_failed.fxml"), //
MAIN_WINDOW("/fxml/main_window.fxml"), //
@@ -1,6 +1,5 @@
package org.cryptomator.ui.common;
import org.cryptomator.common.vaults.LockNotCompletedException;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.common.vaults.Volume;
@@ -176,29 +175,24 @@ public class VaultService {
}
@Override
protected Vault call() throws Volume.VolumeException, LockNotCompletedException {
protected Vault call() throws Volume.VolumeException {
vault.lock(forced);
return vault;
}
@Override
protected void scheduled() {
vault.stateProperty().transition(VaultState.Value.UNLOCKED, VaultState.Value.PROCESSING);
vault.setState(VaultState.PROCESSING);
}
@Override
protected void succeeded() {
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.LOCKED);
vault.setState(VaultState.LOCKED);
}
@Override
protected void failed() {
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.UNLOCKED);
}
@Override
protected void cancelled() {
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.UNLOCKED);
vault.setState(VaultState.UNLOCKED);
}
}
@@ -0,0 +1,35 @@
package org.cryptomator.ui.error;
import org.cryptomator.ui.common.FxController;
import javax.annotation.Nullable;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class AbstractErrorController implements FxController {
protected final Stage window;
protected final Scene returnScene;
public AbstractErrorController(@ErrorReport Stage window, @ErrorReport @Nullable Scene returnScene) {
this.returnScene = returnScene;
this.window = window;
}
@FXML
public void back() {
if (this.returnScene != null) {
this.window.setScene(this.returnScene);
}
}
@FXML
public void close() {
this.window.close();
}
public boolean isReturnScenePresent() {
return this.returnScene != null;
}
}
@@ -0,0 +1,53 @@
package org.cryptomator.ui.error;
import dagger.BindsInstance;
import javax.annotation.Nullable;
import javax.inject.Named;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.stage.Stage;
public interface ErrorComponentBase {
@ErrorReport
Stage window();
@Named("errorScene")
Scene errorScene();
@ErrorReport
Throwable cause();
@ErrorReport
@Nullable
Scene previousScene();
default void showErrorScene() {
if (Platform.isFxApplicationThread()) {
show();
} else {
Platform.runLater(this::show);
}
}
private void show() {
Stage stage = window();
stage.setScene(errorScene());
stage.show();
}
interface BuilderBase<B, C> {
@BindsInstance
B window(@ErrorReport Stage window);
@BindsInstance
B cause(@ErrorReport Throwable cause);
@BindsInstance
B returnToScene(@ErrorReport @Nullable Scene returnScene);
C build();
}
}
@@ -1,9 +1,11 @@
package org.cryptomator.ui.common;
package org.cryptomator.ui.error;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
import org.cryptomator.ui.common.DefaultSceneFactory;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlLoaderFactory;
import javax.inject.Named;
import javax.inject.Provider;
@@ -24,23 +26,16 @@ abstract class ErrorModule {
@Provides
@Named("stackTrace")
static String provideStackTrace(Throwable cause) {
static String provideStackTrace(@ErrorReport Throwable cause) {
// TODO deduplicate VaultDetailUnknownErrorController.java
ByteArrayOutputStream baos = new ByteArrayOutputStream();
cause.printStackTrace(new PrintStream(baos));
return baos.toString(StandardCharsets.UTF_8);
}
@Binds
@IntoMap
@FxControllerKey(ErrorController.class)
abstract FxController bindErrorController(ErrorController controller);
@Provides
@FxmlScene(FxmlFile.ERROR)
static Scene provideErrorScene(FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.ERROR);
@Named("errorScene")
static Scene provideErrorScene(FxmlLoaderFactory fxmlLoaders, @ErrorReport FxmlFile file) {
return fxmlLoaders.createScene(file);
}
}
@@ -0,0 +1,14 @@
package org.cryptomator.ui.error;
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 ErrorReport {
}
@@ -0,0 +1,32 @@
package org.cryptomator.ui.error;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.Subcomponent;
import dagger.multibindings.IntoMap;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxControllerKey;
import org.cryptomator.ui.common.FxmlFile;
@Subcomponent(modules = {ErrorModule.class, GenericErrorComponent.GenericErrorModule.class})
public interface GenericErrorComponent extends ErrorComponentBase {
@Subcomponent.Builder
interface Builder extends BuilderBase<Builder, GenericErrorComponent> { /* Handled by base interface */ }
@Module
abstract class GenericErrorModule {
@Provides
@ErrorReport
static FxmlFile provideFxmlFile() {
return FxmlFile.GENERIC_ERROR;
}
@Binds
@IntoMap
@FxControllerKey(GenericErrorController.class)
abstract FxController bindController(GenericErrorController controller);
}
}
@@ -0,0 +1,24 @@
package org.cryptomator.ui.error;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class GenericErrorController extends AbstractErrorController {
private final String stackTrace;
@Inject
GenericErrorController(@ErrorReport Stage window, @ErrorReport @Nullable Scene previousScene, @Named("stackTrace") String stackTrace) {
super(window, previousScene);
this.stackTrace = stackTrace;
}
/* Getter/Setter */
public String getStackTrace() {
return this.stackTrace;
}
}
@@ -0,0 +1,42 @@
package org.cryptomator.ui.error;
import dagger.Binds;
import dagger.BindsInstance;
import dagger.Module;
import dagger.Provides;
import dagger.Subcomponent;
import dagger.multibindings.IntoMap;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxControllerKey;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.unlock.UnlockInvalidMountPointController;
import org.cryptomator.ui.unlock.UnlockScoped;
@Subcomponent(modules = {ErrorModule.class, InvalidMountPointExceptionComponent.InvalidMountPointExceptionModule.class})
@UnlockScoped
public interface InvalidMountPointExceptionComponent extends ErrorComponentBase {
@Subcomponent.Builder
interface Builder extends BuilderBase<Builder, InvalidMountPointExceptionComponent> {
@BindsInstance
Builder vault(@ErrorReport Vault vault);
}
@Module
abstract class InvalidMountPointExceptionModule {
@Provides
@ErrorReport
static FxmlFile provideFxmlFile() {
return FxmlFile.UNLOCK_INVALID_MOUNT_POINT;
}
@Binds
@IntoMap
@FxControllerKey(UnlockInvalidMountPointController.class)
abstract FxController bindController(UnlockInvalidMountPointController controller);
}
}
@@ -5,13 +5,11 @@ 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.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;
@@ -46,9 +44,8 @@ public class FxApplication extends Application {
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 Provider<UnlockComponent.Builder> unlockWindowBuilderProvider;
private final Provider<LockComponent.Builder> lockWindowBuilderProvider;
private final Optional<TrayIntegrationProvider> trayIntegration;
private final Optional<UiAppearanceProvider> appearanceProvider;
private final VaultService vaultService;
@@ -58,14 +55,13 @@ public class FxApplication extends Application {
private final UiAppearanceListener systemInterfaceThemeListener = this::systemInterfaceThemeChanged;
@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, Lazy<MainWindowComponent> mainWindow, Lazy<PreferencesComponent> preferencesWindow, Provider<UnlockComponent.Builder> unlockWindowBuilderProvider, Provider<LockComponent.Builder> lockWindowBuilderProvider, Lazy<QuitComponent> quitWindow, Optional<TrayIntegrationProvider> trayIntegration, Optional<UiAppearanceProvider> appearanceProvider, VaultService vaultService, LicenseHolder licenseHolder) {
this.settings = settings;
this.mainWindow = mainWindow;
this.preferencesWindow = preferencesWindow;
this.unlockWorkflowBuilderProvider = unlockWorkflowBuilderProvider;
this.lockWorkflowBuilderProvider = lockWorkflowBuilderProvider;
this.unlockWindowBuilderProvider = unlockWindowBuilderProvider;
this.lockWindowBuilderProvider = lockWindowBuilderProvider;
this.quitWindow = quitWindow;
this.errorWindowBuilder = errorWindowBuilder;
this.trayIntegration = trayIntegration;
this.appearanceProvider = appearanceProvider;
this.vaultService = vaultService;
@@ -117,23 +113,15 @@ public class FxApplication extends Application {
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.")));
}
unlockWindowBuilderProvider.get().vault(vault).owner(owner).build().startUnlockWorkflow();
LOG.debug("Showing UnlockWindow for {}", vault.getDisplayName());
});
}
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.")));
}
lockWindowBuilderProvider.get().vault(vault).owner(owner).build().startLockWorkflow();
LOG.debug("Start lock workflow for {}", vault.getDisplayName());
});
}
@@ -9,8 +9,9 @@ import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.StageFactory;
import org.cryptomator.ui.error.GenericErrorComponent;
import org.cryptomator.ui.error.InvalidMountPointExceptionComponent;
import org.cryptomator.ui.lock.LockComponent;
import org.cryptomator.ui.mainwindow.MainWindowComponent;
import org.cryptomator.ui.preferences.PreferencesComponent;
@@ -19,17 +20,15 @@ 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})
abstract class FxApplicationModule {
@Module(includes = {UpdateCheckerModule.class}, subcomponents = {MainWindowComponent.class, PreferencesComponent.class, UnlockComponent.class, LockComponent.class, QuitComponent.class, GenericErrorComponent.class, InvalidMountPointExceptionComponent.class})
public abstract class FxApplicationModule {
@Provides
@Named("windowIcons")
@@ -1,7 +1,6 @@
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;
@@ -25,13 +24,11 @@ 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);
public static final Set<VaultState> STATES_ALLOWING_TERMINATION = EnumSet.of(VaultState.LOCKED, VaultState.NEEDS_MIGRATION, VaultState.MISSING, VaultState.ERROR);
private final FxApplicationStarter fxApplicationStarter;
private final CountDownLatch shutdownLatch;
@@ -130,8 +127,6 @@ public class AppLifecycleListener {
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,11 +1,9 @@
package org.cryptomator.ui.lock;
import dagger.Lazy;
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.common.UserInteractionLock;
@@ -37,23 +35,21 @@ public class LockWorkflow extends Task<Void> {
private final UserInteractionLock<LockModule.ForceLockDecision> forceLockDecisionLock;
private final Lazy<Scene> lockForcedScene;
private final Lazy<Scene> lockFailedScene;
private final ErrorComponent.Builder errorComponent;
@Inject
public LockWorkflow(@LockWindow Stage lockWindow, @LockWindow Vault vault, UserInteractionLock<LockModule.ForceLockDecision> forceLockDecisionLock, @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, UserInteractionLock<LockModule.ForceLockDecision> forceLockDecisionLock, @FxmlScene(FxmlFile.LOCK_FORCED) Lazy<Scene> lockForcedScene, @FxmlScene(FxmlFile.LOCK_FAILED) Lazy<Scene> lockFailedScene) {
this.lockWindow = lockWindow;
this.vault = vault;
this.forceLockDecisionLock = forceLockDecisionLock;
this.lockForcedScene = lockForcedScene;
this.lockFailedScene = lockFailedScene;
this.errorComponent = errorComponent;
}
@Override
protected Void call() throws Volume.VolumeException, InterruptedException, LockNotCompletedException {
protected Void call() throws Volume.VolumeException, InterruptedException {
try {
vault.lock(false);
} catch (Volume.VolumeException | LockNotCompletedException e) {
} catch (Volume.VolumeException e) {
LOG.debug("Regular lock of {} failed.", vault.getDisplayName(), e);
var decision = askUserForAction();
switch (decision) {
@@ -81,29 +77,29 @@ public class LockWorkflow extends Task<Void> {
return forceLockDecisionLock.awaitInteraction();
}
@Override
protected void scheduled() {
vault.setState(VaultState.PROCESSING);
}
@Override
protected void succeeded() {
LOG.info("Lock of {} succeeded.", vault.getDisplayName());
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.LOCKED);
vault.setState(VaultState.LOCKED);
}
@Override
protected void failed() {
final var throwable = super.getException();
LOG.warn("Lock of {} failed.", vault.getDisplayName(), throwable);
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.UNLOCKED);
if (throwable instanceof Volume.VolumeException) {
lockWindow.setScene(lockFailedScene.get());
lockWindow.show();
} else {
errorComponent.cause(throwable).window(lockWindow).build().showErrorScene();
}
LOG.warn("Failed to lock {}.", vault.getDisplayName());
vault.setState(VaultState.UNLOCKED);
lockWindow.setScene(lockFailedScene.get());
lockWindow.show();
}
@Override
protected void cancelled() {
LOG.debug("Lock of {} canceled.", vault.getDisplayName());
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.UNLOCKED);
vault.setState(VaultState.UNLOCKED);
}
}
@@ -95,7 +95,7 @@ public class MainWindowTitleController implements FxController {
@FXML
public void showDonationKeyPreferences() {
application.showPreferencesWindow(SelectedPreferencesTab.CONTRIBUTE);
application.showPreferencesWindow(SelectedPreferencesTab.DONATION_KEY);
}
/* Getter/Setter */
@@ -32,8 +32,7 @@ public class VaultDetailController implements FxController {
this.anyVaultSelected = vault.isNotNull();
}
// TODO deduplicate w/ VaultListCellController
private FontAwesome5Icon getGlyphForVaultState(VaultState.Value state) {
private FontAwesome5Icon getGlyphForVaultState(VaultState state) {
if (state != null) {
return switch (state) {
case LOCKED -> FontAwesome5Icon.LOCK;
@@ -24,8 +24,7 @@ public class VaultListCellController implements FxController {
.map(this::getGlyphForVaultState);
}
// TODO deduplicate w/ VaultDetailController
private FontAwesome5Icon getGlyphForVaultState(VaultState.Value state) {
private FontAwesome5Icon getGlyphForVaultState(VaultState state) {
if (state != null) {
return switch (state) {
case LOCKED -> FontAwesome5Icon.LOCK;
@@ -14,13 +14,19 @@ import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
import javax.inject.Inject;
import javafx.beans.binding.Binding;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.fxml.FXML;
import javafx.stage.Stage;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.Optional;
import static org.cryptomator.common.vaults.VaultState.Value.*;
import static org.cryptomator.common.vaults.VaultState.ERROR;
import static org.cryptomator.common.vaults.VaultState.LOCKED;
import static org.cryptomator.common.vaults.VaultState.MISSING;
import static org.cryptomator.common.vaults.VaultState.NEEDS_MIGRATION;
import static org.cryptomator.common.vaults.VaultState.UNLOCKED;
@MainWindowScoped
public class VaultListContextMenuController implements FxController {
@@ -31,7 +37,7 @@ public class VaultListContextMenuController implements FxController {
private final KeychainManager keychain;
private final RemoveVaultComponent.Builder removeVault;
private final VaultOptionsComponent.Builder vaultOptionsWindow;
private final OptionalBinding<VaultState.Value> selectedVaultState;
private final OptionalBinding<VaultState> selectedVaultState;
private final Binding<Boolean> selectedVaultPassphraseStored;
private final Binding<Boolean> selectedVaultRemovable;
private final Binding<Boolean> selectedVaultUnlockable;
@@ -51,6 +57,7 @@ public class VaultListContextMenuController implements FxController {
this.selectedVaultRemovable = selectedVaultState.map(EnumSet.of(LOCKED, MISSING, ERROR, NEEDS_MIGRATION)::contains).orElse(false);
this.selectedVaultUnlockable = selectedVaultState.map(LOCKED::equals).orElse(false);
this.selectedVaultLockable = selectedVaultState.map(UNLOCKED::equals).orElse(false);
}
private boolean isPasswordStored(Vault vault) {
@@ -1,11 +1,9 @@
package org.cryptomator.ui.mainwindow;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.removevault.RemoveVaultComponent;
import javax.inject.Inject;
import javafx.beans.binding.Bindings;
@@ -17,39 +15,26 @@ import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.ListView;
import javafx.scene.input.ContextMenuEvent;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
import java.util.EnumSet;
import static org.cryptomator.common.vaults.VaultState.Value.ERROR;
import static org.cryptomator.common.vaults.VaultState.Value.LOCKED;
import static org.cryptomator.common.vaults.VaultState.Value.MISSING;
import static org.cryptomator.common.vaults.VaultState.Value.NEEDS_MIGRATION;
@MainWindowScoped
public class VaultListController implements FxController {
private final Stage mainWindow;
private final ObservableList<Vault> vaults;
private final ObjectProperty<Vault> selectedVault;
private final VaultListCellFactory cellFactory;
private final AddVaultWizardComponent.Builder addVaultWizard;
private final BooleanBinding emptyVaultList;
private final RemoveVaultComponent.Builder removeVaultDialogue;
public ListView<Vault> vaultList;
@Inject
VaultListController(@MainWindow Stage mainWindow, ObservableList<Vault> vaults, ObjectProperty<Vault> selectedVault, VaultListCellFactory cellFactory, AddVaultWizardComponent.Builder addVaultWizard, RemoveVaultComponent.Builder removeVaultDialogue) {
this.mainWindow = mainWindow;
VaultListController(ObservableList<Vault> vaults, ObjectProperty<Vault> selectedVault, VaultListCellFactory cellFactory, AddVaultWizardComponent.Builder addVaultWizard) {
this.vaults = vaults;
this.selectedVault = selectedVault;
this.cellFactory = cellFactory;
this.addVaultWizard = addVaultWizard;
this.removeVaultDialogue = removeVaultDialogue;
this.emptyVaultList = Bindings.isEmpty(vaults);
@@ -74,28 +59,6 @@ public class VaultListController implements FxController {
request.consume();
}
});
vaultList.addEventFilter(KeyEvent.KEY_PRESSED, keyEvent -> {
if (keyEvent.getCode() == KeyCode.DELETE) {
pressedShortcutToRemoveVault();
keyEvent.consume();
}
});
if (SystemUtils.IS_OS_MAC) {
vaultList.addEventFilter(KeyEvent.KEY_PRESSED, keyEvent -> {
if (keyEvent.getCode() == KeyCode.BACK_SPACE) {
pressedShortcutToRemoveVault();
keyEvent.consume();
}
});
}
//register vault selection shortcut to the main window
mainWindow.addEventFilter(KeyEvent.KEY_RELEASED, keyEvent -> {
if (keyEvent.isShortcutDown() && keyEvent.getCode().isDigitKey()) {
vaultList.getSelectionModel().select(Integer.parseInt(keyEvent.getText()) - 1);
keyEvent.consume();
}
});
}
private void deselect(MouseEvent released) {
@@ -117,13 +80,6 @@ public class VaultListController implements FxController {
addVaultWizard.build().showAddVaultWizard();
}
private void pressedShortcutToRemoveVault() {
final var vault = selectedVault.get();
if (vault != null && EnumSet.of(LOCKED, MISSING, ERROR, NEEDS_MIGRATION).contains(vault.getState())) {
removeVaultDialogue.vault(vault).build().showRemoveVault();
}
}
// Getter and Setter
public BooleanBinding emptyVaultListProperty() {
@@ -12,7 +12,7 @@ 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.error.GenericErrorComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
@@ -26,7 +26,6 @@ import javax.inject.Named;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.binding.ObjectExpression;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
@@ -57,7 +56,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 GenericErrorComponent.Builder genericErrorBuilder;
private final Lazy<Scene> startScene;
private final Lazy<Scene> successScene;
private final Lazy<Scene> impossibleScene;
@@ -69,14 +68,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, GenericErrorComponent.Builder genericErrorBuilder) {
this.window = window;
this.vault = vault;
this.executor = executor;
this.scheduler = scheduler;
this.keychain = keychain;
this.missingCapability = missingCapability;
this.errorComponent = errorComponent;
this.genericErrorBuilder = genericErrorBuilder;
this.startScene = startScene;
this.successScene = successScene;
this.migrateButtonContentDisplay = Bindings.createObjectBinding(this::getMigrateButtonContentDisplay, vault.stateProperty());
@@ -90,10 +89,7 @@ public class MigrationRunController implements FxController {
if (keychain.isSupported()) {
loadStoredPassword();
}
migrationButtonDisabled.bind(ObjectExpression.objectExpression(vault.stateProperty())
.isNotEqualTo(VaultState.Value.NEEDS_MIGRATION)
.or(passwordField.textProperty().isEmpty()));
migrationButtonDisabled.bind(vault.stateProperty().isNotEqualTo(VaultState.NEEDS_MIGRATION).or(passwordField.textProperty().isEmpty()));
}
@FXML
@@ -105,7 +101,7 @@ public class MigrationRunController implements FxController {
public void migrate() {
LOG.info("Migrating vault {}", vault.getPath());
CharSequence password = passwordField.getCharacters();
vault.stateProperty().transition(VaultState.Value.NEEDS_MIGRATION, VaultState.Value.PROCESSING);
vault.setState(VaultState.PROCESSING);
passwordField.setDisable(true);
ScheduledFuture<?> progressSyncTask = scheduler.scheduleAtFixedRate(() -> {
Platform.runLater(() -> {
@@ -119,10 +115,10 @@ public class MigrationRunController implements FxController {
}).onSuccess(needsAnotherMigration -> {
if (needsAnotherMigration) {
LOG.info("Migration of '{}' succeeded, but another migration is required.", vault.getDisplayName());
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.NEEDS_MIGRATION);
vault.setState(VaultState.NEEDS_MIGRATION);
} else {
LOG.info("Migration of '{}' succeeded.", vault.getDisplayName());
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.LOCKED);
vault.setState(VaultState.LOCKED);
passwordField.wipe();
window.setScene(successScene.get());
}
@@ -131,21 +127,21 @@ public class MigrationRunController implements FxController {
passwordField.setDisable(false);
passwordField.selectAll();
passwordField.requestFocus();
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.NEEDS_MIGRATION);
vault.setState(VaultState.NEEDS_MIGRATION);
}).onError(FileSystemCapabilityChecker.MissingCapabilityException.class, e -> {
LOG.error("Underlying file system not supported.", e);
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.NEEDS_MIGRATION);
vault.setState(VaultState.NEEDS_MIGRATION);
missingCapability.set(e.getMissingCapability());
window.setScene(capabilityErrorScene.get());
}).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();
vault.setState(VaultState.NEEDS_MIGRATION);
genericErrorBuilder.cause(e).window(window).returnToScene(startScene.get()).build().showErrorScene();
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();
vault.setState(VaultState.NEEDS_MIGRATION);
genericErrorBuilder.cause(e).window(window).returnToScene(startScene.get()).build().showErrorScene();
}).andFinally(() -> {
passwordField.setDisable(false);
progressSyncTask.cancel(true);
@@ -14,17 +14,17 @@ import javafx.scene.control.TextArea;
import javafx.scene.control.TextFormatter;
@PreferencesScoped
public class SupporterCertificateController implements FxController {
public class DonationKeyPreferencesController implements FxController {
private static final String SUPPORTER_URI = "https://store.cryptomator.org/desktop";
private static final String DONATION_URI = "https://store.cryptomator.org/desktop";
private final Application application;
private final LicenseHolder licenseHolder;
private final Settings settings;
public TextArea supporterCertificateField;
public TextArea donationKeyField;
@Inject
SupporterCertificateController(Application application, LicenseHolder licenseHolder, Settings settings) {
DonationKeyPreferencesController(Application application, LicenseHolder licenseHolder, Settings settings) {
this.application = application;
this.licenseHolder = licenseHolder;
this.settings = settings;
@@ -32,9 +32,9 @@ public class SupporterCertificateController implements FxController {
@FXML
public void initialize() {
supporterCertificateField.setText(licenseHolder.getLicenseKey().orElse(null));
supporterCertificateField.textProperty().addListener(this::registrationKeyChanged);
supporterCertificateField.setTextFormatter(new TextFormatter<>(this::checkVaultNameLength));
donationKeyField.setText(licenseHolder.getLicenseKey().orElse(null));
donationKeyField.textProperty().addListener(this::registrationKeyChanged);
donationKeyField.setTextFormatter(new TextFormatter<>(this::checkVaultNameLength));
}
private TextFormatter.Change checkVaultNameLength(TextFormatter.Change change) {
@@ -53,8 +53,8 @@ public class SupporterCertificateController implements FxController {
}
@FXML
public void getSupporterCertificate() {
application.getHostServices().showDocument(SUPPORTER_URI);
public void getDonationKey() {
application.getHostServices().showDocument(DONATION_URI);
}
public LicenseHolder getLicenseHolder() {
@@ -8,7 +8,7 @@ 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.error.GenericErrorComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.traymenu.TrayMenuComponent;
import org.slf4j.Logger;
@@ -49,7 +49,7 @@ public class GeneralPreferencesController implements FxController {
private final Application application;
private final Environment environment;
private final Set<KeychainAccessProvider> keychainAccessProviders;
private final ErrorComponent.Builder errorComponent;
private final GenericErrorComponent.Builder genericErrorBuilder;
public ChoiceBox<UiTheme> themeChoiceBox;
public ChoiceBox<KeychainBackend> keychainBackendChoiceBox;
public CheckBox showMinimizeButtonCheckbox;
@@ -63,7 +63,7 @@ public class GeneralPreferencesController implements FxController {
@Inject
GeneralPreferencesController(@PreferencesWindow Stage window, Settings settings, TrayMenuComponent trayMenu, Optional<AutoStartProvider> autoStartProvider, Set<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, Set<KeychainAccessProvider> keychainAccessProviders, ObjectProperty<SelectedPreferencesTab> selectedTabProperty, LicenseHolder licenseHolder, ResourceBundle resourceBundle, Application application, Environment environment, GenericErrorComponent.Builder genericErrorBuilder) {
this.window = window;
this.settings = settings;
this.trayMenuInitialized = trayMenu.isInitialized();
@@ -75,7 +75,7 @@ public class GeneralPreferencesController implements FxController {
this.resourceBundle = resourceBundle;
this.application = application;
this.environment = environment;
this.errorComponent = errorComponent;
this.genericErrorBuilder = genericErrorBuilder;
}
@FXML
@@ -146,7 +146,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();
genericErrorBuilder.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
}
});
}
@@ -157,8 +157,8 @@ public class GeneralPreferencesController implements FxController {
@FXML
public void showContributeTab() {
selectedTabProperty.set(SelectedPreferencesTab.CONTRIBUTE);
public void showDonationTab() {
selectedTabProperty.set(SelectedPreferencesTab.DONATION_KEY);
}
@FXML
@@ -26,7 +26,7 @@ public class PreferencesController implements FxController {
public Tab generalTab;
public Tab volumeTab;
public Tab updatesTab;
public Tab contributeTab;
public Tab donationKeyTab;
public Tab aboutTab;
@Inject
@@ -52,7 +52,7 @@ public class PreferencesController implements FxController {
return switch (selectedTab) {
case UPDATES -> updatesTab;
case VOLUME -> volumeTab;
case CONTRIBUTE -> contributeTab;
case DONATION_KEY -> donationKeyTab;
case GENERAL -> generalTab;
case ABOUT -> aboutTab;
case ANY -> updateAvailable.get() ? updatesTab : generalTab;
@@ -77,8 +77,8 @@ abstract class PreferencesModule {
@Binds
@IntoMap
@FxControllerKey(SupporterCertificateController.class)
abstract FxController bindSupporterCertificatePreferencesController(SupporterCertificateController controller);
@FxControllerKey(DonationKeyPreferencesController.class)
abstract FxController bindDonationKeyPreferencesController(DonationKeyPreferencesController controller);
@Binds
@IntoMap
@@ -22,9 +22,9 @@ public enum SelectedPreferencesTab {
UPDATES,
/**
* Show contribute tab
* Show donation key tab
*/
CONTRIBUTE,
DONATION_KEY,
/**
* Show about tab
@@ -4,7 +4,7 @@ import dagger.Lazy;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.ui.common.Animations;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.error.GenericErrorComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
@@ -32,18 +32,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 GenericErrorComponent.Builder genericErrorBuilder;
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, GenericErrorComponent.Builder genericErrorBuilder) {
this.window = window;
this.successScene = successScene;
this.vault = vault;
this.executor = executor;
this.recoveryKeyFactory = recoveryKeyFactory;
this.recoveryKeyProperty = recoveryKey;
this.errorComponent = errorComponent;
this.genericErrorBuilder = genericErrorBuilder;
}
@FXML
@@ -62,7 +62,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();
genericErrorBuilder.cause(task.getException()).window(window).returnToScene(window.getScene()).build().showErrorScene();
}
});
executor.submit(task);
@@ -2,7 +2,7 @@ package org.cryptomator.ui.recoverykey;
import dagger.Lazy;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.error.GenericErrorComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
@@ -35,10 +35,10 @@ public class RecoveryKeyResetPasswordController implements FxController {
private final ObjectProperty<CharSequence> newPassword;
private final Lazy<Scene> recoverScene;
private final BooleanBinding invalidNewPassword;
private final ErrorComponent.Builder errorComponent;
private final GenericErrorComponent.Builder genericErrorBuilder;
@Inject
public RecoveryKeyResetPasswordController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, RecoveryKeyFactory recoveryKeyFactory, ExecutorService executor, @RecoveryKeyWindow StringProperty recoveryKey, @Named("newPassword") ObjectProperty<CharSequence> newPassword, @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, @Named("newPassword") ObjectProperty<CharSequence> newPassword, @FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy<Scene> recoverScene, GenericErrorComponent.Builder genericErrorBuilder) {
this.window = window;
this.vault = vault;
this.recoveryKeyFactory = recoveryKeyFactory;
@@ -47,7 +47,7 @@ public class RecoveryKeyResetPasswordController implements FxController {
this.newPassword = newPassword;
this.recoverScene = recoverScene;
this.invalidNewPassword = Bindings.createBooleanBinding(this::isInvalidNewPassword, newPassword);
this.errorComponent = errorComponent;
this.genericErrorBuilder = genericErrorBuilder;
}
@FXML
@@ -68,7 +68,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();
genericErrorBuilder.cause(task.getException()).window(window).returnToScene(recoverScene.get()).build().showErrorScene();
});
executor.submit(task);
}
@@ -43,8 +43,8 @@ abstract class VaultStatisticsModule {
var weakStage = new WeakReference<>(stage);
vault.stateProperty().addListener(new ChangeListener<>() {
@Override
public void changed(ObservableValue<? extends VaultState.Value> observable, VaultState.Value oldValue, VaultState.Value newValue) {
if (newValue != VaultState.Value.UNLOCKED) {
public void changed(ObservableValue<? extends VaultState> observable, VaultState oldValue, VaultState newValue) {
if (newValue != VaultState.UNLOCKED) {
Stage stage = weakStage.get();
if (stage != null) {
stage.hide();
@@ -44,9 +44,6 @@ class TrayMenuController {
public void initTrayMenu() {
vaults.addListener(this::vaultListChanged);
vaults.forEach(v -> {
v.displayNameProperty().addListener(this::vaultListChanged);
});
rebuildMenu();
}
@@ -1,49 +1,38 @@
package org.cryptomator.ui.unlock;
import dagger.Lazy;
import org.cryptomator.common.vaults.MountPointRequirement;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.error.AbstractErrorController;
import org.cryptomator.ui.error.ErrorReport;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.stage.Stage;
//At the current point in time only the CustomMountPointChooser may cause this window to be shown.
@UnlockScoped
public class UnlockInvalidMountPointController implements FxController {
public class UnlockInvalidMountPointController extends AbstractErrorController {
private final Stage window;
private final Lazy<Scene> unlockScene;
private final Vault vault;
@Inject
UnlockInvalidMountPointController(@UnlockWindow Stage window, @FxmlScene(FxmlFile.UNLOCK) Lazy<Scene> unlockScene, @UnlockWindow Vault vault) {
this.window = window;
this.unlockScene = unlockScene;
UnlockInvalidMountPointController(@ErrorReport Stage window, @ErrorReport @Nullable Scene previousScene, @ErrorReport Vault vault) {
super(window, previousScene);
this.vault = vault;
}
@FXML
public void back() {
window.setScene(unlockScene.get());
}
/* Getter/Setter */
public String getMountPoint() {
return vault.getVaultSettings().getCustomMountPath().orElse("AUTO");
return this.vault.getVaultSettings().getCustomMountPath().orElse("AUTO");
}
public boolean getMustExist() {
MountPointRequirement requirement = vault.getVolume().orElseThrow(() -> new IllegalStateException("Invalid Mountpoint without a Volume?!")).getMountPointRequirement();
MountPointRequirement requirement = this.vault.getVolume().orElseThrow(() -> new IllegalStateException("Invalid Mountpoint without a Volume?!")).getMountPointRequirement();
assert requirement != MountPointRequirement.NONE; //An invalid MountPoint with no required MountPoint doesn't seem sensible
assert requirement != MountPointRequirement.PARENT_OPT_MOUNT_POINT; //Not implemented anywhere (yet)
return requirement == MountPointRequirement.EMPTY_MOUNT_POINT;
}
}
@@ -130,10 +130,4 @@ abstract class UnlockModule {
@IntoMap
@FxControllerKey(UnlockSuccessController.class)
abstract FxController bindUnlockSuccessController(UnlockSuccessController controller);
@Binds
@IntoMap
@FxControllerKey(UnlockInvalidMountPointController.class)
abstract FxController bindUnlockInvalidMountPointController(UnlockInvalidMountPointController controller);
}
@@ -8,6 +8,6 @@ import java.lang.annotation.RetentionPolicy;
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
@interface UnlockScoped {
public @interface UnlockScoped {
}
@@ -10,11 +10,12 @@ import org.cryptomator.common.vaults.Volume.VolumeException;
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.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.UserInteractionLock;
import org.cryptomator.ui.common.VaultService;
import org.cryptomator.ui.error.GenericErrorComponent;
import org.cryptomator.ui.error.InvalidMountPointExceptionComponent;
import org.cryptomator.ui.unlock.UnlockModule.PasswordEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -56,11 +57,11 @@ public class UnlockWorkflow extends Task<Boolean> {
private final KeychainManager keychain;
private final Lazy<Scene> unlockScene;
private final Lazy<Scene> successScene;
private final Lazy<Scene> invalidMountPointScene;
private final ErrorComponent.Builder errorComponent;
private final GenericErrorComponent.Builder genericErrorBuilder;
private final InvalidMountPointExceptionComponent.Builder invalidMountPointExceptionBuilder;
@Inject
UnlockWorkflow(@UnlockWindow Stage window, @UnlockWindow Vault vault, VaultService vaultService, AtomicReference<char[]> password, @Named("savePassword") AtomicBoolean savePassword, @Named("savedPassword") Optional<char[]> savedPassword, UserInteractionLock<PasswordEntry> passwordEntryLock, KeychainManager keychain, @FxmlScene(FxmlFile.UNLOCK) Lazy<Scene> unlockScene, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy<Scene> invalidMountPointScene, ErrorComponent.Builder errorComponent) {
UnlockWorkflow(@UnlockWindow Stage window, @UnlockWindow Vault vault, VaultService vaultService, AtomicReference<char[]> password, @Named("savePassword") AtomicBoolean savePassword, @Named("savedPassword") Optional<char[]> savedPassword, UserInteractionLock<PasswordEntry> passwordEntryLock, KeychainManager keychain, @FxmlScene(FxmlFile.UNLOCK) Lazy<Scene> unlockScene, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, GenericErrorComponent.Builder genericErrorBuilder, InvalidMountPointExceptionComponent.Builder invalidMountPointExceptionBuilder) {
this.window = window;
this.vault = vault;
this.vaultService = vaultService;
@@ -71,17 +72,24 @@ public class UnlockWorkflow extends Task<Boolean> {
this.keychain = keychain;
this.unlockScene = unlockScene;
this.successScene = successScene;
this.invalidMountPointScene = invalidMountPointScene;
this.errorComponent = errorComponent;
this.genericErrorBuilder = genericErrorBuilder;
this.invalidMountPointExceptionBuilder = invalidMountPointExceptionBuilder;
setOnFailed(event -> {
Throwable throwable = event.getSource().getException();
if (throwable instanceof InvalidMountPointException e) {
handleInvalidMountPoint(e);
} else {
handleGenericError(throwable);
}
});
}
@Override
protected Boolean call() throws InterruptedException, IOException, VolumeException, InvalidMountPointException {
try {
if (attemptUnlock()) {
if (savePassword.get()) {
savePasswordToSystemkeychain(); //savePassword will be wiped on method return, so it must be set here
}
handleSuccess();
return true;
} else {
cancel(false); // set Tasks state to cancelled
@@ -124,6 +132,24 @@ public class UnlockWorkflow extends Task<Boolean> {
return passwordEntryLock.awaitInteraction();
}
private void handleSuccess() {
LOG.info("Unlock of '{}' succeeded.", vault.getDisplayName());
if (savePassword.get()) {
savePasswordToSystemkeychain();
}
switch (vault.getVaultSettings().actionAfterUnlock().get()) {
case ASK -> Platform.runLater(() -> {
window.setScene(successScene.get());
window.show();
});
case REVEAL -> {
Platform.runLater(window::close);
vaultService.reveal(vault);
}
case IGNORE -> Platform.runLater(window::close);
}
}
private void savePasswordToSystemkeychain() {
if (keychain.isSupported()) {
try {
@@ -147,28 +173,28 @@ public class UnlockWorkflow extends Task<Boolean> {
} else {
LOG.error("Unlock failed. Mountpoint doesn't exist (needs to be a folder): {}", cause.getMessage());
}
showInvalidMountPointScene();
showInvalidMountPointScene(cause);
return;
} else if (cause instanceof FileAlreadyExistsException) {
LOG.error("Unlock failed. Mountpoint already exists: {}", cause.getMessage());
showInvalidMountPointScene();
showInvalidMountPointScene(cause);
return;
} else if (cause instanceof DirectoryNotEmptyException) {
LOG.error("Unlock failed. Mountpoint not an empty directory: {}", cause.getMessage());
showInvalidMountPointScene();
showInvalidMountPointScene(cause);
return;
} else {
handleGenericError(impExc);
}
}
private void showInvalidMountPointScene() {
Platform.runLater(() -> {
window.setScene(invalidMountPointScene.get());
window.show();
});
private void showInvalidMountPointScene(Throwable e) {
invalidMountPointExceptionBuilder.cause(e).window(window).returnToScene(window.getScene()).vault(vault).build().showErrorScene();
}
private void handleGenericError(Throwable e) {
LOG.error("Unlock failed for technical reasons.", e);
errorComponent.cause(e).window(window).build().showErrorScene();
genericErrorBuilder.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
}
private void wipePassword(char[] pw) {
@@ -177,41 +203,24 @@ public class UnlockWorkflow extends Task<Boolean> {
}
}
@Override
protected void scheduled() {
vault.setState(VaultState.PROCESSING);
}
@Override
protected void succeeded() {
LOG.info("Unlock of '{}' succeeded.", vault.getDisplayName());
switch (vault.getVaultSettings().actionAfterUnlock().get()) {
case ASK -> Platform.runLater(() -> {
window.setScene(successScene.get());
window.show();
});
case REVEAL -> {
Platform.runLater(window::close);
vaultService.reveal(vault);
}
case IGNORE -> Platform.runLater(window::close);
}
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.UNLOCKED);
vault.setState(VaultState.UNLOCKED);
}
@Override
protected void failed() {
LOG.info("Unlock of '{}' failed.", vault.getDisplayName());
Throwable throwable = super.getException();
if (throwable instanceof InvalidMountPointException e) {
handleInvalidMountPoint(e);
} else {
handleGenericError(throwable);
}
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.LOCKED);
vault.setState(VaultState.LOCKED);
}
@Override
protected void cancelled() {
LOG.debug("Unlock of '{}' canceled.", vault.getDisplayName());
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.LOCKED);
vault.setState(VaultState.LOCKED);
}
}
@@ -20,8 +20,6 @@
alignment="CENTER_LEFT">
<fx:define>
<ToggleGroup fx:id="predefinedLocationToggler"/>
<FontAwesome5IconView fx:id="badLocation" styleClass="glyph-icon-red" glyph="TIMES" />
<FontAwesome5IconView fx:id="goodLocation" styleClass="glyph-icon-primary" glyph="CHECK" />
</fx:define>
<padding>
<Insets topRightBottomLeft="24"/>
@@ -35,8 +33,6 @@
<RadioButton fx:id="dropboxRadioButton" toggleGroup="${predefinedLocationToggler}" text="Dropbox" visible="${controller.locationPresets.foundDropbox}" managed="${controller.locationPresets.foundDropbox}"/>
<RadioButton fx:id="gdriveRadioButton" toggleGroup="${predefinedLocationToggler}" text="Google Drive" visible="${controller.locationPresets.foundGdrive}" managed="${controller.locationPresets.foundGdrive}"/>
<RadioButton fx:id="onedriveRadioButton" toggleGroup="${predefinedLocationToggler}" text="OneDrive" visible="${controller.locationPresets.foundOnedrive}" managed="${controller.locationPresets.foundOnedrive}"/>
<RadioButton fx:id="megaRadioButton" toggleGroup="${predefinedLocationToggler}" text="MEGA" visible="${controller.locationPresets.foundMega}" managed="${controller.locationPresets.foundMega}"/>
<RadioButton fx:id="pcloudRadioButton" toggleGroup="${predefinedLocationToggler}" text="pCloud" visible="${controller.locationPresets.foundPcloud}" managed="${controller.locationPresets.foundPcloud}"/>
<HBox spacing="12" alignment="CENTER_LEFT">
<RadioButton fx:id="customRadioButton" toggleGroup="${predefinedLocationToggler}" text="%addvaultwizard.new.directoryPickerLabel"/>
<Button contentDisplay="LEFT" text="%addvaultwizard.new.directoryPickerButton" onAction="#chooseCustomVaultPath" disable="${controller.usePresetPath}">
@@ -51,8 +47,12 @@
<VBox spacing="6">
<Label text="%addvaultwizard.new.locationLabel" labelFor="$locationTextField"/>
<TextField promptText="%addvaultwizard.new.locationPrompt" text="${controller.vaultPath}" editable="false" disable="${!controller.anyRadioButtonSelected}" HBox.hgrow="ALWAYS"/>
<Label fx:id="vaultPathStatus" styleClass="label-muted" alignment="CENTER_RIGHT" wrapText="true" visible="${controller.anyRadioButtonSelected}" maxWidth="Infinity" graphicTextGap="6" text="${controller.statusText}" graphic="${controller.statusGraphic}" />
<TextField fx:id="locationTextField" promptText="%addvaultwizard.new.locationPrompt" text="${controller.vaultPath}" disable="true" HBox.hgrow="ALWAYS"/>
<Label text="${controller.warningText}" wrapText="true" visible="${controller.showWarning}">
<graphic>
<FontAwesome5IconView glyph="EXCLAMATION_TRIANGLE"/>
</graphic>
</Label>
</VBox>
<Region VBox.vgrow="ALWAYS"/>
@@ -12,7 +12,7 @@
<?import javafx.scene.shape.Circle?>
<VBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.common.ErrorController"
fx:controller="org.cryptomator.ui.error.GenericErrorController"
prefWidth="450"
prefHeight="450"
spacing="12"
@@ -36,7 +36,7 @@
<ButtonBar buttonMinWidth="120" buttonOrder="B+C">
<buttons>
<Button text="%generic.button.back" ButtonBar.buttonData="BACK_PREVIOUS" onAction="#back" visible="${controller.previousScenePresent}"/>
<Button text="%generic.button.back" ButtonBar.buttonData="BACK_PREVIOUS" onAction="#back" visible="${controller.returnScenePresent}"/>
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" onAction="#close"/>
</buttons>
</ButtonBar>
@@ -38,12 +38,12 @@
<fx:include source="/fxml/preferences_updates.fxml"/>
</content>
</Tab>
<Tab fx:id="contributeTab" id="CONTRIBUTE" text="%preferences.contribute">
<Tab fx:id="donationKeyTab" id="DONATION_KEY" text="%preferences.donationKey">
<graphic>
<FontAwesome5IconView glyph="HEART"/>
</graphic>
<content>
<fx:include source="/fxml/preferences_contribute.fxml"/>
<fx:include source="/fxml/preferences_donationkey.fxml"/>
</content>
</Tab>
<Tab fx:id="aboutTab" id="ABOUT" text="%preferences.about">
@@ -12,7 +12,7 @@
<?import javafx.scene.shape.Circle?>
<VBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.preferences.SupporterCertificateController"
fx:controller="org.cryptomator.ui.preferences.DonationKeyPreferencesController"
spacing="18">
<padding>
<Insets topRightBottomLeft="12"/>
@@ -24,7 +24,7 @@
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="CROWN" glyphSize="24"/>
</StackPane>
<FormattedLabel format="%preferences.contribute.registeredFor" arg1="${controller.licenseHolder.licenseSubject}" wrapText="true"/>
<FormattedLabel format="%preferences.donationKey.registeredFor" arg1="${controller.licenseHolder.licenseSubject}" wrapText="true"/>
</HBox>
<HBox spacing="12" alignment="CENTER_LEFT" visible="${!controller.licenseHolder.validLicense}">
@@ -33,8 +33,8 @@
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="HAND_HOLDING_HEART" glyphSize="24"/>
</StackPane>
<VBox HBox.hgrow="ALWAYS" spacing="6">
<Label text="%preferences.contribute.noCertificate" wrapText="true" VBox.vgrow="ALWAYS"/>
<Hyperlink text="%preferences.contribute.getCertificate" onAction="#getSupporterCertificate" contentDisplay="LEFT">
<Label text="%preferences.donationKey.noDonationKey" wrapText="true" VBox.vgrow="ALWAYS"/>
<Hyperlink text="%preferences.donationKey.getDonationKey" onAction="#getDonationKey" contentDisplay="LEFT">
<graphic>
<FontAwesome5IconView glyph="LINK"/>
</graphic>
@@ -43,6 +43,6 @@
</HBox>
</StackPane>
<TextArea fx:id="supporterCertificateField" promptText="%preferences.contribute.promptText" wrapText="true" VBox.vgrow="ALWAYS" prefRowCount="6"/>
<TextArea fx:id="donationKeyField" wrapText="true" VBox.vgrow="ALWAYS" prefRowCount="6"/>
</children>
</VBox>
@@ -23,7 +23,7 @@
<HBox spacing="6" alignment="CENTER_LEFT">
<Label text="%preferences.general.theme"/>
<ChoiceBox fx:id="themeChoiceBox" disable="${!controller.licenseHolder.validLicense}"/>
<Hyperlink styleClass="hyperlink-underline,hyperlink-muted" text="%preferences.general.unlockThemes" onAction="#showContributeTab" visible="${!controller.licenseHolder.validLicense}" managed="${!controller.licenseHolder.validLicense}"/>
<Hyperlink styleClass="hyperlink-underline,hyperlink-muted" text="%preferences.general.unlockThemes" onAction="#showDonationTab" visible="${!controller.licenseHolder.validLicense}" managed="${!controller.licenseHolder.validLicense}"/>
</HBox>
<HBox spacing="6" alignment="CENTER_LEFT">
@@ -45,10 +45,8 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Custom Location
addvaultwizard.new.directoryPickerButton=Choose…
addvaultwizard.new.directoryPickerTitle=Select Directory
addvaultwizard.new.fileAlreadyExists=A file or directory with the vault name already exists
addvaultwizard.new.locationDoesNotExist=A directory in the specified path does not exist or cannot be accessed
addvaultwizard.new.locationIsNotWritable=No write access at the specified path
addvaultwizard.new.locationIsOk=Suitable location for your vault
addvaultwizard.new.fileAlreadyExists=Vault can not be created at this path because some object already exists.
addvaultwizard.new.locationDoesNotExist=Vault can not be created at this path because at least one path component does not exist.
addvaultwizard.new.invalidName=Invalid vault name. Please consider a regular directory name.
### Password
addvaultwizard.new.createVaultBtn=Create Vault
@@ -177,14 +175,11 @@ preferences.updates.currentVersion=Current Version: %s
preferences.updates.autoUpdateCheck=Check for updates automatically
preferences.updates.checkNowBtn=Check Now
preferences.updates.updateAvailable=Update to version %s available.
## Contribution
preferences.contribute=Support Us
preferences.contribute.registeredFor=Supporter certificate registered for %s
preferences.contribute.noCertificate=Support Cryptomator and receive a supporter certificate. It's like a license key but for awesome people using free software. ;-)
preferences.contribute.getCertificate=Don't have one already? Learn how you can obtain it.
preferences.contribute.promptText=Paste supporter certificate code here
#<-- Add entries for donations and code/translation/documentation contribution -->
## Donation Key
preferences.donationKey=Donation
preferences.donationKey.registeredFor=Registered for %s
preferences.donationKey.noDonationKey=No valid donation key found. It's like a license key but for awesome people using free software. ;-)
preferences.donationKey.getDonationKey=Get a donation key
## About
preferences.about=About
@@ -44,6 +44,8 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=موقع مخصص
addvaultwizard.new.directoryPickerButton=اختر…
addvaultwizard.new.directoryPickerTitle=اختر القاموس
addvaultwizard.new.fileAlreadyExists=لا يمكن إنشاء مخزن على هذا المسار لأن بعض الملفات موجودة مسبقا.
addvaultwizard.new.locationDoesNotExist=لا يمكن إنشاء المخزن على هذا المسار لأن جزء من هذا المسار غير موجود.
addvaultwizard.new.invalidName=اسم المخزن غير صالح. يرجى النظر في اسم الدليل المعتاد.
### Password
addvaultwizard.new.createVaultBtn=انشئ حافظة
@@ -165,9 +167,11 @@ preferences.updates.currentVersion=الإصدار الحالي: %s
preferences.updates.autoUpdateCheck=تحقق من التحديثات اوتوماتيكيا
preferences.updates.checkNowBtn=تحقق الان
preferences.updates.updateAvailable=التحديث إلى الإصدار %s متاح.
## Contribution
#<-- Add entries for donations and code/translation/documentation contribution -->
## Donation Key
preferences.donationKey=التبرعات
preferences.donationKey.registeredFor=مسجل لـ %s
preferences.donationKey.noDonationKey=لم يتم العثور على مفتاح تبرع صحيح. إنه مثل مفتاح الترخيص إلا أنه للأشخاص الرائعين الذين يستخدمون البرمجيات المجانية؛-)
preferences.donationKey.getDonationKey=الحصول على مفتاح تبرع
## About
preferences.about=حول البرنامج
@@ -44,6 +44,8 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Prilagođena lokacija
addvaultwizard.new.directoryPickerButton=Odaberi…
addvaultwizard.new.directoryPickerTitle=Izaberi folder
addvaultwizard.new.fileAlreadyExists=Sef ne može biti kreiran na odabranoj lokaciji jer neki objekti tu već postoje.
addvaultwizard.new.locationDoesNotExist=Sef ne može biti kreiran na odabranoj lokaciji jer najmanje jedna komponenta lokacije ne postoji.
addvaultwizard.new.invalidName=Naziv sefa neispravan. Molimo razmislite o drugom nazivu.
### Password
addvaultwizard.new.createVaultBtn=Kreiraj novi sef
@@ -93,10 +95,8 @@ forgetPassword.confirmBtn=Zaboravili ste šifru
# Unlock
unlock.title=Otključaj sef
unlock.passwordPrompt=Unesite lozinku za "%s":
unlock.savePassword=Zapamti šifru
unlock.unlockBtn=Otključaj
## Success
unlock.success.message=Uspješno ste otključali "%s"! Vaš sef je sada dostupan putem svog virtualnog diska.
unlock.success.rememberChoice=Zapamtite izbor, ne pokazujte ovo ponovo
unlock.success.revealBtn=Otkrij pogon
## Failure
@@ -172,9 +172,11 @@ preferences.updates.currentVersion=Trenutna verzija: %s
preferences.updates.autoUpdateCheck=Automatski provjeri ima li ažuriranja
preferences.updates.checkNowBtn=Provjeri sada
preferences.updates.updateAvailable=Dostupno ažuriranje na verziju %s.
## Contribution
#<-- Add entries for donations and code/translation/documentation contribution -->
## Donation Key
preferences.donationKey=Donacija
preferences.donationKey.registeredFor=Registrovan za %s
preferences.donationKey.noDonationKey=Nije pronađen važeći ključ za donaciju. To je poput licence, ali za sjajne ljude koji koriste besplatni softver. ;-)
preferences.donationKey.getDonationKey=Nabavite ključ za donaciju
## About
preferences.about=O programu
@@ -219,11 +221,9 @@ main.dropZone.dropVault=Dodajte ovaj sef
main.dropZone.unknownDragboardContent=Ako želite dodati sef, povucite ga u ovaj prozor
## Vault List
main.vaultlist.emptyList.onboardingInstruction=Kliknite ovdje da dodate sef
main.vaultlist.contextMenu.remove=Ukloni…
main.vaultlist.contextMenu.lock=Zaključaj
main.vaultlist.contextMenu.unlock=Otključaj…
main.vaultlist.contextMenu.unlockNow=Otključaj sada
main.vaultlist.contextMenu.vaultoptions=Pokaži opcije sefa
main.vaultlist.contextMenu.reveal=Otkrij pogon
main.vaultlist.addVaultBtn=Dodaj sef
## Vault Detail
@@ -44,6 +44,8 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Ubicació personalitzada
addvaultwizard.new.directoryPickerButton=Trieu…
addvaultwizard.new.directoryPickerTitle=Seleccioneu el directori
addvaultwizard.new.fileAlreadyExists=No es pot crear una caixa forta en aquest camí perquè ja hi ha algun objecte.
addvaultwizard.new.locationDoesNotExist=No ha estat possible crear la caixa forta en aquest camí perquè, si més no, un component no existeix.
addvaultwizard.new.invalidName=El nom de la caixa forta no és vàlid. Si us plau, escribiu un mom de directori amb caràcters estàndard.
### Password
addvaultwizard.new.createVaultBtn=Crea la caixa forta
@@ -96,7 +98,6 @@ unlock.passwordPrompt=Introduïu la contrasenya de "%s":
unlock.savePassword=Recorda la contrasenya
unlock.unlockBtn=Desbloqueja
## Success
unlock.success.message=S'ha desblocat %s correctament! Podeu accedir a la vostra caixa forta a través de la unitat virtual.
unlock.success.rememberChoice=Recorda l'elecció. No ho tornis a mostrar.
unlock.success.revealBtn=Mostra la unitat
## Failure
@@ -172,14 +173,11 @@ preferences.updates.currentVersion=Versió actual: %s
preferences.updates.autoUpdateCheck=Comprova automàticament si hi ha actualitzacions
preferences.updates.checkNowBtn=Comprova-ho ara
preferences.updates.updateAvailable=L'actualització a la versió %s es troba disponible.
## Contribution
preferences.contribute=Doneu-nos suport
preferences.contribute.registeredFor=Certificat de col·laborador registrat per a %s
preferences.contribute.noCertificate=Doneu suport a Cryptomator i rebeu un certificat de col·laborador. És com una clau de llicència però per a gent meravellosa que fa servir programari lliure. ;-)
preferences.contribute.getCertificate=Encara no en teniu cap? Sapigueu com aconseguir-ne un.
preferences.contribute.promptText=Enganxeu aquí el certificat de col·laborador
#<-- Add entries for donations and code/translation/documentation contribution -->
## Donation Key
preferences.donationKey=Donacions
preferences.donationKey.registeredFor=Registrat per %s
preferences.donationKey.noDonationKey=No s'ha trobar una clau de donació vàlida. És sembla a una clau de llicència però per gent meravellosa qui utilitza programari lliure. ;-)
preferences.donationKey.getDonationKey=Obtingau una clau de donació
## About
preferences.about=Quant a
@@ -224,11 +222,9 @@ main.dropZone.dropVault=Afegeix aquesta caixa forta
main.dropZone.unknownDragboardContent=Si voleu afegir una caixa forta, arrossegueu-la a aquesta finestra
## Vault List
main.vaultlist.emptyList.onboardingInstruction=Feu clic aquí per afegir una caixa forta
main.vaultlist.contextMenu.remove=Elimina…
main.vaultlist.contextMenu.lock=Bloqueja
main.vaultlist.contextMenu.unlock=Desbloca…
main.vaultlist.contextMenu.unlockNow=Desbloqueja ara
main.vaultlist.contextMenu.vaultoptions=Opcions de la caixa forta
main.vaultlist.contextMenu.reveal=Mostra la unitat
main.vaultlist.addVaultBtn=Afegir una caixa forta
## Vault Detail
@@ -44,10 +44,8 @@ addvaultwizard.new.locationPrompt=...
addvaultwizard.new.directoryPickerLabel=Vlastní umístění
addvaultwizard.new.directoryPickerButton=Vybrat...
addvaultwizard.new.directoryPickerTitle=Vyberte adresář
addvaultwizard.new.fileAlreadyExists=Soubor nebo složka s tímto názvem trezoru již existuje
addvaultwizard.new.locationDoesNotExist=Složka v zadané cestě neexistuje nebo není přístupná
addvaultwizard.new.locationIsNotWritable=Není možné zapisovat na zadané cestě
addvaultwizard.new.locationIsOk=Vhodné umístění pro váš trezor
addvaultwizard.new.fileAlreadyExists=Trezor nemůže být vytvořen na tomto umístění, protože stejný objekt již existuje.
addvaultwizard.new.locationDoesNotExist=Trezor nemůže být vytvořen v tomto umístění, protože přinejmenším jedna položka z daného umístě neexistuje.
addvaultwizard.new.invalidName=Neplatné jméno trezoru. Prosím změňte jméno adresáře.
### Password
addvaultwizard.new.createVaultBtn=Vytvořit trezor
@@ -100,7 +98,6 @@ unlock.passwordPrompt=Zadejte heslo pro "%s":
unlock.savePassword=Zapamatovat heslo
unlock.unlockBtn=Odemknout
## Success
unlock.success.message=Trezor "%s" byl úspěšně odemčen a nyní je dostupný jako virtuální jednotka.
unlock.success.rememberChoice=Pamatovat si volbu, nezobrazovat to znovu
unlock.success.revealBtn=Zobrazit jednotku
## Failure
@@ -112,11 +109,8 @@ unlock.error.invalidMountPoint.existing=Připojovací bod %s již existuje nebo
# Lock
## Force
lock.forced.heading=Běžné uzamčení selhalo
lock.forced.message=Uzamčení "%s" bylo zablokováno nevyřízenými operacemi nebo otevřenými soubory. Můžete vynutit uzamčení tohoto trezoru, ale přerušení I/O může mít za následek ztrátu neuložených dat.
lock.forced.confirmBtn=Přesto uzamknout
## Failure
lock.fail.heading=Uzamčení trezoru selhalo.
lock.fail.message=Trezor "%s" nelze uzamknout. Ujistěte se, že je neuložená práce uložena jinde a že jsou dokončeny důležité operace čtení/zápis. Za účelem uzavření trezoru ukončte proces Cryptomatoru.
# Migration
migration.title=Upgrade trezoru
@@ -176,14 +170,11 @@ preferences.updates.currentVersion=Aktuální verze: %s
preferences.updates.autoUpdateCheck=Automaticky kontrolovat aktualizace
preferences.updates.checkNowBtn=Zkontrolovat nyní
preferences.updates.updateAvailable=Dostupná nová verze %s.
## Contribution
preferences.contribute=Podpořte nás
preferences.contribute.registeredFor=Certifikát podporovatele byl registrován pro %s
preferences.contribute.noCertificate=Podpořte Cryptomator a získejte certifikát podporovatele. Je to jako licenční klíč, ale pro skvělé lidi, kteří používají svobodný software. ;-)
preferences.contribute.getCertificate=Ještě žádný nemáte? Naučte se, jak ho můžete získat.
preferences.contribute.promptText=Sem vložte kód certifikátu podporovatele
#<-- Add entries for donations and code/translation/documentation contribution -->
## Donation Key
preferences.donationKey=Příspěvek
preferences.donationKey.registeredFor=Registrováno na: %s
preferences.donationKey.noDonationKey=Nebyl nalezen žádný platný darovací klíč. Je to jako licenční klíč, ale pro úžasné lidi využívající software zadarmo. ;-)
preferences.donationKey.getDonationKey=Získat darovací klíč
## About
preferences.about=O aplikaci
@@ -232,7 +223,6 @@ main.vaultlist.contextMenu.remove=Odstranit…
main.vaultlist.contextMenu.lock=Zamknout
main.vaultlist.contextMenu.unlock=Odemknout…
main.vaultlist.contextMenu.unlockNow=Odemknout nyní
main.vaultlist.contextMenu.vaultoptions=Zobrazit možnosti trezoru
main.vaultlist.contextMenu.reveal=Zobrazit jednotku
main.vaultlist.addVaultBtn=Přidat trezor
## Vault Detail
@@ -44,10 +44,8 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Eigener 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.locationDoesNotExist=Der 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
addvaultwizard.new.fileAlreadyExists=Der Tresor konnte nicht erstellt werden, da am Speicherort so eine Datei bereits existiert.
addvaultwizard.new.locationDoesNotExist=Der Tresor kann nicht erstellt werden, da mindestens ein Speicherort nicht existiert.
addvaultwizard.new.invalidName=Ungültiger Tresorname. Bitte wähle einen regulären Namen, mit dem auch Verzeichnisse erstellt werden können.
### Password
addvaultwizard.new.createVaultBtn=Tresor erstellen
@@ -176,14 +174,11 @@ preferences.updates.currentVersion=Aktuelle Version: %s
preferences.updates.autoUpdateCheck=Automatisch nach Updates suchen
preferences.updates.checkNowBtn=Jetzt suchen
preferences.updates.updateAvailable=Update auf Version %s verfügbar.
## Contribution
preferences.contribute=Unterstütze uns
preferences.contribute.registeredFor=Supporter-Zertifikat registriert für %s
preferences.contribute.noCertificate=Unterstütze Cryptomator und erhalte ein Supporter-Zertifikat. Es ist wie ein Lizenzschlüssel, aber für großartige Menschen, die freie Software verwenden. ;-)
preferences.contribute.getCertificate=Du hast noch keines? Erfahre, wie du es erhalten kannst.
preferences.contribute.promptText=Code des Supporter-Zertifikats hier einfügen
#<-- Add entries for donations and code/translation/documentation contribution -->
## Donation Key
preferences.donationKey=Spende
preferences.donationKey.registeredFor=Registriert für %s
preferences.donationKey.noDonationKey=Kein gültiger Spendenschlüssel gefunden. Er ist wie ein Lizenzschlüssel, aber für tolle Leute, die freie Software verwenden. ;-)
preferences.donationKey.getDonationKey=Hol dir einen Spendenschlüssel
## About
preferences.about=Über
@@ -44,10 +44,8 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Προσαρμοσμένη τοποθεσία
addvaultwizard.new.directoryPickerButton=Επιλογή…
addvaultwizard.new.directoryPickerTitle=Επιλογή φακέλου
addvaultwizard.new.fileAlreadyExists=Ένα αρχείο ή φάκελος με το όνομα του vault υπάρχει ήδη
addvaultwizard.new.locationDoesNotExist=Στην καθορισμένη διαδρομή δεν υπάρχει ή δεν μπορεί να προσπελαστεί ένας φάκελος
addvaultwizard.new.locationIsNotWritable=Δεν υπάρχει πρόσβαση εγγραφής στην καθορισμένη διαδρομή
addvaultwizard.new.locationIsOk=Κατάλληλη τοποθεσία για το vault σας
addvaultwizard.new.fileAlreadyExists=Το vault δεν μπορεί να δημιουργηθεί σε αυτό το μονοπάτι καθώς κάποια αντικείμενα υπάρχουν ήδη.
addvaultwizard.new.locationDoesNotExist=Το vault δεν μπορεί να δημιουργηθεί σε αυτό το μονοπάτι καθώς τουλάχιστον ένα στοιχείο του μονοπατιού δεν υπάρχει.
addvaultwizard.new.invalidName=Λάθος όνομα vault. Παρακαλώ χρησιμοποιείστε ένα κανονικό όνομα φακέλου.
### Password
addvaultwizard.new.createVaultBtn=Δημιουργία Vault
@@ -100,7 +98,6 @@ unlock.passwordPrompt=Εισάγετε τον κωδικό για "%s":
unlock.savePassword=Απομνημόνευση κωδικού πρόσβασης
unlock.unlockBtn=Ξεκλείδωμα
## Success
unlock.success.message=Ξεκλειδώθηκε "%s" επιτυχώς! Το vault σας είναι διαθέσιμο μέσω του εικονικού δίσκου του.
unlock.success.rememberChoice=Απομνημόνευση επιλογής, μην ρωτήσεις ξανά
unlock.success.revealBtn=Αποκάλυψη εικονικού δίσκου
## Failure
@@ -176,14 +173,11 @@ preferences.updates.currentVersion=Τρέχουσα έκδοση: %s
preferences.updates.autoUpdateCheck=Αυτόματος έλεγχος για ενημερώσεις
preferences.updates.checkNowBtn=Έλεγχος τώρα
preferences.updates.updateAvailable=Η ενημέρωση για την έκδοση %s είναι διαθέσιμη.
## Contribution
preferences.contribute=Υποστηρίξτε μας
preferences.contribute.registeredFor=Το πιστοποιητικό υποστήριξης καταχωρήθηκε για %s
preferences.contribute.noCertificate=Υποστηρίξτε το Cryptomator και λάβετε ένα πιστοποιητικό υποστηρικτή. Είναι σαν κλειδί άδειας χρήσης, αλλά για φοβερά άτομα που χρησιμοποιούν ελεύθερο λογισμικό. ;-)
preferences.contribute.getCertificate=Δεν έχετε ένα ήδη; Μάθετε πώς μπορείτε να το αποκτήσετε.
preferences.contribute.promptText=Επικολλήστε τον κωδικό πιστοποιητικού υποστηρικτή εδώ
#<-- Add entries for donations and code/translation/documentation contribution -->
## Donation Key
preferences.donationKey=Δωρεά
preferences.donationKey.registeredFor=Καταχωρημένο σε %s
preferences.donationKey.noDonationKey=Δεν βρέθηκε ενεργό κλειδί δωρεάς. Είναι σαν κλειδί αγοράς αλλά για απίθανους ανθρώπους που χρησιμοποιούν δωρεάν λογισμικό. ;-)
preferences.donationKey.getDonationKey=Πάρε ένα κλειδί δωρεάς
## About
preferences.about=Σχετικά με
@@ -228,11 +222,9 @@ main.dropZone.dropVault=Προσθήκη vault
main.dropZone.unknownDragboardContent=Αν θέλετε να προσθέσετε ένα vault, σύρετε το σε αυτό το παράθυρο
## Vault List
main.vaultlist.emptyList.onboardingInstruction=Κάντε κλικ εδώ για να προσθέσετε ένα vault
main.vaultlist.contextMenu.remove=Αφαίρεση…
main.vaultlist.contextMenu.lock=Κλείδωμα
main.vaultlist.contextMenu.unlock=Ξεκλείδωμα…
main.vaultlist.contextMenu.unlockNow=Ξεκλείδωμα τώρα
main.vaultlist.contextMenu.vaultoptions=Εμφάνιση επιλογών Vault
main.vaultlist.contextMenu.reveal=Αποκάλυψη εικονικού δίσκου
main.vaultlist.addVaultBtn=Προσθήκη Vault
## Vault Detail
@@ -44,10 +44,8 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Ubicación personalizada
addvaultwizard.new.directoryPickerButton=Elegir…
addvaultwizard.new.directoryPickerTitle=Seleccionar directorio
addvaultwizard.new.fileAlreadyExists=Ya existe un archivo o directorio con el nombre de la bóveda
addvaultwizard.new.locationDoesNotExist=No existe un directorio en la ruta especificada o no se puede acceder
addvaultwizard.new.locationIsNotWritable=Sin acceso de escritura en la ruta especificada
addvaultwizard.new.locationIsOk=Ubicación adecuada para la bóveda
addvaultwizard.new.fileAlreadyExists=La bóveda no puede crearse en esta ruta porque ya existe un objeto.
addvaultwizard.new.locationDoesNotExist=La bóveda no puede ser creada en esta ruta porque al menos un componente no existe.
addvaultwizard.new.invalidName=Nombre de bóveda inválido. Considere un nombre de directorio regular.
### Password
addvaultwizard.new.createVaultBtn=Crear bóveda
@@ -176,9 +174,11 @@ preferences.updates.currentVersion=Versión actual: %s
preferences.updates.autoUpdateCheck=Buscar actualizaciones automáticamente
preferences.updates.checkNowBtn=Buscar ahora
preferences.updates.updateAvailable=Actualización a la versión %s disponible.
## Contribution
#<-- Add entries for donations and code/translation/documentation contribution -->
## Donation Key
preferences.donationKey=Donación
preferences.donationKey.registeredFor=Registrado para %s
preferences.donationKey.noDonationKey=No se encontró una clave de donación válida. Es como una clave de licencia pero para personas geniales que usan software libre.
preferences.donationKey.getDonationKey=Obtener clave de donación
## About
preferences.about=Acerca de
@@ -44,10 +44,8 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Emplacement personnalisé
addvaultwizard.new.directoryPickerButton=Choisir...
addvaultwizard.new.directoryPickerTitle=Choisissez le répertoire
addvaultwizard.new.fileAlreadyExists=Un fichier ou un répertoire avec ce nom existe déjà
addvaultwizard.new.locationDoesNotExist=Un répertoire dans le chemin spécifié n'existe pas ou ne peut pas être accédé
addvaultwizard.new.locationIsNotWritable=Pas d'accès en écriture au chemin spécifié
addvaultwizard.new.locationIsOk=Lieu approprié pour votre coffre
addvaultwizard.new.fileAlreadyExists=Le coffre ne peut pas être créé sur ce chemin car un objet existe déjà.
addvaultwizard.new.locationDoesNotExist=Le coffre ne peut pas être créé à cet endroit car au moins un répertoire du chemin n'existe pas.
addvaultwizard.new.invalidName=Nom de coffre invalide. Préférez un nom de répertoire habituel.
### Password
addvaultwizard.new.createVaultBtn=Créer un coffre
@@ -176,14 +174,11 @@ preferences.updates.currentVersion=Version actuelle : “%s”
preferences.updates.autoUpdateCheck=Vérifier automatiquement lexistence de mise à jour
preferences.updates.checkNowBtn=Vérifier maintenant
preferences.updates.updateAvailable=Mise à jour “%s” disponible.
## Contribution
preferences.contribute=Nous soutenir
preferences.contribute.registeredFor=Certificat de soutien enregistré pour %s
preferences.contribute.noCertificate=Soutenez Cryptomator et recevez un certificat de soutien. C'est comme une clé de licence, mais pour des gens géniaux qui utilisent des logiciels libres. ;-)
preferences.contribute.getCertificate=Vous n'en avez pas encore? Découvrez comment vous pouvez l'obtenir.
preferences.contribute.promptText=Collez le code du certificat du supporter ici
#<-- Add entries for donations and code/translation/documentation contribution -->
## Donation Key
preferences.donationKey=Faire un Don
preferences.donationKey.registeredFor=Licence enregistrée à %s
preferences.donationKey.noDonationKey=Aucune clé de don valide trouvée. C'est comme une clé de licence mais pour des personnes géniales utilisant un logiciel libre ;-)
preferences.donationKey.getDonationKey=Obtenir une clé de don
## About
preferences.about=A propos
@@ -44,6 +44,8 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=अपने पसंद की जगह डालें
addvaultwizard.new.directoryPickerButton=चुनें…
addvaultwizard.new.directoryPickerTitle=निर्देशिका चुनें
addvaultwizard.new.fileAlreadyExists=तिजोरी इस रास्ते पर नहीं बनाई जा सकती क्योंकि कुछ वस्तु पहले से मौजूद है।
addvaultwizard.new.locationDoesNotExist=तिजोरी इस रास्ते पर नहीं बनाई जा सकती क्योंकि कम से कम एक पथ घटक मौजूद नहीं है।
addvaultwizard.new.invalidName=अमान्य वॉल्ट नाम। कृपया एक नियमित निर्देशिका नाम पर विचार करें।
### Password
addvaultwizard.new.createVaultBtn=वॉल्ट बनाएं
@@ -92,9 +94,7 @@ preferences.title=प्राथमिकताएं
preferences.general=सामान्य
## Volume
## Updates
## Contribution
#<-- Add entries for donations and code/translation/documentation contribution -->
## Donation Key
## About
# Vault Statistics
@@ -44,9 +44,7 @@
## General
## Volume
## Updates
## Contribution
#<-- Add entries for donations and code/translation/documentation contribution -->
## Donation Key
## About
# Vault Statistics
@@ -43,6 +43,8 @@ addvaultwizard.new.locationLabel=Tárolási hely
addvaultwizard.new.directoryPickerLabel=Egyedi hely
addvaultwizard.new.directoryPickerButton=Választás…
addvaultwizard.new.directoryPickerTitle=Könyvtár kiválasztása
addvaultwizard.new.fileAlreadyExists=Nem lehet a széfet létrehozni ezen a helyen, mert egy fájl már létezik itt.
addvaultwizard.new.locationDoesNotExist=Nem lehet a széfet létrehozni ezen a helyen, mert az útvonal legalább egy darabja nem létezik.
addvaultwizard.new.invalidName=Érvénytelen széf elnevezés. Kérjük vegye figyelembe a szabályos könyvtárelnevezésre vonatkozó szabályokat.
### Password
addvaultwizard.new.createVaultBtn=Új széf létrehozása
@@ -156,9 +158,11 @@ preferences.updates.currentVersion=Jelenlegi verzió: %s
preferences.updates.autoUpdateCheck=Frissítések autómatikus keresése
preferences.updates.checkNowBtn=Ellenőrzés most
preferences.updates.updateAvailable=Frissítés a %s verzióra elérhető.
## Contribution
#<-- Add entries for donations and code/translation/documentation contribution -->
## Donation Key
preferences.donationKey=Adomány
preferences.donationKey.registeredFor=Regisztrálva %s számára
preferences.donationKey.noDonationKey=Nem található érvényes adománykulcs. Mint egy licenckulcs, csak olyan nagyszerű emberek számára, akik ingyenes szofvereket használnak. ;-)
preferences.donationKey.getDonationKey=Adománykulcs beszerzése
## About
preferences.about=Rólunk
@@ -44,6 +44,8 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Sesuaikan Lokasi
addvaultwizard.new.directoryPickerButton=Pilih…
addvaultwizard.new.directoryPickerTitle=Pilih Folder
addvaultwizard.new.fileAlreadyExists=Brankas tidak dibuat disini karena beberapa item telah ada disini.
addvaultwizard.new.locationDoesNotExist=Brankas tidak bisa dibuat disini karena setidaknya satu jalur komponen tidak ada.
addvaultwizard.new.invalidName=Nama brankas salah. Harap pilih nama folder yang umum.
### Password
addvaultwizard.new.createVaultBtn=Buat Brankas
@@ -95,9 +97,7 @@ preferences.title=Preferensi
## General
## Volume
## Updates
## Contribution
#<-- Add entries for donations and code/translation/documentation contribution -->
## Donation Key
## About
# Vault Statistics
@@ -44,6 +44,8 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Posizione personalizzata
addvaultwizard.new.directoryPickerButton=Scegli…
addvaultwizard.new.directoryPickerTitle=Seleziona cartella
addvaultwizard.new.fileAlreadyExists=La cassaforte non può essere creata in questo percorso perché un oggetto esiste già.
addvaultwizard.new.locationDoesNotExist=La cassaforte non può essere creata in questo percorso perché almeno un componente di percorso non esiste.
addvaultwizard.new.invalidName=Nome della cassaforte non valido. Si prega di considerare un nome di directory regolare.
### Password
addvaultwizard.new.createVaultBtn=Crea Cassaforte
@@ -96,9 +98,8 @@ unlock.passwordPrompt=Inserisci la password per "%s":
unlock.savePassword=Ricorda la Password
unlock.unlockBtn=Sblocca
## Success
unlock.success.message="%s" sbloccato con successo! La tua cassaforte è ora accessibile tramite il suo drive virtuale.
unlock.success.rememberChoice=Ricorda la scelta, non mostrare ancora
unlock.success.revealBtn=Rivela Drive
unlock.success.revealBtn=Visualizza disco
## Failure
unlock.error.heading=Impossibile sbloccare la cassaforte
### Invalid Mount Point
@@ -171,9 +172,11 @@ preferences.updates.currentVersion=Versione attuale %s
preferences.updates.autoUpdateCheck=Controlla automaticamente la presenza di aggiornamenti
preferences.updates.checkNowBtn=Controlla adesso
preferences.updates.updateAvailable=Disponibile aggiornamento alla versione %s.
## Contribution
#<-- Add entries for donations and code/translation/documentation contribution -->
## Donation Key
preferences.donationKey=Donazione
preferences.donationKey.registeredFor=Registrato per %s
preferences.donationKey.noDonationKey=Nessuna chiave di donazione valida. È come una chiave di licenza ma per persone fantastiche che usano software gratuito. ;-)
preferences.donationKey.getDonationKey=Ottieni una chiave di donazione
## About
preferences.about=Informazioni
@@ -217,12 +220,10 @@ main.dropZone.dropVault=Aggiungi questa cassaforte
main.dropZone.unknownDragboardContent=Se vuoi aggiungere una cassaforte, trascinala in questa finestra
## Vault List
main.vaultlist.emptyList.onboardingInstruction=Clicca qui per aggiungere una cassaforte
main.vaultlist.contextMenu.remove=Rimuovi…
main.vaultlist.contextMenu.lock=Blocca
main.vaultlist.contextMenu.unlock=Sblocca…
main.vaultlist.contextMenu.unlockNow=Sblocca Ora
main.vaultlist.contextMenu.vaultoptions=Mostra Opzioni Cassaforte
main.vaultlist.contextMenu.reveal=Rivela Drive
main.vaultlist.contextMenu.unlockNow=Sblocca adesso
main.vaultlist.contextMenu.reveal=Visualizza disco
main.vaultlist.addVaultBtn=Aggiungi Cassaforte
## Vault Detail
### Welcome
@@ -44,10 +44,8 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=ユーザー設定
addvaultwizard.new.directoryPickerButton=選択...
addvaultwizard.new.directoryPickerTitle=ディレクトリを選択
addvaultwizard.new.fileAlreadyExists=金庫名と同じ名前のファイルまたはディレクトリがすでに存在しています
addvaultwizard.new.locationDoesNotExist=指定されたパスのディレクトリが存在しないかアクセスできません
addvaultwizard.new.locationIsNotWritable=指定されたパスに書き込み権限がありません
addvaultwizard.new.locationIsOk=金庫に適した場所
addvaultwizard.new.fileAlreadyExists=いくつかのオブジェクトが既に存在しているため、このパスに金庫を作成することはできません。
addvaultwizard.new.locationDoesNotExist=このパスには階層がないため金庫を作成することができません
addvaultwizard.new.invalidName=無効な金庫の名前です。一般的なディレクトリの名前を検討してください。
### Password
addvaultwizard.new.createVaultBtn=金庫を作成
@@ -100,7 +98,6 @@ unlock.passwordPrompt="%s" のパスワードを入力してください:
unlock.savePassword=パスワードを記憶させる
unlock.unlockBtn=解錠
## Success
unlock.success.message="%s" の解錠に成功しました! 仮想ドライブから金庫にアクセス可能です。
unlock.success.rememberChoice=選択を記憶させて、再度表示しない
unlock.success.revealBtn=ドライブを表示
## Failure
@@ -176,14 +173,11 @@ preferences.updates.currentVersion=現在のバージョン: %s
preferences.updates.autoUpdateCheck=自動的に更新を確認する
preferences.updates.checkNowBtn=今すぐ確認
preferences.updates.updateAvailable=利用可能なバージョン %s に更新します。
## Contribution
preferences.contribute=サポートする
preferences.contribute.registeredFor=サポーター証明書が %s 登録されました
preferences.contribute.noCertificate=Cryptomator を支援し、サポーター証明書を受け取りましょう。ライセンスキーに似ていますがフリーソフトを使う寄付者向けのキーです。 ;-)
preferences.contribute.getCertificate=まだ証明書を手に入れていませんか? 詳細を確認しましょう。
preferences.contribute.promptText=サポーター証明書をここに張り付けてください
#<-- Add entries for donations and code/translation/documentation contribution -->
## Donation Key
preferences.donationKey=寄付
preferences.donationKey.registeredFor=%s 登録されています
preferences.donationKey.noDonationKey=有効な Donation Key が見つかりませんでした。ライセンスキーに似ていますがフリーソフトを使う寄付者向けのキーです。 ;-)
preferences.donationKey.getDonationKey=Donation Key を入手する
## About
preferences.about=詳細情報
@@ -228,11 +222,9 @@ main.dropZone.dropVault=この金庫を追加
main.dropZone.unknownDragboardContent=このウィンドウにドラッグして、金庫を追加
## Vault List
main.vaultlist.emptyList.onboardingInstruction=ここをクリックして金庫を追加
main.vaultlist.contextMenu.remove=削除...
main.vaultlist.contextMenu.lock=施錠
main.vaultlist.contextMenu.unlock=解錠...
main.vaultlist.contextMenu.unlockNow=今すぐ解錠
main.vaultlist.contextMenu.vaultoptions=金庫のオプションを表示
main.vaultlist.contextMenu.reveal=ドライブを表示
main.vaultlist.addVaultBtn=金庫を追加
## Vault Detail
@@ -44,10 +44,8 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=사용자 지정 위치
addvaultwizard.new.directoryPickerButton=선택
addvaultwizard.new.directoryPickerTitle=디렉터리 선택
addvaultwizard.new.fileAlreadyExists=Vault 내에 이미 존재하는 파일 또는 디렉터리 이름입니다.
addvaultwizard.new.locationDoesNotExist=지정된 디렉터리가 존재하지 않거나 접근 할 수 없습니다.
addvaultwizard.new.locationIsNotWritable=지정된 경로에 쓰기 권한이 없습니다.
addvaultwizard.new.locationIsOk=Vault에 적당한 위치
addvaultwizard.new.fileAlreadyExists=이미 다른 객체가 존재하고 있어 해당 경로에 Vault를 생성할 수 없습니다.
addvaultwizard.new.locationDoesNotExist=하나 이상의 경로 구성 요소가 없기 때문에 이 경로에 Vault가 생성 될 수 없습니다.
addvaultwizard.new.invalidName=유효하지 않은 Vault 이름입니다. 일반적인 디렉터리 이름으로 지정해주십시요.
### Password
addvaultwizard.new.createVaultBtn=Vault 생성
@@ -100,7 +98,6 @@ unlock.passwordPrompt="%s"의 비밀번호를 입력하십시요.
unlock.savePassword=비밀번호 기억
unlock.unlockBtn=잠금해제
## Success
unlock.success.message="%s"이(가) 성공적으로 잠금해제되었습니다. 이제 이 Vault를 가상드라이브로 접근할 수 있습니다.
unlock.success.rememberChoice=선택 기억함, 다시 묻지 않음
unlock.success.revealBtn=드라이브 표시
## Failure
@@ -176,9 +173,11 @@ preferences.updates.currentVersion=현재 버전: %s
preferences.updates.autoUpdateCheck=자동으로 업데이트 확인
preferences.updates.checkNowBtn=지금 확인
preferences.updates.updateAvailable=버전 %s의 업데이트가 가능합니다.
## Contribution
#<-- Add entries for donations and code/translation/documentation contribution -->
## Donation Key
preferences.donationKey=기부하기
preferences.donationKey.registeredFor=%s (으)로 등록되어 있습니다.
preferences.donationKey.noDonationKey=유효한 도네이션(기부) 키를 찾지 못했습니다. 라이센스 키와 비슷하나, 무료 소프트웨어를 사용하는 멋진 사람들을 위한 것입니다. ;-)
preferences.donationKey.getDonationKey=도네이션(기부) 키 얻기
## About
preferences.about=제품 정보
@@ -223,11 +222,9 @@ main.dropZone.dropVault=이 Vault를 추가
main.dropZone.unknownDragboardContent=Vault를 추가하려면, 이 창에 드래그 하십시요.
## Vault List
main.vaultlist.emptyList.onboardingInstruction=Vault를 추가하기 위해 이곳을 클릭합니다.
main.vaultlist.contextMenu.remove=제거...
main.vaultlist.contextMenu.lock=잠금
main.vaultlist.contextMenu.unlock=잠금해제...
main.vaultlist.contextMenu.unlockNow=지금 잠금해제
main.vaultlist.contextMenu.vaultoptions=Vault 옵션 보기
main.vaultlist.contextMenu.reveal=드라이브 표시
main.vaultlist.addVaultBtn=Vault 추가
## Vault Detail
@@ -43,6 +43,7 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Pielāgota atrašanās vieta
addvaultwizard.new.directoryPickerButton=Izvēlies...
addvaultwizard.new.directoryPickerTitle=Izvēlēties mapi
addvaultwizard.new.fileAlreadyExists=Glabātuvi nevar izveidot šajā ceļā, jo kāds objekts jau pastāv.
addvaultwizard.new.invalidName=Nederīgs glabātuves nosaukums. Lūdzu izvēlieties vienkāršu mapes nosaukumu.
### Password
addvaultwizard.new.createVaultBtn=Izveidot glabātuvi
@@ -147,9 +148,11 @@ preferences.updates.currentVersion=Pašreizējā versija: %s
preferences.updates.autoUpdateCheck=Automātiski pārbaudīt atjauninājumus
preferences.updates.checkNowBtn=Pārbaudīt tagad
preferences.updates.updateAvailable=Pieejams atjauninājums uz versiju %s.
## Contribution
#<-- Add entries for donations and code/translation/documentation contribution -->
## Donation Key
preferences.donationKey=Ziedojums
preferences.donationKey.registeredFor=Reģistrēts uz %s
preferences.donationKey.noDonationKey=Nav atrasta derīga ziedojuma atslēga. Tā ir kā licences atslēga , bet priekš satriecošiem cilvēkiem, kas izmanto bezmaksas programmatūru. ;-)
preferences.donationKey.getDonationKey=Iegūt ziedojuma atslēgu
## About
preferences.about=Par lietotni
@@ -44,6 +44,8 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Tilpasset lagringssted
addvaultwizard.new.directoryPickerButton=Velg…
addvaultwizard.new.directoryPickerTitle=Velg mappe
addvaultwizard.new.fileAlreadyExists=Hvelvet kan ikke opprettes på denne søkestien fordi et objekt allerede eksisterer.
addvaultwizard.new.locationDoesNotExist=Hvelvet kan ikke opprettes på denne søkestien fordi minst én søkestikomponent ikke finnes.
addvaultwizard.new.invalidName=Ugyldig navn på hvelvet. Vennligst vurder et vanlig mappenavn.
### Password
addvaultwizard.new.createVaultBtn=Opprett hvelv
@@ -171,9 +173,11 @@ preferences.updates.currentVersion=Gjeldende versjon: %s
preferences.updates.autoUpdateCheck=Se etter oppdateringer automatisk
preferences.updates.checkNowBtn=Sjekk nå
preferences.updates.updateAvailable=Oppdatering til versjon %s er tilgjengelig.
## Contribution
#<-- Add entries for donations and code/translation/documentation contribution -->
## Donation Key
preferences.donationKey=Donasjon
preferences.donationKey.registeredFor=Registrert for %s
preferences.donationKey.noDonationKey=Ingen gyldig donasjonsnøkkel funnet. Det er som en lisensnøkkel, men for fantastiske mennesker som bruker gratis programvare. ;-)
preferences.donationKey.getDonationKey=Få en donasjonsnøkkel
## About
preferences.about=Om
@@ -44,6 +44,8 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Andere locatie
addvaultwizard.new.directoryPickerButton=Kies…
addvaultwizard.new.directoryPickerTitle=Selecteer map
addvaultwizard.new.fileAlreadyExists=Er kan op deze locatie geen kluis aangemaakt worden, omdat er een bepaald object bestaat.
addvaultwizard.new.locationDoesNotExist=Er kan geen kluis op dit pad aangemaakt worden omdat ten minste één onderdeel van het pad niet bestaat.
addvaultwizard.new.invalidName=Ongeldige kluisnaam. Overweeg een standaard mapnaam.
### Password
addvaultwizard.new.createVaultBtn=Kluis aanmaken
@@ -170,9 +172,11 @@ preferences.updates.currentVersion=Huidige Versie: "%s"
preferences.updates.autoUpdateCheck=Automatisch controleren op updates
preferences.updates.checkNowBtn=Controleer nu
preferences.updates.updateAvailable=Update naar versie "%s" beschikbaar.
## Contribution
#<-- Add entries for donations and code/translation/documentation contribution -->
## Donation Key
preferences.donationKey=Donatie
preferences.donationKey.registeredFor=Geregistreerd voor %s
preferences.donationKey.noDonationKey=Geen geldige donatiesleutel gevonden. Het is als een licentiesleutel, maar dan voor geweldige mensen die gratis software gebruiken. ;-)
preferences.donationKey.getDonationKey=Verkrijg een donatiesleutel
## About
preferences.about=Over
@@ -43,6 +43,8 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Tilpassa lagringsstad
addvaultwizard.new.directoryPickerButton=Vel…
addvaultwizard.new.directoryPickerTitle=Vel mappe
addvaultwizard.new.fileAlreadyExists=Kvelven kan ikkje opprettast på denne søkestien fordi eit objekt allereie eksisterer her.
addvaultwizard.new.locationDoesNotExist=Kvelven kan ikkje opprettast på denne søkjestien fordi minst éin søkjestikomponent ikkje finst.
addvaultwizard.new.invalidName=Ugyldig namn på kvelven. Ver vennleg og vurder eit vanleg mappenamn.
### Password
addvaultwizard.new.createVaultBtn=Opprett kvelven
@@ -153,9 +155,11 @@ preferences.updates.currentVersion=Gjeldande versjon: %s
preferences.updates.autoUpdateCheck=Sjå etter oppdateringar automatisk
preferences.updates.checkNowBtn=Sjekk no
preferences.updates.updateAvailable=Oppdatering til versjon %s er tilgjengeleg.
## Contribution
#<-- Add entries for donations and code/translation/documentation contribution -->
## Donation Key
preferences.donationKey=Donasjon
preferences.donationKey.registeredFor=Registrert for %s
preferences.donationKey.noDonationKey=Ingen gyldig donasjonsnøkkel funne. Det er som ein lisensnøkkel, men for fantastiske menneske som bruker gratis programvare. ;-)
preferences.donationKey.getDonationKey=Få ein donasjonsnøkkel
## About
preferences.about=Om
@@ -44,6 +44,8 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=ਪਸੰਦੀਦਾ ਟਿਕਾਣਾ
addvaultwizard.new.directoryPickerButton=…ਚੁਣੋ
addvaultwizard.new.directoryPickerTitle=ਡਾਇਰੈਕਟਰੀ ਚੁਣੋ
addvaultwizard.new.fileAlreadyExists=ਇਸ ਮਾਰਗ ਉੱਤੇ ਵਾਲਟ ਨਹੀਂ ਬਣਾਇਆ ਜਾ ਸਕਦਾ ਹੈ, ਕਿਉਂਕਿ ਕੁਝ ਚੀਜ਼ਾਂ ਪਹਿਲਾਂ ਹੀ ਮੌਜੂਦ ਹਨ।
addvaultwizard.new.locationDoesNotExist=ਇਸ ਮਾਰਗ ਉੱਤੇ ਵਾਲੇਟ ਨਹੀਂ ਬਣਾਇਆ ਜਾ ਸਕਦਾ, ਕਿਉਂਕਿ ਮਾਰਗ ਦਾ ਇੱਕ ਭਾਗ ਮੌਜੂਦ ਨਹੀਂ ਹੈ।
addvaultwizard.new.invalidName=ਵਾਲਟ ਦਾ ਨਾਂ ਗਲਤ ਹੈ। ਨਿਯਮਤ ਡਾਇਰੈਕਟਰੀ ਨਾਂ ਦੇਣ ਬਾਰੇ ਵਿਚਾਰੋ।
### Password
addvaultwizard.new.createVaultBtn=ਵਾਲਟ ਬਣਾਓ
@@ -171,9 +173,11 @@ preferences.updates.currentVersion=ਮੌਜੂਦਾ ਵਰਜ਼ਨ: %s
preferences.updates.autoUpdateCheck=ਅੱਪਡੇਟ ਲਈ ਆਪਣੇ-ਆਪ ਜਾਂਚ ਕਰੋ
preferences.updates.checkNowBtn=ਹੁਣੇ ਜਾਂਚ ਕਰੋ
preferences.updates.updateAvailable=ਉਪਲੱਬਧ %s ਵਰਜ਼ਨ ਲਈ ਅੱਪਡੇਟ ਕਰੋ।
## Contribution
#<-- Add entries for donations and code/translation/documentation contribution -->
## Donation Key
preferences.donationKey=ਦਾਨ ਦਿਓ
preferences.donationKey.registeredFor=%s ਲਈ ਰਜਿਸਟਰ ਕਰੋ
preferences.donationKey.noDonationKey=ਕੋਈ ਵਾਜਬ ਦਾਨ ਕੁੰਜੀ ਨਹੀਂ ਲੱਭੀ ਹੈ। ਇਹ ਲਸੰਸ ਕੁੰਜੀ ਵਾਂਗ ਹੀ ਹੈ, ਪਰ ਮੁਫ਼ਤ ਸਾਫਟਵੇਅਰ ਵਰਤਣ ਵਾਲੇ ਬੇਮਿਸਾਲ ਲੋਕਾਂ ਲਈ। ;-)
preferences.donationKey.getDonationKey=ਦਾਨ ਕੁੰਜੀ ਦਿਓ
## About
preferences.about=ਇਸ ਬਾਰੇ
@@ -44,10 +44,8 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Własna lokalizacja
addvaultwizard.new.directoryPickerButton=Wybierz…
addvaultwizard.new.directoryPickerTitle=Wybierz katalog
addvaultwizard.new.fileAlreadyExists=Plik lub katalog z nazwą sejfu już istnieje
addvaultwizard.new.locationDoesNotExist=Katalog w określonej ścieżce nie istnieje lub nie można uzyskać dostępu
addvaultwizard.new.locationIsNotWritable=Brak możliwości zapisu w określonej ścieżce
addvaultwizard.new.locationIsOk=Odpowiednia lokalizacja dla Twojego sejfu
addvaultwizard.new.fileAlreadyExists=Sejf nie może być utworzony w tym miejscu, ponieważ jakiś obiekt już istnieje.
addvaultwizard.new.locationDoesNotExist=Sejf nie może być utworzony w tej ścieżce, ponieważ co najmniej jeden katalog nie istnieje.
addvaultwizard.new.invalidName=Nieprawidłowa nazwa sejfu. Proszę spróbować prawidłową nazwę dla katalogu.
### Password
addvaultwizard.new.createVaultBtn=Utwórz sejf
@@ -102,7 +100,7 @@ unlock.unlockBtn=Odblokuj
## Success
unlock.success.message=Odblokowano "%s" pomyślnie! Twój sejf jest teraz dostępny za pomocą dysku wirtualnego.
unlock.success.rememberChoice=Zapamiętaj wybór, nie pokazuj tego ponownie
unlock.success.revealBtn=Pokaż Dysk
unlock.success.revealBtn=Ujawnij Dysk
## Failure
unlock.error.heading=Nie można odblokować sejfu
### Invalid Mount Point
@@ -176,14 +174,11 @@ preferences.updates.currentVersion=Obecna wersja: %s
preferences.updates.autoUpdateCheck=Automatycznie sprawdzaj aktualizacje
preferences.updates.checkNowBtn=Sprawdź teraz
preferences.updates.updateAvailable=Dostępna jest aktualizacja do wersji %s.
## Contribution
preferences.contribute=Wesprzyj nas
preferences.contribute.registeredFor=Certyfikat darczyńcy zarejestrowany dla %s
preferences.contribute.noCertificate=Wspomóż Cryptomator i otrzymaj certyfikat darczyńcy. Jest on jak klucz licencyjny, ale dla niesamowitych ludzi korzystających z darmowego oprogramowania. ;-)
preferences.contribute.getCertificate=Nie masz jeszcze? Dowiedz się, jak możesz to uzyskać.
preferences.contribute.promptText=Wklej tutaj kod certyfikatu darczyńcy
#<-- Add entries for donations and code/translation/documentation contribution -->
## Donation Key
preferences.donationKey=Dotacja
preferences.donationKey.registeredFor=Zarejestrowano dla %s
preferences.donationKey.noDonationKey=Nie znaleziono aktualnego klucza donacji. To taki klucz licencyjny, ale dla niesamowitych ludzi używających darmowego softu. ;-)
preferences.donationKey.getDonationKey=Zdobądź klucz donacji
## About
preferences.about=O programie
@@ -43,6 +43,8 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Outro Local
addvaultwizard.new.directoryPickerButton=Escolher…
addvaultwizard.new.directoryPickerTitle=Selecionar diretório
addvaultwizard.new.fileAlreadyExists=O cofre não pode ser criado neste diretório porque algum objeto já existe.
addvaultwizard.new.locationDoesNotExist=O cofre não pode ser criado neste diretório porque pelo menos um componente de diretório não existe.
addvaultwizard.new.invalidName=Nome de cofre inválido. Por favor considere um nome de diretório regular.
### Password
addvaultwizard.new.createVaultBtn=Criar Cofre
@@ -118,9 +120,11 @@ preferences.updates.currentVersion=Versão atual: %s
preferences.updates.autoUpdateCheck=Verificar automaticamente por atualizações
preferences.updates.checkNowBtn=Verificar Agora
preferences.updates.updateAvailable=Atualização para a versão %s disponível.
## Contribution
#<-- Add entries for donations and code/translation/documentation contribution -->
## Donation Key
preferences.donationKey=Doação
preferences.donationKey.registeredFor=Registado para %s
preferences.donationKey.noDonationKey=Não foi encontrada uma chave de doação válida. É como uma chave de licença, mas para pessoas incríveis que usam software livre. ;-)
preferences.donationKey.getDonationKey=Obter chave de doação
## About
preferences.about=Sobre
@@ -44,10 +44,8 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Local Personalizado
addvaultwizard.new.directoryPickerButton=Escolher…
addvaultwizard.new.directoryPickerTitle=Selecionar Diretório
addvaultwizard.new.fileAlreadyExists=Um arquivo ou diretório com o nome do cofre já existe
addvaultwizard.new.locationDoesNotExist=Um diretório no caminho especificado não existe ou não pode ser acessado
addvaultwizard.new.locationIsNotWritable=Sem acesso de escrita no caminho especificado
addvaultwizard.new.locationIsOk=Localização adequada para o seu cofre
addvaultwizard.new.fileAlreadyExists=O cofre não pode ser criado neste local porque algum objeto já existe.
addvaultwizard.new.locationDoesNotExist=O cofre não pode ser criado neste caminho porque pelo menos um componente do caminho não existe.
addvaultwizard.new.invalidName=Nome de cofre inválido. Por favor considere usar um nome de diretório comum.
### Password
addvaultwizard.new.createVaultBtn=Criar Cofre
@@ -100,7 +98,6 @@ unlock.passwordPrompt=Digite a senha para "%s":
unlock.savePassword=Lembrar Senha
unlock.unlockBtn=Desbloquear
## Success
unlock.success.message="%s" desbloqueado com êxito! Seu cofre agora está acessível na unidade virtual.
unlock.success.rememberChoice=Lembrar opção escolhida, não mostrar isto novamente
unlock.success.revealBtn=Revelar Volume
## Failure
@@ -176,14 +173,11 @@ preferences.updates.currentVersion=Versão atual: %s
preferences.updates.autoUpdateCheck=Buscar atualizações automaticamente
preferences.updates.checkNowBtn=Buscar Agora
preferences.updates.updateAvailable=Atualizar para versão %s disponível.
## Contribution
preferences.contribute=Nos Apoie
preferences.contribute.registeredFor=Certificado de apoiador registrado para %s
preferences.contribute.noCertificate=Apoie o Cryptomator e receba um certificado de apoiador. Ele é uma chave de licença mas é para pessoas maravilhosas que usam software livre. ;-)
preferences.contribute.getCertificate=Não tem um ainda? Saiba como obtê-lo.
preferences.contribute.promptText=Cole o código do certificado de apoiador aqui
#<-- Add entries for donations and code/translation/documentation contribution -->
## Donation Key
preferences.donationKey=Doação
preferences.donationKey.registeredFor=Registrado para %s
preferences.donationKey.noDonationKey=Nenhuma chave de doação válida encontrada. É como uma chave de licença, mas para pessoas incríveis usando software gratuito. ;-)
preferences.donationKey.getDonationKey=Obtenha uma chave de doação
## About
preferences.about=Sobre
@@ -228,11 +222,9 @@ main.dropZone.dropVault=Adicionar este cofre
main.dropZone.unknownDragboardContent=Se você quer adicionar um cofre, arraste-o para esta janela
## Vault List
main.vaultlist.emptyList.onboardingInstruction=Clique aqui para adicionar um cofre
main.vaultlist.contextMenu.remove=Remover…
main.vaultlist.contextMenu.lock=Bloquear
main.vaultlist.contextMenu.unlock=Desbloquear…
main.vaultlist.contextMenu.unlockNow=Desbloquear Agora
main.vaultlist.contextMenu.vaultoptions=Exibir Opções de Cofre
main.vaultlist.contextMenu.reveal=Revelar Volume
main.vaultlist.addVaultBtn=Adicionar Cofre
## Vault Detail
@@ -44,9 +44,7 @@
## General
## Volume
## Updates
## Contribution
#<-- Add entries for donations and code/translation/documentation contribution -->
## Donation Key
## About
# Vault Statistics
@@ -44,10 +44,8 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Своё место
addvaultwizard.new.directoryPickerButton=Выбрать…
addvaultwizard.new.directoryPickerTitle=Выберите папку
addvaultwizard.new.fileAlreadyExists=Файл или папка с именем хранилища уже существует
addvaultwizard.new.locationDoesNotExist=Папка с указанным адресом не существует, или недоступна
addvaultwizard.new.locationIsNotWritable=Указанный адрес только для чтения
addvaultwizard.new.locationIsOk=Подходящее расположение для вашего хранилища
addvaultwizard.new.fileAlreadyExists=Хранилище не может быть создано по этому пути, потому что некоторые объекты уже существуют.
addvaultwizard.new.locationDoesNotExist=Хранилище не может быть создано по этому пути, потому что по крайней мере один компонент пути не существует.
addvaultwizard.new.invalidName=Неверное имя хранилища. Укажите корректное имя папки.
### Password
addvaultwizard.new.createVaultBtn=Создать хранилище
@@ -176,14 +174,11 @@ preferences.updates.currentVersion=Текущая версия: %s
preferences.updates.autoUpdateCheck=Автоматически проверять наличие обновлений
preferences.updates.checkNowBtn=Проверить
preferences.updates.updateAvailable=Доступно обновление до версии %s.
## Contribution
preferences.contribute=Поддержите нас
preferences.contribute.registeredFor=Сертификат мецената зарегистрирован на имя %s
preferences.contribute.noCertificate=Поддержите Cryptomator и получите сертификат мецената. Он действует как лицензионный ключ, но для тех, кто пользуется бесплатным ПО. ;-)
preferences.contribute.getCertificate=Ещё нет сертификата? Узнайте, как его получить.
preferences.contribute.promptText=Вставьте сюда код сертификата мецената
#<-- Add entries for donations and code/translation/documentation contribution -->
## Donation Key
preferences.donationKey=Пожертвование
preferences.donationKey.registeredFor=Зарегистрировано: %s
preferences.donationKey.noDonationKey=Не найден ключ пожертвования. Этот ключ похож на лицензионный, но для тех, кто использует бесплатное ПО. ;-)
preferences.donationKey.getDonationKey=Получить ключ пожертвования
## About
preferences.about=О программе
@@ -43,6 +43,8 @@ addvaultwizard.new.locationLabel=Umiestnenie úložiska
addvaultwizard.new.directoryPickerLabel=Vlastné umiestnenie
addvaultwizard.new.directoryPickerButton=Vybrať…
addvaultwizard.new.directoryPickerTitle=Vybrať adresár
addvaultwizard.new.fileAlreadyExists=Na tejto ceste nie je možné vytvoriť trezor, pretože niektorý objekt už existuje.
addvaultwizard.new.locationDoesNotExist=Peňaženka sa nedá vytvoriť na danej ceste pretože ani jedna cesta neexistuje.
addvaultwizard.new.invalidName=Neplatný názov trezoru. Zvážte bežný názov adresára.
### Password
addvaultwizard.new.createVaultBtn=Vytvoriť trezor
@@ -94,8 +96,6 @@ unlock.passwordPrompt=Zadajte heslo pre "%s":
unlock.savePassword=Odomknúť.uložiťHeslo
unlock.unlockBtn=Odomknúť
## Success
unlock.success.message=Odomknutie "%s" úspešné! Vaša peňaženka je už prístupná cez jej virtuálny disk.
unlock.success.revealBtn=Odkry disk
## Failure
unlock.error.heading=Nie je možné odomknúť účet
### Invalid Mount Point
@@ -131,14 +131,7 @@ preferences.general.keychainBackend.org.cryptomator.macos.keychain.MacSystemKeyc
preferences.general.keychainBackend.org.cryptomator.windows.keychain.WindowsProtectedKeychainAccess=Windows ochrana dát
## Volume
## Updates
## Contribution
preferences.contribute=Podporte nás
preferences.contribute.registeredFor=Certifikát podporovateľa registrovaný na %s
preferences.contribute.noCertificate=Podpor Cryptomator a získaj certifikát podporovateľa. Je to ako licenčný klúč ale dobrí ľudia používajú voľný software. ;-)
preferences.contribute.getCertificate=Ešte ho nemáš? Sleduj ako ho vieš získať.
preferences.contribute.promptText=Vlož sem kód certifikátu podporovateľa
#<-- Add entries for donations and code/translation/documentation contribution -->
## Donation Key
## About
# Vault Statistics
@@ -177,12 +170,9 @@ main.preferencesBtn.tooltip=Predvoľby
main.debugModeEnabled.tooltip=Debagovací mód je aktivovaný
## Drag 'n' Drop
## Vault List
main.vaultlist.contextMenu.remove=Odstráň…
main.vaultlist.contextMenu.lock=Uzamknúť
main.vaultlist.contextMenu.unlock=Odomknúť…
main.vaultlist.contextMenu.unlockNow=Odomknúť teraz
main.vaultlist.contextMenu.vaultoptions=Ukáž možnosti peňaženky
main.vaultlist.contextMenu.reveal=Odkry disk
main.vaultlist.addVaultBtn=Pridať trezor
## Vault Detail
### Welcome
@@ -191,7 +181,6 @@ main.vaultDetail.unlockBtn=Odomknúť…
main.vaultDetail.unlockNowBtn=Odomknúť teraz
main.vaultDetail.passwordSavedInKeychain=Heslo uložené
### Unlocked
main.vaultDetail.revealBtn=Odkry disk
main.vaultDetail.lockBtn=Uzamknúť
main.vaultDetail.bytesPerSecondRead=Čítanie:
main.vaultDetail.bytesPerSecondWritten=Zápis:
@@ -206,7 +195,6 @@ main.vaultDetail.missing.remove=Odstrániť zo zoznamu peňaženky…
# Vault Options
## General
vaultOptions.general.vaultName=Názov trezoru
vaultOptions.general.actionAfterUnlock.reveal=Odkry disk
## Mount
vaultOptions.mount.mountPoint.directoryPickerButton=Vybrať…
## Master Key
@@ -44,9 +44,7 @@
## General
## Volume
## Updates
## Contribution
#<-- Add entries for donations and code/translation/documentation contribution -->
## Donation Key
## About
# Vault Statistics
@@ -1,77 +0,0 @@
# 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
## Success
## Failure
### Invalid Mount Point
# Lock
## Force
## Failure
# Migration
## Start
## Run
## Sucess
## Missing file system capabilities
## Impossible
# 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
# Wrong File Alert
# Vault Options
## General
## Mount
## Master Key
# Recovery Key
# New Password
# Quit
@@ -44,6 +44,8 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Anpassad plats
addvaultwizard.new.directoryPickerButton=Välj…
addvaultwizard.new.directoryPickerTitle=Välj katalog
addvaultwizard.new.fileAlreadyExists=Valvet kan inte skapas på denna plats då vissa filer redan existerar.
addvaultwizard.new.locationDoesNotExist=Valvet kan inte skapas på den här platsen då åtminstone en komponent inte finns.
addvaultwizard.new.invalidName=Felaktigt namn på valvet. Vänligen ange ett vanligt katalognamn.
### Password
addvaultwizard.new.createVaultBtn=Skapa Valv
@@ -96,7 +98,6 @@ unlock.passwordPrompt=Ange lösenord för "%s":
unlock.savePassword=Kom ihåg lösenord
unlock.unlockBtn=Lås upp
## Success
unlock.success.message="%s" upplåst! Ditt valv är nu åtkomligt från den virtuella enheten.
unlock.success.rememberChoice=Kom ihåg valet, visa inte detta igen
unlock.success.revealBtn=Visa enhet
## Failure
@@ -172,9 +173,11 @@ preferences.updates.currentVersion=Nuvarande version: %s
preferences.updates.autoUpdateCheck=Sök efter uppdateringar automatiskt
preferences.updates.checkNowBtn=Kontrollera nu
preferences.updates.updateAvailable=Uppdatering till version %s finns tillgänglig.
## Contribution
#<-- Add entries for donations and code/translation/documentation contribution -->
## Donation Key
preferences.donationKey=Donation
preferences.donationKey.registeredFor=Registrerad till %s
preferences.donationKey.noDonationKey=Ingen giltig donationsnyckel funnen. Det är som en licensnyckel fast för grymma personer som kör fri mjukvara ;-)
preferences.donationKey.getDonationKey=Skaffa en donationsnyckel
## About
preferences.about=Om
@@ -219,11 +222,9 @@ main.dropZone.dropVault=Lägg till detta valv
main.dropZone.unknownDragboardContent=Om du vill lägga till ett valv, dra in det till detta fönster
## Vault List
main.vaultlist.emptyList.onboardingInstruction=Klicka här för att lägga till valv
main.vaultlist.contextMenu.remove=Ta bort…
main.vaultlist.contextMenu.lock=Lås
main.vaultlist.contextMenu.unlock=Lås upp…
main.vaultlist.contextMenu.unlockNow=Lås upp nu
main.vaultlist.contextMenu.vaultoptions=Visa inställningar för valv
main.vaultlist.contextMenu.reveal=Visa enhet
main.vaultlist.addVaultBtn=Lägg till valv
## Vault Detail
@@ -44,10 +44,8 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Özel Konum
addvaultwizard.new.directoryPickerButton=Seç…
addvaultwizard.new.directoryPickerTitle=Dizin Seç
addvaultwizard.new.fileAlreadyExists=Kasa adına sahip bir dosya ya da dizin zaten mevcut
addvaultwizard.new.locationDoesNotExist=Belirtilen yoldaki bir dizin mevcut değil veya erişilemez
addvaultwizard.new.locationIsNotWritable=Belirtilen yolda yazma erişimi yok
addvaultwizard.new.locationIsOk=Kasanız için uygun yer
addvaultwizard.new.fileAlreadyExists=Bazı nesneler varolduğu için kasa bu yolda oluşturulamıyor.
addvaultwizard.new.locationDoesNotExist=En az bir yol bileşeni varolmadığı için kasa bu yolda oluşturulamıyor.
addvaultwizard.new.invalidName=Geçersiz kasa adı. Lütfen normal bir dizin adı kullanın.
### Password
addvaultwizard.new.createVaultBtn=Kasa Oluştur
@@ -176,14 +174,11 @@ preferences.updates.currentVersion=Şuanki Sürüm: %s
preferences.updates.autoUpdateCheck=Güncellemeleri otomatik kontrol et
preferences.updates.checkNowBtn=Şimdi Kontrol Et
preferences.updates.updateAvailable=%s sürümüne güncelleme mevcut.
## Contribution
preferences.contribute=Bizi Destekle
preferences.contribute.registeredFor=%s için destekçi sertifikası kaydedildi
preferences.contribute.noCertificate=Cryptomator'u destekle ve bir destekçi sertifikası al. Bu bir lisans anahtarı gibidir ama ücretsiz program kullanan müthiş insanlar için. ;-)
preferences.contribute.getCertificate=Herhangi bir tane yok mu? Nasıl elde edebileceğinizi öğrenin.
preferences.contribute.promptText=Destekçi sertifika kodunu buraya yapıştırın
#<-- Add entries for donations and code/translation/documentation contribution -->
## Donation Key
preferences.donationKey=Bağış
preferences.donationKey.registeredFor=%s için kaydedildi
preferences.donationKey.noDonationKey=Geçerli bir bağış anahtarı bulunamadı. Bu anahtar, lisans anahtarı gibidir ama ücretsiz uygulama kullanan muhteşem insanlar içindir ;-)
preferences.donationKey.getDonationKey=Bir bağış anahtarı al
## About
preferences.about=Hakkında
@@ -44,10 +44,8 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=自定义位置
addvaultwizard.new.directoryPickerButton=选择...
addvaultwizard.new.directoryPickerTitle=选择目录
addvaultwizard.new.fileAlreadyExists=已存在同名文件或文件夹
addvaultwizard.new.locationDoesNotExist=指定路径中的目录不存在或无法访问
addvaultwizard.new.locationIsNotWritable=没有对指定路径的写入权限
addvaultwizard.new.locationIsOk=合适的保险库路径
addvaultwizard.new.fileAlreadyExists=无法在此路径上创建保险库,因为某些对象已经存在。
addvaultwizard.new.locationDoesNotExist=无法在此路径上创建保险库,因为至少有一个路径组件不存在
addvaultwizard.new.invalidName=无效的保险库名称,请考虑一个常规的目录名称
### Password
addvaultwizard.new.createVaultBtn=创建保险库
@@ -100,7 +98,6 @@ unlock.passwordPrompt=输入 "%s" 的密码
unlock.savePassword=记住密码
unlock.unlockBtn=解锁
## Success
unlock.success.message=已成功解锁 "%s"! 您现在可以通过其虚拟驱动器访问它
unlock.success.rememberChoice=记住选项且不再显示
unlock.success.revealBtn=显示驱动器
## Failure
@@ -176,14 +173,11 @@ preferences.updates.currentVersion=当前版本:%s
preferences.updates.autoUpdateCheck=自动检查更新
preferences.updates.checkNowBtn=立即检查
preferences.updates.updateAvailable=可更新到版本 %s
## Contribution
preferences.contribute=支持我们
preferences.contribute.registeredFor=为 %s 注册支持者证书
preferences.contribute.noCertificate=支持 Cryptomator 并获得一份支持者证书。它类似于许可证密钥,特别之处是提供给使用免费软件的牛人的 ;-)
preferences.contribute.getCertificate=还没有该证书吗?了解您如何获取
preferences.contribute.promptText=请在此粘贴支持者证书代码
#<-- Add entries for donations and code/translation/documentation contribution -->
## Donation Key
preferences.donationKey=捐赠
preferences.donationKey.registeredFor=注册给 %s
preferences.donationKey.noDonationKey=找不到有效的捐赠密钥,它相当于一个专门给使用免费软件的帅哥美女的许可证密钥 ;-)
preferences.donationKey.getDonationKey=获取一个捐赠密钥
## About
preferences.about=关于
@@ -228,11 +222,9 @@ main.dropZone.dropVault=添加此保险库
main.dropZone.unknownDragboardContent=如果您想要添加一个保险库,将其拖动到此窗口
## Vault List
main.vaultlist.emptyList.onboardingInstruction=点击此处添加一个保险库
main.vaultlist.contextMenu.remove=删除…
main.vaultlist.contextMenu.lock=锁定
main.vaultlist.contextMenu.unlock=解锁…
main.vaultlist.contextMenu.unlockNow=立即解锁
main.vaultlist.contextMenu.vaultoptions=显示保险库选项
main.vaultlist.contextMenu.reveal=显示驱动器
main.vaultlist.addVaultBtn=添加保险库
## Vault Detail
@@ -44,10 +44,8 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=自訂位置
addvaultwizard.new.directoryPickerButton=選取
addvaultwizard.new.directoryPickerTitle=選取資料夾
addvaultwizard.new.fileAlreadyExists=存在與加密檔案庫同名的檔案或資料夾
addvaultwizard.new.locationDoesNotExist=指定的目錄不存在或無法存取
addvaultwizard.new.locationIsNotWritable=指定路徑沒有寫入權限
addvaultwizard.new.locationIsOk=可放置您的加密檔案庫
addvaultwizard.new.fileAlreadyExists=無法在這個路徑建立加密檔案庫,因為其中已有其他檔案。
addvaultwizard.new.locationDoesNotExist=無法在這個路徑建立加密檔案庫,因為部份路徑不存在。
addvaultwizard.new.invalidName=無效的加密檔案庫名稱。請考慮一般資料夾會使用的名稱。
### Password
addvaultwizard.new.createVaultBtn=新建加密檔案庫
@@ -176,14 +174,11 @@ preferences.updates.currentVersion=目前版本:%s
preferences.updates.autoUpdateCheck=勾選以啟用自動更新
preferences.updates.checkNowBtn=立即檢查
preferences.updates.updateAvailable=有版本 %s 可更新。
## Contribution
preferences.contribute=贊助我們
preferences.contribute.registeredFor=贊助憑證註冊給 %s
preferences.contribute.noCertificate=支持 Cryptomator 並取得一個贊助憑證。它就像一個授權金鑰,是給使用自由軟體的大好人的。;-)
preferences.contribute.getCertificate=還沒有嗎?看看您該如何取得它。
preferences.contribute.promptText=在這裡貼上贊助憑證
#<-- Add entries for donations and code/translation/documentation contribution -->
## Donation Key
preferences.donationKey=贊助
preferences.donationKey.registeredFor=註冊給 %s
preferences.donationKey.noDonationKey=未發現贊助金鑰。贊助金鑰就像授權金鑰,不過是給使用自由軟體的善心人士的。
preferences.donationKey.getDonationKey=取得贊助金鑰
## About
preferences.about=關於
@@ -61,10 +61,10 @@ Cryptomator uses 45 third-party dependencies under the following licenses:
- jnr-posix (com.github.jnr:jnr-posix:3.0.54 - http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix)
GPLv2+CE:
- Java Servlet API (javax.servlet:javax.servlet-api:4.0.1 - https://javaee.github.io/servlet-spec/)
- javafx-base (org.openjfx:javafx-base:16 - https://openjdk.java.net/projects/openjfx/javafx-base/)
- javafx-controls (org.openjfx:javafx-controls:16 - https://openjdk.java.net/projects/openjfx/javafx-controls/)
- javafx-fxml (org.openjfx:javafx-fxml:16 - https://openjdk.java.net/projects/openjfx/javafx-fxml/)
- javafx-graphics (org.openjfx:javafx-graphics:16 - https://openjdk.java.net/projects/openjfx/javafx-graphics/)
- javafx-base (org.openjfx:javafx-base:15 - https://openjdk.java.net/projects/openjfx/javafx-base/)
- javafx-controls (org.openjfx:javafx-controls:15 - https://openjdk.java.net/projects/openjfx/javafx-controls/)
- javafx-fxml (org.openjfx:javafx-fxml:15 - https://openjdk.java.net/projects/openjfx/javafx-fxml/)
- javafx-graphics (org.openjfx:javafx-graphics:15 - https://openjdk.java.net/projects/openjfx/javafx-graphics/)
LGPL 2.1:
- jnr-posix (com.github.jnr:jnr-posix:3.0.54 - http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix)
- Java Native Access (net.java.dev.jna:jna:5.7.0 - https://github.com/java-native-access/jna)