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:
Sebastian Stenzel
2016-08-14 15:12:05 +02:00
82 changed files with 1111 additions and 376 deletions
+1 -1
View File
@@ -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