diff --git a/README.md b/README.md index 400222c7d..8872955be 100644 --- a/README.md +++ b/README.md @@ -53,9 +53,11 @@ For more information on the security details visit [cryptomator.org](https://cry ``` cd main -mvn clean install +mvn clean install -Prelease ``` +An executable jar file will be created inside `main/uber-jar/target`. + ## Contributing to Cryptomator Please read our [contribution guide](https://github.com/cryptomator/cryptomator/blob/master/CONTRIBUTING.md), if you would like to report a bug, ask a question or help us with coding. diff --git a/main/ant-kit/pom.xml b/main/ant-kit/pom.xml index 607902dba..7292a21ef 100644 --- a/main/ant-kit/pom.xml +++ b/main/ant-kit/pom.xml @@ -8,7 +8,7 @@ org.cryptomator main - 1.2.2 + 1.2.3 ant-kit pom diff --git a/main/commons-test/pom.xml b/main/commons-test/pom.xml index d53fdd079..a9bb921f9 100644 --- a/main/commons-test/pom.xml +++ b/main/commons-test/pom.xml @@ -10,7 +10,7 @@ org.cryptomator main - 1.2.2 + 1.2.3 commons-test Cryptomator common test dependencies diff --git a/main/commons/pom.xml b/main/commons/pom.xml index cda650dbe..8e3f3101f 100644 --- a/main/commons/pom.xml +++ b/main/commons/pom.xml @@ -10,7 +10,7 @@ org.cryptomator main - 1.2.2 + 1.2.3 commons Cryptomator common diff --git a/main/filesystem-api/pom.xml b/main/filesystem-api/pom.xml index 19b43b448..84d3ba083 100644 --- a/main/filesystem-api/pom.xml +++ b/main/filesystem-api/pom.xml @@ -9,7 +9,7 @@ org.cryptomator main - 1.2.2 + 1.2.3 filesystem-api Cryptomator filesystem: API diff --git a/main/filesystem-charsets/pom.xml b/main/filesystem-charsets/pom.xml index c37512ce1..127ab648a 100644 --- a/main/filesystem-charsets/pom.xml +++ b/main/filesystem-charsets/pom.xml @@ -12,7 +12,7 @@ org.cryptomator main - 1.2.2 + 1.2.3 filesystem-charsets Cryptomator filesystem: Charset compatibility layer diff --git a/main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFolder.java b/main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFolder.java index e2762059b..d03c42d86 100644 --- a/main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFolder.java +++ b/main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFolder.java @@ -40,9 +40,9 @@ class NormalizedNameFolder extends DelegatingFolder org.cryptomator main - 1.2.2 + 1.2.3 filesystem-crypto-integration-tests Cryptomator filesystem: Encryption layer tests diff --git a/main/filesystem-crypto/pom.xml b/main/filesystem-crypto/pom.xml index 7c60a38b8..9cd80702a 100644 --- a/main/filesystem-crypto/pom.xml +++ b/main/filesystem-crypto/pom.xml @@ -12,7 +12,7 @@ org.cryptomator main - 1.2.2 + 1.2.3 filesystem-crypto Cryptomator filesystem: Encryption layer diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/ConflictResolver.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/ConflictResolver.java index a15c0a6fe..9ef4142bf 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/ConflictResolver.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/ConflictResolver.java @@ -68,7 +68,7 @@ final class ConflictResolver { String alternativeCiphertext = nameEncryptor.apply(alternativeCleartext).get(); alternativeFile = folder.file(isDirectory ? DIR_PREFIX + alternativeCiphertext : alternativeCiphertext); } while (alternativeFile.exists()); - LOG.info("Detected conflict {}:\n{}\n{}", conflictId, canonicalFile, conflictingFile); + LOG.debug("Detected conflict {}:\n{}\n{}", conflictId, canonicalFile, conflictingFile); conflictingFile.moveTo(alternativeFile); return alternativeFile; } diff --git a/main/filesystem-inmemory/pom.xml b/main/filesystem-inmemory/pom.xml index a640963ca..c52333cc9 100644 --- a/main/filesystem-inmemory/pom.xml +++ b/main/filesystem-inmemory/pom.xml @@ -12,7 +12,7 @@ org.cryptomator main - 1.2.2 + 1.2.3 filesystem-inmemory Cryptomator filesystem: In-memory mock diff --git a/main/filesystem-invariants-tests/pom.xml b/main/filesystem-invariants-tests/pom.xml index 442b4dcd0..765eb2595 100644 --- a/main/filesystem-invariants-tests/pom.xml +++ b/main/filesystem-invariants-tests/pom.xml @@ -9,7 +9,7 @@ org.cryptomator main - 1.2.2 + 1.2.3 filesystem-invariants-tests Cryptomator filesystem: Invariants tests diff --git a/main/filesystem-nameshortening/pom.xml b/main/filesystem-nameshortening/pom.xml index c2da992b3..95f58e244 100644 --- a/main/filesystem-nameshortening/pom.xml +++ b/main/filesystem-nameshortening/pom.xml @@ -12,7 +12,7 @@ org.cryptomator main - 1.2.2 + 1.2.3 filesystem-nameshortening Cryptomator filesystem: Name shortening layer diff --git a/main/filesystem-nio/pom.xml b/main/filesystem-nio/pom.xml index 834da5a2a..7b31518c4 100644 --- a/main/filesystem-nio/pom.xml +++ b/main/filesystem-nio/pom.xml @@ -7,7 +7,7 @@ org.cryptomator main - 1.2.2 + 1.2.3 filesystem-nio Cryptomator filesystem: NIO-based physical layer diff --git a/main/filesystem-stats/pom.xml b/main/filesystem-stats/pom.xml index 8d6055807..dd93fccdb 100644 --- a/main/filesystem-stats/pom.xml +++ b/main/filesystem-stats/pom.xml @@ -12,7 +12,7 @@ org.cryptomator main - 1.2.2 + 1.2.3 filesystem-stats Cryptomator filesystem: Throughput statistics diff --git a/main/frontend-api/pom.xml b/main/frontend-api/pom.xml index a95764b6e..da7ec0d45 100644 --- a/main/frontend-api/pom.xml +++ b/main/frontend-api/pom.xml @@ -12,7 +12,7 @@ org.cryptomator main - 1.2.2 + 1.2.3 frontend-api Cryptomator frontend: API diff --git a/main/frontend-webdav/pom.xml b/main/frontend-webdav/pom.xml index 0182184f3..c788b15e9 100644 --- a/main/frontend-webdav/pom.xml +++ b/main/frontend-webdav/pom.xml @@ -12,7 +12,7 @@ org.cryptomator main - 1.2.2 + 1.2.3 frontend-webdav Cryptomator frontend: WebDAV frontend diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/ContextPathBuilder.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/ContextPathBuilder.java index ee036d221..6e9c6a7e8 100644 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/ContextPathBuilder.java +++ b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/ContextPathBuilder.java @@ -18,6 +18,10 @@ class ContextPaths { return format("/%s/%s", id, name); } + public static String removeFrontendId(String path) { + return path.replaceAll("/" + FRONTEND_ID_PATTERN + "/", "/[...]/"); + } + public static Optional extractFrontendId(String path) { Matcher matcher = SERVLET_PATH_WITH_FRONTEND_ID_PATTERN.matcher(path); if (matcher.matches()) { diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/FontendIdHidingServletContextHandler.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/FontendIdHidingServletContextHandler.java new file mode 100644 index 000000000..9b983d9ac --- /dev/null +++ b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/FontendIdHidingServletContextHandler.java @@ -0,0 +1,17 @@ +package org.cryptomator.frontend.webdav; + +import org.eclipse.jetty.server.HandlerContainer; +import org.eclipse.jetty.servlet.ServletContextHandler; + +class FontendIdHidingServletContextHandler extends ServletContextHandler { + + public FontendIdHidingServletContextHandler(HandlerContainer parent, String contextPath, int options) { + super(parent, contextPath, options); + } + + @Override + public String toString() { + return ContextPaths.removeFrontendId(super.toString()); + } + +} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavServer.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavServer.java index cf40f1ce1..81ed65087 100644 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavServer.java +++ b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavServer.java @@ -8,8 +8,6 @@ *******************************************************************************/ package org.cryptomator.frontend.webdav; -import static java.lang.String.format; - import java.net.URI; import java.net.URISyntaxException; import java.util.Collection; @@ -110,7 +108,7 @@ public class WebDavServer implements FrontendFactory { @Override public Frontend create(Folder root, FrontendId id, String name) throws FrontendCreationFailedException { - String contextPath = format("/%s/%s", id, name); + String contextPath = ContextPaths.from(id, name); final URI uri; try { uri = new URI("http", null, "localhost", getPort(), contextPath, null, null); @@ -118,10 +116,10 @@ public class WebDavServer implements FrontendFactory { throw new IllegalStateException(e); } final ServletContextHandler handler = addServlet(root, uri); - LOG.info("Servlet available under " + uri); + LOG.info("Servlet available under " + ContextPaths.removeFrontendId(uri.toString())); return new WebDavFrontend(webdavMounterProvider, handler, uri); } - + public void setValidFrontendIds(Collection validFrontendIds) { tarpit.setValidFrontendIds(validFrontendIds); } diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavServletContextFactory.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavServletContextFactory.java index 85915d192..ca6fcbebf 100644 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavServletContextFactory.java +++ b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavServletContextFactory.java @@ -35,9 +35,10 @@ import org.eclipse.jetty.servlet.ServletHolder; class WebDavServletContextFactory { private static final String WILDCARD = "/*"; - + @Inject - public WebDavServletContextFactory() {} + public WebDavServletContextFactory() { + } /** * Creates a new Jetty ServletContextHandler, that can be be added to a servletCollection as follows: @@ -63,7 +64,7 @@ class WebDavServletContextFactory { } }; final String contextPath = StringUtils.removeEnd(contextRoot.getPath(), "/"); - final ServletContextHandler servletContext = new ServletContextHandler(null, contextPath, ServletContextHandler.SESSIONS); + final ServletContextHandler servletContext = new FontendIdHidingServletContextHandler(null, contextPath, ServletContextHandler.SESSIONS); final ServletHolder servletHolder = new ServletHolder(contextPath, new WebDavServlet(contextRoot, root)); servletContext.addServlet(servletHolder, WILDCARD); servletContext.addFilter(LoopbackFilter.class, WILDCARD, EnumSet.of(DispatcherType.REQUEST)); diff --git a/main/jacoco-report/pom.xml b/main/jacoco-report/pom.xml index 419454760..6f4f898ae 100644 --- a/main/jacoco-report/pom.xml +++ b/main/jacoco-report/pom.xml @@ -5,7 +5,7 @@ org.cryptomator main - 1.2.2 + 1.2.3 jacoco-report Cryptomator Code Coverage Report diff --git a/main/keychain/pom.xml b/main/keychain/pom.xml index fdeb8384d..756e81eeb 100644 --- a/main/keychain/pom.xml +++ b/main/keychain/pom.xml @@ -3,7 +3,7 @@ org.cryptomator main - 1.2.2 + 1.2.3 keychain System Keychain Access diff --git a/main/pom.xml b/main/pom.xml index 009875667..f0f365e73 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -6,7 +6,7 @@ 4.0.0 org.cryptomator main - 1.2.2 + 1.2.3 pom Cryptomator diff --git a/main/uber-jar/pom.xml b/main/uber-jar/pom.xml index f6222e5cd..4d715f850 100644 --- a/main/uber-jar/pom.xml +++ b/main/uber-jar/pom.xml @@ -12,7 +12,7 @@ org.cryptomator main - 1.2.2 + 1.2.3 uber-jar pom diff --git a/main/ui/pom.xml b/main/ui/pom.xml index 368a698d8..06a479090 100644 --- a/main/ui/pom.xml +++ b/main/ui/pom.xml @@ -12,7 +12,7 @@ org.cryptomator main - 1.2.2 + 1.2.3 ui Cryptomator GUI diff --git a/main/ui/src/main/java/org/cryptomator/ui/Cryptomator.java b/main/ui/src/main/java/org/cryptomator/ui/Cryptomator.java index 3150ddc4d..1f7e07531 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/Cryptomator.java +++ b/main/ui/src/main/java/org/cryptomator/ui/Cryptomator.java @@ -20,6 +20,7 @@ import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.ui.util.ApplicationVersion; import org.cryptomator.ui.util.SingleInstanceManager; import org.cryptomator.ui.util.SingleInstanceManager.RemoteInstance; import org.eclipse.jetty.util.ConcurrentHashSet; @@ -33,63 +34,62 @@ public class Cryptomator { public static final CompletableFuture> OPEN_FILE_HANDLER = new CompletableFuture<>(); private static final Logger LOG = LoggerFactory.getLogger(Cryptomator.class); private static final Set SHUTDOWN_TASKS = new ConcurrentHashSet<>(); - private static final CleanShutdownPerformer CLEAN_SHUTDOWN_PERFORMER = new CleanShutdownPerformer(); public static void main(String[] args) { - String cryptomatorVersion = Optional.ofNullable(Cryptomator.class.getPackage().getImplementationVersion()).orElse("SNAPSHOT"); - LOG.info("Starting Cryptomator {} on {} {} ({})", cryptomatorVersion, SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH); + LOG.info("Starting Cryptomator {} on {} {} ({})", ApplicationVersion.orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH); + 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 - * 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 { - 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); - } + addOsxFileOpenHandler(); } - /* - * Perform certain things on VM termination. - */ - Runtime.getRuntime().addShutdownHook(CLEAN_SHUTDOWN_PERFORMER); + new CleanShutdownPerformer().registerShutdownHook(); - /* - * 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 = SingleInstanceManager.getRemoteInstance(MainApplication.APPLICATION_KEY); - - if (remoteInstance.isPresent()) { - 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], 100); - } - } catch (Exception e) { - LOG.error("Error forwarding arguments to remote instance", e); - } + final Optional runningInstance = SingleInstanceManager.getRemoteInstance(MainApplication.APPLICATION_KEY); + if (runningInstance.isPresent()) { + sendArgsToRunningInstance(args, runningInstance); } else { Application.launch(MainApplication.class, args); } } + private static void addOsxFileOpenHandler() { + /* + * 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 { + 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); + } + } + + private static void sendArgsToRunningInstance(String[] args, final Optional remoteInstance) { + 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], 100); + } + } catch (Exception e) { + LOG.error("Error forwarding arguments to remote instance", e); + } + } + public static void addShutdownTask(Runnable r) { SHUTDOWN_TASKS.add(r); } @@ -111,6 +111,10 @@ public class Cryptomator { }); SHUTDOWN_TASKS.clear(); } + + public void registerShutdownHook() { + Runtime.getRuntime().addShutdownHook(this); + } } private static void handleOpenFileRequest(File file) { diff --git a/main/ui/src/main/java/org/cryptomator/ui/CryptomatorComponent.java b/main/ui/src/main/java/org/cryptomator/ui/CryptomatorComponent.java index e82e006e9..c4360fe56 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/CryptomatorComponent.java +++ b/main/ui/src/main/java/org/cryptomator/ui/CryptomatorComponent.java @@ -37,6 +37,8 @@ interface CryptomatorComponent { ExitUtil exitUtil(); + DebugMode debugMode(); + Optional nativeMacFunctions(); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/DebugMode.java b/main/ui/src/main/java/org/cryptomator/ui/DebugMode.java new file mode 100644 index 000000000..037e68a42 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/DebugMode.java @@ -0,0 +1,75 @@ +package org.cryptomator.ui; + +import static java.util.Arrays.asList; +import static org.apache.logging.log4j.LogManager.ROOT_LOGGER_NAME; + +import java.util.Collection; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.LoggerConfig; +import org.cryptomator.ui.settings.Settings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Singleton +public class DebugMode { + + private static final Logger LOG = LoggerFactory.getLogger(DebugMode.class); + + private static final Collection LOGGER_UPGRADES = asList( // + loggerUpgrade(ROOT_LOGGER_NAME, Level.INFO), // + loggerUpgrade("org.cryptomator", Level.TRACE), // + loggerUpgrade("org.eclipse.jetty.server.Server", Level.DEBUG) // + ); + + private final Settings settings; + + @Inject + public DebugMode(Settings settings) { + this.settings = settings; + } + + public void initialize() { + if (settings.getDebugMode()) { + enable(); + LOG.debug("Debug mode initialized"); + } + } + + private void enable() { + LoggerContext context = (LoggerContext) LogManager.getContext(false); + Configuration config = context.getConfiguration(); + LOGGER_UPGRADES.forEach(loggerUpgrade -> loggerUpgrade.execute(config)); + context.updateLoggers(); + } + + private static LoggerUpgrade loggerUpgrade(String loggerName, Level minLevel) { + return new LoggerUpgrade(loggerName, minLevel); + } + + private static class LoggerUpgrade { + + private final Level level; + private final String loggerName; + + public LoggerUpgrade(String loggerName, Level minLevel) { + this.loggerName = loggerName; + this.level = minLevel; + } + + public void execute(Configuration config) { + LoggerConfig loggerConfig = config.getLoggerConfig(loggerName); + if (loggerConfig.getLevel().isMoreSpecificThan(level)) { + loggerConfig.setLevel(level); + } + } + + } + +} \ No newline at end of file diff --git a/main/ui/src/main/java/org/cryptomator/ui/MainApplication.java b/main/ui/src/main/java/org/cryptomator/ui/MainApplication.java index 11628a65c..e54277d10 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/MainApplication.java +++ b/main/ui/src/main/java/org/cryptomator/ui/MainApplication.java @@ -43,18 +43,46 @@ public class MainApplication extends Application { @Override public void start(Stage primaryStage) throws IOException { LOG.info("JavaFX application started"); - final CryptomatorComponent comp; + + CryptomatorComponent comp = createCryptomatorComponent(primaryStage); + MainController mainCtrl = comp.mainController(); + closer = comp.deferredCloser(); + + comp.debugMode().initialize(); + + setupFXMLClassLoader(); + setupStylesheets(); + + initializeStage(primaryStage, mainCtrl); + showWindow(primaryStage); + + registerExitHandler(comp); + + openFilesRequestedDuringStartup(primaryStage, mainCtrl); + registerApplicationToProcessOpenFileRequests(primaryStage, comp, mainCtrl); + } + + @Override + public void stop() { try { - comp = DaggerCryptomatorComponent.builder() // + closer.close(); + } catch (ExecutionException e) { + LOG.error("Error closing ressources", e); + } + } + + private CryptomatorComponent createCryptomatorComponent(Stage primaryStage) { + try { + return DaggerCryptomatorComponent.builder() // .cryptomatorModule(new CryptomatorModule(this, primaryStage)) // .secureRandomModule(new SecureRandomModule(SecureRandom.getInstanceStrong())) // .build(); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("Every implementation of the Java platform is required to support at least one strong SecureRandom implementation.", e); } - final MainController mainCtrl = comp.mainController(); - closer = comp.deferredCloser(); + } + private void setupFXMLClassLoader() { ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); FXMLLoader.setDefaultClassLoader(contextClassLoader); Platform.runLater(() -> { @@ -66,35 +94,55 @@ public class MainApplication extends Application { Thread.currentThread().setContextClassLoader(contextClassLoader); } }); + } - // Set stylesheets and initialize stage: + private void setupStylesheets() { Font.loadFont(getClass().getResourceAsStream("/css/ionicons.ttf"), 12.0); chooseNativeStylesheet(); + } + + private void initializeStage(Stage primaryStage, MainController mainCtrl) { mainCtrl.initStage(primaryStage); primaryStage.titleProperty().bind(mainCtrl.windowTitle()); primaryStage.setResizable(false); if (SystemUtils.IS_OS_WINDOWS) { primaryStage.getIcons().add(new Image(MainApplication.class.getResourceAsStream("/window_icon.png"))); } + } - // show window and start observing its focus: + private void showWindow(Stage primaryStage) { primaryStage.show(); ActiveWindowStyleSupport.startObservingFocus(primaryStage); - comp.exitUtil().initExitHandler(this::quit); + } - // open files, if requested during startup: + private void registerExitHandler(CryptomatorComponent comp) { + comp.exitUtil().initExitHandler(this::quit); + } + + private void openFilesRequestedDuringStartup(Stage primaryStage, final MainController mainCtrl) { for (String arg : getParameters().getUnnamed()) { handleCommandLineArg(arg, primaryStage, mainCtrl); } if (SystemUtils.IS_OS_MAC_OSX) { Cryptomator.OPEN_FILE_HANDLER.complete(file -> handleCommandLineArg(file.getAbsolutePath(), primaryStage, mainCtrl)); } + } - // register this application instance as primary application, that other instances can send open file requests to: + private void registerApplicationToProcessOpenFileRequests(Stage primaryStage, final CryptomatorComponent comp, final MainController mainCtrl) throws IOException { LocalInstance cryptomatorGuiInstance = closer.closeLater(SingleInstanceManager.startLocalInstance(APPLICATION_KEY, comp.executorService()), LocalInstance::close).get().get(); cryptomatorGuiInstance.registerListener(arg -> handleCommandLineArg(arg, primaryStage, mainCtrl)); } + private void chooseNativeStylesheet() { + if (SystemUtils.IS_OS_MAC_OSX) { + setUserAgentStylesheet(getClass().getResource("/css/mac_theme.css").toString()); + } else if (SystemUtils.IS_OS_LINUX) { + setUserAgentStylesheet(getClass().getResource("/css/linux_theme.css").toString()); + } else if (SystemUtils.IS_OS_WINDOWS) { + setUserAgentStylesheet(getClass().getResource("/css/win_theme.css").toString()); + } + } + private void handleCommandLineArg(String arg, Stage primaryStage, MainController mainCtrl) { // find correct location: final Path path = FileSystems.getDefault().getPath(arg); @@ -118,16 +166,6 @@ public class MainApplication extends Application { }); } - private void chooseNativeStylesheet() { - if (SystemUtils.IS_OS_MAC_OSX) { - setUserAgentStylesheet(getClass().getResource("/css/mac_theme.css").toString()); - } else if (SystemUtils.IS_OS_LINUX) { - setUserAgentStylesheet(getClass().getResource("/css/linux_theme.css").toString()); - } else if (SystemUtils.IS_OS_WINDOWS) { - setUserAgentStylesheet(getClass().getResource("/css/win_theme.css").toString()); - } - } - private void quit() { Platform.runLater(() -> { stop(); @@ -136,13 +174,4 @@ public class MainApplication extends Application { }); } - @Override - public void stop() { - try { - closer.close(); - } catch (ExecutionException e) { - LOG.error("Error closing ressources", e); - } - } - } diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java index 34ef59b0d..7861a5962 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java @@ -59,6 +59,9 @@ public class SettingsController extends LocalizedFXMLViewController { @FXML private ChoiceBox prefGvfsScheme; + @FXML + private CheckBox debugModeCheckbox; + @Override public void initialize() { checkForUpdatesCheckbox.setDisable(areUpdatesManagedExternally()); @@ -74,11 +77,13 @@ public class SettingsController extends LocalizedFXMLViewController { prefGvfsScheme.getItems().add("dav"); prefGvfsScheme.getItems().add("webdav"); prefGvfsScheme.setValue(settings.getPreferredGvfsScheme()); + debugModeCheckbox.setSelected(settings.getDebugMode()); EasyBind.subscribe(checkForUpdatesCheckbox.selectedProperty(), this::checkForUpdateDidChange); EasyBind.subscribe(portField.textProperty(), this::portDidChange); EasyBind.subscribe(useIpv6Checkbox.selectedProperty(), this::useIpv6DidChange); EasyBind.subscribe(prefGvfsScheme.valueProperty(), this::prefGvfsSchemeDidChange); + EasyBind.subscribe(debugModeCheckbox.selectedProperty(), this::debugModeDidChange); } @Override @@ -114,6 +119,11 @@ public class SettingsController extends LocalizedFXMLViewController { settings.save(); } + private void debugModeDidChange(Boolean newValue) { + settings.setDebugMode(newValue); + settings.save(); + } + private void prefGvfsSchemeDidChange(String newValue) { settings.setPreferredGvfsScheme(newValue); settings.save(); diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java index a8d241431..72341c74f 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java @@ -30,6 +30,7 @@ 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.cryptomator.ui.util.DialogBuilderUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,7 +41,9 @@ import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.event.ActionEvent; import javafx.fxml.FXML; +import javafx.scene.control.Alert; import javafx.scene.control.Button; +import javafx.scene.control.ButtonType; import javafx.scene.control.CheckBox; import javafx.scene.control.ChoiceBox; import javafx.scene.control.Hyperlink; @@ -276,6 +279,37 @@ public class UnlockController extends LocalizedFXMLViewController { } } + // **************************************** + // Save password checkbox + // **************************************** + + @FXML + private void didClickSavePasswordCheckbox(ActionEvent event) { + if (!savePassword.isSelected() && hasStoredPassword()) { + Alert confirmDialog = DialogBuilderUtil.buildConfirmationDialog( // + localization.getString("unlock.savePassword.delete.confirmation.title"), // + localization.getString("unlock.savePassword.delete.confirmation.header"), // + localization.getString("unlock.savePassword.delete.confirmation.content"), // + SystemUtils.IS_OS_MAC_OSX ? ButtonType.CANCEL : ButtonType.OK); + + Optional choice = confirmDialog.showAndWait(); + if (ButtonType.OK.equals(choice.get())) { + keychainAccess.get().deletePassphrase(vault.getId()); + } else if (ButtonType.CANCEL.equals(choice.get())) { + savePassword.setSelected(true); + } + } + } + + private boolean hasStoredPassword() { + char[] storedPw = keychainAccess.get().loadPassphrase(vault.getId()); + boolean hasPw = (storedPw != null); + if (storedPw != null) { + Arrays.fill(storedPw, ' '); + } + return hasPw; + } + // **************************************** // Unlock button // **************************************** diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java index 3d0e58aa4..610260092 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java @@ -38,6 +38,7 @@ import javafx.scene.chart.XYChart.Data; import javafx.scene.chart.XYChart.Series; import javafx.scene.control.ContextMenu; import javafx.scene.control.Label; +import javafx.scene.control.MenuItem; import javafx.scene.control.ToggleButton; import javafx.scene.input.Clipboard; import javafx.scene.input.ClipboardContent; @@ -57,15 +58,6 @@ public class UnlockedController extends LocalizedFXMLViewController { private Optional listener = Optional.empty(); private Timeline ioAnimation; - @Inject - public UnlockedController(Localization localization, Provider macWarningsControllerProvider, AsyncTaskService asyncTaskService) { - super(localization); - this.macWarningsController = macWarningsControllerProvider.get(); - this.asyncTaskService = asyncTaskService; - - macWarningsController.vault.bind(this.vault); - } - @FXML private Label messageLabel; @@ -81,11 +73,25 @@ public class UnlockedController extends LocalizedFXMLViewController { @FXML private ContextMenu moreOptionsMenu; + @FXML + private MenuItem revealVaultMenuItem; + + @Inject + public UnlockedController(Localization localization, Provider macWarningsControllerProvider, AsyncTaskService asyncTaskService) { + super(localization); + this.macWarningsController = macWarningsControllerProvider.get(); + this.asyncTaskService = asyncTaskService; + + macWarningsController.vault.bind(this.vault); + } + @Override public void initialize() { macWarningsController.initStage(macWarningsWindow); ActiveWindowStyleSupport.startObservingFocus(macWarningsWindow); + revealVaultMenuItem.disableProperty().bind(EasyBind.map(vault, vault -> vault != null && !vault.isMounted())); + EasyBind.subscribe(vault, this::vaultChanged); EasyBind.subscribe(moreOptionsMenu.showingProperty(), moreOptionsButton::setSelected); } @@ -109,6 +115,11 @@ public class UnlockedController extends LocalizedFXMLViewController { } }); + if (!vault.get().isMounted()) { + // TODO Markus Kreusch #393: hyperlink auf FAQ oder sowas? + messageLabel.setText(localization.getString("unlocked.label.mountFailed")); + } + // (re)start throughput statistics: stopIoSampling(); startIoSampling(); diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java index 143a43ef9..e1a59956f 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java @@ -13,7 +13,6 @@ import java.net.URL; import java.util.Comparator; import java.util.HashMap; import java.util.Map; -import java.util.Optional; import javax.inject.Inject; import javax.inject.Named; @@ -29,6 +28,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.ApplicationVersion; import org.cryptomator.ui.util.AsyncTaskService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -104,7 +104,7 @@ public class WelcomeController extends LocalizedFXMLViewController { 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().setParameter(HttpClientParams.USER_AGENT, "Cryptomator VersionChecker/" + ApplicationVersion.orElse("SNAPSHOT")); client.getParams().setCookiePolicy(CookiePolicy.IGNORE_COOKIES); client.getParams().setConnectionManagerTimeout(5000); client.executeMethod(method); @@ -124,10 +124,6 @@ public class WelcomeController extends LocalizedFXMLViewController { }).run(); } - private Optional applicationVersion() { - return Optional.ofNullable(getClass().getPackage().getImplementationVersion()); - } - private void compareVersions(final Map latestVersions) { final String latestVersion; if (SystemUtils.IS_OS_MAC_OSX) { @@ -140,7 +136,7 @@ public class WelcomeController extends LocalizedFXMLViewController { // no version check possible on unsupported OS return; } - final String currentVersion = applicationVersion().orElse(null); + final String currentVersion = ApplicationVersion.orElse(null); LOG.debug("Current version: {}, lastest version: {}", currentVersion, latestVersion); if (currentVersion != null && semVerComparator.compare(currentVersion, latestVersion) < 0) { final String msg = String.format(localization.getString("welcome.newVersionMessage"), latestVersion, currentVersion); diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java b/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java index 086fffa90..0d36158fc 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java @@ -50,6 +50,8 @@ import org.cryptomator.ui.util.DeferredClosable; import org.cryptomator.ui.util.DeferredCloser; import org.cryptomator.ui.util.FXThreads; import org.fxmisc.easybind.EasyBind; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableMap; @@ -65,6 +67,8 @@ import javafx.collections.ObservableList; public class Vault implements CryptoFileSystemDelegate { + private static final Logger LOG = LoggerFactory.getLogger(CryptoFileSystemDelegate.class); + public static final String VAULT_FILE_EXTENSION = ".cryptomator"; private final ObjectProperty path; @@ -72,6 +76,7 @@ public class Vault implements CryptoFileSystemDelegate { private final CryptoFileSystemFactory cryptoFileSystemFactory; private final DeferredCloser closer; private final BooleanProperty unlocked = new SimpleBooleanProperty(); + private final BooleanProperty mounted = new SimpleBooleanProperty(); private final ObservableList namesOfResourcesWithInvalidMac = FXThreads.observableListOnMainThread(FXCollections.observableArrayList()); private final Set whitelistedResourcesWithInvalidMac = new HashSet<>(); private final AtomicReference nioFileSystem = new AtomicReference<>(); @@ -131,7 +136,8 @@ public class Vault implements CryptoFileSystemDelegate { } public synchronized void activateFrontend(FrontendFactory frontendFactory, Settings settings, CharSequence passphrase) throws FrontendCreationFailedException { - boolean success = false; + boolean launchSuccess = false; + boolean mountSuccess = false; try { FileSystem fs = getNioFileSystem(); FileSystem shorteningFs = shorteningFileSystemFactory.get(fs); @@ -140,22 +146,32 @@ public class Vault implements CryptoFileSystemDelegate { StatsFileSystem statsFs = new StatsFileSystem(normalizingFs); statsFileSystem = Optional.of(statsFs); Frontend frontend = frontendFactory.create(statsFs, FrontendId.from(id), stripStart(mountName, "/")); + launchSuccess = true; filesystemFrontend = closer.closeLater(frontend); frontend.mount(getMountParams(settings)); - success = true; - } catch (UncheckedIOException | CommandFailedException e) { + mountSuccess = true; + } catch (UncheckedIOException e) { throw new FrontendCreationFailedException(e); + } catch (CommandFailedException e) { + LOG.error("Failed to mount vault " + mountName, e); } finally { // unlocked is a observable property and should only be changed by the FX application thread - final boolean finalSuccess = success; - Platform.runLater(() -> unlocked.set(finalSuccess)); + boolean finalLaunchSuccess = launchSuccess; + boolean finalMountSuccess = mountSuccess; + Platform.runLater(() -> { + unlocked.set(finalLaunchSuccess); + mounted.set(finalMountSuccess); + }); } } public synchronized void deactivateFrontend() throws Exception { filesystemFrontend.close(); statsFileSystem = Optional.empty(); - Platform.runLater(() -> unlocked.set(false)); + Platform.runLater(() -> { + mounted.set(false); + unlocked.set(false); + }); } private Map> getMountParams(Settings settings) { @@ -239,10 +255,18 @@ public class Vault implements CryptoFileSystemDelegate { return unlocked; } + public BooleanProperty mountedProperty() { + return mounted; + } + public boolean isUnlocked() { return unlocked.get(); } + public boolean isMounted() { + return mounted.get(); + } + public ObservableList getNamesOfResourcesWithInvalidMac() { return namesOfResourcesWithInvalidMac; } diff --git a/main/ui/src/main/java/org/cryptomator/ui/settings/Settings.java b/main/ui/src/main/java/org/cryptomator/ui/settings/Settings.java index f9323bf99..e4180f969 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/settings/Settings.java +++ b/main/ui/src/main/java/org/cryptomator/ui/settings/Settings.java @@ -28,6 +28,7 @@ public class Settings implements Serializable { 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"; + public static final boolean DEFAULT_DEBUG_MODE = false; private final Consumer saveCmd; @@ -49,6 +50,9 @@ public class Settings implements Serializable { @JsonProperty("preferredGvfsScheme") private String preferredGvfsScheme; + @JsonProperty("debugMode") + private Boolean debugMode; + /** * Package-private constructor; use {@link SettingsProvider}. */ @@ -125,4 +129,12 @@ public class Settings implements Serializable { this.preferredGvfsScheme = preferredGvfsScheme; } + public boolean getDebugMode() { + return debugMode == null ? DEFAULT_DEBUG_MODE : debugMode; + } + + public void setDebugMode(boolean debugMode) { + this.debugMode = debugMode; + } + } diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/ApplicationVersion.java b/main/ui/src/main/java/org/cryptomator/ui/util/ApplicationVersion.java new file mode 100644 index 000000000..beaaf46df --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/util/ApplicationVersion.java @@ -0,0 +1,17 @@ +package org.cryptomator.ui.util; + +import java.util.Optional; + +import org.cryptomator.ui.Cryptomator; + +public class ApplicationVersion { + + public static String orElse(String other) { + return get().orElse(other); + } + + public static Optional get() { + return Optional.ofNullable(Cryptomator.class.getPackage().getImplementationVersion()); + } + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/SingleInstanceManager.java b/main/ui/src/main/java/org/cryptomator/ui/util/SingleInstanceManager.java index 9f6c55a65..adcf9bcd6 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/util/SingleInstanceManager.java +++ b/main/ui/src/main/java/org/cryptomator/ui/util/SingleInstanceManager.java @@ -85,7 +85,7 @@ public class SingleInstanceManager { return true; } return !buf.hasRemaining(); - } , timeout, 10); + }, timeout, 10); return !buf.hasRemaining(); } @@ -117,13 +117,14 @@ public class SingleInstanceManager { final String applicationKey; final ServerSocketChannel channel; final Selector selector; - int port = 0; + final int port; - public LocalInstance(String applicationKey, ServerSocketChannel channel, Selector selector) { + public LocalInstance(String applicationKey, ServerSocketChannel channel, Selector selector, int port) { Objects.requireNonNull(applicationKey); this.applicationKey = applicationKey; this.channel = channel; this.selector = selector; + this.port = port; } /** @@ -317,28 +318,27 @@ public class SingleInstanceManager { */ public static LocalInstance startLocalInstance(String applicationKey, ExecutorService exec) throws IOException { final ServerSocketChannel channel = ServerSocketChannel.open(); - channel.configureBlocking(false); - channel.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); + boolean success = false; + try { + channel.configureBlocking(false); + channel.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); - final int port = ((InetSocketAddress) channel.getLocalAddress()).getPort(); - Preferences.userNodeForPackage(Cryptomator.class).putInt(applicationKey, port); - LOG.debug("InstanceManager bound to port {}", port); + final int port = ((InetSocketAddress) channel.getLocalAddress()).getPort(); + Preferences.userNodeForPackage(Cryptomator.class).putInt(applicationKey, port); + LOG.debug("InstanceManager bound to port {}", port); - Selector selector = Selector.open(); - channel.register(selector, SelectionKey.OP_ACCEPT); - - LocalInstance instance = new LocalInstance(applicationKey, channel, selector); - - exec.submit(() -> { - try { - instance.port = ((InetSocketAddress) channel.getLocalAddress()).getPort(); - } catch (IOException e) { + Selector selector = Selector.open(); + channel.register(selector, SelectionKey.OP_ACCEPT); + LocalInstance instance = new LocalInstance(applicationKey, channel, selector, port); + exec.submit(instance::selectionLoop); + success = true; + return instance; + } finally { + if (!success) { + channel.close(); } - instance.selectionLoop(); - }); - - return instance; + } } /** @@ -368,7 +368,7 @@ public class SingleInstanceManager { } } return !buf.hasRemaining(); - } , timeout, 1); + }, timeout, 1); } } } \ No newline at end of file diff --git a/main/ui/src/main/resources/fxml/settings.fxml b/main/ui/src/main/resources/fxml/settings.fxml index 41f770be2..e18995898 100644 --- a/main/ui/src/main/resources/fxml/settings.fxml +++ b/main/ui/src/main/resources/fxml/settings.fxml @@ -46,6 +46,10 @@