mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-04-19 09:06:54 -04:00
implemented force quit dialog
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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"), //
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
59
src/main/resources/fxml/quit_forced.fxml
Normal file
59
src/main/resources/fxml/quit_forced.fxml
Normal file
@@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5Spinner?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ButtonBar?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.shape.Circle?>
|
||||
<?import javafx.scene.Group?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<HBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.quitforced.QuitForcedController"
|
||||
minWidth="400"
|
||||
maxWidth="400"
|
||||
minHeight="145"
|
||||
spacing="12"
|
||||
alignment="TOP_LEFT">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<children>
|
||||
<Group>
|
||||
<StackPane>
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="6"/>
|
||||
</padding>
|
||||
<Circle styleClass="glyph-icon-orange" radius="24"/>
|
||||
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="EXCLAMATION" glyphSize="24"/>
|
||||
</StackPane>
|
||||
</Group>
|
||||
|
||||
<VBox HBox.hgrow="ALWAYS">
|
||||
<Label styleClass="label-large" text="%forcedQuit.message" wrapText="true" textAlignment="LEFT">
|
||||
<padding>
|
||||
<Insets bottom="6" top="6"/>
|
||||
</padding>
|
||||
</Label>
|
||||
|
||||
<Label text="%forcedQuit.description" wrapText="true" textAlignment="LEFT"/>
|
||||
|
||||
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
|
||||
<ButtonBar buttonMinWidth="120" buttonOrder="+CI">
|
||||
<buttons>
|
||||
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" defaultButton="true" cancelButton="true" onAction="#cancel"/>
|
||||
<Button fx:id="forceLockAndQuitButton" text="%forcedQuit.forceLockAndQuitBtn" ButtonBar.buttonData="FINISH" onAction="#forceLockAndQuit" contentDisplay="TEXT_ONLY">
|
||||
<graphic>
|
||||
<FontAwesome5Spinner glyphSize="12"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</VBox>
|
||||
</children>
|
||||
</HBox>
|
||||
@@ -382,4 +382,10 @@ passwordStrength.messageLabel.4=Very strong
|
||||
|
||||
# Quit
|
||||
quit.prompt=Quit application? There are unlocked vaults.
|
||||
quit.lockAndQuit=Lock and Quit
|
||||
quit.lockAndQuit=Lock and Quit
|
||||
|
||||
# Forced Quit
|
||||
forcedQuit.title=Quit application
|
||||
forcedQuit.message=Some vaults could not be locked
|
||||
forcedQuit.description=Locking vaults was blocked by pending operations or open files. You can force lock remaining vaults, however interrupting I/O may result in the loss of unsaved data.
|
||||
forcedQuit.forceLockAndQuitBtn=Force and Quit
|
||||
|
||||
Reference in New Issue
Block a user