瀏覽代碼

Merge branch 'feature/issue-363' into develop

Markus Kreusch 8 年之前
父節點
當前提交
4e728fd387
共有 19 個文件被更改,包括 278 次插入103 次删除
  1. 4 4
      main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFolder.java
  2. 1 1
      main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/ConflictResolver.java
  3. 4 0
      main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/ContextPathBuilder.java
  4. 17 0
      main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/FontendIdHidingServletContextHandler.java
  5. 3 5
      main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavServer.java
  6. 4 3
      main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavServletContextFactory.java
  7. 48 44
      main/ui/src/main/java/org/cryptomator/ui/Cryptomator.java
  8. 2 0
      main/ui/src/main/java/org/cryptomator/ui/CryptomatorComponent.java
  9. 75 0
      main/ui/src/main/java/org/cryptomator/ui/DebugMode.java
  10. 56 27
      main/ui/src/main/java/org/cryptomator/ui/MainApplication.java
  11. 10 0
      main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java
  12. 9 9
      main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java
  13. 3 7
      main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java
  14. 12 0
      main/ui/src/main/java/org/cryptomator/ui/settings/Settings.java
  15. 17 0
      main/ui/src/main/java/org/cryptomator/ui/util/ApplicationVersion.java
  16. 4 0
      main/ui/src/main/resources/fxml/settings.fxml
  17. 1 0
      main/ui/src/main/resources/localization/de.txt
  18. 1 0
      main/ui/src/main/resources/localization/en.txt
  19. 7 3
      main/ui/src/main/resources/log4j2.xml

+ 4 - 4
main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFolder.java

@@ -40,9 +40,9 @@ class NormalizedNameFolder extends DelegatingFolder<NormalizedNameFolder, Normal
 		NormalizedNameFile nfcFile = super.file(nfcName);
 		NormalizedNameFile nfdFile = super.file(nfdName);
 		if (!nfcName.equals(nfdName) && nfcFile.exists() && nfdFile.exists()) {
-			LOG.warn("Ambiguous file names \"" + nfcName + "\" (NFC) vs. \"" + nfdName + "\" (NFD). Both files exist. Using \"" + nfcName + "\" (NFC).");
+			LOG.debug("Ambiguous file names \"" + nfcName + "\" (NFC) vs. \"" + nfdName + "\" (NFD). Both files exist. Using \"" + nfcName + "\" (NFC).");
 		} else if (!nfcName.equals(nfdName) && !nfcFile.exists() && nfdFile.exists()) {
-			LOG.info("Moving file from \"" + nfcName + "\" (NFD) to \"" + nfdName + "\" (NFC).");
+			LOG.debug("Moving file from \"" + nfcName + "\" (NFD) to \"" + nfdName + "\" (NFC).");
 			nfdFile.moveTo(nfcFile);
 		}
 		return nfcFile;
@@ -60,9 +60,9 @@ class NormalizedNameFolder extends DelegatingFolder<NormalizedNameFolder, Normal
 		NormalizedNameFolder nfcFolder = super.folder(nfcName);
 		NormalizedNameFolder nfdFolder = super.folder(nfdName);
 		if (!nfcName.equals(nfdName) && nfcFolder.exists() && nfdFolder.exists()) {
-			LOG.warn("Ambiguous folder names \"" + nfcName + "\" (NFC) vs. \"" + nfdName + "\" (NFD). Both files exist. Using \"" + nfcName + "\" (NFC).");
+			LOG.debug("Ambiguous folder names \"" + nfcName + "\" (NFC) vs. \"" + nfdName + "\" (NFD). Both files exist. Using \"" + nfcName + "\" (NFC).");
 		} else if (!nfcName.equals(nfdName) && !nfcFolder.exists() && nfdFolder.exists()) {
-			LOG.info("Moving folder from \"" + nfcName + "\" (NFD) to \"" + nfdName + "\" (NFC).");
+			LOG.debug("Moving folder from \"" + nfcName + "\" (NFD) to \"" + nfdName + "\" (NFC).");
 			nfdFolder.moveTo(nfcFolder);
 		}
 		return nfcFolder;

