Kaynağa Gözat

FxApplication now part of the Dagger graph
(references #663)

Sebastian Stenzel 6 yıl önce
ebeveyn
işleme
dd3c969f0f

+ 81 - 48
main/launcher/src/main/java/org/cryptomator/launcher/Cryptomator.java

@@ -5,74 +5,107 @@
  *******************************************************************************/
 package org.cryptomator.launcher;
 
-import javafx.application.Application;
-import javafx.stage.Stage;
+import javafx.application.Platform;
 import org.apache.commons.lang3.SystemUtils;
-import org.cryptomator.ui.controllers.MainController;
+import org.cryptomator.logging.DebugMode;
+import org.cryptomator.logging.LoggerConfiguration;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
 import java.io.IOException;
+import java.util.Optional;
+import java.util.concurrent.CountDownLatch;
 
+@Singleton
 public class Cryptomator {
 
-	private static final Logger LOG;
-	private static final CryptomatorComponent CRYPTOMATOR_COMPONENT;
+	// DaggerCryptomatorComponent gets generated by Dagger.
+	// Run Maven and include target/generated-sources/annotations in your IDE.
+	private static final CryptomatorComponent CRYPTOMATOR_COMPONENT = DaggerCryptomatorComponent.create();
+	private static final Logger LOG = LoggerFactory.getLogger(Cryptomator.class);
 
-	static {
-		// DaggerCryptomatorComponent gets generated by Dagger.
-		// Run Maven and include target/generated-sources/annotations in your IDE.
-		CRYPTOMATOR_COMPONENT = DaggerCryptomatorComponent.create();
-		CRYPTOMATOR_COMPONENT.initLogging().run();
-		LOG = LoggerFactory.getLogger(Cryptomator.class);
+	private final LoggerConfiguration logConfig;
+	private final DebugMode debugMode;
+	private final IpcFactory ipcFactory;
+	private final Optional<String> applicationVersion;
+	private final CountDownLatch shutdownLatch;
+
+	@Inject
+	Cryptomator(LoggerConfiguration logConfig, DebugMode debugMode, IpcFactory ipcFactory, @Named("applicationVersion") Optional<String> applicationVersion, @Named("shutdownLatch") CountDownLatch shutdownLatch) {
+		this.logConfig = logConfig;
+		this.debugMode = debugMode;
+		this.ipcFactory = ipcFactory;
+		this.applicationVersion = applicationVersion;
+		this.shutdownLatch = shutdownLatch;
 	}
 
 	public static void main(String[] args) {
-		LOG.info("Starting Cryptomator {} on {} {} ({})", CRYPTOMATOR_COMPONENT.applicationVersion().orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
-
-		try (IpcFactory.IpcEndpoint endpoint = CRYPTOMATOR_COMPONENT.ipcFactory().create()) {
-			endpoint.getRemote().handleLaunchArgs(args); // if we are the server, getRemote() returns self.
-			if (endpoint.isConnectedToRemote()) {
-				LOG.info("Found running application instance. Shutting down.");
-			} else {
-				CRYPTOMATOR_COMPONENT.debugMode().initialize();
-				CleanShutdownPerformer.registerShutdownHook();
-				Application.launch(MainApp.class, args);
-			}
-		} catch (IOException e) {
-			LOG.error("Failed to initiate inter-process communication.", e);
-			System.exit(2);
-		} catch (Throwable e) {
-			LOG.error("Error during startup", e);
-			System.exit(1);
-		}
-		System.exit(0); // end remaining non-daemon threads.
+		int exitCode = CRYPTOMATOR_COMPONENT.application().run(args);
+		System.exit(exitCode); // end remaining non-daemon threads.
 	}
 
-	// We need a separate FX Application class, until we can use the module system. See https://stackoverflow.com/q/54756176/4014509
-	public static class MainApp extends Application {
-
-		@Override
-		public void start(Stage primaryStage) {
-			LOG.info("JavaFX application started.");
-			primaryStage.setMinWidth(652.0);
-			primaryStage.setMinHeight(440.0);
+	/**
+	 * Main entry point of the application launcher.
+	 * @param args The arguments passed to this program via {@link #main(String[])}.
+	 * @return Nonzero exit code in case of an error.
+	 */
+	private int run(String[] args) {
+		logConfig.init();
+		LOG.info("Starting Cryptomator {} on {} {} ({})", applicationVersion.orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
 
-			FxApplicationComponent fxApplicationComponent = CRYPTOMATOR_COMPONENT.fxApplicationComponent() //
-					.fxApplication(this) //
-					.mainWindow(primaryStage) //
-					.build();
+		if (sendArgsToRunningInstance(args)) {
+			LOG.info("Found running application instance. Shutting down...");
+			return 0;
+		}
 
-			MainController mainCtrl = fxApplicationComponent.fxmlLoader().load("/fxml/main.fxml");
-			mainCtrl.initStage(primaryStage);
-			primaryStage.show();
+		try {
+			runGuiApplication();
+			LOG.info("Shutting down...");
+			return 0;
+		} catch (Throwable e) {
+			LOG.error("Terminating due to error", e);
+			return 1;
 		}
+	}
 
-		@Override
-		public void stop() {
-			LOG.info("JavaFX application stopped.");
+	/**
+	 * Attempts to create an IPC connection to a running Cryptomator instance and sends it the given args.
+	 * If no external process could be reached, the args will be handled by the loopback IPC endpoint.
+	 *
+	 * @param args Arguments to send to the instance (if possible)
+	 * @return <code>true</code> if a different process could be reached, <code>false</code> otherwise.
+	 */
+	private boolean sendArgsToRunningInstance(String[] args) {
+		try (IpcFactory.IpcEndpoint endpoint = ipcFactory.create()) {
+			endpoint.getRemote().handleLaunchArgs(args); // if we are the server, getRemote() returns self.
+			return endpoint.isConnectedToRemote();
+		} catch (IOException e) {
+			LOG.error("Failed to initiate inter-process communication.", e);
+			return false;
 		}
+	}
 
+	/**
+	 * Launches the JavaFX application and waits until shutdown is requested.
+	 *
+	 * @implNote This method blocks until {@link #shutdownLatch} reached zero.
+	 */
+	private void runGuiApplication() {
+		try {
+			debugMode.initialize();
+			CleanShutdownPerformer.registerShutdownHook();
+			Platform.startup(() -> {
+				assert Platform.isFxApplicationThread();
+				FxApplication app = CRYPTOMATOR_COMPONENT.fxApplicationComponent().application();
+				app.start();
+			});
+			shutdownLatch.await();
+		} catch (InterruptedException e) {
+			Thread.currentThread().interrupt();
+		}
 	}
 
 }

+ 2 - 10
main/launcher/src/main/java/org/cryptomator/launcher/CryptomatorComponent.java

@@ -14,16 +14,8 @@ import java.util.Optional;
 @Component(modules = {CryptomatorModule.class, CommonsModule.class, LoggerModule.class})
 public interface CryptomatorComponent {
 
-	@Named("initLogging")
-	Runnable initLogging();
+	Cryptomator application();
 
-	DebugMode debugMode();
-
-	IpcFactory ipcFactory();
-
-	@Named("applicationVersion")
-	Optional<String> applicationVersion();
-
-	FxApplicationComponent.Builder fxApplicationComponent();
+	FxApplicationComponent fxApplicationComponent();
 
 }

+ 11 - 3
main/launcher/src/main/java/org/cryptomator/launcher/CryptomatorModule.java

@@ -11,27 +11,35 @@ import javax.inject.Singleton;
 import java.util.Optional;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
 
 @Module
 class CryptomatorModule {
 
 	@Provides
 	@Singleton
-	Settings provideSettings(SettingsProvider settingsProvider) {
+	@Named("shutdownLatch")
+	static CountDownLatch provideShutdownLatch() {
+		return new CountDownLatch(1);
+	}
+
+	@Provides
+	@Singleton
+	static Settings provideSettings(SettingsProvider settingsProvider) {
 		return settingsProvider.get();
 	}
 
 	@Provides
 	@Singleton
 	@Named("launchEventQueue")
-	BlockingQueue<AppLaunchEvent> provideFileOpenRequests() {
+	static BlockingQueue<AppLaunchEvent> provideFileOpenRequests() {
 		return new ArrayBlockingQueue<>(10);
 	}
 
 	@Provides
 	@Singleton
 	@Named("applicationVersion")
-	Optional<String> provideApplicationVersion() {
+	static Optional<String> provideApplicationVersion() {
 		return Optional.ofNullable(Cryptomator.class.getPackage().getImplementationVersion());
 	}
 

+ 1 - 0
main/launcher/src/main/java/org/cryptomator/launcher/FileOpenRequestHandler.java

@@ -8,6 +8,7 @@ package org.cryptomator.launcher;
 
 import java.awt.Desktop;
 import java.awt.desktop.OpenFilesEvent;
+import java.awt.desktop.QuitStrategy;
 import java.io.File;
 import java.nio.file.FileSystem;
 import java.nio.file.FileSystems;

+ 40 - 0
main/launcher/src/main/java/org/cryptomator/launcher/FxApplication.java

@@ -0,0 +1,40 @@
+package org.cryptomator.launcher;
+
+import javafx.application.Application;
+import javafx.stage.Stage;
+import org.cryptomator.common.FxApplicationScoped;
+import org.cryptomator.ui.controllers.MainController;
+import org.cryptomator.ui.controllers.ViewControllerLoader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+@FxApplicationScoped
+public class FxApplication extends Application {
+
+	private static final Logger LOG = LoggerFactory.getLogger(FxApplication.class);
+
+	private final Stage primaryStage;
+	private final ViewControllerLoader fxmlLoader;
+
+	@Inject
+	FxApplication(@Named("mainWindow") Stage primaryStage, ViewControllerLoader fxmlLoader) {
+		this.primaryStage = primaryStage;
+		this.fxmlLoader = fxmlLoader;
+	}
+
+	public void start() {
+		LOG.info("Starting GUI...");
+		start(primaryStage);
+	}
+
+	@Override
+	public void start(Stage primaryStage) {
+		MainController mainCtrl = fxmlLoader.load("/fxml/main.fxml");
+		mainCtrl.initStage(primaryStage);
+		primaryStage.show();
+	}
+
+}

+ 1 - 21
main/launcher/src/main/java/org/cryptomator/launcher/FxApplicationComponent.java

@@ -5,33 +5,13 @@
  *******************************************************************************/
 package org.cryptomator.launcher;
 
-import dagger.BindsInstance;
 import dagger.Subcomponent;
-import javafx.application.Application;
-import javafx.stage.Stage;
 import org.cryptomator.common.FxApplicationScoped;
-import org.cryptomator.logging.DebugMode;
-import org.cryptomator.ui.controllers.ViewControllerLoader;
-
-import javax.inject.Named;
 
 @FxApplicationScoped
 @Subcomponent(modules = FxApplicationModule.class)
 interface FxApplicationComponent {
 
-	ViewControllerLoader fxmlLoader();
-
-	@Subcomponent.Builder
-	interface Builder {
-
-		@BindsInstance
-		Builder fxApplication(Application application);
-
-		@BindsInstance
-		Builder mainWindow(@Named("mainWindow") Stage mainWindow);
-
-		FxApplicationComponent build();
-
-	}
+	FxApplication application();
 
 }

+ 21 - 2
main/launcher/src/main/java/org/cryptomator/launcher/FxApplicationModule.java

@@ -5,8 +5,12 @@
  *******************************************************************************/
 package org.cryptomator.launcher;
 
+import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
+import javafx.application.Application;
+import javafx.application.Platform;
+import javafx.stage.Stage;
 import org.cryptomator.common.FxApplicationScoped;
 import org.cryptomator.ui.UiModule;
 
@@ -14,13 +18,28 @@ import javax.inject.Named;
 import java.util.function.Consumer;
 
 @Module(includes = {UiModule.class})
-class FxApplicationModule {
+abstract class FxApplicationModule {
 
 	@Provides
 	@FxApplicationScoped
 	@Named("shutdownTaskScheduler")
-	Consumer<Runnable> provideShutdownTaskScheduler() {
+	static Consumer<Runnable> provideShutdownTaskScheduler() {
 		return CleanShutdownPerformer::scheduleShutdownTask;
 	}
 
+	@Provides
+	@FxApplicationScoped
+	@Named("mainWindow")
+	static Stage providePrimaryStage() {
+		Stage stage = new Stage();
+		stage.setMinWidth(652.0);
+		stage.setMinHeight(440.0);
+		return stage;
+	}
+
+	@Binds
+	@FxApplicationScoped
+	abstract Application bindApplication(FxApplication application);
+
+
 }

+ 71 - 0
main/launcher/src/main/java/org/cryptomator/logging/LoggerConfiguration.java

@@ -0,0 +1,71 @@
+package org.cryptomator.logging;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.Appender;
+import ch.qos.logback.core.hook.DelayingShutdownHook;
+import ch.qos.logback.core.util.Duration;
+import org.cryptomator.common.Environment;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+import java.util.Map;
+
+@Singleton
+public class LoggerConfiguration {
+
+	private static final double SHUTDOWN_DELAY_MS = 100;
+
+	private final LoggerContext context;
+	private final Environment environment;
+	private final Appender<ILoggingEvent> stdout;
+	private final Appender<ILoggingEvent> upgrade;
+	private final Appender<ILoggingEvent> file;
+
+	@Inject
+	LoggerConfiguration(LoggerContext context, //
+						Environment environment, //
+						@Named("stdoutAppender") Appender<ILoggingEvent> stdout, //
+						@Named("upgradeAppender") Appender<ILoggingEvent> upgrade, //
+						@Named("fileAppender") Appender<ILoggingEvent> file) {
+		this.context = context;
+		this.environment = environment;
+		this.stdout = stdout;
+		this.upgrade = upgrade;
+		this.file = file;
+	}
+
+	public void init() {
+		if (environment.useCustomLogbackConfig()) {
+			Logger root = context.getLogger(Logger.ROOT_LOGGER_NAME);
+			root.info("Using external logback configuration file.");
+		} else {
+			context.reset();
+
+			// configure loggers:
+			for (Map.Entry<String, Level> loglevel : LoggerModule.DEFAULT_LOG_LEVELS.entrySet()) {
+				Logger logger = context.getLogger(loglevel.getKey());
+				logger.setLevel(loglevel.getValue());
+				logger.setAdditive(false);
+				logger.addAppender(stdout);
+				logger.addAppender(file);
+			}
+
+			// configure upgrade logger:
+			Logger upgrades = context.getLogger("org.cryptomator.ui.model.upgrade");
+			upgrades.setLevel(Level.DEBUG);
+			upgrades.addAppender(stdout);
+			upgrades.addAppender(upgrade);
+			upgrades.setAdditive(false);
+
+			// add shutdown hook
+			DelayingShutdownHook shutdownHook = new DelayingShutdownHook();
+			shutdownHook.setContext(context);
+			shutdownHook.setDelay(Duration.buildByMilliseconds(SHUTDOWN_DELAY_MS));
+		}
+	}
+
+}

+ 5 - 47
main/launcher/src/main/java/org/cryptomator/logging/LoggerModule.java

@@ -32,7 +32,6 @@ public class LoggerModule {
 	private static final String LOGFILE_ROLLING_PATTERN = "cryptomator%i.log";
 	private static final int LOGFILE_ROLLING_MIN = 1;
 	private static final int LOGFILE_ROLLING_MAX = 9;
-	private static final double SHUTDOWN_DELAY_MS = 100;
 	private static final String LOG_PATTERN = "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n";
 	static final Map<String, Level> DEFAULT_LOG_LEVELS = Map.of( //
 			Logger.ROOT_LOGGER_NAME, Level.INFO, //
@@ -45,7 +44,7 @@ public class LoggerModule {
 
 	@Provides
 	@Singleton
-	LoggerContext provideLoggerContext() {
+	static LoggerContext provideLoggerContext() {
 		ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory();
 		if (loggerFactory instanceof LoggerContext) {
 			return (LoggerContext) loggerFactory;
@@ -56,7 +55,7 @@ public class LoggerModule {
 
 	@Provides
 	@Singleton
-	PatternLayoutEncoder provideLayoutEncoder(LoggerContext context) {
+	static PatternLayoutEncoder provideLayoutEncoder(LoggerContext context) {
 		PatternLayoutEncoder ple = new PatternLayoutEncoder();
 		ple.setPattern(LOG_PATTERN);
 		ple.setContext(context);
@@ -67,7 +66,7 @@ public class LoggerModule {
 	@Provides
 	@Singleton
 	@Named("stdoutAppender")
-	Appender<ILoggingEvent> provideStdoutAppender(LoggerContext context, PatternLayoutEncoder encoder) {
+	static Appender<ILoggingEvent> provideStdoutAppender(LoggerContext context, PatternLayoutEncoder encoder) {
 		ConsoleAppender<ILoggingEvent> appender = new ConsoleAppender<>();
 		appender.setContext(context);
 		appender.setEncoder(encoder);
@@ -78,7 +77,7 @@ public class LoggerModule {
 	@Provides
 	@Singleton
 	@Named("fileAppender")
-	Appender<ILoggingEvent> provideFileAppender(LoggerContext context, PatternLayoutEncoder encoder, Environment environment) {
+	static Appender<ILoggingEvent> provideFileAppender(LoggerContext context, PatternLayoutEncoder encoder, Environment environment) {
 		if (environment.getLogDir().isPresent()) {
 			Path logDir = environment.getLogDir().get();
 			RollingFileAppender<ILoggingEvent> appender = new RollingFileAppender<>();
@@ -109,7 +108,7 @@ public class LoggerModule {
 	@Provides
 	@Singleton
 	@Named("upgradeAppender")
-	Appender<ILoggingEvent> provideUpgradeAppender(LoggerContext context, PatternLayoutEncoder encoder, Environment environment) {
+	static Appender<ILoggingEvent> provideUpgradeAppender(LoggerContext context, PatternLayoutEncoder encoder, Environment environment) {
 		if (environment.getLogDir().isPresent()) {
 			FileAppender<ILoggingEvent> appender = new FileAppender<>();
 			appender.setFile(environment.getLogDir().get().resolve(UPGRADE_FILENAME).toString());
@@ -124,46 +123,5 @@ public class LoggerModule {
 		}
 	}
 
-	@Provides
-	@Singleton
-	@Named("initLogging")
-	Runnable provideLogbackInitializer(LoggerContext context, //
-									   Environment environment, //
-									   @Named("stdoutAppender") Appender<ILoggingEvent> stdout, //
-									   @Named("upgradeAppender") Appender<ILoggingEvent> upgrade, //
-									   @Named("fileAppender") Appender<ILoggingEvent> file) {
-		if (environment.useCustomLogbackConfig()) {
-			return () -> {
-				Logger root = context.getLogger(Logger.ROOT_LOGGER_NAME);
-				root.info("Using external logback configuration file.");
-			};
-		} else {
-			return () -> {
-				context.reset();
-
-				// configure loggers:
-				for (Map.Entry<String, Level> loglevel : DEFAULT_LOG_LEVELS.entrySet()) {
-					Logger logger = context.getLogger(loglevel.getKey());
-					logger.setLevel(loglevel.getValue());
-					logger.setAdditive(false);
-					logger.addAppender(stdout);
-					logger.addAppender(file);
-				}
-
-				// configure upgrade logger:
-				Logger upgrades = context.getLogger("org.cryptomator.ui.model.upgrade");
-				upgrades.setLevel(Level.DEBUG);
-				upgrades.addAppender(stdout);
-				upgrades.addAppender(upgrade);
-				upgrades.setAdditive(false);
-
-				// add shutdown hook
-				DelayingShutdownHook shutdownHook = new DelayingShutdownHook();
-				shutdownHook.setContext(context);
-				shutdownHook.setDelay(Duration.buildByMilliseconds(SHUTDOWN_DELAY_MS));
-			};
-		}
-	}
-
 
 }

+ 6 - 3
main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java

@@ -78,6 +78,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutorService;
 import java.util.stream.Stream;
 
@@ -99,6 +100,7 @@ public class MainController implements ViewController {
 	private final ViewControllerLoader viewControllerLoader;
 	private final ObjectProperty<ViewController> activeController = new SimpleObjectProperty<>();
 	private final ObservableList<Vault> vaults;
+	private final CountDownLatch shutdownLatch;
 	private final BooleanBinding areAllVaultsLocked;
 	private final ObjectProperty<Vault> selectedVault = new SimpleObjectProperty<>();
 	private final ObjectExpression<Vault.State> selectedVaultState = ObjectExpression.objectExpression(EasyBind.select(selectedVault).selectObject(Vault::stateProperty));
@@ -112,7 +114,7 @@ public class MainController implements ViewController {
 
 	@Inject
 	public MainController(@Named("mainWindow") Stage mainWindow, ExecutorService executorService, @Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue, ExitUtil exitUtil, Localization localization,
-						  VaultFactory vaultFactoy, ViewControllerLoader viewControllerLoader, UpgradeStrategies upgradeStrategies, VaultList vaults, AutoUnlocker autoUnlocker) {
+						  VaultFactory vaultFactoy, ViewControllerLoader viewControllerLoader, UpgradeStrategies upgradeStrategies, VaultList vaults, AutoUnlocker autoUnlocker, @Named("shutdownLatch") CountDownLatch shutdownLatch) {
 		this.mainWindow = mainWindow;
 		this.executorService = executorService;
 		this.launchEventQueue = launchEventQueue;
@@ -121,6 +123,7 @@ public class MainController implements ViewController {
 		this.vaultFactoy = vaultFactoy;
 		this.viewControllerLoader = viewControllerLoader;
 		this.vaults = vaults;
+		this.shutdownLatch = shutdownLatch;
 
 		// derived bindings:
 		this.isShowingSettings = Bindings.equal(SettingsController.class, EasyBind.monadic(activeController).map(ViewController::getClass));
@@ -231,13 +234,13 @@ public class MainController implements ViewController {
 				if (tryAgainButtonType.equals(btnType)) {
 					gracefulShutdown();
 				} else if (forceShutdownButtonType.equals(btnType)) {
-					Platform.runLater(Platform::exit);
+					shutdownLatch.countDown();
 				} else {
 					return;
 				}
 			});
 		} else {
-			Platform.runLater(Platform::exit);
+			shutdownLatch.countDown();
 		}
 	}
 

+ 2 - 4
main/ui/src/test/java/org/cryptomator/ui/controls/SecPasswordFieldTest.java

@@ -1,5 +1,6 @@
 package org.cryptomator.ui.controls;
 
+import javafx.application.Platform;
 import javafx.embed.swing.JFXPanel;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Assumptions;
@@ -22,10 +23,7 @@ class SecPasswordFieldTest {
 	static void initJavaFx() throws InterruptedException {
 		Assumptions.assumeFalse(GraphicsEnvironment.isHeadless());
 		final CountDownLatch latch = new CountDownLatch(1);
-		SwingUtilities.invokeLater(() -> {
-			new JFXPanel(); // initializes JavaFX environment
-			latch.countDown();
-		});
+		Platform.startup(latch::countDown);
 
 		if (!latch.await(5L, TimeUnit.SECONDS)) {
 			throw new ExceptionInInitializerError();