mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-09-20 15:04:23 +00:00
Merge branch 'release/1.1.4'
Fixes #308, fixes #319, fixes #318, fixes #317, fixes #311, fixes #267 # Conflicts: # main/ant-kit/pom.xml # main/commons-test/pom.xml # main/commons/pom.xml # main/filesystem-api/pom.xml # main/filesystem-charsets/pom.xml # main/filesystem-crypto-integration-tests/pom.xml # main/filesystem-crypto/pom.xml # main/filesystem-inmemory/pom.xml # main/filesystem-invariants-tests/pom.xml # main/filesystem-nameshortening/pom.xml # main/filesystem-nio/pom.xml # main/filesystem-stats/pom.xml # main/frontend-api/pom.xml # main/frontend-webdav/pom.xml # main/jacoco-report/pom.xml # main/pom.xml # main/uber-jar/pom.xml # main/ui/pom.xml
This commit is contained in:
+1
-1
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.1.3</version>
|
||||
<version>1.1.4</version>
|
||||
</parent>
|
||||
<artifactId>ui</artifactId>
|
||||
<name>Cryptomator GUI</name>
|
||||
|
||||
@@ -14,6 +14,7 @@ import javax.inject.Singleton;
|
||||
|
||||
import org.cryptomator.ui.controllers.MainController;
|
||||
import org.cryptomator.ui.settings.Localization;
|
||||
import org.cryptomator.ui.util.AsyncTaskService;
|
||||
import org.cryptomator.ui.util.DeferredCloser;
|
||||
|
||||
import dagger.Component;
|
||||
@@ -21,6 +22,9 @@ import dagger.Component;
|
||||
@Singleton
|
||||
@Component(modules = CryptomatorModule.class)
|
||||
interface CryptomatorComponent {
|
||||
|
||||
AsyncTaskService asyncTaskService();
|
||||
|
||||
ExecutorService executorService();
|
||||
|
||||
DeferredCloser deferredCloser();
|
||||
|
||||
@@ -17,13 +17,14 @@ import javax.inject.Singleton;
|
||||
import org.cryptomator.common.CommonsModule;
|
||||
import org.cryptomator.crypto.engine.impl.CryptoEngineModule;
|
||||
import org.cryptomator.frontend.FrontendFactory;
|
||||
import org.cryptomator.frontend.webdav.WebDavModule;
|
||||
import org.cryptomator.frontend.webdav.WebDavServer;
|
||||
import org.cryptomator.frontend.webdav.mount.WebDavMounter;
|
||||
import org.cryptomator.frontend.webdav.mount.WebDavMounterProvider;
|
||||
import org.cryptomator.ui.model.VaultObjectMapperProvider;
|
||||
import org.cryptomator.ui.settings.Settings;
|
||||
import org.cryptomator.ui.settings.SettingsProvider;
|
||||
import org.cryptomator.ui.util.DeferredCloser;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
@@ -32,9 +33,10 @@ import dagger.Provides;
|
||||
import javafx.application.Application;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
@Module(includes = {CryptoEngineModule.class, CommonsModule.class})
|
||||
@Module(includes = {CryptoEngineModule.class, CommonsModule.class, WebDavModule.class})
|
||||
class CryptomatorModule {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CryptomatorModule.class);
|
||||
private final Application application;
|
||||
private final Stage mainWindow;
|
||||
|
||||
@@ -60,7 +62,13 @@ class CryptomatorModule {
|
||||
@Singleton
|
||||
DeferredCloser provideDeferredCloser() {
|
||||
DeferredCloser closer = new DeferredCloser();
|
||||
Cryptomator.addShutdownTask(closer::close);
|
||||
Cryptomator.addShutdownTask(() -> {
|
||||
try {
|
||||
closer.close();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error during shutdown.", e);
|
||||
}
|
||||
});
|
||||
return closer;
|
||||
}
|
||||
|
||||
@@ -83,12 +91,6 @@ class CryptomatorModule {
|
||||
return closer.closeLater(Executors.newCachedThreadPool(), ExecutorService::shutdown).get().orElseThrow(IllegalStateException::new);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
WebDavMounter provideWebDavMounter(WebDavMounterProvider webDavMounterProvider) {
|
||||
return webDavMounterProvider.get();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
FrontendFactory provideFrontendFactory(DeferredCloser closer, WebDavServer webDavServer, Settings settings) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import java.io.IOException;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.ui.controllers.MainController;
|
||||
@@ -126,7 +127,11 @@ public class MainApplication extends Application {
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
closer.close();
|
||||
try {
|
||||
closer.close();
|
||||
} catch (ExecutionException e) {
|
||||
LOG.error("Error closing ressources", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.fxmisc.easybind.EasyBind;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.ChoiceBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
@@ -52,6 +53,12 @@ public class SettingsController extends LocalizedFXMLViewController {
|
||||
@FXML
|
||||
private Label versionLabel;
|
||||
|
||||
@FXML
|
||||
private Label prefGvfsSchemeLabel;
|
||||
|
||||
@FXML
|
||||
private ChoiceBox<String> prefGvfsScheme;
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
checkForUpdatesCheckbox.setDisable(areUpdatesManagedExternally());
|
||||
@@ -62,10 +69,16 @@ public class SettingsController extends LocalizedFXMLViewController {
|
||||
useIpv6Checkbox.setVisible(SystemUtils.IS_OS_WINDOWS);
|
||||
useIpv6Checkbox.setSelected(SystemUtils.IS_OS_WINDOWS && settings.shouldUseIpv6());
|
||||
versionLabel.setText(String.format(localization.getString("settings.version.label"), applicationVersion().orElse("SNAPSHOT")));
|
||||
prefGvfsSchemeLabel.setVisible(SystemUtils.IS_OS_LINUX);
|
||||
prefGvfsScheme.setVisible(SystemUtils.IS_OS_LINUX);
|
||||
prefGvfsScheme.getItems().add("dav");
|
||||
prefGvfsScheme.getItems().add("webdav");
|
||||
prefGvfsScheme.setValue(settings.getPreferredGvfsScheme());
|
||||
|
||||
EasyBind.subscribe(checkForUpdatesCheckbox.selectedProperty(), this::checkForUpdateDidChange);
|
||||
EasyBind.subscribe(portField.textProperty(), this::portDidChange);
|
||||
EasyBind.subscribe(useIpv6Checkbox.selectedProperty(), this::useIpv6DidChange);
|
||||
EasyBind.subscribe(prefGvfsScheme.valueProperty(), this::prefGvfsSchemeDidChange);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -101,6 +114,11 @@ public class SettingsController extends LocalizedFXMLViewController {
|
||||
settings.save();
|
||||
}
|
||||
|
||||
private void prefGvfsSchemeDidChange(String newValue) {
|
||||
settings.setPreferredGvfsScheme(newValue);
|
||||
settings.save();
|
||||
}
|
||||
|
||||
private void filterNumericKeyEvents(KeyEvent t) {
|
||||
if (t.getCharacter() == null || t.getCharacter().length() == 0) {
|
||||
return;
|
||||
|
||||
@@ -11,7 +11,6 @@ package org.cryptomator.ui.controllers;
|
||||
import java.net.URL;
|
||||
import java.util.Comparator;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -27,6 +26,7 @@ import org.cryptomator.ui.controls.SecPasswordField;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.cryptomator.ui.settings.Localization;
|
||||
import org.cryptomator.ui.settings.Settings;
|
||||
import org.cryptomator.ui.util.AsyncTaskService;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -56,7 +56,7 @@ public class UnlockController extends LocalizedFXMLViewController {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UnlockController.class);
|
||||
|
||||
private final Application app;
|
||||
private final ExecutorService exec;
|
||||
private final AsyncTaskService asyncTaskService;
|
||||
private final Lazy<FrontendFactory> frontendFactory;
|
||||
private final Settings settings;
|
||||
private final WindowsDriveLetters driveLetters;
|
||||
@@ -65,10 +65,10 @@ public class UnlockController extends LocalizedFXMLViewController {
|
||||
private Optional<UnlockListener> listener = Optional.empty();
|
||||
|
||||
@Inject
|
||||
public UnlockController(Application app, Localization localization, ExecutorService exec, Lazy<FrontendFactory> frontendFactory, Settings settings, WindowsDriveLetters driveLetters) {
|
||||
public UnlockController(Application app, Localization localization, AsyncTaskService asyncTaskService, Lazy<FrontendFactory> frontendFactory, Settings settings, WindowsDriveLetters driveLetters) {
|
||||
super(localization);
|
||||
this.app = app;
|
||||
this.exec = exec;
|
||||
this.asyncTaskService = asyncTaskService;
|
||||
this.frontendFactory = frontendFactory;
|
||||
this.settings = settings;
|
||||
this.driveLetters = driveLetters;
|
||||
@@ -246,6 +246,7 @@ public class UnlockController extends LocalizedFXMLViewController {
|
||||
return;
|
||||
}
|
||||
vault.get().setWinDriveLetter(newValue);
|
||||
settings.save();
|
||||
}
|
||||
|
||||
private void chooseSelectedDriveLetter() {
|
||||
@@ -274,8 +275,7 @@ public class UnlockController extends LocalizedFXMLViewController {
|
||||
progressIndicator.setVisible(true);
|
||||
downloadsPageLink.setVisible(false);
|
||||
CharSequence password = passwordField.getCharacters();
|
||||
exec.submit(() -> this.unlock(vault.get(), password));
|
||||
|
||||
asyncTaskService.asyncTaskOf(() -> this.unlock(vault.get(), password)).run();
|
||||
}
|
||||
|
||||
private void unlock(Vault vault, CharSequence password) {
|
||||
|
||||
@@ -10,7 +10,6 @@ package org.cryptomator.ui.controllers;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
@@ -19,6 +18,7 @@ import org.cryptomator.frontend.CommandFailedException;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.cryptomator.ui.settings.Localization;
|
||||
import org.cryptomator.ui.util.ActiveWindowStyleSupport;
|
||||
import org.cryptomator.ui.util.AsyncTaskService;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
|
||||
import javafx.animation.Animation;
|
||||
@@ -52,16 +52,16 @@ public class UnlockedController extends LocalizedFXMLViewController {
|
||||
|
||||
private final Stage macWarningsWindow = new Stage();
|
||||
private final MacWarningsController macWarningsController;
|
||||
private final ExecutorService exec;
|
||||
private final AsyncTaskService asyncTaskService;
|
||||
private final ObjectProperty<Vault> vault = new SimpleObjectProperty<>();
|
||||
private Optional<LockListener> listener = Optional.empty();
|
||||
private Timeline ioAnimation;
|
||||
|
||||
@Inject
|
||||
public UnlockedController(Localization localization, Provider<MacWarningsController> macWarningsControllerProvider, ExecutorService exec) {
|
||||
public UnlockedController(Localization localization, Provider<MacWarningsController> macWarningsControllerProvider, AsyncTaskService asyncTaskService) {
|
||||
super(localization);
|
||||
this.macWarningsController = macWarningsControllerProvider.get();
|
||||
this.exec = exec;
|
||||
this.asyncTaskService = asyncTaskService;
|
||||
|
||||
macWarningsController.vault.bind(this.vault);
|
||||
}
|
||||
@@ -116,18 +116,13 @@ public class UnlockedController extends LocalizedFXMLViewController {
|
||||
|
||||
@FXML
|
||||
private void didClickLockVault(ActionEvent event) {
|
||||
exec.submit(() -> {
|
||||
try {
|
||||
vault.get().unmount();
|
||||
} catch (CommandFailedException e) {
|
||||
Platform.runLater(() -> {
|
||||
messageLabel.setText(localization.getString("unlocked.label.unmountFailed"));
|
||||
});
|
||||
return;
|
||||
}
|
||||
asyncTaskService.asyncTaskOf(() -> {
|
||||
vault.get().deactivateFrontend();
|
||||
listener.ifPresent(this::invokeListenerLater);
|
||||
});
|
||||
}).onSuccess(() -> {
|
||||
listener.ifPresent(listener -> listener.didLock(this));
|
||||
}).onError(Exception.class, () -> {
|
||||
messageLabel.setText(localization.getString("unlocked.label.unmountFailed"));
|
||||
}).run();
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -142,15 +137,11 @@ public class UnlockedController extends LocalizedFXMLViewController {
|
||||
|
||||
@FXML
|
||||
private void didClickRevealVault(ActionEvent event) {
|
||||
exec.submit(() -> {
|
||||
try {
|
||||
vault.get().reveal();
|
||||
} catch (CommandFailedException e) {
|
||||
Platform.runLater(() -> {
|
||||
messageLabel.setText(localization.getString("unlocked.label.revealFailed"));
|
||||
});
|
||||
}
|
||||
});
|
||||
asyncTaskService.asyncTaskOf(() -> {
|
||||
vault.get().reveal();
|
||||
}).onError(CommandFailedException.class, () -> {
|
||||
messageLabel.setText(localization.getString("unlocked.label.revealFailed"));
|
||||
}).run();
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -258,12 +249,6 @@ public class UnlockedController extends LocalizedFXMLViewController {
|
||||
this.listener = Optional.ofNullable(listener);
|
||||
}
|
||||
|
||||
private void invokeListenerLater(LockListener listener) {
|
||||
Platform.runLater(() -> {
|
||||
listener.didLock(this);
|
||||
});
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface LockListener {
|
||||
void didLock(UnlockedController ctrl);
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.cryptomator.ui.controllers;
|
||||
import java.net.URL;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -13,11 +12,9 @@ import org.cryptomator.ui.model.UpgradeStrategy;
|
||||
import org.cryptomator.ui.model.UpgradeStrategy.UpgradeFailedException;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.cryptomator.ui.settings.Localization;
|
||||
import org.cryptomator.ui.util.AsyncTaskService;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.event.ActionEvent;
|
||||
@@ -28,19 +25,17 @@ import javafx.scene.control.ProgressIndicator;
|
||||
|
||||
public class UpgradeController extends LocalizedFXMLViewController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UpgradeController.class);
|
||||
|
||||
final ObjectProperty<Vault> vault = new SimpleObjectProperty<>();
|
||||
final ObjectProperty<Optional<UpgradeStrategy>> strategy = new SimpleObjectProperty<>();
|
||||
private final UpgradeStrategies strategies;
|
||||
private final ExecutorService exec;
|
||||
private final AsyncTaskService asyncTaskService;
|
||||
private Optional<UpgradeListener> listener = Optional.empty();
|
||||
|
||||
@Inject
|
||||
public UpgradeController(Localization localization, UpgradeStrategies strategies, ExecutorService exec) {
|
||||
public UpgradeController(Localization localization, UpgradeStrategies strategies, AsyncTaskService asyncTaskService) {
|
||||
super(localization);
|
||||
this.strategies = strategies;
|
||||
this.exec = exec;
|
||||
this.asyncTaskService = asyncTaskService;
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -103,26 +98,22 @@ public class UpgradeController extends LocalizedFXMLViewController {
|
||||
Vault v = Objects.requireNonNull(vault.getValue());
|
||||
passwordField.setDisable(true);
|
||||
progressIndicator.setVisible(true);
|
||||
exec.submit(() -> {
|
||||
if (!instruction.isApplicable(v)) {
|
||||
LOG.error("No upgrade needed for " + v.path().getValue());
|
||||
throw new IllegalStateException("No ugprade needed for " + v.path().getValue());
|
||||
}
|
||||
try {
|
||||
instruction.upgrade(v, passwordField.getCharacters());
|
||||
Platform.runLater(this::showNextUpgrade);
|
||||
} catch (UpgradeFailedException e) {
|
||||
Platform.runLater(() -> {
|
||||
asyncTaskService //
|
||||
.asyncTaskOf(() -> {
|
||||
if (!instruction.isApplicable(v)) {
|
||||
throw new IllegalStateException("No ugprade needed for " + v.path().getValue());
|
||||
}
|
||||
instruction.upgrade(v, passwordField.getCharacters());
|
||||
}) //
|
||||
.onSuccess(this::showNextUpgrade) //
|
||||
.onError(UpgradeFailedException.class, e -> {
|
||||
errorLabel.setText(e.getLocalizedMessage());
|
||||
});
|
||||
} finally {
|
||||
Platform.runLater(() -> {
|
||||
}) //
|
||||
.andFinally(() -> {
|
||||
progressIndicator.setVisible(false);
|
||||
passwordField.setDisable(false);
|
||||
passwordField.swipe();
|
||||
});
|
||||
}
|
||||
});
|
||||
}).run();
|
||||
}
|
||||
|
||||
private void showNextUpgrade() {
|
||||
|
||||
@@ -8,14 +8,12 @@
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui.controllers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
@@ -31,6 +29,7 @@ import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.ui.settings.Localization;
|
||||
import org.cryptomator.ui.settings.Settings;
|
||||
import org.cryptomator.ui.util.AsyncTaskService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -54,15 +53,15 @@ public class WelcomeController extends LocalizedFXMLViewController {
|
||||
private final Application app;
|
||||
private final Settings settings;
|
||||
private final Comparator<String> semVerComparator;
|
||||
private final ExecutorService executor;
|
||||
private final AsyncTaskService asyncTaskService;
|
||||
|
||||
@Inject
|
||||
public WelcomeController(Application app, Localization localization, Settings settings, @Named("SemVer") Comparator<String> semVerComparator, ExecutorService executor) {
|
||||
public WelcomeController(Application app, Localization localization, Settings settings, @Named("SemVer") Comparator<String> semVerComparator, AsyncTaskService asyncTaskService) {
|
||||
super(localization);
|
||||
this.app = app;
|
||||
this.settings = settings;
|
||||
this.semVerComparator = semVerComparator;
|
||||
this.executor = executor;
|
||||
this.asyncTaskService = asyncTaskService;
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -82,7 +81,7 @@ public class WelcomeController extends LocalizedFXMLViewController {
|
||||
if (areUpdatesManagedExternally()) {
|
||||
checkForUpdatesContainer.setVisible(false);
|
||||
} else if (settings.isCheckForUpdatesEnabled()) {
|
||||
executor.execute(this::checkForUpdates);
|
||||
this.checkForUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,16 +99,14 @@ public class WelcomeController extends LocalizedFXMLViewController {
|
||||
}
|
||||
|
||||
private void checkForUpdates() {
|
||||
Platform.runLater(() -> {
|
||||
checkForUpdatesStatus.setText(localization.getString("welcome.checkForUpdates.label.currentlyChecking"));
|
||||
checkForUpdatesIndicator.setVisible(true);
|
||||
});
|
||||
final HttpClient client = new HttpClient();
|
||||
final HttpMethod method = new GetMethod("https://cryptomator.org/downloads/latestVersion.json");
|
||||
client.getParams().setParameter(HttpClientParams.USER_AGENT, "Cryptomator VersionChecker/" + applicationVersion().orElse("SNAPSHOT"));
|
||||
client.getParams().setCookiePolicy(CookiePolicy.IGNORE_COOKIES);
|
||||
client.getParams().setConnectionManagerTimeout(5000);
|
||||
try {
|
||||
checkForUpdatesStatus.setText(localization.getString("welcome.checkForUpdates.label.currentlyChecking"));
|
||||
checkForUpdatesIndicator.setVisible(true);
|
||||
asyncTaskService.asyncTaskOf(() -> {
|
||||
final HttpClient client = new HttpClient();
|
||||
final HttpMethod method = new GetMethod("https://cryptomator.org/downloads/latestVersion.json");
|
||||
client.getParams().setParameter(HttpClientParams.USER_AGENT, "Cryptomator VersionChecker/" + applicationVersion().orElse("SNAPSHOT"));
|
||||
client.getParams().setCookiePolicy(CookiePolicy.IGNORE_COOKIES);
|
||||
client.getParams().setConnectionManagerTimeout(5000);
|
||||
client.executeMethod(method);
|
||||
final InputStream responseBodyStream = method.getResponseBodyAsStream();
|
||||
if (method.getStatusCode() == HttpStatus.SC_OK && responseBodyStream != null) {
|
||||
@@ -121,14 +118,10 @@ public class WelcomeController extends LocalizedFXMLViewController {
|
||||
this.compareVersions(map);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// no error handling required. Maybe next time the version check is successful.
|
||||
} finally {
|
||||
Platform.runLater(() -> {
|
||||
checkForUpdatesStatus.setText("");
|
||||
checkForUpdatesIndicator.setVisible(false);
|
||||
});
|
||||
}
|
||||
}).andFinally(() -> {
|
||||
checkForUpdatesStatus.setText("");
|
||||
checkForUpdatesIndicator.setVisible(false);
|
||||
}).run();
|
||||
}
|
||||
|
||||
private Optional<String> applicationVersion() {
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.stripStart;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
@@ -40,6 +42,7 @@ import org.cryptomator.frontend.Frontend;
|
||||
import org.cryptomator.frontend.Frontend.MountParam;
|
||||
import org.cryptomator.frontend.FrontendCreationFailedException;
|
||||
import org.cryptomator.frontend.FrontendFactory;
|
||||
import org.cryptomator.frontend.FrontendId;
|
||||
import org.cryptomator.ui.settings.Settings;
|
||||
import org.cryptomator.ui.util.DeferredClosable;
|
||||
import org.cryptomator.ui.util.DeferredCloser;
|
||||
@@ -70,6 +73,7 @@ public class Vault implements CryptoFileSystemDelegate {
|
||||
private final ObservableList<String> namesOfResourcesWithInvalidMac = FXThreads.observableListOnMainThread(FXCollections.observableArrayList());
|
||||
private final Set<String> whitelistedResourcesWithInvalidMac = new HashSet<>();
|
||||
private final AtomicReference<FileSystem> nioFileSystem = new AtomicReference<>();
|
||||
private final String id;
|
||||
|
||||
private String mountName;
|
||||
private Character winDriveLetter;
|
||||
@@ -78,13 +82,15 @@ public class Vault implements CryptoFileSystemDelegate {
|
||||
|
||||
/**
|
||||
* Package private constructor, use {@link VaultFactory}.
|
||||
*
|
||||
* @param string
|
||||
*/
|
||||
Vault(Path vaultDirectoryPath, ShorteningFileSystemFactory shorteningFileSystemFactory, CryptoFileSystemFactory cryptoFileSystemFactory, DeferredCloser closer) {
|
||||
Vault(String id, Path vaultDirectoryPath, ShorteningFileSystemFactory shorteningFileSystemFactory, CryptoFileSystemFactory cryptoFileSystemFactory, DeferredCloser closer) {
|
||||
this.path = new SimpleObjectProperty<Path>(vaultDirectoryPath);
|
||||
this.shorteningFileSystemFactory = shorteningFileSystemFactory;
|
||||
this.cryptoFileSystemFactory = cryptoFileSystemFactory;
|
||||
this.closer = closer;
|
||||
|
||||
this.id = id;
|
||||
try {
|
||||
setMountName(name().getValue());
|
||||
} catch (IllegalArgumentException e) {
|
||||
@@ -129,8 +135,7 @@ public class Vault implements CryptoFileSystemDelegate {
|
||||
FileSystem normalizingFs = new NormalizedNameFileSystem(cryptoFs, SystemUtils.IS_OS_MAC_OSX ? Form.NFD : Form.NFC);
|
||||
StatsFileSystem statsFs = new StatsFileSystem(normalizingFs);
|
||||
statsFileSystem = Optional.of(statsFs);
|
||||
String contextPath = StringUtils.prependIfMissing(mountName, "/");
|
||||
Frontend frontend = frontendFactory.create(statsFs, contextPath);
|
||||
Frontend frontend = frontendFactory.create(statsFs, FrontendId.from(id), stripStart(mountName, "/"));
|
||||
filesystemFrontend = closer.closeLater(frontend);
|
||||
frontend.mount(getMountParams(settings));
|
||||
success = true;
|
||||
@@ -143,7 +148,7 @@ public class Vault implements CryptoFileSystemDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void deactivateFrontend() {
|
||||
public synchronized void deactivateFrontend() throws Exception {
|
||||
filesystemFrontend.close();
|
||||
statsFileSystem = Optional.empty();
|
||||
Platform.runLater(() -> unlocked.set(false));
|
||||
@@ -154,7 +159,8 @@ public class Vault implements CryptoFileSystemDelegate {
|
||||
return ImmutableMap.of( //
|
||||
MountParam.MOUNT_NAME, Optional.ofNullable(mountName), //
|
||||
MountParam.WIN_DRIVE_LETTER, Optional.ofNullable(CharUtils.toString(winDriveLetter)), //
|
||||
MountParam.HOSTNAME, Optional.of(hostname) //
|
||||
MountParam.HOSTNAME, Optional.of(hostname), //
|
||||
MountParam.PREFERRED_GVFS_SCHEME, Optional.ofNullable(settings.getPreferredGvfsScheme()) //
|
||||
);
|
||||
}
|
||||
|
||||
@@ -162,10 +168,6 @@ public class Vault implements CryptoFileSystemDelegate {
|
||||
Optionals.ifPresent(filesystemFrontend.get(), Frontend::reveal);
|
||||
}
|
||||
|
||||
public void unmount() throws CommandFailedException {
|
||||
Optionals.ifPresent(filesystemFrontend.get(), Frontend::unmount);
|
||||
}
|
||||
|
||||
// ******************************************************************************
|
||||
// Delegate methods
|
||||
// ********************************************************************************/
|
||||
@@ -305,6 +307,10 @@ public class Vault implements CryptoFileSystemDelegate {
|
||||
this.winDriveLetter = winDriveLetter;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
// ******************************************************************************
|
||||
// Hashcode / Equals
|
||||
// *******************************************************************************/
|
||||
|
||||
@@ -15,6 +15,7 @@ import javax.inject.Singleton;
|
||||
|
||||
import org.cryptomator.filesystem.crypto.CryptoFileSystemFactory;
|
||||
import org.cryptomator.filesystem.shortening.ShorteningFileSystemFactory;
|
||||
import org.cryptomator.frontend.FrontendId;
|
||||
import org.cryptomator.ui.util.DeferredCloser;
|
||||
|
||||
@Singleton
|
||||
@@ -31,8 +32,12 @@ public class VaultFactory {
|
||||
this.closer = closer;
|
||||
}
|
||||
|
||||
public Vault createVault(String id, Path path) {
|
||||
return new Vault(id, path, shorteningFileSystemFactory, cryptoFileSystemFactory, closer);
|
||||
}
|
||||
|
||||
public Vault createVault(Path path) {
|
||||
return new Vault(path, shorteningFileSystemFactory, cryptoFileSystemFactory, closer);
|
||||
return createVault(FrontendId.generate().toString(), path);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ public class VaultObjectMapperProvider implements Provider<ObjectMapper> {
|
||||
jgen.writeStartObject();
|
||||
jgen.writeStringField("path", value.path().getValue().toString());
|
||||
jgen.writeStringField("mountName", value.getMountName());
|
||||
jgen.writeStringField("id", value.getId());
|
||||
final Character winDriveLetter = value.getWinDriveLetter();
|
||||
if (winDriveLetter != null) {
|
||||
jgen.writeStringField("winDriveLetter", Character.toString(winDriveLetter));
|
||||
@@ -76,7 +77,12 @@ public class VaultObjectMapperProvider implements Provider<ObjectMapper> {
|
||||
}
|
||||
final String pathStr = node.get("path").asText();
|
||||
final Path path = FileSystems.getDefault().getPath(pathStr);
|
||||
final Vault vault = vaultFactoy.createVault(path);
|
||||
final Vault vault;
|
||||
if (node.has("id")) {
|
||||
vault = vaultFactoy.createVault(node.get("id").asText(), path);
|
||||
} else {
|
||||
vault = vaultFactoy.createVault(path);
|
||||
}
|
||||
if (node.has("mountName")) {
|
||||
vault.setMountName(node.get("mountName").asText());
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import org.cryptomator.ui.model.Vault;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
|
||||
@JsonPropertyOrder(value = {"directories", "checkForUpdatesEnabled", "port", "useIpv6", "numTrayNotifications"})
|
||||
@JsonPropertyOrder(value = {"directories", "checkForUpdatesEnabled", "port", "useIpv6", "numTrayNotifications", "preferredGvfsScheme"})
|
||||
public class Settings implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 7609959894417878744L;
|
||||
@@ -27,6 +27,7 @@ public class Settings implements Serializable {
|
||||
public static final int DEFAULT_PORT = 42427;
|
||||
public static final boolean DEFAULT_USE_IPV6 = false;
|
||||
public static final Integer DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
|
||||
public static final String DEFAULT_GVFS_SCHEME = "dav";
|
||||
|
||||
private final Consumer<Settings> saveCmd;
|
||||
|
||||
@@ -45,6 +46,9 @@ public class Settings implements Serializable {
|
||||
@JsonProperty("numTrayNotifications")
|
||||
private Integer numTrayNotifications;
|
||||
|
||||
@JsonProperty("preferredGvfsScheme")
|
||||
private String preferredGvfsScheme;
|
||||
|
||||
/**
|
||||
* Package-private constructor; use {@link SettingsProvider}.
|
||||
*/
|
||||
@@ -113,4 +117,12 @@ public class Settings implements Serializable {
|
||||
this.numTrayNotifications = numTrayNotifications;
|
||||
}
|
||||
|
||||
public String getPreferredGvfsScheme() {
|
||||
return preferredGvfsScheme == null ? DEFAULT_GVFS_SCHEME : preferredGvfsScheme;
|
||||
}
|
||||
|
||||
public void setPreferredGvfsScheme(String preferredGvfsScheme) {
|
||||
this.preferredGvfsScheme = preferredGvfsScheme;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
package org.cryptomator.ui.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.cryptomator.common.ConsumerThrowingException;
|
||||
import org.cryptomator.common.RunnableThrowingException;
|
||||
import org.cryptomator.common.SupplierThrowingException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javafx.application.Platform;
|
||||
|
||||
@Singleton
|
||||
public class AsyncTaskService {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AsyncTaskService.class);
|
||||
|
||||
private final ExecutorService executor;
|
||||
|
||||
@Inject
|
||||
public AsyncTaskService(ExecutorService executor) {
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
public AsyncTaskWithoutSuccessHandler<Void> asyncTaskOf(RunnableThrowingException<?> task) {
|
||||
return new AsyncTaskImpl<>(() -> {
|
||||
task.run();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public <ResultType> AsyncTaskWithoutSuccessHandler<ResultType> asyncTaskOf(SupplierThrowingException<ResultType, ?> task) {
|
||||
return new AsyncTaskImpl<>(task);
|
||||
}
|
||||
|
||||
private class AsyncTaskImpl<ResultType> implements AsyncTaskWithoutSuccessHandler<ResultType> {
|
||||
|
||||
private final SupplierThrowingException<ResultType, ?> task;
|
||||
|
||||
private ConsumerThrowingException<ResultType, ?> successHandler = value -> {
|
||||
};
|
||||
private List<ErrorHandler<Throwable>> errorHandlers = new ArrayList<>();
|
||||
private RunnableThrowingException<?> finallyHandler = () -> {
|
||||
};
|
||||
|
||||
public AsyncTaskImpl(SupplierThrowingException<ResultType, ?> task) {
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncTaskWithoutErrorHandler onSuccess(ConsumerThrowingException<ResultType, ?> handler) {
|
||||
successHandler = handler;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncTaskWithoutErrorHandler onSuccess(RunnableThrowingException<?> handler) {
|
||||
return onSuccess(result -> handler.run());
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
@Override
|
||||
public <ErrorType extends Throwable> AsyncTaskWithoutErrorHandler onError(Class<ErrorType> type, ConsumerThrowingException<ErrorType, ?> handler) {
|
||||
errorHandlers.add((ErrorHandler) new ErrorHandler<>(type, handler));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <ErrorType extends Throwable> AsyncTaskWithoutErrorHandler onError(Class<ErrorType> type, RunnableThrowingException<?> handler) {
|
||||
return onError(type, error -> handler.run());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncTask andFinally(RunnableThrowingException<?> handler) {
|
||||
finallyHandler = handler;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
errorHandlers.add(ErrorHandler.LOGGING_HANDLER);
|
||||
executor.execute(() -> logExceptions(() -> {
|
||||
try {
|
||||
ResultType result = task.get();
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
successHandler.accept(result);
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Uncaught exception", e);
|
||||
}
|
||||
});
|
||||
} catch (Throwable e) {
|
||||
ErrorHandler<Throwable> errorHandler = errorHandlerFor(e);
|
||||
Platform.runLater(toRunnableLoggingException(() -> errorHandler.accept(e)));
|
||||
} finally {
|
||||
Platform.runLater(toRunnableLoggingException(finallyHandler));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private ErrorHandler<Throwable> errorHandlerFor(Throwable e) {
|
||||
return errorHandlers.stream().filter(handler -> handler.handles(e)).findFirst().get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static Runnable toRunnableLoggingException(RunnableThrowingException<?> delegate) {
|
||||
return () -> logExceptions(delegate);
|
||||
}
|
||||
|
||||
private static void logExceptions(RunnableThrowingException<?> delegate) {
|
||||
try {
|
||||
delegate.run();
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Uncaught exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ErrorHandler<ErrorType> implements ConsumerThrowingException<ErrorType, Throwable> {
|
||||
|
||||
public static final ErrorHandler<Throwable> LOGGING_HANDLER = new ErrorHandler<Throwable>(Throwable.class, error -> {
|
||||
LOG.error("Uncaught exception", error);
|
||||
});
|
||||
|
||||
private final Class<ErrorType> type;
|
||||
private final ConsumerThrowingException<ErrorType, ?> delegate;
|
||||
|
||||
public ErrorHandler(Class<ErrorType> type, ConsumerThrowingException<ErrorType, ?> delegate) {
|
||||
this.type = type;
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
public boolean handles(Throwable error) {
|
||||
return type.isInstance(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(ErrorType error) throws Throwable {
|
||||
delegate.accept(error);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public interface AsyncTaskWithoutSuccessHandler<ResultType> extends AsyncTaskWithoutErrorHandler {
|
||||
|
||||
AsyncTaskWithoutErrorHandler onSuccess(ConsumerThrowingException<ResultType, ?> handler);
|
||||
|
||||
AsyncTaskWithoutErrorHandler onSuccess(RunnableThrowingException<?> handler);
|
||||
|
||||
}
|
||||
|
||||
public interface AsyncTaskWithoutErrorHandler extends AsyncTaskWithoutFinallyHandler {
|
||||
|
||||
<ErrorType extends Throwable> AsyncTaskWithoutErrorHandler onError(Class<ErrorType> type, ConsumerThrowingException<ErrorType, ?> handler);
|
||||
|
||||
<ErrorType extends Throwable> AsyncTaskWithoutErrorHandler onError(Class<ErrorType> type, RunnableThrowingException<?> handler);
|
||||
|
||||
}
|
||||
|
||||
public interface AsyncTaskWithoutFinallyHandler extends AsyncTask {
|
||||
|
||||
AsyncTask andFinally(RunnableThrowingException<?> handler);
|
||||
|
||||
}
|
||||
|
||||
public interface AsyncTask extends Runnable {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,12 +28,6 @@ public interface DeferredClosable<T> extends AutoCloseable {
|
||||
*/
|
||||
public Optional<T> get();
|
||||
|
||||
/**
|
||||
* Quietly closes the Object. If the object was closed before, nothing
|
||||
* happens.
|
||||
*/
|
||||
public void close();
|
||||
|
||||
/**
|
||||
* @return an empty object.
|
||||
*/
|
||||
|
||||
@@ -13,12 +13,10 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentSkipListMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.cryptomator.common.ConsumerThrowingException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
@@ -31,7 +29,7 @@ import com.google.common.annotations.VisibleForTesting;
|
||||
*
|
||||
* <p>
|
||||
* If you have a {@link DeferredCloser} instance present, call
|
||||
* {@link #closeLater(Object, Closer)} immediately after you have opened the
|
||||
* {@link #closeLater(Object, ConsumerThrowingException)} immediately after you have opened the
|
||||
* resource and return a resource handle. If {@link #close()} is called, the
|
||||
* resource will be closed. Calling {@link DeferredClosable#close()} on the resource
|
||||
* handle will also close the resource and prevent a second closing by
|
||||
@@ -42,8 +40,6 @@ import com.google.common.annotations.VisibleForTesting;
|
||||
*/
|
||||
public class DeferredCloser implements AutoCloseable {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DeferredCloser.class);
|
||||
|
||||
@VisibleForTesting
|
||||
final Map<Long, ManagedResource<?>> cleanups = new ConcurrentSkipListMap<>();
|
||||
|
||||
@@ -51,33 +47,32 @@ public class DeferredCloser implements AutoCloseable {
|
||||
final AtomicLong counter = new AtomicLong();
|
||||
|
||||
private class ManagedResource<T> implements DeferredClosable<T> {
|
||||
|
||||
private final long number = counter.incrementAndGet();
|
||||
|
||||
private final AtomicReference<T> object = new AtomicReference<>();
|
||||
private final T object;
|
||||
private final ConsumerThrowingException<T, Exception> closer;
|
||||
private boolean closed = false;
|
||||
|
||||
public ManagedResource(T object, ConsumerThrowingException<T, Exception> closer) {
|
||||
super();
|
||||
this.object.set(object);
|
||||
this.closer = closer;
|
||||
this.object = Objects.requireNonNull(object);
|
||||
this.closer = Objects.requireNonNull(closer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
final T oldObject = object.getAndSet(null);
|
||||
if (oldObject != null) {
|
||||
cleanups.remove(number);
|
||||
try {
|
||||
closer.accept(oldObject);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Closing resource failed.", e);
|
||||
}
|
||||
}
|
||||
public synchronized void close() throws Exception {
|
||||
closer.accept(object);
|
||||
cleanups.remove(number);
|
||||
closed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<T> get() throws IllegalStateException {
|
||||
return Optional.ofNullable(object.get());
|
||||
if (closed) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return Optional.of(object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,11 +80,23 @@ public class DeferredCloser implements AutoCloseable {
|
||||
* Closes all added objects which have not been closed before and releases references.
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
public void close() throws ExecutionException {
|
||||
ExecutionException exception = null;
|
||||
for (Iterator<ManagedResource<?>> iterator = cleanups.values().iterator(); iterator.hasNext();) {
|
||||
final ManagedResource<?> closableProvider = iterator.next();
|
||||
closableProvider.close();
|
||||
iterator.remove();
|
||||
try {
|
||||
closableProvider.close();
|
||||
iterator.remove();
|
||||
} catch (Exception e) {
|
||||
if (exception == null) {
|
||||
exception = new ExecutionException(e);
|
||||
} else {
|
||||
exception.addSuppressed(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (exception != null) {
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -325,6 +325,32 @@
|
||||
-fx-background-color: COLOR_TEXT;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* ChoiceBox *
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
.choice-box {
|
||||
-fx-background-color: COLOR_BORDER_DARK, COLOR_BACKGROUND;
|
||||
-fx-background-insets: 0, 1;
|
||||
-fx-background-radius: 0, 0;
|
||||
-fx-padding: 0.1em 0.6em 0.1em 0.6em;
|
||||
-fx-text-fill: COLOR_TEXT;
|
||||
}
|
||||
|
||||
.choice-box > .open-button > .arrow {
|
||||
-fx-background-color: transparent, COLOR_TEXT;
|
||||
-fx-background-insets: 0 0 -1 0, 0;
|
||||
-fx-padding: 0.166667em 0.333333em 0.166667em 0.333333em; /* 2 4 2 4 */
|
||||
-fx-shape: "M 0 0 h 7 l -3.5 4 z";
|
||||
}
|
||||
|
||||
.choice-box .context-menu {
|
||||
-fx-background-color: COLOR_BORDER, #FFF;
|
||||
-fx-background-insets: 0, 1;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* *
|
||||
* ProgressIndicator *
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<?import javafx.scene.control.CheckBox?>
|
||||
<?import javafx.scene.control.TextField?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.control.ChoiceBox?>
|
||||
|
||||
<VBox prefWidth="400.0" alignment="TOP_CENTER" spacing="12.0" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
|
||||
<Label VBox.vgrow="NEVER" fx:id="versionLabel" alignment="CENTER" cacheShape="true" cache="true" />
|
||||
@@ -40,6 +41,11 @@
|
||||
<!-- Row 2 -->
|
||||
<Label GridPane.rowIndex="2" GridPane.columnIndex="0" fx:id="useIpv6Label" text="%settings.useipv6.label" cacheShape="true" cache="true" />
|
||||
<CheckBox GridPane.rowIndex="2" GridPane.columnIndex="1" fx:id="useIpv6Checkbox" cacheShape="true" cache="true" />
|
||||
|
||||
<!-- Row 3 -->
|
||||
<Label GridPane.rowIndex="3" GridPane.columnIndex="0" fx:id="prefGvfsSchemeLabel" text="%settings.prefGvfsScheme.label" cacheShape="true" cache="true" />
|
||||
<ChoiceBox GridPane.rowIndex="3" GridPane.columnIndex="1" fx:id="prefGvfsScheme" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
|
||||
|
||||
</children>
|
||||
</GridPane>
|
||||
<Label VBox.vgrow="NEVER" text="%settings.requiresRestartLabel" alignment="CENTER" cacheShape="true" cache="true" />
|
||||
|
||||
@@ -94,6 +94,7 @@ settings.checkForUpdates.label=Check for updates
|
||||
settings.port.label=WebDAV Port *
|
||||
settings.port.prompt=0 = Choose automatically
|
||||
settings.useipv6.label=Use IPv6 literal
|
||||
settings.prefGvfsScheme.label=WebDAV scheme
|
||||
settings.requiresRestartLabel=* Cryptomator needs to restart
|
||||
|
||||
# tray icon
|
||||
|
||||
Reference in New Issue
Block a user