+ 1 - 1
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;
 			}

+ 4 - 0
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<FrontendId> extractFrontendId(String path) {
 		Matcher matcher = SERVLET_PATH_WITH_FRONTEND_ID_PATTERN.matcher(path);
 		if (matcher.matches()) {

+ 17 - 0
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());
+	}
+
+}

+ 3 - 5
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<FrontendId> validFrontendIds) {
 		tarpit.setValidFrontendIds(validFrontendIds);
 	}

+ 4 - 3
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));

+ 48 - 44
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,60 +34,59 @@ public class Cryptomator {
 	public static final CompletableFuture<Consumer<File>> OPEN_FILE_HANDLER = new CompletableFuture<>();
 	private static final Logger LOG = LoggerFactory.getLogger(Cryptomator.class);
 	private static final Set<Runnable> 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();
+
+		final Optional<RemoteInstance> runningInstance = SingleInstanceManager.getRemoteInstance(MainApplication.APPLICATION_KEY);
+		if (runningInstance.isPresent()) {
+			sendArgsToRunningInstance(args, runningInstance);
+		} else {
+			Application.launch(MainApplication.class, args);
+		}
+	}
 
+	private static void addOsxFileOpenHandler() {
 		/*
-		 * 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.
+		 * 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
 		 */
-		final Optional<RemoteInstance> remoteInstance = SingleInstanceManager.getRemoteInstance(MainApplication.APPLICATION_KEY);
+		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);
+		}
+	}
 
-		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);
+	private static void sendArgsToRunningInstance(String[] args, final Optional<RemoteInstance> 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);
 			}
-		} else {
-			Application.launch(MainApplication.class, args);
+		} catch (Exception e) {
+			LOG.error("Error forwarding arguments to remote instance", e);
 		}
 	}
 
@@ -111,6 +111,10 @@ public class Cryptomator {
 			});
 			SHUTDOWN_TASKS.clear();
 		}
+
+		public void registerShutdownHook() {
+			Runtime.getRuntime().addShutdownHook(this);
+		}
 	}
 
 	private static void handleOpenFileRequest(File file) {

+ 2 - 0
main/ui/src/main/java/org/cryptomator/ui/CryptomatorComponent.java

@@ -37,6 +37,8 @@ interface CryptomatorComponent {
 
 	ExitUtil exitUtil();
 
+	DebugMode debugMode();
+
 	Optional<MacFunctions> nativeMacFunctions();
 
 }

+ 75 - 0
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<LoggerUpgrade> 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);
+			}
+		}
+
+	}
+
+}

+ 56 - 27
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);
+	}
+
+	private void registerExitHandler(CryptomatorComponent comp) {
 		comp.exitUtil().initExitHandler(this::quit);
+	}
 
-		// open files, if requested during startup:
+	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);
-		}
-	}
-
 }

+ 10 - 0
main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java

