implemented force quit dialog

This commit is contained in:
Jan-Peter Klein
2022-06-29 15:12:43 +02:00
parent bee9c9f452
commit d10d8fb208
12 changed files with 307 additions and 5 deletions

View File

@@ -51,6 +51,7 @@ module org.cryptomator.desktop {
opens org.cryptomator.ui.migration to javafx.fxml;
opens org.cryptomator.ui.preferences to javafx.fxml;
opens org.cryptomator.ui.quit to javafx.fxml;
opens org.cryptomator.ui.quitforced to javafx.fxml;
opens org.cryptomator.ui.recoverykey to javafx.fxml;
opens org.cryptomator.ui.removevault to javafx.fxml;
opens org.cryptomator.ui.stats to javafx.fxml;

View File

@@ -23,6 +23,7 @@ public enum FxmlFile {
MIGRATION_SUCCESS("/fxml/migration_success.fxml"), //
PREFERENCES("/fxml/preferences.fxml"), //
QUIT("/fxml/quit.fxml"), //
QUIT_FORCED("/fxml/quit_forced.fxml"), //
RECOVERYKEY_CREATE("/fxml/recoverykey_create.fxml"), //
RECOVERYKEY_RECOVER("/fxml/recoverykey_recover.fxml"), //
RECOVERYKEY_RESET_PASSWORD("/fxml/recoverykey_reset_password.fxml"), //

View File

@@ -14,6 +14,7 @@ import org.cryptomator.ui.lock.LockComponent;
import org.cryptomator.ui.mainwindow.MainWindowComponent;
import org.cryptomator.ui.preferences.PreferencesComponent;
import org.cryptomator.ui.quit.QuitComponent;
import org.cryptomator.ui.quitforced.QuitForcedComponent;
import org.cryptomator.ui.traymenu.TrayMenuComponent;
import org.cryptomator.ui.unlock.UnlockComponent;
@@ -25,7 +26,7 @@ import java.io.UncheckedIOException;
import java.util.Collections;
import java.util.List;
@Module(includes = {UpdateCheckerModule.class}, subcomponents = {TrayMenuComponent.class, MainWindowComponent.class, PreferencesComponent.class, UnlockComponent.class, LockComponent.class, QuitComponent.class, ErrorComponent.class})
@Module(includes = {UpdateCheckerModule.class}, subcomponents = {TrayMenuComponent.class, MainWindowComponent.class, PreferencesComponent.class, UnlockComponent.class, LockComponent.class, QuitComponent.class, QuitForcedComponent.class, ErrorComponent.class})
abstract class FxApplicationModule {
private static Image createImageFromResource(String resourceName) throws IOException {
@@ -57,4 +58,10 @@ abstract class FxApplicationModule {
static QuitComponent provideQuitComponent(QuitComponent.Builder builder) {
return builder.build();
}
@Provides
@FxApplicationScoped
static QuitForcedComponent provideQuitForcedComponent(QuitForcedComponent.Builder builder) {
return builder.build();
}
}

View File

@@ -115,8 +115,7 @@ public class FxApplicationTerminator {
});
lockAllTask.setOnFailed(event -> {
LOG.warn("Unable to lock all vaults.");
exitingResponse.cancelQuit();
//TODO: notify user!?!
appWindows.showQuitForcedWindow(exitingResponse);
});
lockAllTask.run();
} else {

View File

@@ -11,6 +11,7 @@ import org.cryptomator.ui.mainwindow.MainWindowComponent;
import org.cryptomator.ui.preferences.PreferencesComponent;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.cryptomator.ui.quit.QuitComponent;
import org.cryptomator.ui.quitforced.QuitForcedComponent;
import org.cryptomator.ui.unlock.UnlockComponent;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
@@ -41,6 +42,7 @@ public class FxApplicationWindows {
private final Lazy<MainWindowComponent> mainWindow;
private final Lazy<PreferencesComponent> preferencesWindow;
private final Lazy<QuitComponent> quitWindow;
private final Lazy<QuitForcedComponent> quitForcedWindow;
private final UnlockComponent.Factory unlockWorkflowFactory;
private final LockComponent.Factory lockWorkflowFactory;
private final ErrorComponent.Factory errorWindowFactory;
@@ -48,12 +50,13 @@ public class FxApplicationWindows {
private final FilteredList<Window> visibleWindows;
@Inject
public FxApplicationWindows(@PrimaryStage Stage primaryStage, Optional<TrayIntegrationProvider> trayIntegration, Lazy<MainWindowComponent> mainWindow, Lazy<PreferencesComponent> preferencesWindow, Lazy<QuitComponent> quitWindow, UnlockComponent.Factory unlockWorkflowFactory, LockComponent.Factory lockWorkflowFactory, ErrorComponent.Factory errorWindowFactory, ExecutorService executor) {
public FxApplicationWindows(@PrimaryStage Stage primaryStage, Optional<TrayIntegrationProvider> trayIntegration, Lazy<MainWindowComponent> mainWindow, Lazy<PreferencesComponent> preferencesWindow, Lazy<QuitComponent> quitWindow, Lazy<QuitForcedComponent> quitForcedWindow, UnlockComponent.Factory unlockWorkflowFactory, LockComponent.Factory lockWorkflowFactory, ErrorComponent.Factory errorWindowFactory, ExecutorService executor) {
this.primaryStage = primaryStage;
this.trayIntegration = trayIntegration;
this.mainWindow = mainWindow;
this.preferencesWindow = preferencesWindow;
this.quitWindow = quitWindow;
this.quitForcedWindow = quitForcedWindow;
this.unlockWorkflowFactory = unlockWorkflowFactory;
this.lockWorkflowFactory = lockWorkflowFactory;
this.errorWindowFactory = errorWindowFactory;
@@ -108,6 +111,10 @@ public class FxApplicationWindows {
return CompletableFuture.supplyAsync(() -> quitWindow.get().showQuitWindow(response), Platform::runLater).whenComplete(this::reportErrors);
}
public CompletionStage<Stage> showQuitForcedWindow(QuitResponse response) {
return CompletableFuture.supplyAsync(() -> quitForcedWindow.get().showQuitForcedWindow(response), Platform::runLater).whenComplete(this::reportErrors);
}
public CompletionStage<Void> startUnlockWorkflow(Vault vault, @Nullable Stage owner) {
return CompletableFuture.supplyAsync(() -> {
Preconditions.checkState(vault.stateProperty().transition(VaultState.Value.LOCKED, VaultState.Value.PROCESSING), "Vault not locked.");

View File

@@ -0,0 +1,44 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.quitforced;
import dagger.Lazy;
import dagger.Subcomponent;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.awt.desktop.QuitResponse;
@QuitForcedScoped
@Subcomponent(modules = {QuitForcedModule.class})
public interface QuitForcedComponent {
@QuitForcedWindow
Stage window();
@FxmlScene(FxmlFile.QUIT_FORCED)
Lazy<Scene> scene();
QuitForcedController controller();
default Stage showQuitForcedWindow(QuitResponse response) {
controller().updateQuitRequest(response);
Stage stage = window();
stage.setScene(scene().get());
stage.show();
stage.requestFocus();
return stage;
}
@Subcomponent.Builder
interface Builder {
QuitForcedComponent build();
}
}

View File

@@ -0,0 +1,93 @@
package org.cryptomator.ui.quitforced;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.VaultService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
import javafx.stage.Stage;
import java.awt.desktop.QuitResponse;
import java.util.Collection;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@QuitForcedScoped
public class QuitForcedController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(QuitForcedController.class);
private final Stage window;
private final ObservableList<Vault> unlockedVaults;
private final ExecutorService executorService;
private final VaultService vaultService;
private final AtomicReference<QuitResponse> quitResponse = new AtomicReference<>();
/* FXML */
public Button forceLockAndQuitButton;
@Inject
QuitForcedController(@QuitForcedWindow Stage window, ObservableList<Vault> vaults, ExecutorService executorService, VaultService vaultService) {
this.window = window;
this.unlockedVaults = vaults.filtered(Vault::isUnlocked);
this.executorService = executorService;
this.vaultService = vaultService;
window.setOnCloseRequest(windowEvent -> cancel());
}
public void updateQuitRequest(QuitResponse newResponse) {
var oldResponse = quitResponse.getAndSet(newResponse);
if (oldResponse != null) {
oldResponse.cancelQuit();
}
}
private void respondToQuitRequest(Consumer<QuitResponse> action) {
var response = quitResponse.getAndSet(null);
if (response != null) {
action.accept(response);
}
}
@FXML
public void cancel() {
LOG.info("Quitting application canceled by user.");
window.close();
respondToQuitRequest(QuitResponse::cancelQuit);
}
@FXML
public void forceLockAndQuit() {
forceLockAndQuitButton.setDisable(true);
forceLockAndQuitButton.setContentDisplay(ContentDisplay.LEFT);
Task<Collection<Vault>> lockAllTask = vaultService.createLockAllTask(unlockedVaults, true); // forced set to true
lockAllTask.setOnSucceeded(evt -> {
LOG.info("Locked {}", lockAllTask.getValue().stream().map(Vault::getDisplayName).collect(Collectors.joining(", ")));
if (unlockedVaults.isEmpty()) {
window.close();
respondToQuitRequest(QuitResponse::performQuit);
}
});
lockAllTask.setOnFailed(evt -> {
//TODO: what will happen if force lock and quit app fails?
LOG.error("Forced locking failed", lockAllTask.getException());
forceLockAndQuitButton.setDisable(false);
forceLockAndQuitButton.setContentDisplay(ContentDisplay.TEXT_ONLY);
window.close();
respondToQuitRequest(QuitResponse::cancelQuit);
});
executorService.execute(lockAllTask);
}
}

View File

@@ -0,0 +1,58 @@
package org.cryptomator.ui.quitforced;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
import org.cryptomator.ui.common.DefaultSceneFactory;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxControllerKey;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlLoaderFactory;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.StageFactory;
import javax.inject.Provider;
import javafx.scene.Scene;
import javafx.stage.Modality;
import javafx.stage.Stage;
import java.util.Map;
import java.util.ResourceBundle;
@Module
abstract class QuitForcedModule {
@Provides
@QuitForcedWindow
@QuitForcedScoped
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
}
@Provides
@QuitForcedWindow
@QuitForcedScoped
static Stage provideStage(StageFactory factory, ResourceBundle resourceBundle) {
Stage stage = factory.create();
stage.setMinWidth(300);
stage.setMinHeight(100);
stage.initModality(Modality.APPLICATION_MODAL);
stage.setTitle(resourceBundle.getString("forcedQuit.title"));
return stage;
}
@Provides
@FxmlScene(FxmlFile.QUIT_FORCED)
@QuitForcedScoped
static Scene provideQuitScene(@QuitForcedWindow FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.QUIT_FORCED);
}
// ------------------
@Binds
@IntoMap
@FxControllerKey(QuitForcedController.class)
abstract FxController bindQuitController(QuitForcedController controller);
}

View File

@@ -0,0 +1,13 @@
package org.cryptomator.ui.quitforced;
import javax.inject.Scope;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
@interface QuitForcedScoped {
}

View File

@@ -0,0 +1,14 @@
package org.cryptomator.ui.quitforced;
import javax.inject.Qualifier;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Qualifier
@Documented
@Retention(RUNTIME)
@interface QuitForcedWindow {
}