diff --git a/main/ui/src/main/java/org/cryptomator/ui/launcher/AppLifecycleListener.java b/main/ui/src/main/java/org/cryptomator/ui/launcher/AppLifecycleListener.java new file mode 100644 index 000000000..e06a23a63 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/launcher/AppLifecycleListener.java @@ -0,0 +1,118 @@ +package org.cryptomator.ui.launcher; + +import javafx.application.Platform; +import javafx.beans.Observable; +import javafx.collections.ObservableList; +import org.cryptomator.common.ShutdownHook; +import org.cryptomator.common.vaults.Vault; +import org.cryptomator.common.vaults.VaultState; +import org.cryptomator.common.vaults.Volume; +import org.cryptomator.ui.preferences.SelectedPreferencesTab; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import java.awt.Desktop; +import java.awt.desktop.QuitResponse; +import java.util.EnumSet; +import java.util.EventObject; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; + +@Singleton +public class AppLifecycleListener { + + private static final Logger LOG = LoggerFactory.getLogger(AppLifecycleListener.class); + public static final Set STATES_ALLOWING_TERMINATION = EnumSet.of(VaultState.LOCKED, VaultState.NEEDS_MIGRATION, VaultState.MISSING, VaultState.ERROR); + + private final FxApplicationStarter fxApplicationStarter; + private final CountDownLatch shutdownLatch; + private final ObservableList vaults; + private final AtomicBoolean allowSuddenTermination; + + @Inject + AppLifecycleListener(FxApplicationStarter fxApplicationStarter, @Named("shutdownLatch") CountDownLatch shutdownLatch, ShutdownHook shutdownHook, ObservableList vaults) { + this.fxApplicationStarter = fxApplicationStarter; + this.shutdownLatch = shutdownLatch; + this.vaults = vaults; + this.allowSuddenTermination = new AtomicBoolean(true); + vaults.addListener(this::vaultListChanged); + + // register preferences shortcut + if (Desktop.getDesktop().isSupported(Desktop.Action.APP_PREFERENCES)) { + Desktop.getDesktop().setPreferencesHandler(this::showPreferencesWindow); + } + + // register quit handler + if (Desktop.getDesktop().isSupported(Desktop.Action.APP_QUIT_HANDLER)) { + Desktop.getDesktop().setQuitHandler(this::handleQuitRequest); + } + + // allow sudden termination + if (Desktop.getDesktop().isSupported(Desktop.Action.APP_SUDDEN_TERMINATION)) { + Desktop.getDesktop().enableSuddenTermination(); + } + + shutdownHook.runOnShutdown(this::forceUnmountRemainingVaults); + } + + /** + * Gracefully terminates the application. + */ + public void quit() { + handleQuitRequest(null, new QuitResponse() { + @Override + public void performQuit() { + shutdownLatch.countDown(); + } + + @Override + public void cancelQuit() { + // no-op + } + }); + } + + private void vaultListChanged(@SuppressWarnings("unused") Observable observable) { + assert Platform.isFxApplicationThread(); + boolean allVaultsAllowTermination = vaults.stream().map(Vault::getState).allMatch(STATES_ALLOWING_TERMINATION::contains); + boolean suddenTerminationChanged = allowSuddenTermination.compareAndSet(!allVaultsAllowTermination, allVaultsAllowTermination); + if (suddenTerminationChanged && Desktop.getDesktop().isSupported(Desktop.Action.APP_SUDDEN_TERMINATION)) { + if (allVaultsAllowTermination) { + Desktop.getDesktop().enableSuddenTermination(); + LOG.debug("sudden termination enabled"); + } else { + Desktop.getDesktop().disableSuddenTermination(); + LOG.debug("sudden termination disabled"); + } + } + } + + private void showPreferencesWindow(@SuppressWarnings("unused") EventObject actionEvent) { + fxApplicationStarter.get(true).thenAccept(app -> app.showPreferencesWindow(SelectedPreferencesTab.ANY)); + } + + private void handleQuitRequest(@SuppressWarnings("unused") EventObject e, QuitResponse response) { + if (allowSuddenTermination.get()) { + response.performQuit(); // really? + } else { + fxApplicationStarter.get(true).thenAccept(app -> app.showQuitWindow(response)); + } + } + + private void forceUnmountRemainingVaults() { + for (Vault vault : vaults) { + if (vault.isUnlocked()) { + try { + vault.lock(true); + } catch (Volume.VolumeException e) { + LOG.error("Failed to unmount vault " + vault.getPath(), e); + } + } + } + } + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowTitleController.java b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowTitleController.java index 009a0a6af..573a835ff 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowTitleController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowTitleController.java @@ -5,19 +5,16 @@ import javafx.fxml.FXML; import javafx.scene.layout.HBox; import javafx.stage.Stage; import org.cryptomator.common.LicenseHolder; -import org.cryptomator.common.vaults.VaultListManager; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.fxapp.FxApplication; import org.cryptomator.ui.fxapp.UpdateChecker; +import org.cryptomator.ui.launcher.AppLifecycleListener; import org.cryptomator.ui.preferences.SelectedPreferencesTab; -import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Named; -import java.awt.desktop.QuitResponse; -import java.util.concurrent.CountDownLatch; @MainWindowScoped public class MainWindowTitleController implements FxController { @@ -26,9 +23,9 @@ public class MainWindowTitleController implements FxController { public HBox titleBar; + private final AppLifecycleListener appLifecycle; private final Stage window; private final FxApplication application; - private final CountDownLatch shutdownLatch; private final boolean minimizeToSysTray; private final UpdateChecker updateChecker; private final BooleanBinding updateAvailable; @@ -38,10 +35,10 @@ public class MainWindowTitleController implements FxController { private double yOffset; @Inject - MainWindowTitleController(@MainWindow Stage window, FxApplication application, @Named("shutdownLatch") CountDownLatch shutdownLatch, @Named("trayMenuSupported") boolean minimizeToSysTray, UpdateChecker updateChecker, LicenseHolder licenseHolder) { + MainWindowTitleController(AppLifecycleListener appLifecycle, @MainWindow Stage window, FxApplication application, @Named("trayMenuSupported") boolean minimizeToSysTray, UpdateChecker updateChecker, LicenseHolder licenseHolder) { + this.appLifecycle = appLifecycle; this.window = window; this.application = application; - this.shutdownLatch = shutdownLatch; this.minimizeToSysTray = minimizeToSysTray; this.updateChecker = updateChecker; this.updateAvailable = updateChecker.latestVersionProperty().isNotNull(); @@ -67,24 +64,10 @@ public class MainWindowTitleController implements FxController { if (minimizeToSysTray) { window.close(); } else { - quitApplication(); + appLifecycle.quit(); } } - private void quitApplication() { - application.showQuitWindow(new QuitResponse() { - @Override - public void performQuit() { - shutdownLatch.countDown(); - } - - @Override - public void cancelQuit() { - // no-op - } - }); - } - @FXML public void minimize() { window.setIconified(true); diff --git a/main/ui/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java b/main/ui/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java index bef3c441c..55735f5b1 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java @@ -3,57 +3,37 @@ package org.cryptomator.ui.traymenu; import javafx.application.Platform; import javafx.beans.Observable; import javafx.collections.ObservableList; -import org.cryptomator.common.ShutdownHook; -import org.cryptomator.common.settings.Settings; import org.cryptomator.common.vaults.Vault; -import org.cryptomator.common.vaults.VaultState; -import org.cryptomator.common.vaults.Volume; -import org.cryptomator.ui.fxapp.FxApplication; +import org.cryptomator.ui.launcher.AppLifecycleListener; import org.cryptomator.ui.launcher.FxApplicationStarter; import org.cryptomator.ui.preferences.SelectedPreferencesTab; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import javax.inject.Inject; -import javax.inject.Named; -import java.awt.Desktop; import java.awt.Menu; import java.awt.MenuItem; import java.awt.PopupMenu; -import java.awt.desktop.QuitResponse; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.util.EnumSet; import java.util.EventObject; import java.util.ResourceBundle; -import java.util.Set; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; @TrayMenuScoped class TrayMenuController { - private static final Logger LOG = LoggerFactory.getLogger(TrayMenuController.class); - public static final Set STATES_ALLOWING_TERMINATION = EnumSet.of(VaultState.LOCKED, VaultState.NEEDS_MIGRATION, VaultState.MISSING, VaultState.ERROR); - private final ResourceBundle resourceBundle; + private final AppLifecycleListener appLifecycle; private final FxApplicationStarter fxApplicationStarter; - private final CountDownLatch shutdownLatch; - private final ShutdownHook shutdownHook; private final ObservableList vaults; private final PopupMenu menu; - private final AtomicBoolean allowSuddenTermination; @Inject - TrayMenuController(ResourceBundle resourceBundle, FxApplicationStarter fxApplicationStarter, @Named("shutdownLatch") CountDownLatch shutdownLatch, ShutdownHook shutdownHook, ObservableList vaults) { + TrayMenuController(ResourceBundle resourceBundle, AppLifecycleListener appLifecycle, FxApplicationStarter fxApplicationStarter, ObservableList vaults) { this.resourceBundle = resourceBundle; + this.appLifecycle = appLifecycle; this.fxApplicationStarter = fxApplicationStarter; - this.shutdownLatch = shutdownLatch; - this.shutdownHook = shutdownHook; this.vaults = vaults; this.menu = new PopupMenu(); - this.allowSuddenTermination = new AtomicBoolean(true); } public PopupMenu getMenu() { @@ -62,40 +42,12 @@ class TrayMenuController { public void initTrayMenu() { vaults.addListener(this::vaultListChanged); - rebuildMenu(); - - // register preferences shortcut - if (Desktop.getDesktop().isSupported(Desktop.Action.APP_PREFERENCES)) { - Desktop.getDesktop().setPreferencesHandler(this::showPreferencesWindow); - } - - // register quit handler - if (Desktop.getDesktop().isSupported(Desktop.Action.APP_QUIT_HANDLER)) { - Desktop.getDesktop().setQuitHandler(this::handleQuitRequest); - } - shutdownHook.runOnShutdown(this::forceUnmountRemainingVaults); - - // allow sudden termination - if (Desktop.getDesktop().isSupported(Desktop.Action.APP_SUDDEN_TERMINATION)) { - Desktop.getDesktop().enableSuddenTermination(); - } } private void vaultListChanged(@SuppressWarnings("unused") Observable observable) { assert Platform.isFxApplicationThread(); rebuildMenu(); - boolean allVaultsAllowTermination = vaults.stream().map(Vault::getState).allMatch(STATES_ALLOWING_TERMINATION::contains); - boolean suddenTerminationChanged = allowSuddenTermination.compareAndSet(!allVaultsAllowTermination, allVaultsAllowTermination); - if (suddenTerminationChanged && Desktop.getDesktop().isSupported(Desktop.Action.APP_SUDDEN_TERMINATION)) { - if (allVaultsAllowTermination) { - Desktop.getDesktop().enableSuddenTermination(); - LOG.debug("sudden termination enabled"); - } else { - Desktop.getDesktop().disableSuddenTermination(); - LOG.debug("sudden termination disabled"); - } - } } private void rebuildMenu() { @@ -174,37 +126,8 @@ class TrayMenuController { fxApplicationStarter.get(true).thenAccept(app -> app.showPreferencesWindow(SelectedPreferencesTab.ANY)); } - private void handleQuitRequest(EventObject e, QuitResponse response) { - if (allowSuddenTermination.get()) { - response.performQuit(); // really? - } else { - fxApplicationStarter.get(true).thenAccept(app -> app.showQuitWindow(response)); - } - } - private void quitApplication(EventObject actionEvent) { - handleQuitRequest(actionEvent, new QuitResponse() { - @Override - public void performQuit() { - shutdownLatch.countDown(); - } - - @Override - public void cancelQuit() { - // no-op - } - }); + appLifecycle.quit(); } - private void forceUnmountRemainingVaults() { - for (Vault vault : vaults) { - if (vault.isUnlocked()) { - try { - vault.lock(true); - } catch (Volume.VolumeException e) { - LOG.error("Failed to unmount vault " + vault.getPath(), e); - } - } - } - } }