mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-14 08:41:28 +00:00
refactored "add vault" functionality, which fixes #14
removed some dependencies refactored Main/MainApplication, which fixes #16
This commit is contained in:
@@ -1,83 +0,0 @@
|
||||
package org.cryptomator.files;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
|
||||
import org.cryptomator.crypto.Cryptor;
|
||||
import org.cryptomator.crypto.CryptorIOSupport;
|
||||
|
||||
public class EncryptingFileVisitor extends SimpleFileVisitor<Path> implements CryptorIOSupport {
|
||||
|
||||
private final Path rootDir;
|
||||
private final Cryptor cryptor;
|
||||
private final EncryptionDecider encryptionDecider;
|
||||
private Path currentDir;
|
||||
|
||||
public EncryptingFileVisitor(Path rootDir, Cryptor cryptor, EncryptionDecider encryptionDecider) {
|
||||
this.rootDir = rootDir;
|
||||
this.cryptor = cryptor;
|
||||
this.encryptionDecider = encryptionDecider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
|
||||
if (rootDir.equals(dir) || encryptionDecider.shouldEncrypt(dir)) {
|
||||
this.currentDir = dir;
|
||||
return FileVisitResult.CONTINUE;
|
||||
} else {
|
||||
return FileVisitResult.SKIP_SUBTREE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path plaintextFile, BasicFileAttributes attrs) throws IOException {
|
||||
if (encryptionDecider.shouldEncrypt(plaintextFile)) {
|
||||
final String plaintextName = plaintextFile.getFileName().toString();
|
||||
final String encryptedName = cryptor.encryptPath(plaintextName, '/', '/', this);
|
||||
final Path encryptedPath = plaintextFile.resolveSibling(encryptedName);
|
||||
final InputStream plaintextIn = Files.newInputStream(plaintextFile, StandardOpenOption.READ);
|
||||
final SeekableByteChannel ciphertextOut = Files.newByteChannel(encryptedPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);
|
||||
cryptor.encryptFile(plaintextIn, ciphertextOut);
|
||||
Files.delete(plaintextFile);
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
|
||||
if (encryptionDecider.shouldEncrypt(dir)) {
|
||||
final String plaintext = dir.getFileName().toString();
|
||||
final String encrypted = cryptor.encryptPath(plaintext, '/', '/', this);
|
||||
final Path newPath = dir.resolveSibling(encrypted);
|
||||
Files.move(dir, newPath, StandardCopyOption.ATOMIC_MOVE);
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writePathSpecificMetadata(String metadataFile, byte[] encryptedMetadata) throws IOException {
|
||||
final Path path = currentDir.resolve(metadataFile);
|
||||
Files.write(path, encryptedMetadata, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.DSYNC);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] readPathSpecificMetadata(String metadataFile) throws IOException {
|
||||
final Path path = currentDir.resolve(metadataFile);
|
||||
return Files.readAllBytes(path);
|
||||
}
|
||||
|
||||
/* callback */
|
||||
|
||||
public interface EncryptionDecider {
|
||||
boolean shouldEncrypt(Path path);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -63,8 +63,4 @@ interface FileNamingConventions {
|
||||
*/
|
||||
PathMatcher ENCRYPTED_FILE_GLOB_MATCHER = FileSystems.getDefault().getPathMatcher("glob:**/*{" + BASIC_FILE_EXT + "," + LONG_NAME_FILE_EXT + "}");
|
||||
|
||||
/**
|
||||
* On OSX, folders with this extension are treated as a package.
|
||||
*/
|
||||
String FOLDER_EXTENSION = ".cryptomator";
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- UI -->
|
||||
<!-- UI
|
||||
<dependency>
|
||||
<groupId>org.controlsfx</groupId>
|
||||
<artifactId>controlsfx</artifactId>
|
||||
@@ -60,7 +60,7 @@
|
||||
<dependency>
|
||||
<groupId>com.github.axet</groupId>
|
||||
<artifactId>desktop</artifactId>
|
||||
</dependency>
|
||||
</dependency> -->
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -11,39 +11,28 @@ package org.cryptomator.ui;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.FileVisitor;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Alert.AlertType;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ProgressIndicator;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.CharUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.cryptomator.crypto.aes256.Aes256Cryptor;
|
||||
import org.cryptomator.files.EncryptingFileVisitor;
|
||||
import org.cryptomator.ui.controls.ClearOnDisableListener;
|
||||
import org.cryptomator.ui.controls.SecPasswordField;
|
||||
import org.cryptomator.ui.model.Directory;
|
||||
import org.cryptomator.ui.util.FXThreads;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -53,7 +42,7 @@ public class InitializeController implements Initializable {
|
||||
private static final int MAX_USERNAME_LENGTH = 250;
|
||||
|
||||
private ResourceBundle localization;
|
||||
private Directory directory;
|
||||
private Vault directory;
|
||||
private InitializationListener listener;
|
||||
|
||||
@FXML
|
||||
@@ -68,9 +57,6 @@ public class InitializeController implements Initializable {
|
||||
@FXML
|
||||
private Button okButton;
|
||||
|
||||
@FXML
|
||||
private ProgressIndicator progressIndicator;
|
||||
|
||||
@FXML
|
||||
private Label messageLabel;
|
||||
|
||||
@@ -130,43 +116,25 @@ public class InitializeController implements Initializable {
|
||||
@FXML
|
||||
protected void initializeVault(ActionEvent event) {
|
||||
setControlsDisabled(true);
|
||||
if (!isDirectoryEmpty() && !shouldEncryptExistingFiles()) {
|
||||
return;
|
||||
}
|
||||
final String masterKeyFileName = usernameField.getText() + Aes256Cryptor.MASTERKEY_FILE_EXT;
|
||||
final Path masterKeyPath = directory.getPath().resolve(masterKeyFileName);
|
||||
final CharSequence password = passwordField.getCharacters();
|
||||
OutputStream masterKeyOutputStream = null;
|
||||
try {
|
||||
progressIndicator.setVisible(true);
|
||||
masterKeyOutputStream = Files.newOutputStream(masterKeyPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);
|
||||
try (OutputStream masterKeyOutputStream = Files.newOutputStream(masterKeyPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW)) {
|
||||
directory.getCryptor().encryptMasterKey(masterKeyOutputStream, password);
|
||||
final Future<?> futureDone = FXThreads.runOnBackgroundThread(this::encryptExistingContents);
|
||||
FXThreads.runOnMainThreadWhenFinished(futureDone, (result) -> {
|
||||
progressIndicator.setVisible(false);
|
||||
progressIndicator.setVisible(false);
|
||||
directory.getCryptor().swipeSensitiveData();
|
||||
if (listener != null) {
|
||||
listener.didInitialize(this);
|
||||
}
|
||||
});
|
||||
if (listener != null) {
|
||||
listener.didInitialize(this);
|
||||
}
|
||||
} catch (FileAlreadyExistsException ex) {
|
||||
setControlsDisabled(false);
|
||||
progressIndicator.setVisible(false);
|
||||
messageLabel.setText(localization.getString("initialize.messageLabel.alreadyInitialized"));
|
||||
} catch (InvalidPathException ex) {
|
||||
setControlsDisabled(false);
|
||||
progressIndicator.setVisible(false);
|
||||
messageLabel.setText(localization.getString("initialize.messageLabel.invalidPath"));
|
||||
} catch (IOException ex) {
|
||||
setControlsDisabled(false);
|
||||
progressIndicator.setVisible(false);
|
||||
LOG.error("I/O Exception", ex);
|
||||
} finally {
|
||||
setControlsDisabled(false);
|
||||
usernameField.setText(null);
|
||||
passwordField.swipe();
|
||||
retypePasswordField.swipe();
|
||||
IOUtils.closeQuietly(masterKeyOutputStream);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,47 +145,13 @@ public class InitializeController implements Initializable {
|
||||
okButton.setDisable(disable);
|
||||
}
|
||||
|
||||
private boolean isDirectoryEmpty() {
|
||||
try {
|
||||
final DirectoryStream<Path> dirContents = Files.newDirectoryStream(directory.getPath());
|
||||
return !dirContents.iterator().hasNext();
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to analyze directory.", e);
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldEncryptExistingFiles() {
|
||||
final Alert alert = new Alert(AlertType.CONFIRMATION);
|
||||
alert.setTitle(localization.getString("initialize.alert.directoryIsNotEmpty.title"));
|
||||
alert.setHeaderText(null);
|
||||
alert.setContentText(localization.getString("initialize.alert.directoryIsNotEmpty.content"));
|
||||
|
||||
final Optional<ButtonType> result = alert.showAndWait();
|
||||
return ButtonType.OK.equals(result.get());
|
||||
}
|
||||
|
||||
private void encryptExistingContents() {
|
||||
try {
|
||||
final FileVisitor<Path> visitor = new EncryptingFileVisitor(directory.getPath(), directory.getCryptor(), this::shouldEncryptExistingFile);
|
||||
Files.walkFileTree(directory.getPath(), visitor);
|
||||
} catch (IOException ex) {
|
||||
LOG.error("I/O Exception", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldEncryptExistingFile(Path path) {
|
||||
final String name = path.getFileName().toString();
|
||||
return !directory.getPath().equals(path) && !name.endsWith(Aes256Cryptor.BASIC_FILE_EXT) && !name.endsWith(Aes256Cryptor.METADATA_FILE_EXT) && !name.endsWith(Aes256Cryptor.MASTERKEY_FILE_EXT);
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public Directory getDirectory() {
|
||||
public Vault getDirectory() {
|
||||
return directory;
|
||||
}
|
||||
|
||||
public void setDirectory(Directory directory) {
|
||||
public void setDirectory(Vault directory) {
|
||||
this.directory = directory;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,17 @@
|
||||
*
|
||||
* Contributors:
|
||||
* Tillmann Gaida - initial implementation
|
||||
* Sebastian Stenzel - refactoring
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -18,35 +24,40 @@ import javafx.application.Application;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.ui.util.SingleInstanceManager;
|
||||
import org.cryptomator.ui.util.SingleInstanceManager.RemoteInstance;
|
||||
import org.eclipse.jetty.util.ConcurrentHashSet;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.github.axet.desktop.os.mac.AppleHandlers;
|
||||
|
||||
public class Main {
|
||||
public static final Logger LOG = LoggerFactory.getLogger(MainApplication.class);
|
||||
|
||||
public static final CompletableFuture<Consumer<File>> OPEN_FILE_HANDLER = new CompletableFuture<>();
|
||||
|
||||
private static final Set<Runnable> SHUTDOWN_TASKS = new ConcurrentHashSet<>();
|
||||
private static final CleanShutdownPerformer CLEAN_SHUTDOWN_PERFORMER = new CleanShutdownPerformer();
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (SystemUtils.IS_OS_MAC_OSX) {
|
||||
/*
|
||||
* On OSX we're in an awkward position. We need to register a
|
||||
* handler in the main thread of this application. However, we can't
|
||||
* even pass objects to the application, so we're forced to use a
|
||||
* static CompletableFuture for the handler, which actually opens
|
||||
* On OSX we're in an awkward position. We need to register a handler in the main thread of this application. However, we can't
|
||||
* even pass objects to the application, so we're forced to use a static CompletableFuture for the handler, which actually opens
|
||||
* the file in the application.
|
||||
*
|
||||
* Code taken from https://github.com/axet/desktop/blob/master/src/main/java/com/github/axet/desktop/os/mac/AppleHandlers.java
|
||||
*/
|
||||
try {
|
||||
AppleHandlers.getAppleHandlers().addOpenFileListener(file -> {
|
||||
try {
|
||||
OPEN_FILE_HANDLER.get().accept(file);
|
||||
} catch (Exception e) {
|
||||
LOG.error("exception handling file open event", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
} catch (RuntimeException e) {
|
||||
final Class<?> applicationClass = Class.forName("com.apple.eawt.Application");
|
||||
final Class<?> openFilesHandlerClass = Class.forName("com.apple.eawt.OpenFilesHandler");
|
||||
final Method getApplication = applicationClass.getMethod("getApplication");
|
||||
final Object application = getApplication.invoke(null);
|
||||
final Method setOpenFileHandler = applicationClass.getMethod("setOpenFileHandler", openFilesHandlerClass);
|
||||
|
||||
final ClassLoader openFilesHandlerClassLoader = openFilesHandlerClass.getClassLoader();
|
||||
final OpenFilesHandlerClassHandler openFilesHandlerHandler = new OpenFilesHandlerClassHandler();
|
||||
final Object openFilesHandlerObject = Proxy.newProxyInstance(openFilesHandlerClassLoader, new Class<?>[] {openFilesHandlerClass}, openFilesHandlerHandler);
|
||||
|
||||
setOpenFileHandler.invoke(application, openFilesHandlerObject);
|
||||
} catch (ReflectiveOperationException | RuntimeException e) {
|
||||
// Since we're trying to call OS-specific code, we'll just have
|
||||
// to hope for the best.
|
||||
LOG.error("exception adding OSX file open handler", e);
|
||||
@@ -54,9 +65,13 @@ public class Main {
|
||||
}
|
||||
|
||||
/*
|
||||
* Before starting the application, we check if there is already an
|
||||
* instance running on this computer. If so, we send our command line
|
||||
* arguments to that instance and quit.
|
||||
* Perform certain things on VM termination.
|
||||
*/
|
||||
Runtime.getRuntime().addShutdownHook(CLEAN_SHUTDOWN_PERFORMER);
|
||||
|
||||
/*
|
||||
* Before starting the application, we check if there is already an instance running on this computer. If so, we send our command
|
||||
* line arguments to that instance and quit.
|
||||
*/
|
||||
final Optional<RemoteInstance> remoteInstance = SingleInstanceManager.getRemoteInstance(MainApplication.APPLICATION_KEY);
|
||||
|
||||
@@ -64,7 +79,7 @@ public class Main {
|
||||
try (RemoteInstance instance = remoteInstance.get()) {
|
||||
LOG.info("An instance of Cryptomator is already running at {}.", instance.getRemotePort());
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
remoteInstance.get().sendMessage(args[i], 1000);
|
||||
remoteInstance.get().sendMessage(args[i], 100);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error forwarding arguments to remote instance", e);
|
||||
@@ -73,4 +88,62 @@ public class Main {
|
||||
Application.launch(MainApplication.class, args);
|
||||
}
|
||||
}
|
||||
|
||||
public static void addShutdownTask(Runnable r) {
|
||||
SHUTDOWN_TASKS.add(r);
|
||||
}
|
||||
|
||||
public static void removeShutdownTask(Runnable r) {
|
||||
SHUTDOWN_TASKS.remove(r);
|
||||
}
|
||||
|
||||
private static class CleanShutdownPerformer extends Thread {
|
||||
@Override
|
||||
public void run() {
|
||||
LOG.debug("Shutting down");
|
||||
SHUTDOWN_TASKS.forEach(r -> {
|
||||
try {
|
||||
r.run();
|
||||
} catch (RuntimeException e) {
|
||||
LOG.error("exception while shutting down", e);
|
||||
}
|
||||
});
|
||||
SHUTDOWN_TASKS.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleOpenFileRequest(File file) {
|
||||
try {
|
||||
OPEN_FILE_HANDLER.get().accept(file);
|
||||
} catch (Exception e) {
|
||||
LOG.error("exception handling file open event for file " + file.getAbsolutePath(), e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler class taken from https://github.com/axet/desktop/blob/master/src/main/java/com/github/axet/desktop/os/mac/AppleHandlers.java
|
||||
*/
|
||||
private static class OpenFilesHandlerClassHandler implements InvocationHandler {
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
if (method.getName().equals("openFiles")) {
|
||||
final Class<?> openFilesEventClass = Class.forName("com.apple.eawt.AppEvent$OpenFilesEvent");
|
||||
final Method getFiles = openFilesEventClass.getMethod("getFiles");
|
||||
Object e = args[0];
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
final List<File> ff = (List<File>) getFiles.invoke(e);
|
||||
for (File f : ff) {
|
||||
handleOpenFileRequest(f);
|
||||
}
|
||||
} catch (RuntimeException ee) {
|
||||
throw ee;
|
||||
} catch (Exception ee) {
|
||||
throw new RuntimeException(ee);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
@@ -24,24 +23,19 @@ import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.crypto.aes256.Aes256Cryptor;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.cryptomator.ui.settings.Settings;
|
||||
import org.cryptomator.ui.util.ActiveWindowStyleSupport;
|
||||
import org.cryptomator.ui.util.SingleInstanceManager;
|
||||
import org.cryptomator.ui.util.SingleInstanceManager.LocalInstance;
|
||||
import org.cryptomator.ui.util.TrayIconUtil;
|
||||
import org.cryptomator.webdav.WebDavServer;
|
||||
import org.eclipse.jetty.util.ConcurrentHashSet;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class MainApplication extends Application {
|
||||
|
||||
private static final Set<Runnable> SHUTDOWN_TASKS = new ConcurrentHashSet<>();
|
||||
private static final CleanShutdownPerformer CLEAN_SHUTDOWN_PERFORMER = new CleanShutdownPerformer();
|
||||
|
||||
public static final String APPLICATION_KEY = "CryptomatorGUI";
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MainApplication.class);
|
||||
@@ -53,21 +47,15 @@ public class MainApplication extends Application {
|
||||
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
|
||||
Platform.runLater(() -> {
|
||||
/*
|
||||
* This fixes a bug on OSX where the magic file open handler leads
|
||||
* to no context class loader being set in the AppKit (event) thread
|
||||
* if the application is not started opening a file.
|
||||
* This fixes a bug on OSX where the magic file open handler leads to no context class loader being set in the AppKit (event)
|
||||
* thread if the application is not started opening a file.
|
||||
*/
|
||||
if (Thread.currentThread().getContextClassLoader() == null) {
|
||||
Thread.currentThread().setContextClassLoader(contextClassLoader);
|
||||
}
|
||||
});
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(MainApplication.CLEAN_SHUTDOWN_PERFORMER);
|
||||
|
||||
executorService = Executors.newCachedThreadPool();
|
||||
addShutdownTask(() -> {
|
||||
executorService.shutdown();
|
||||
});
|
||||
|
||||
WebDavServer.getInstance().start();
|
||||
chooseNativeStylesheet();
|
||||
@@ -91,40 +79,42 @@ public class MainApplication extends Application {
|
||||
handleCommandLineArg(ctrl, arg);
|
||||
}
|
||||
|
||||
if (org.controlsfx.tools.Platform.getCurrent().equals(org.controlsfx.tools.Platform.OSX)) {
|
||||
if (SystemUtils.IS_OS_MAC_OSX) {
|
||||
Main.OPEN_FILE_HANDLER.complete(file -> handleCommandLineArg(ctrl, file.getAbsolutePath()));
|
||||
}
|
||||
|
||||
LocalInstance cryptomatorGuiInstance = SingleInstanceManager.startLocalInstance(APPLICATION_KEY, executorService);
|
||||
addShutdownTask(() -> {
|
||||
cryptomatorGuiInstance.close();
|
||||
});
|
||||
|
||||
final LocalInstance cryptomatorGuiInstance = SingleInstanceManager.startLocalInstance(APPLICATION_KEY, executorService);
|
||||
cryptomatorGuiInstance.registerListener(arg -> handleCommandLineArg(ctrl, arg));
|
||||
|
||||
Main.addShutdownTask(() -> {
|
||||
cryptomatorGuiInstance.close();
|
||||
Settings.save();
|
||||
executorService.shutdown();
|
||||
});
|
||||
}
|
||||
|
||||
void handleCommandLineArg(final MainController ctrl, String arg) {
|
||||
Path file = FileSystems.getDefault().getPath(arg);
|
||||
if (!Files.exists(file)) {
|
||||
try {
|
||||
if (!Files.isDirectory(Files.createDirectories(file))) {
|
||||
return;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
return;
|
||||
}
|
||||
// directory created.
|
||||
} else if (Files.isRegularFile(file)) {
|
||||
if (StringUtils.endsWithIgnoreCase(file.getFileName().toString(), Aes256Cryptor.MASTERKEY_FILE_EXT)) {
|
||||
file = file.getParent();
|
||||
} else {
|
||||
// is a file, but not a masterkey file
|
||||
return;
|
||||
}
|
||||
// only open files with our file extension:
|
||||
if (!arg.endsWith(Vault.VAULT_FILE_EXTENSION)) {
|
||||
LOG.warn("Invalid vault path %s", arg);
|
||||
return;
|
||||
}
|
||||
Path f = file;
|
||||
|
||||
// find correct location:
|
||||
final Path path = FileSystems.getDefault().getPath(arg);
|
||||
final Path vaultPath;
|
||||
if (Files.isDirectory(path)) {
|
||||
vaultPath = path;
|
||||
} else if (Files.isRegularFile(path) && path.getParent().getFileName().toString().endsWith(Vault.VAULT_FILE_EXTENSION)) {
|
||||
vaultPath = path.getParent();
|
||||
} else {
|
||||
LOG.warn("Invalid vault path %s", arg);
|
||||
return;
|
||||
}
|
||||
|
||||
// add vault to ctrl:
|
||||
Platform.runLater(() -> {
|
||||
ctrl.addDirectory(f);
|
||||
ctrl.addVault(vaultPath, true);
|
||||
ctrl.toFront();
|
||||
});
|
||||
}
|
||||
@@ -142,39 +132,10 @@ public class MainApplication extends Application {
|
||||
private void quit() {
|
||||
Platform.runLater(() -> {
|
||||
WebDavServer.getInstance().stop();
|
||||
CLEAN_SHUTDOWN_PERFORMER.run();
|
||||
Settings.save();
|
||||
Platform.exit();
|
||||
System.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
CLEAN_SHUTDOWN_PERFORMER.run();
|
||||
Settings.save();
|
||||
}
|
||||
|
||||
public static void addShutdownTask(Runnable r) {
|
||||
SHUTDOWN_TASKS.add(r);
|
||||
}
|
||||
|
||||
public static void removeShutdownTask(Runnable r) {
|
||||
SHUTDOWN_TASKS.remove(r);
|
||||
}
|
||||
|
||||
private static class CleanShutdownPerformer extends Thread {
|
||||
@Override
|
||||
public void run() {
|
||||
SHUTDOWN_TASKS.forEach(r -> {
|
||||
try {
|
||||
r.run();
|
||||
} catch (RuntimeException e) {
|
||||
LOG.error("exception while shutting down", e);
|
||||
}
|
||||
});
|
||||
SHUTDOWN_TASKS.clear();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -25,20 +26,23 @@ import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.geometry.Side;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.stage.DirectoryChooser;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.WindowEvent;
|
||||
|
||||
import org.cryptomator.ui.InitializeController.InitializationListener;
|
||||
import org.cryptomator.ui.UnlockController.UnlockListener;
|
||||
import org.cryptomator.ui.UnlockedController.LockListener;
|
||||
import org.cryptomator.ui.controls.DirectoryListCell;
|
||||
import org.cryptomator.ui.model.Directory;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.cryptomator.ui.settings.Settings;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -52,11 +56,17 @@ public class MainController implements Initializable, InitializationListener, Un
|
||||
@FXML
|
||||
private ContextMenu directoryContextMenu;
|
||||
|
||||
@FXML
|
||||
private ContextMenu addVaultContextMenu;
|
||||
|
||||
@FXML
|
||||
private HBox rootPane;
|
||||
|
||||
@FXML
|
||||
private ListView<Directory> directoryList;
|
||||
private ListView<Vault> directoryList;
|
||||
|
||||
@FXML
|
||||
private ToggleButton addVaultButton;
|
||||
|
||||
@FXML
|
||||
private Pane contentPane;
|
||||
@@ -67,44 +77,83 @@ public class MainController implements Initializable, InitializationListener, Un
|
||||
public void initialize(URL url, ResourceBundle rb) {
|
||||
this.rb = rb;
|
||||
|
||||
final ObservableList<Directory> items = FXCollections.observableList(Settings.load().getDirectories());
|
||||
final ObservableList<Vault> items = FXCollections.observableList(Settings.load().getDirectories());
|
||||
directoryList.setItems(items);
|
||||
directoryList.setCellFactory(this::createDirecoryListCell);
|
||||
directoryList.getSelectionModel().getSelectedItems().addListener(this::selectedDirectoryDidChange);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void didClickAddDirectory(ActionEvent event) {
|
||||
final DirectoryChooser dirChooser = new DirectoryChooser();
|
||||
final File file = dirChooser.showDialog(stage);
|
||||
if (file != null) {
|
||||
addDirectory(file.toPath());
|
||||
private void didClickAddVault(ActionEvent event) {
|
||||
if (addVaultContextMenu.isShowing()) {
|
||||
addVaultContextMenu.hide();
|
||||
} else {
|
||||
addVaultContextMenu.show(addVaultButton, Side.RIGHT, 0.0, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void willShowAddVaultContextMenu(WindowEvent event) {
|
||||
addVaultButton.setSelected(true);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void didHideAddVaultContextMenu(WindowEvent event) {
|
||||
addVaultButton.setSelected(false);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void didClickCreateNewVault(ActionEvent event) {
|
||||
final FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Cryptomator vault", "*.cryptomator"));
|
||||
final File file = fileChooser.showSaveDialog(stage);
|
||||
try {
|
||||
if (file != null) {
|
||||
final Path vaultDir = Files.createDirectory(file.toPath());
|
||||
final Path vaultShortcutFile = vaultDir.resolve(vaultDir.getFileName());
|
||||
Files.createFile(vaultShortcutFile);
|
||||
addVault(vaultDir, true);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error("Unable to create vault", e);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void didClickAddExistingVaults(ActionEvent event) {
|
||||
final FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Cryptomator vault", "*.cryptomator"));
|
||||
final List<File> files = fileChooser.showOpenMultipleDialog(stage);
|
||||
if (files != null) {
|
||||
for (final File file : files) {
|
||||
addVault(file.toPath(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* adds the given directory or selects it if it is already in the list of directories.
|
||||
*
|
||||
* @param file non-null, writable, existing directory
|
||||
* @param dir non-null, writable, existing directory
|
||||
*/
|
||||
void addDirectory(final Path file) {
|
||||
if (file != null && Files.isWritable(file)) {
|
||||
final Directory dir = new Directory(file);
|
||||
if (!directoryList.getItems().contains(dir)) {
|
||||
directoryList.getItems().add(dir);
|
||||
void addVault(final Path dir, boolean select) {
|
||||
if (dir != null && Files.isWritable(dir)) {
|
||||
final Vault vault = new Vault(dir);
|
||||
if (!directoryList.getItems().contains(vault)) {
|
||||
directoryList.getItems().add(vault);
|
||||
}
|
||||
directoryList.getSelectionModel().select(dir);
|
||||
directoryList.getSelectionModel().select(vault);
|
||||
}
|
||||
}
|
||||
|
||||
private ListCell<Directory> createDirecoryListCell(ListView<Directory> param) {
|
||||
private ListCell<Vault> createDirecoryListCell(ListView<Vault> param) {
|
||||
final DirectoryListCell cell = new DirectoryListCell();
|
||||
cell.setContextMenu(directoryContextMenu);
|
||||
return cell;
|
||||
}
|
||||
|
||||
private void selectedDirectoryDidChange(ListChangeListener.Change<? extends Directory> change) {
|
||||
final Directory selectedDir = directoryList.getSelectionModel().getSelectedItem();
|
||||
private void selectedDirectoryDidChange(ListChangeListener.Change<? extends Vault> change) {
|
||||
final Vault selectedDir = directoryList.getSelectionModel().getSelectedItem();
|
||||
if (selectedDir == null) {
|
||||
stage.setTitle(rb.getString("app.name"));
|
||||
showWelcomeView();
|
||||
@@ -116,7 +165,7 @@ public class MainController implements Initializable, InitializationListener, Un
|
||||
|
||||
@FXML
|
||||
private void didClickRemoveSelectedEntry(ActionEvent e) {
|
||||
final Directory selectedDir = directoryList.getSelectionModel().getSelectedItem();
|
||||
final Vault selectedDir = directoryList.getSelectionModel().getSelectedItem();
|
||||
directoryList.getItems().remove(selectedDir);
|
||||
directoryList.getSelectionModel().clearSelection();
|
||||
}
|
||||
@@ -125,7 +174,7 @@ public class MainController implements Initializable, InitializationListener, Un
|
||||
// Subcontroller for right panel
|
||||
// ****************************************
|
||||
|
||||
private void showDirectory(Directory directory) {
|
||||
private void showDirectory(Vault directory) {
|
||||
try {
|
||||
if (directory.isUnlocked()) {
|
||||
this.showUnlockedView(directory);
|
||||
@@ -155,7 +204,7 @@ public class MainController implements Initializable, InitializationListener, Un
|
||||
this.showView("/fxml/welcome.fxml");
|
||||
}
|
||||
|
||||
private void showInitializeView(Directory directory) {
|
||||
private void showInitializeView(Vault directory) {
|
||||
final InitializeController ctrl = showView("/fxml/initialize.fxml");
|
||||
ctrl.setDirectory(directory);
|
||||
ctrl.setListener(this);
|
||||
@@ -166,7 +215,7 @@ public class MainController implements Initializable, InitializationListener, Un
|
||||
showUnlockView(ctrl.getDirectory());
|
||||
}
|
||||
|
||||
private void showUnlockView(Directory directory) {
|
||||
private void showUnlockView(Vault directory) {
|
||||
final UnlockController ctrl = showView("/fxml/unlock.fxml");
|
||||
ctrl.setDirectory(directory);
|
||||
ctrl.setListener(this);
|
||||
@@ -178,7 +227,7 @@ public class MainController implements Initializable, InitializationListener, Un
|
||||
Platform.setImplicitExit(false);
|
||||
}
|
||||
|
||||
private void showUnlockedView(Directory directory) {
|
||||
private void showUnlockedView(Vault directory) {
|
||||
final UnlockedController ctrl = showView("/fxml/unlocked.fxml");
|
||||
ctrl.setDirectory(directory);
|
||||
ctrl.setListener(this);
|
||||
@@ -194,11 +243,11 @@ public class MainController implements Initializable, InitializationListener, Un
|
||||
|
||||
/* Convenience */
|
||||
|
||||
public Collection<Directory> getDirectories() {
|
||||
public Collection<Vault> getDirectories() {
|
||||
return directoryList.getItems();
|
||||
}
|
||||
|
||||
public Collection<Directory> getUnlockedDirectories() {
|
||||
public Collection<Vault> getUnlockedDirectories() {
|
||||
return getDirectories().stream().filter(d -> d.isUnlocked()).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
|
||||
@@ -37,10 +37,9 @@ import org.cryptomator.crypto.exceptions.DecryptFailedException;
|
||||
import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
|
||||
import org.cryptomator.crypto.exceptions.WrongPasswordException;
|
||||
import org.cryptomator.ui.controls.SecPasswordField;
|
||||
import org.cryptomator.ui.model.Directory;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.cryptomator.ui.util.FXThreads;
|
||||
import org.cryptomator.ui.util.MasterKeyFilter;
|
||||
import org.cryptomator.webdav.WebDavServer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -50,7 +49,7 @@ public class UnlockController implements Initializable {
|
||||
|
||||
private ResourceBundle rb;
|
||||
private UnlockListener listener;
|
||||
private Directory directory;
|
||||
private Vault directory;
|
||||
|
||||
@FXML
|
||||
private ComboBox<String> usernameBox;
|
||||
@@ -186,11 +185,11 @@ public class UnlockController implements Initializable {
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public Directory getDirectory() {
|
||||
public Vault getDirectory() {
|
||||
return directory;
|
||||
}
|
||||
|
||||
public void setDirectory(Directory directory) {
|
||||
public void setDirectory(Vault directory) {
|
||||
this.directory = directory;
|
||||
this.findExistingUsernames();
|
||||
this.checkIntegrity.setSelected(directory.shouldVerifyFileIntegrity());
|
||||
|
||||
@@ -26,7 +26,7 @@ import javafx.scene.control.Label;
|
||||
import javafx.util.Duration;
|
||||
|
||||
import org.cryptomator.crypto.CryptorIOSampling;
|
||||
import org.cryptomator.ui.model.Directory;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.cryptomator.webdav.WebDavServer;
|
||||
|
||||
public class UnlockedController implements Initializable {
|
||||
@@ -35,7 +35,7 @@ public class UnlockedController implements Initializable {
|
||||
private static final double IO_SAMPLING_INTERVAL = 0.25;
|
||||
private ResourceBundle rb;
|
||||
private LockListener listener;
|
||||
private Directory directory;
|
||||
private Vault directory;
|
||||
private Timeline ioAnimation;
|
||||
|
||||
@FXML
|
||||
@@ -118,11 +118,11 @@ public class UnlockedController implements Initializable {
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public Directory getDirectory() {
|
||||
public Vault getDirectory() {
|
||||
return directory;
|
||||
}
|
||||
|
||||
public void setDirectory(Directory directory) {
|
||||
public void setDirectory(Vault directory) {
|
||||
this.directory = directory;
|
||||
final String msg = String.format(rb.getString("unlocked.messageLabel.runningOnPort"), WebDavServer.getInstance().getPort());
|
||||
messageLabel.setText(msg);
|
||||
|
||||
@@ -8,9 +8,9 @@ import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.Circle;
|
||||
|
||||
import org.cryptomator.ui.model.Directory;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
|
||||
public class DirectoryListCell extends DraggableListCell<Directory> implements ChangeListener<Boolean> {
|
||||
public class DirectoryListCell extends DraggableListCell<Vault> implements ChangeListener<Boolean> {
|
||||
|
||||
// fill: #FD4943, stroke: #E1443F
|
||||
private static final Color RED_FILL = Color.rgb(253, 73, 67);
|
||||
@@ -29,8 +29,8 @@ public class DirectoryListCell extends DraggableListCell<Directory> implements C
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(Directory item, boolean empty) {
|
||||
final Directory oldItem = super.getItem();
|
||||
protected void updateItem(Vault item, boolean empty) {
|
||||
final Vault oldItem = super.getItem();
|
||||
if (oldItem != null) {
|
||||
oldItem.unlockedProperty().removeListener(this);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.cryptomator.crypto.Cryptor;
|
||||
import org.cryptomator.crypto.SamplingDecorator;
|
||||
import org.cryptomator.crypto.aes256.Aes256Cryptor;
|
||||
import org.cryptomator.ui.MainApplication;
|
||||
import org.cryptomator.ui.Main;
|
||||
import org.cryptomator.ui.util.MasterKeyFilter;
|
||||
import org.cryptomator.ui.util.mount.CommandFailedException;
|
||||
import org.cryptomator.ui.util.mount.WebDavMount;
|
||||
@@ -27,31 +27,34 @@ import org.slf4j.LoggerFactory;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
|
||||
@JsonSerialize(using = DirectorySerializer.class)
|
||||
@JsonDeserialize(using = DirectoryDeserializer.class)
|
||||
public class Directory implements Serializable {
|
||||
@JsonSerialize(using = VaultSerializer.class)
|
||||
@JsonDeserialize(using = VaultDeserializer.class)
|
||||
public class Vault implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 3754487289683599469L;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Directory.class);
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Vault.class);
|
||||
|
||||
public static final String VAULT_FILE_EXTENSION = ".cryptomator";
|
||||
|
||||
private final Cryptor cryptor = SamplingDecorator.decorate(new Aes256Cryptor());
|
||||
private final ObjectProperty<Boolean> unlocked = new SimpleObjectProperty<Boolean>(this, "unlocked", Boolean.FALSE);
|
||||
private final Runnable shutdownTask = new ShutdownTask();
|
||||
private final Path path;
|
||||
private boolean verifyFileIntegrity;
|
||||
private String mountName = "Cryptomator";
|
||||
private String mountName;
|
||||
private ServletLifeCycleAdapter webDavServlet;
|
||||
private WebDavMount webDavMount;
|
||||
|
||||
public Directory(final Path path) {
|
||||
if (!Files.isDirectory(path)) {
|
||||
throw new IllegalArgumentException("Not a directory: " + path);
|
||||
public Vault(final Path vaultDirectoryPath) {
|
||||
if (!Files.isDirectory(vaultDirectoryPath) || !vaultDirectoryPath.getFileName().toString().endsWith(VAULT_FILE_EXTENSION)) {
|
||||
throw new IllegalArgumentException("Not a valid vault directory: " + vaultDirectoryPath);
|
||||
}
|
||||
this.path = path;
|
||||
this.path = vaultDirectoryPath;
|
||||
|
||||
try {
|
||||
setMountName(getName());
|
||||
} catch (IllegalArgumentException e) {
|
||||
|
||||
// mount name needs to be set by the user explicitly later
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +68,7 @@ public class Directory implements Serializable {
|
||||
}
|
||||
webDavServlet = WebDavServer.getInstance().createServlet(path, verifyFileIntegrity, cryptor, getMountName());
|
||||
if (webDavServlet.start()) {
|
||||
MainApplication.addShutdownTask(shutdownTask);
|
||||
Main.addShutdownTask(shutdownTask);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@@ -74,7 +77,7 @@ public class Directory implements Serializable {
|
||||
|
||||
public void stopServer() {
|
||||
if (webDavServlet != null && webDavServlet.isRunning()) {
|
||||
MainApplication.removeShutdownTask(shutdownTask);
|
||||
Main.removeShutdownTask(shutdownTask);
|
||||
this.unmount();
|
||||
webDavServlet.stop();
|
||||
cryptor.swipeSensitiveData();
|
||||
@@ -122,14 +125,10 @@ public class Directory implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Directory name without preceeding path components
|
||||
* @return Directory name without preceeding path components and file extension
|
||||
*/
|
||||
public String getName() {
|
||||
String name = path.getFileName().toString();
|
||||
if (StringUtils.endsWithIgnoreCase(name, Aes256Cryptor.FOLDER_EXTENSION)) {
|
||||
name = name.substring(0, name.length() - Aes256Cryptor.FOLDER_EXTENSION.length());
|
||||
}
|
||||
return name;
|
||||
return StringUtils.removeEnd(path.getFileName().toString(), VAULT_FILE_EXTENSION);
|
||||
}
|
||||
|
||||
public Cryptor getCryptor() {
|
||||
@@ -182,8 +181,7 @@ public class Directory implements Serializable {
|
||||
* sets the mount name while normalizing it
|
||||
*
|
||||
* @param mountName
|
||||
* @throws IllegalArgumentException
|
||||
* if the name is empty after normalization
|
||||
* @throws IllegalArgumentException if the name is empty after normalization
|
||||
*/
|
||||
public void setMountName(String mountName) throws IllegalArgumentException {
|
||||
mountName = normalize(mountName);
|
||||
@@ -202,8 +200,8 @@ public class Directory implements Serializable {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof Directory) {
|
||||
final Directory other = (Directory) obj;
|
||||
if (obj instanceof Vault) {
|
||||
final Vault other = (Vault) obj;
|
||||
return this.path.equals(other.path);
|
||||
} else {
|
||||
return false;
|
||||
@@ -10,14 +10,14 @@ import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
public class DirectoryDeserializer extends JsonDeserializer<Directory> {
|
||||
public class VaultDeserializer extends JsonDeserializer<Vault> {
|
||||
|
||||
@Override
|
||||
public Directory deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
|
||||
public Vault deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
|
||||
final JsonNode node = jp.readValueAsTree();
|
||||
final String pathStr = node.get("path").asText();
|
||||
final Path path = FileSystems.getDefault().getPath(pathStr);
|
||||
final Directory dir = new Directory(path);
|
||||
final Vault dir = new Vault(path);
|
||||
final boolean verifyFileIntegrity = node.has("checkIntegrity") ? node.get("checkIntegrity").asBoolean() : false;
|
||||
dir.setVerifyFileIntegrity(verifyFileIntegrity);
|
||||
if (node.has("mountName")) {
|
||||
@@ -7,10 +7,10 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
|
||||
public class DirectorySerializer extends JsonSerializer<Directory> {
|
||||
public class VaultSerializer extends JsonSerializer<Vault> {
|
||||
|
||||
@Override
|
||||
public void serialize(Directory value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
|
||||
public void serialize(Vault value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
|
||||
jgen.writeStartObject();
|
||||
jgen.writeStringField("path", value.getPath().toString());
|
||||
jgen.writeBooleanField("checkIntegrity", value.shouldVerifyFileIntegrity());
|
||||
@@ -21,7 +21,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.ui.model.Directory;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -54,7 +54,7 @@ public class Settings implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
private List<Directory> directories;
|
||||
private List<Vault> directories;
|
||||
|
||||
private Settings() {
|
||||
// private constructor
|
||||
@@ -95,14 +95,14 @@ public class Settings implements Serializable {
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public List<Directory> getDirectories() {
|
||||
public List<Vault> getDirectories() {
|
||||
if (directories == null) {
|
||||
directories = new ArrayList<>();
|
||||
}
|
||||
return directories;
|
||||
}
|
||||
|
||||
public void setDirectories(List<Directory> directories) {
|
||||
public void setDirectories(List<Vault> directories) {
|
||||
this.directories = directories;
|
||||
}
|
||||
|
||||
|
||||
@@ -775,6 +775,13 @@ is being used to size a border should also be in pixels.
|
||||
-fx-orientation: horizontal;
|
||||
}
|
||||
|
||||
.tool-bar.list-related-toolbar {
|
||||
-fx-background-color: transparent;
|
||||
-fx-padding: 0.1em 0;
|
||||
-fx-spacing: 0;
|
||||
-fx-alignment: CENTER_LEFT;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Slider *
|
||||
|
||||
@@ -206,7 +206,6 @@
|
||||
}
|
||||
.button:armed,
|
||||
.button:default:armed,
|
||||
.toggle-button:armed,
|
||||
.menu-button:armed,
|
||||
.split-menu-button:armed > .label,
|
||||
.split-menu-button > .arrow-button:pressed,
|
||||
@@ -362,6 +361,30 @@
|
||||
-fx-orientation: vertical;
|
||||
}
|
||||
|
||||
.tool-bar.list-related-toolbar {
|
||||
-fx-background-color: #B4B4B4, #F7F7F7;
|
||||
-fx-background-insets: 0, 0 1 1 1;
|
||||
-fx-padding: 0;
|
||||
-fx-spacing: 0;
|
||||
-fx-alignment: CENTER_LEFT;
|
||||
}
|
||||
|
||||
.tool-bar.list-related-toolbar .button,
|
||||
.tool-bar.list-related-toolbar .toggle-button {
|
||||
-fx-background-color: transparent;
|
||||
-fx-background-insets: 0;
|
||||
-fx-background-radius: 0;
|
||||
-fx-border-color: transparent #B4B4B4 transparent transparent;
|
||||
-fx-border-width: 1;
|
||||
}
|
||||
|
||||
.tool-bar.list-related-toolbar .button:armed,
|
||||
.tool-bar.list-related-toolbar .toggle-button:armed,
|
||||
.tool-bar.list-related-toolbar .toggle-button:selected {
|
||||
-fx-background-color: linear-gradient(to bottom, #C0C0C0 0%, #ADADAD 100%);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* ScrollBar *
|
||||
|
||||
@@ -358,6 +358,13 @@
|
||||
-fx-orientation: vertical;
|
||||
}
|
||||
|
||||
.tool-bar.list-related-toolbar {
|
||||
-fx-background-color: transparent;
|
||||
-fx-padding: 0.1em 0;
|
||||
-fx-spacing: 0;
|
||||
-fx-alignment: CENTER_LEFT;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* ScrollBar *
|
||||
|
||||
@@ -13,11 +13,15 @@
|
||||
<?import javafx.scene.control.ListView?>
|
||||
<?import javafx.scene.layout.Pane?>
|
||||
<?import javafx.scene.control.ToolBar?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ToggleButton?>
|
||||
<?import javafx.scene.control.ContextMenu?>
|
||||
<?import javafx.scene.control.MenuItem?>
|
||||
<?import javafx.scene.control.Separator?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
|
||||
<HBox fx:id="rootPane" prefHeight="400.0" prefWidth="600.0" fx:controller="org.cryptomator.ui.MainController" xmlns:fx="http://javafx.com/fxml">
|
||||
<HBox fx:id="rootPane" prefHeight="440.0" prefWidth="640.0" fx:controller="org.cryptomator.ui.MainController" xmlns:fx="http://javafx.com/fxml">
|
||||
|
||||
<padding><Insets top="20" right="20" bottom="20" left="20.0"/></padding>
|
||||
|
||||
<fx:define>
|
||||
<fx:include fx:id="welcomeView" source="welcome.fxml" />
|
||||
@@ -29,15 +33,21 @@
|
||||
<MenuItem text="%main.directoryList.contextMenu.changePassword" disable="true" />
|
||||
</items>
|
||||
</ContextMenu>
|
||||
<ContextMenu fx:id="addVaultContextMenu" onShowing="#willShowAddVaultContextMenu" onHidden="#didHideAddVaultContextMenu">
|
||||
<items>
|
||||
<MenuItem text="%main.addDirectory.contextMenu.new" onAction="#didClickCreateNewVault" />
|
||||
<MenuItem text="%main.addDirectory.contextMenu.open" onAction="#didClickAddExistingVaults" />
|
||||
</items>
|
||||
</ContextMenu>
|
||||
</fx:define>
|
||||
|
||||
<children>
|
||||
<VBox prefWidth="200.0">
|
||||
<children>
|
||||
<ListView fx:id="directoryList" VBox.vgrow="ALWAYS" focusTraversable="false" />
|
||||
<ToolBar VBox.vgrow="NEVER">
|
||||
<ToolBar VBox.vgrow="NEVER" styleClass="list-related-toolbar">
|
||||
<items>
|
||||
<Button text="+" onAction="#didClickAddDirectory" />
|
||||
<ToggleButton text="+" fx:id="addVaultButton" onAction="#didClickAddVault" focusTraversable="false"/>
|
||||
</items>
|
||||
</ToolBar>
|
||||
</children>
|
||||
|
||||
@@ -25,9 +25,9 @@
|
||||
<Label AnchorPane.leftAnchor="100.0" AnchorPane.topAnchor="50.0" style="-fx-font-size: 1.5em;" text="%welcome.welcomeLabel"/>
|
||||
<Label AnchorPane.leftAnchor="120.0" AnchorPane.topAnchor="280.0" text="%welcome.addButtonInstructionLabel"/>
|
||||
|
||||
<QuadCurve AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="300.0" startX="200.0" startY="0.0" endX="0.0" endY="80.0" controlX="180.0" controlY="80.0" fill="TRANSPARENT" stroke="BLACK" strokeWidth="2.0"/>
|
||||
<Line AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="370.0" startX="0.0" endX="10.0" startY="10.0" endY="0.0" strokeWidth="2.0"/>
|
||||
<Line AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="380.0" startX="0.0" endX="10.0" startY="0.0" endY="10.0" strokeWidth="2.0"/>
|
||||
<QuadCurve AnchorPane.leftAnchor="4.0" AnchorPane.topAnchor="300.0" startX="200.0" startY="0.0" endX="0.0" endY="80.0" controlX="180.0" controlY="80.0" fill="TRANSPARENT" stroke="BLACK" strokeWidth="2.0"/>
|
||||
<Line AnchorPane.leftAnchor="4.0" AnchorPane.topAnchor="370.0" startX="0.0" endX="10.0" startY="10.0" endY="0.0" strokeWidth="2.0"/>
|
||||
<Line AnchorPane.leftAnchor="4.0" AnchorPane.topAnchor="380.0" startX="0.0" endX="10.0" startY="0.0" endY="10.0" strokeWidth="2.0"/>
|
||||
</children>
|
||||
|
||||
</AnchorPane>
|
||||
@@ -13,6 +13,8 @@ app.name=Cryptomator
|
||||
main.directoryList.contextMenu.remove=Remove from list
|
||||
main.directoryList.contextMenu.addUser=Add user
|
||||
main.directoryList.contextMenu.changePassword=Change password
|
||||
main.addDirectory.contextMenu.new=Create new vault
|
||||
main.addDirectory.contextMenu.open=Add existing vault
|
||||
|
||||
|
||||
# welcome.fxml
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.cryptomator.ui.model.Directory;
|
||||
import org.junit.Test;
|
||||
|
||||
public class DirectoryTest {
|
||||
@Test
|
||||
public void testNormalize() throws Exception {
|
||||
assertEquals("_", Directory.normalize(" "));
|
||||
|
||||
assertEquals("a", Directory.normalize("ä"));
|
||||
|
||||
assertEquals("C", Directory.normalize("Ĉ"));
|
||||
|
||||
assertEquals("_", Directory.normalize(":"));
|
||||
|
||||
assertEquals("", Directory.normalize("汉语"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class VaultTest {
|
||||
|
||||
@Test
|
||||
public void testNormalize() throws Exception {
|
||||
assertEquals("_", Vault.normalize(" "));
|
||||
assertEquals("a", Vault.normalize("ä"));
|
||||
assertEquals("C", Vault.normalize("Ĉ"));
|
||||
assertEquals("_", Vault.normalize(":"));
|
||||
assertEquals("", Vault.normalize("汉语"));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user