mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-19 11:11:28 +00:00
Merge pull request #2273 from cryptomator/feature/lock-and-quit-without-asking
implemented functionality of feature request issue #1713 "On closing …
This commit is contained in:
@@ -32,6 +32,7 @@ public class Settings {
|
||||
public static final boolean DEFAULT_ASKED_FOR_UPDATE_CHECK = false;
|
||||
public static final boolean DEFAULT_CHECK_FOR_UPDATES = false;
|
||||
public static final boolean DEFAULT_START_HIDDEN = false;
|
||||
public static final boolean DEFAULT_AUTO_CLOSE_VAULTS = false;
|
||||
public static final int DEFAULT_PORT = 42427;
|
||||
public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
|
||||
public static final WebDavUrlScheme DEFAULT_GVFS_SCHEME = WebDavUrlScheme.DAV;
|
||||
@@ -51,6 +52,7 @@ public class Settings {
|
||||
private final BooleanProperty askedForUpdateCheck = new SimpleBooleanProperty(DEFAULT_ASKED_FOR_UPDATE_CHECK);
|
||||
private final BooleanProperty checkForUpdates = new SimpleBooleanProperty(DEFAULT_CHECK_FOR_UPDATES);
|
||||
private final BooleanProperty startHidden = new SimpleBooleanProperty(DEFAULT_START_HIDDEN);
|
||||
private final BooleanProperty autoCloseVaults = new SimpleBooleanProperty(DEFAULT_AUTO_CLOSE_VAULTS);
|
||||
private final IntegerProperty port = new SimpleIntegerProperty(DEFAULT_PORT);
|
||||
private final IntegerProperty numTrayNotifications = new SimpleIntegerProperty(DEFAULT_NUM_TRAY_NOTIFICATIONS);
|
||||
private final ObjectProperty<WebDavUrlScheme> preferredGvfsScheme = new SimpleObjectProperty<>(DEFAULT_GVFS_SCHEME);
|
||||
@@ -82,6 +84,7 @@ public class Settings {
|
||||
askedForUpdateCheck.addListener(this::somethingChanged);
|
||||
checkForUpdates.addListener(this::somethingChanged);
|
||||
startHidden.addListener(this::somethingChanged);
|
||||
autoCloseVaults.addListener(this::somethingChanged);
|
||||
port.addListener(this::somethingChanged);
|
||||
numTrayNotifications.addListener(this::somethingChanged);
|
||||
preferredGvfsScheme.addListener(this::somethingChanged);
|
||||
@@ -133,6 +136,10 @@ public class Settings {
|
||||
return startHidden;
|
||||
}
|
||||
|
||||
public BooleanProperty autoCloseVaults() {
|
||||
return autoCloseVaults;
|
||||
}
|
||||
|
||||
public IntegerProperty port() {
|
||||
return port;
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
||||
out.name("askedForUpdateCheck").value(value.askedForUpdateCheck().get());
|
||||
out.name("checkForUpdatesEnabled").value(value.checkForUpdates().get());
|
||||
out.name("startHidden").value(value.startHidden().get());
|
||||
out.name("autoCloseVaults").value(value.autoCloseVaults().get());
|
||||
out.name("port").value(value.port().get());
|
||||
out.name("numTrayNotifications").value(value.numTrayNotifications().get());
|
||||
out.name("preferredGvfsScheme").value(value.preferredGvfsScheme().get().name());
|
||||
@@ -82,6 +83,7 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
||||
case "askedForUpdateCheck" -> settings.askedForUpdateCheck().set(in.nextBoolean());
|
||||
case "checkForUpdatesEnabled" -> settings.checkForUpdates().set(in.nextBoolean());
|
||||
case "startHidden" -> settings.startHidden().set(in.nextBoolean());
|
||||
case "autoCloseVaults" -> settings.autoCloseVaults().set(in.nextBoolean());
|
||||
case "port" -> settings.port().set(in.nextInt());
|
||||
case "numTrayNotifications" -> settings.numTrayNotifications().set(in.nextInt());
|
||||
case "preferredGvfsScheme" -> settings.preferredGvfsScheme().set(parseWebDavUrlSchemePrefix(in.nextString()));
|
||||
|
||||
@@ -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"), //
|
||||
|
||||
@@ -86,7 +86,8 @@ public class VaultService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates but doesn't start a lock-all task.
|
||||
* Creates a lock-all task.
|
||||
* This task itself is _not started_, but its subtasks locking each vault will be already executed.
|
||||
*
|
||||
* @param vaults The list of vaults to be locked
|
||||
* @param forced Whether to attempt a forced lock
|
||||
|
||||
@@ -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.traymenu.TrayMenuComponent;
|
||||
import org.cryptomator.ui.unlock.UnlockComponent;
|
||||
|
||||
@@ -57,4 +58,4 @@ abstract class FxApplicationModule {
|
||||
static QuitComponent provideQuitComponent(QuitComponent.Builder builder) {
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,12 @@ package org.cryptomator.ui.fxapp;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import org.cryptomator.common.ShutdownHook;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.vaults.LockNotCompletedException;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultState;
|
||||
import org.cryptomator.common.vaults.Volume;
|
||||
import org.cryptomator.ui.common.VaultService;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -27,18 +29,24 @@ import static org.cryptomator.common.vaults.VaultState.Value.*;
|
||||
public class FxApplicationTerminator {
|
||||
|
||||
private static final Set<VaultState.Value> STATES_ALLOWING_TERMINATION = EnumSet.of(LOCKED, NEEDS_MIGRATION, MISSING, ERROR);
|
||||
private static final Set<VaultState.Value> STATES_PREVENT_TERMINATION = EnumSet.of(PROCESSING);
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FxApplicationTerminator.class);
|
||||
|
||||
private final ObservableList<Vault> vaults;
|
||||
private final ShutdownHook shutdownHook;
|
||||
private final FxApplicationWindows appWindows;
|
||||
private final AtomicBoolean allowQuitWithoutPrompt = new AtomicBoolean();
|
||||
private final AtomicBoolean preventQuitWithGracefulLock = new AtomicBoolean();
|
||||
private final Settings settings;
|
||||
private final VaultService vaultService;
|
||||
|
||||
@Inject
|
||||
public FxApplicationTerminator(ObservableList<Vault> vaults, ShutdownHook shutdownHook, FxApplicationWindows appWindows) {
|
||||
public FxApplicationTerminator(ObservableList<Vault> vaults, ShutdownHook shutdownHook, FxApplicationWindows appWindows, Settings settings, VaultService vaultService) {
|
||||
this.vaults = vaults;
|
||||
this.shutdownHook = shutdownHook;
|
||||
this.appWindows = appWindows;
|
||||
this.settings = settings;
|
||||
this.vaultService = vaultService;
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
@@ -72,6 +80,10 @@ public class FxApplicationTerminator {
|
||||
private void vaultListChanged(@SuppressWarnings("unused") Observable observable) {
|
||||
boolean allowSuddenTermination = vaults.stream().map(Vault::getState).allMatch(STATES_ALLOWING_TERMINATION::contains);
|
||||
boolean stateChanged = allowQuitWithoutPrompt.compareAndSet(!allowSuddenTermination, allowSuddenTermination);
|
||||
|
||||
boolean preventGracefulTermination = vaults.stream().map(Vault::getState).anyMatch(STATES_PREVENT_TERMINATION::contains);
|
||||
preventQuitWithGracefulLock.set(preventGracefulTermination);
|
||||
|
||||
Desktop desktop = Desktop.getDesktop();
|
||||
if (stateChanged && desktop.isSupported(Desktop.Action.APP_SUDDEN_TERMINATION)) {
|
||||
if (allowSuddenTermination) {
|
||||
@@ -92,10 +104,22 @@ public class FxApplicationTerminator {
|
||||
*/
|
||||
private void handleQuitRequest(@SuppressWarnings("unused") @Nullable EventObject e, QuitResponse response) {
|
||||
var exitingResponse = new ExitingQuitResponse(response);
|
||||
|
||||
if (allowQuitWithoutPrompt.get()) {
|
||||
exitingResponse.performQuit();
|
||||
} else if (settings.autoCloseVaults().get() && !preventQuitWithGracefulLock.get()) {
|
||||
var lockAllTask = vaultService.createLockAllTask(vaults.filtered(Vault::isUnlocked), false);
|
||||
lockAllTask.setOnSucceeded(event -> {
|
||||
LOG.info("Locked remaining vaults was succesful.");
|
||||
exitingResponse.performQuit();
|
||||
});
|
||||
lockAllTask.setOnFailed(event -> {
|
||||
LOG.warn("Unable to lock all vaults.");
|
||||
appWindows.showQuitWindow(exitingResponse, true);
|
||||
});
|
||||
lockAllTask.run();
|
||||
} else {
|
||||
appWindows.showQuitWindow(exitingResponse);
|
||||
appWindows.showQuitWindow(exitingResponse, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +139,7 @@ public class FxApplicationTerminator {
|
||||
|
||||
/**
|
||||
* A dummy QuitResponse that ignores the response.
|
||||
*
|
||||
* <p>
|
||||
* To be used with {@link #handleQuitRequest(EventObject, QuitResponse)} if the invoking method is not interested in the response.
|
||||
*/
|
||||
private static class NoopQuitResponse implements QuitResponse {
|
||||
|
||||
@@ -40,7 +40,7 @@ public class FxApplicationWindows {
|
||||
private final Optional<TrayIntegrationProvider> trayIntegration;
|
||||
private final Lazy<MainWindowComponent> mainWindow;
|
||||
private final Lazy<PreferencesComponent> preferencesWindow;
|
||||
private final Lazy<QuitComponent> quitWindow;
|
||||
private final QuitComponent.Builder quitWindowBuilder;
|
||||
private final UnlockComponent.Factory unlockWorkflowFactory;
|
||||
private final LockComponent.Factory lockWorkflowFactory;
|
||||
private final ErrorComponent.Factory errorWindowFactory;
|
||||
@@ -48,12 +48,12 @@ 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, QuitComponent.Builder quitWindowBuilder, 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.quitWindowBuilder = quitWindowBuilder;
|
||||
this.unlockWorkflowFactory = unlockWorkflowFactory;
|
||||
this.lockWorkflowFactory = lockWorkflowFactory;
|
||||
this.errorWindowFactory = errorWindowFactory;
|
||||
@@ -104,8 +104,8 @@ public class FxApplicationWindows {
|
||||
return CompletableFuture.supplyAsync(() -> preferencesWindow.get().showPreferencesWindow(selectedTab), Platform::runLater).whenComplete(this::reportErrors);
|
||||
}
|
||||
|
||||
public CompletionStage<Stage> showQuitWindow(QuitResponse response) {
|
||||
return CompletableFuture.supplyAsync(() -> quitWindow.get().showQuitWindow(response), Platform::runLater).whenComplete(this::reportErrors);
|
||||
public void showQuitWindow(QuitResponse response, boolean forced) {
|
||||
CompletableFuture.runAsync(() -> quitWindowBuilder.build().showQuitWindow(response,forced), Platform::runLater);
|
||||
}
|
||||
|
||||
public CompletionStage<Void> startUnlockWorkflow(Vault vault, @Nullable Stage owner) {
|
||||
|
||||
@@ -36,6 +36,7 @@ public class GeneralPreferencesController implements FxController {
|
||||
private final FxApplicationWindows appWindows;
|
||||
public ChoiceBox<KeychainAccessProvider> keychainBackendChoiceBox;
|
||||
public CheckBox startHiddenCheckbox;
|
||||
public CheckBox autoCloseVaultsCheckbox;
|
||||
public CheckBox debugModeCheckbox;
|
||||
public CheckBox autoStartCheckbox;
|
||||
public ToggleGroup nodeOrientation;
|
||||
@@ -54,9 +55,8 @@ public class GeneralPreferencesController implements FxController {
|
||||
@FXML
|
||||
public void initialize() {
|
||||
startHiddenCheckbox.selectedProperty().bindBidirectional(settings.startHidden());
|
||||
|
||||
autoCloseVaultsCheckbox.selectedProperty().bindBidirectional(settings.autoCloseVaults());
|
||||
debugModeCheckbox.selectedProperty().bindBidirectional(settings.debugMode());
|
||||
|
||||
autoStartProvider.ifPresent(autoStart -> autoStartCheckbox.setSelected(autoStart.isEnabled()));
|
||||
|
||||
var keychainSettingsConverter = new KeychainProviderClassNameConverter(keychainAccessProviders);
|
||||
|
||||
@@ -13,6 +13,7 @@ import org.cryptomator.ui.common.FxmlScene;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
import java.awt.desktop.QuitResponse;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
@QuitScoped
|
||||
@Subcomponent(modules = {QuitModule.class})
|
||||
@@ -22,23 +23,28 @@ public interface QuitComponent {
|
||||
Stage window();
|
||||
|
||||
@FxmlScene(FxmlFile.QUIT)
|
||||
Lazy<Scene> scene();
|
||||
Lazy<Scene> quitScene();
|
||||
|
||||
QuitController controller();
|
||||
@FxmlScene(FxmlFile.QUIT_FORCED)
|
||||
Lazy<Scene> quitForcedScene();
|
||||
|
||||
default Stage showQuitWindow(QuitResponse response) {
|
||||
controller().updateQuitRequest(response);
|
||||
@QuitWindow
|
||||
AtomicReference<QuitResponse> quitResponse();
|
||||
|
||||
default void showQuitWindow(QuitResponse response, boolean forced) {
|
||||
Stage stage = window();
|
||||
stage.setScene(scene().get());
|
||||
quitResponse().set(response);
|
||||
if(forced){
|
||||
stage.setScene(quitForcedScene().get());
|
||||
} else{
|
||||
stage.setScene(quitScene().get());
|
||||
}
|
||||
stage.sizeToScene();
|
||||
stage.show();
|
||||
stage.requestFocus();
|
||||
return stage;
|
||||
}
|
||||
|
||||
@Subcomponent.Builder
|
||||
interface Builder {
|
||||
|
||||
QuitComponent build();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
package org.cryptomator.ui.quit;
|
||||
|
||||
import dagger.Lazy;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.VaultService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -10,6 +13,7 @@ import javax.inject.Inject;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.stage.Stage;
|
||||
@@ -29,27 +33,22 @@ public class QuitController implements FxController {
|
||||
private final ObservableList<Vault> unlockedVaults;
|
||||
private final ExecutorService executorService;
|
||||
private final VaultService vaultService;
|
||||
private final AtomicReference<QuitResponse> quitResponse = new AtomicReference<>();
|
||||
|
||||
private final AtomicReference<QuitResponse> quitResponse;
|
||||
private final Lazy<Scene> quitForcedScene;
|
||||
/* FXML */
|
||||
public Button lockAndQuitButton;
|
||||
|
||||
@Inject
|
||||
QuitController(@QuitWindow Stage window, ObservableList<Vault> vaults, ExecutorService executorService, VaultService vaultService) {
|
||||
QuitController(@QuitWindow Stage window, ObservableList<Vault> vaults, ExecutorService executorService, VaultService vaultService, @FxmlScene(FxmlFile.QUIT_FORCED) Lazy<Scene> quitForcedScene, @QuitWindow AtomicReference<QuitResponse> quitResponse) {
|
||||
this.window = window;
|
||||
this.unlockedVaults = vaults.filtered(Vault::isUnlocked);
|
||||
this.executorService = executorService;
|
||||
this.vaultService = vaultService;
|
||||
this.quitForcedScene = quitForcedScene;
|
||||
this.quitResponse = quitResponse;
|
||||
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) {
|
||||
@@ -79,13 +78,8 @@ public class QuitController implements FxController {
|
||||
});
|
||||
lockAllTask.setOnFailed(evt -> {
|
||||
LOG.warn("Locking failed", lockAllTask.getException());
|
||||
lockAndQuitButton.setDisable(false);
|
||||
lockAndQuitButton.setContentDisplay(ContentDisplay.TEXT_ONLY);
|
||||
// TODO: show force lock or force quit scene (and DO NOT cancelQuit() here!) (see https://github.com/cryptomator/cryptomator/pull/1416)
|
||||
window.close();
|
||||
respondToQuitRequest(QuitResponse::cancelQuit);
|
||||
window.setScene(quitForcedScene.get());
|
||||
});
|
||||
executorService.execute(lockAllTask);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package org.cryptomator.ui.quit;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
/* FXML */
|
||||
public Button forceLockAndQuitButton;
|
||||
|
||||
@Inject
|
||||
QuitForcedController(@QuitWindow Stage window, ObservableList<Vault> vaults, ExecutorService executorService, VaultService vaultService, @QuitWindow AtomicReference<QuitResponse> quitResponse) {
|
||||
this.window = window;
|
||||
this.unlockedVaults = vaults.filtered(Vault::isUnlocked);
|
||||
this.executorService = executorService;
|
||||
this.vaultService = vaultService;
|
||||
this.quitResponse = quitResponse;
|
||||
window.setOnCloseRequest(windowEvent -> cancel());
|
||||
}
|
||||
|
||||
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 forced 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,8 +16,10 @@ import javax.inject.Provider;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import java.awt.desktop.QuitResponse;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
@Module
|
||||
abstract class QuitModule {
|
||||
@@ -41,6 +43,14 @@ abstract class QuitModule {
|
||||
return stage;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@QuitWindow
|
||||
@QuitScoped
|
||||
static AtomicReference<QuitResponse> provideQuitResponse() {
|
||||
return new AtomicReference<QuitResponse>();
|
||||
}
|
||||
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.QUIT)
|
||||
@QuitScoped
|
||||
@@ -48,6 +58,14 @@ abstract class QuitModule {
|
||||
return fxmlLoaders.createScene(FxmlFile.QUIT);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.QUIT_FORCED)
|
||||
@QuitScoped
|
||||
static Scene provideQuitForcedScene(@QuitWindow FxmlLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.QUIT_FORCED);
|
||||
}
|
||||
|
||||
|
||||
// ------------------
|
||||
|
||||
@Binds
|
||||
@@ -55,4 +73,9 @@ abstract class QuitModule {
|
||||
@FxControllerKey(QuitController.class)
|
||||
abstract FxController bindQuitController(QuitController controller);
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(QuitForcedController.class)
|
||||
abstract FxController bindQuitForcedController(QuitForcedController controller);
|
||||
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
|
||||
<CheckBox fx:id="startHiddenCheckbox" text="%preferences.general.startHidden" />
|
||||
|
||||
<CheckBox fx:id="autoCloseVaultsCheckbox" text="%preferences.general.autoCloseVaults" />
|
||||
|
||||
<HBox spacing="12" alignment="CENTER_LEFT">
|
||||
<Label text="%preferences.general.keychainBackend"/>
|
||||
<ChoiceBox fx:id="keychainBackendChoiceBox"/>
|
||||
|
||||
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.quit.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="%quit.forced.message" wrapText="true" textAlignment="LEFT">
|
||||
<padding>
|
||||
<Insets bottom="6" top="6"/>
|
||||
</padding>
|
||||
</Label>
|
||||
|
||||
<Label text="%quit.forced.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="%quit.forced.forceAndQuitBtn" ButtonBar.buttonData="FINISH" onAction="#forceLockAndQuit" contentDisplay="TEXT_ONLY">
|
||||
<graphic>
|
||||
<FontAwesome5Spinner glyphSize="12"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</VBox>
|
||||
</children>
|
||||
</HBox>
|
||||
@@ -198,6 +198,7 @@ preferences.title=Preferences
|
||||
## General
|
||||
preferences.general=General
|
||||
preferences.general.startHidden=Hide window when starting Cryptomator
|
||||
preferences.general.autoCloseVaults=Lock open vaults automatically when quitting application
|
||||
preferences.general.debugLogging=Enable debug logging
|
||||
preferences.general.debugDirectory=Reveal log files
|
||||
preferences.general.autoStart=Launch Cryptomator on system start
|
||||
@@ -392,3 +393,8 @@ quit.title=Quit Application
|
||||
quit.message=There are unlocked vaults
|
||||
quit.description=Please confirm that you want to quit. Cryptomator will gracefully lock all unlocked vaults to prevent data loss.
|
||||
quit.lockAndQuitBtn=Lock and Quit
|
||||
|
||||
# Forced Quit
|
||||
quit.forced.message=Some vaults could not be locked
|
||||
quit.forced.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.
|
||||
quit.forced.forceAndQuitBtn=Force and Quit
|
||||
@@ -27,6 +27,7 @@ public class SettingsJsonAdapterTest {
|
||||
{"id": "1", "path": "/vault1", "mountName": "vault1", "winDriveLetter": "X"},
|
||||
{"id": "2", "path": "/vault2", "mountName": "vault2", "winDriveLetter": "Y"}
|
||||
],
|
||||
"autoCloseVaults" : true,
|
||||
"checkForUpdatesEnabled": true,
|
||||
"port": 8080,
|
||||
"language": "de-DE",
|
||||
@@ -40,6 +41,7 @@ public class SettingsJsonAdapterTest {
|
||||
Assertions.assertTrue(settings.checkForUpdates().get());
|
||||
Assertions.assertEquals(2, settings.getDirectories().size());
|
||||
Assertions.assertEquals(8080, settings.port().get());
|
||||
Assertions.assertEquals(true, settings.autoCloseVaults().get());
|
||||
Assertions.assertEquals("de-DE", settings.languageProperty().get());
|
||||
Assertions.assertEquals(42, settings.numTrayNotifications().get());
|
||||
Assertions.assertEquals(WebDavUrlScheme.DAV, settings.preferredGvfsScheme().get());
|
||||
|
||||
Reference in New Issue
Block a user