@@ -59,6 +59,9 @@ public class SettingsController extends LocalizedFXMLViewController {
 	@FXML
 	private ChoiceBox<String> 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();

+ 9 - 9
main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java

@@ -58,15 +58,6 @@ public class UnlockedController extends LocalizedFXMLViewController {
 	private Optional<LockListener> listener = Optional.empty();
 	private Timeline ioAnimation;
 
-	@Inject
-	public UnlockedController(Localization localization, Provider<MacWarningsController> macWarningsControllerProvider, AsyncTaskService asyncTaskService) {
-		super(localization);
-		this.macWarningsController = macWarningsControllerProvider.get();
-		this.asyncTaskService = asyncTaskService;
-
-		macWarningsController.vault.bind(this.vault);
-	}
-
 	@FXML
 	private Label messageLabel;
 
@@ -85,6 +76,15 @@ public class UnlockedController extends LocalizedFXMLViewController {
 	@FXML
 	private MenuItem revealVaultMenuItem;
 
+	@Inject
+	public UnlockedController(Localization localization, Provider<MacWarningsController> macWarningsControllerProvider, AsyncTaskService asyncTaskService) {
+		super(localization);
+		this.macWarningsController = macWarningsControllerProvider.get();
+		this.asyncTaskService = asyncTaskService;
+
+		macWarningsController.vault.bind(this.vault);
+	}
+
 	@Override
 	public void initialize() {
 		macWarningsController.initStage(macWarningsWindow);

+ 3 - 7
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<String> applicationVersion() {
-		return Optional.ofNullable(getClass().getPackage().getImplementationVersion());
-	}
-
 	private void compareVersions(final Map<String, String> 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);

+ 12 - 0
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<Settings> 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;
+	}
+
 }

+ 17 - 0
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<String> get() {
+		return Optional.ofNullable(Cryptomator.class.getPackage().getImplementationVersion());
+	}
+
+}

+ 4 - 0
main/ui/src/main/resources/fxml/settings.fxml

@@ -46,6 +46,10 @@
 			<Label GridPane.rowIndex="3" GridPane.columnIndex="0" fx:id="prefGvfsSchemeLabel" text="%settings.prefGvfsScheme.label" cacheShape="true" cache="true" />
 			<ChoiceBox GridPane.rowIndex="3" GridPane.columnIndex="1" fx:id="prefGvfsScheme" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
 			
+			<!-- Row 4 -->
+			<Label GridPane.rowIndex="4" GridPane.columnIndex="0" fx:id="debugModeLabel" text="%settings.debugMode.label" cacheShape="true" cache="true" />
+			<CheckBox GridPane.rowIndex="4" GridPane.columnIndex="1" fx:id="debugModeCheckbox" cacheShape="true" cache="true" />
+			
 		</children>
 	</GridPane>
 	<Label VBox.vgrow="NEVER" text="%settings.requiresRestartLabel" alignment="CENTER" cacheShape="true" cache="true" />

+ 1 - 0
main/ui/src/main/resources/localization/de.txt

@@ -66,6 +66,7 @@ settings.checkForUpdates.label = Auf Updates prüfen
 settings.port.label = WebDAV Port *
 settings.port.prompt = 0 \= Automatisch wählen
 settings.useipv6.label = IPv6-Literal nutzen
+settings.debugMode.label= Debug Modus *
 settings.requiresRestartLabel = * benötigt Neustart von Cryptomator
 # tray icon
 tray.menu.open = Öffnen

+ 1 - 0
main/ui/src/main/resources/localization/en.txt

@@ -102,6 +102,7 @@ settings.port.label=WebDAV Port *
 settings.port.prompt=0 = Choose automatically
 settings.useipv6.label=Use IPv6 literal
 settings.prefGvfsScheme.label=WebDAV scheme
+settings.debugMode.label=Debug mode *
 settings.requiresRestartLabel=* Cryptomator needs to restart
 
 # tray icon

+ 7 - 3
main/ui/src/main/resources/log4j2.xml

@@ -27,9 +27,13 @@
 	</Appenders>
 
 	<Loggers>
-		<!-- package-specific config: -->
-		<Logger name="org.cryptomator" level="DEBUG" />
-		<Logger name="org.eclipse.jetty.server.Server" level="DEBUG" />
+		<!--
+			= NOTE =
+			All loggers below are set to info.
+			This is required to allow changing the levels of the loggers programmatically.
+		-->
+		<Logger name="org.cryptomator" level="INFO" />
+		<Logger name="org.eclipse.jetty.server.Server" level="INFO" />
 		<Logger name="org.cryptomator.ui.model" level="INFO">
 			<AppenderRef ref="UpgradeLog" />
 		</Logger>