Certain app lifecycle event handlers are now now longer @TrayMenuScoped but rather @Singleton, so they get set up even if there is no TrayIcon (references #1035)

This commit is contained in:
Sebastian Stenzel 2020-01-14 21:43:45 +01:00
parent f0b26c60c4
commit 362f3eac63
No known key found for this signature in database
GPG Key ID: 667B866EA8240A09
3 changed files with 128 additions and 104 deletions

View File

@ -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<VaultState> 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<Vault> vaults;
private final AtomicBoolean allowSuddenTermination;
@Inject
AppLifecycleListener(FxApplicationStarter fxApplicationStarter, @Named("shutdownLatch") CountDownLatch shutdownLatch, ShutdownHook shutdownHook, ObservableList<Vault> 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);
}
}
}
}
}

View File

@ -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);

View File

@ -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<VaultState> 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<Vault> vaults;
private final PopupMenu menu;
private final AtomicBoolean allowSuddenTermination;
@Inject
TrayMenuController(ResourceBundle resourceBundle, FxApplicationStarter fxApplicationStarter, @Named("shutdownLatch") CountDownLatch shutdownLatch, ShutdownHook shutdownHook, ObservableList<Vault> vaults) {
TrayMenuController(ResourceBundle resourceBundle, AppLifecycleListener appLifecycle, FxApplicationStarter fxApplicationStarter, ObservableList<Vault> 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);
}
}
}
}
}