diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md index 4775a0aa2..528dd76ad 100644 --- a/.github/ISSUE_TEMPLATE/bug.md +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -5,8 +5,11 @@ labels: type:bug --- diff --git a/main/commons/src/main/java/org/cryptomator/common/mountpoint/MacVolumeMountChooser.java b/main/commons/src/main/java/org/cryptomator/common/mountpoint/MacVolumeMountChooser.java index 64c0ed979..334746860 100644 --- a/main/commons/src/main/java/org/cryptomator/common/mountpoint/MacVolumeMountChooser.java +++ b/main/commons/src/main/java/org/cryptomator/common/mountpoint/MacVolumeMountChooser.java @@ -28,7 +28,7 @@ class MacVolumeMountChooser implements MountPointChooser { @Override public Optional chooseMountPoint(Volume caller) { - return Optional.of(VOLUME_PATH).map(dir -> this.helper.chooseTemporaryMountPoint(this.vaultSettings, dir)); + return Optional.of(helper.chooseTemporaryMountPoint(vaultSettings, VOLUME_PATH)); } @Override diff --git a/main/commons/src/main/java/org/cryptomator/common/mountpoint/MountPointHelper.java b/main/commons/src/main/java/org/cryptomator/common/mountpoint/MountPointHelper.java index c72aeb44c..704f2f62d 100644 --- a/main/commons/src/main/java/org/cryptomator/common/mountpoint/MountPointHelper.java +++ b/main/commons/src/main/java/org/cryptomator/common/mountpoint/MountPointHelper.java @@ -20,11 +20,10 @@ import java.util.Optional; class MountPointHelper { public static Logger LOG = LoggerFactory.getLogger(MountPointHelper.class); - private static final int MAX_TMPMOUNTPOINT_CREATION_RETRIES = 10; private final Optional tmpMountPointDir; - private volatile boolean alreadyChecked = false; + private volatile boolean unmountDebrisCleared = false; @Inject public MountPointHelper(Environment env) { @@ -55,13 +54,13 @@ class MountPointHelper { } public synchronized void clearIrregularUnmountDebrisIfNeeded() { - if (alreadyChecked || tmpMountPointDir.isEmpty()) { - return; //nuthin to do + if (unmountDebrisCleared || tmpMountPointDir.isEmpty()) { + return; // nothing to do } if (Files.exists(tmpMountPointDir.get(), LinkOption.NOFOLLOW_LINKS)) { clearIrregularUnmountDebris(tmpMountPointDir.get()); } - alreadyChecked = true; + unmountDebrisCleared = true; } private void clearIrregularUnmountDebris(Path dirContainingMountPoints) { @@ -92,7 +91,7 @@ class MountPointHelper { } catch (IOException e) { LOG.warn("Unable to perform cleanup of mountpoint dir {}.", dirContainingMountPoints, e); } finally { - alreadyChecked = true; + unmountDebrisCleared = true; } } diff --git a/main/commons/src/main/java/org/cryptomator/common/vaults/AbstractVolume.java b/main/commons/src/main/java/org/cryptomator/common/vaults/AbstractVolume.java index 6a09683ca..d66ad19d8 100644 --- a/main/commons/src/main/java/org/cryptomator/common/vaults/AbstractVolume.java +++ b/main/commons/src/main/java/org/cryptomator/common/vaults/AbstractVolume.java @@ -20,7 +20,8 @@ public abstract class AbstractVolume implements Volume { } protected Path determineMountPoint() throws InvalidMountPointException { - for (var chooser : Iterables.filter(choosers, c -> c.isApplicable(this))) { + var applicableChoosers = Iterables.filter(choosers, c -> c.isApplicable(this)); + for (var chooser : applicableChoosers) { Optional chosenPath = chooser.chooseMountPoint(this); if (chosenPath.isEmpty()) { // chooser couldn't find a feasible mountpoint continue; @@ -29,7 +30,7 @@ public abstract class AbstractVolume implements Volume { this.usedChooser = chooser; return chosenPath.get(); } - throw new InvalidMountPointException("No feasible MountPoint found!"); + throw new InvalidMountPointException(String.format("No feasible MountPoint found by choosers: %s", applicableChoosers)); } protected void cleanupMountPoint() { diff --git a/main/commons/src/main/java/org/cryptomator/common/vaults/DokanyVolume.java b/main/commons/src/main/java/org/cryptomator/common/vaults/DokanyVolume.java index 89fd73203..0bf39f669 100644 --- a/main/commons/src/main/java/org/cryptomator/common/vaults/DokanyVolume.java +++ b/main/commons/src/main/java/org/cryptomator/common/vaults/DokanyVolume.java @@ -13,7 +13,6 @@ import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Named; -import java.util.SortedSet; import java.util.concurrent.ExecutorService; public class DokanyVolume extends AbstractVolume { @@ -42,7 +41,6 @@ public class DokanyVolume extends AbstractVolume { @Override public void mount(CryptoFileSystem fs, String mountFlags) throws InvalidMountPointException, VolumeException { this.mountPoint = determineMountPoint(); - String mountName = vaultSettings.mountName().get(); try { this.mount = mountFactory.mount(fs.getPath("/"), mountPoint, vaultSettings.mountName().get(), FS_TYPE_NAME, mountFlags.strip()); } catch (MountFailedException e) { @@ -62,8 +60,18 @@ public class DokanyVolume extends AbstractVolume { } @Override - public void unmount() { - mount.close(); + public void unmount() throws VolumeException { + try { + mount.unmount(); + } catch (IllegalStateException e) { + throw new VolumeException("Unmount Failed.", e); + } + cleanupMountPoint(); + } + + @Override + public void unmountForced() { + mount.close(); //TODO: with next dokany-nio-release, change this to unmountForced() cleanupMountPoint(); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java index 1efd5ba50..4eeea5c3d 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java @@ -1,6 +1,5 @@ package org.cryptomator.ui.addvaultwizard; -import com.tobiasdiez.easybind.EasyBind; import dagger.Lazy; import org.cryptomator.ui.common.ErrorComponent; import org.cryptomator.ui.common.FxController; @@ -84,11 +83,11 @@ public class CreateNewVaultLocationController implements FxController { public void initialize() { predefinedLocationToggler.selectedToggleProperty().addListener(this::togglePredefinedLocation); usePresetPath.bind(predefinedLocationToggler.selectedToggleProperty().isNotEqualTo(customRadioButton)); - EasyBind.subscribe(vaultPath, this::vaultPathDidChange); + vaultPath.addListener(this::vaultPathDidChange); } - private void vaultPathDidChange(Path newValue) { - if (newValue != null && !Files.notExists(newValue)) { + private void vaultPathDidChange(@SuppressWarnings("unused") ObservableValue observable, @SuppressWarnings("unused") Path oldValue, Path newValue) { + if (!Files.notExists(newValue)) { warningText.set(resourceBundle.getString("addvaultwizard.new.fileAlreadyExists")); } else { warningText.set(null); diff --git a/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java b/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java index 005d35154..460d074e6 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java +++ b/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java @@ -1,6 +1,5 @@ package org.cryptomator.ui.fxapp; -import com.tobiasdiez.easybind.EasyBind; import dagger.Lazy; import org.cryptomator.common.LicenseHolder; import org.cryptomator.common.settings.Settings; @@ -41,9 +40,9 @@ public class FxApplication extends Application { private final Settings settings; private final Lazy mainWindow; private final Lazy preferencesWindow; + private final Lazy quitWindow; private final Provider unlockWindowBuilderProvider; private final Provider lockWindowBuilderProvider; - private final Provider quitWindowBuilderProvider; private final Optional trayIntegration; private final Optional appearanceProvider; private final VaultService vaultService; @@ -52,13 +51,13 @@ public class FxApplication extends Application { private final UiAppearanceListener systemInterfaceThemeListener = this::systemInterfaceThemeChanged; @Inject - FxApplication(Settings settings, Lazy mainWindow, Lazy preferencesWindow, Provider unlockWindowBuilderProvider, Provider lockWindowBuilderProvider, Provider quitWindowBuilderProvider, Optional trayIntegration, Optional appearanceProvider, VaultService vaultService, LicenseHolder licenseHolder, ObservableSet visibleStages) { + FxApplication(Settings settings, Lazy mainWindow, Lazy preferencesWindow, Provider unlockWindowBuilderProvider, Provider lockWindowBuilderProvider, Lazy quitWindow, Optional trayIntegration, Optional appearanceProvider, VaultService vaultService, LicenseHolder licenseHolder, ObservableSet visibleStages) { this.settings = settings; this.mainWindow = mainWindow; this.preferencesWindow = preferencesWindow; this.unlockWindowBuilderProvider = unlockWindowBuilderProvider; this.lockWindowBuilderProvider = lockWindowBuilderProvider; - this.quitWindowBuilderProvider = quitWindowBuilderProvider; + this.quitWindow = quitWindow; this.trayIntegration = trayIntegration; this.appearanceProvider = appearanceProvider; this.vaultService = vaultService; @@ -70,7 +69,7 @@ public class FxApplication extends Application { LOG.trace("FxApplication.start()"); Platform.setImplicitExit(false); - EasyBind.subscribe(hasVisibleStages, this::hasVisibleStagesChanged); + hasVisibleStages.addListener(this::hasVisibleStagesChanged); settings.theme().addListener(this::appThemeChanged); loadSelectedStyleSheet(settings.theme().get()); @@ -81,7 +80,7 @@ public class FxApplication extends Application { throw new UnsupportedOperationException("Use start() instead."); } - private void hasVisibleStagesChanged(boolean newValue) { + private void hasVisibleStagesChanged(@SuppressWarnings("unused") ObservableValue observableValue, @SuppressWarnings("unused") boolean oldValue, boolean newValue) { if (newValue) { trayIntegration.ifPresent(TrayIntegrationProvider::restoredFromTray); } else { @@ -119,7 +118,7 @@ public class FxApplication extends Application { public void showQuitWindow(QuitResponse response) { Platform.runLater(() -> { - quitWindowBuilderProvider.get().quitResponse(response).build().showQuitWindow(); + quitWindow.get().showQuitWindow(response); LOG.debug("Showing QuitWindow"); }); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java b/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java index 39b3020be..87f408490 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java +++ b/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java @@ -89,4 +89,8 @@ abstract class FxApplicationModule { return builder.build(); } + @Provides + static QuitComponent provideQuitComponent(QuitComponent.Builder builder) { + return builder.build(); + } } diff --git a/main/ui/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java index 0533cf50f..fc751d862 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java @@ -1,6 +1,5 @@ package org.cryptomator.ui.mainwindow; -import com.tobiasdiez.easybind.EasyBind; import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultListManager; import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent; @@ -13,6 +12,7 @@ import javax.inject.Inject; import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; import javafx.beans.property.ObjectProperty; +import javafx.beans.value.ObservableValue; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.fxml.FXML; @@ -41,7 +41,7 @@ public class VaultListController implements FxController { this.removeVault = removeVault; this.noVaultSelected = selectedVault.isNull(); this.emptyVaultList = Bindings.isEmpty(vaults); - EasyBind.subscribe(selectedVault, this::selectedVaultDidChange); + selectedVault.addListener(this::selectedVaultDidChange); } public void initialize() { @@ -58,10 +58,11 @@ public class VaultListController implements FxController { }); } - private void selectedVaultDidChange(Vault newValue) { - if (newValue != null) { - VaultListManager.redetermineVaultState(newValue); + private void selectedVaultDidChange(@SuppressWarnings("unused") ObservableValue observableValue, @SuppressWarnings("unused") Vault oldValue, Vault newValue) { + if (newValue == null) { + return; } + VaultListManager.redetermineVaultState(newValue); } @FXML diff --git a/main/ui/src/main/java/org/cryptomator/ui/preferences/DonationKeyPreferencesController.java b/main/ui/src/main/java/org/cryptomator/ui/preferences/DonationKeyPreferencesController.java index e7708af81..a4814ec82 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/preferences/DonationKeyPreferencesController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/preferences/DonationKeyPreferencesController.java @@ -1,5 +1,6 @@ package org.cryptomator.ui.preferences; +import com.google.common.base.CharMatcher; import org.cryptomator.common.LicenseHolder; import org.cryptomator.common.settings.Settings; import org.cryptomator.common.settings.UiTheme; @@ -10,6 +11,7 @@ import javafx.application.Application; import javafx.beans.value.ObservableValue; import javafx.fxml.FXML; import javafx.scene.control.TextArea; +import javafx.scene.control.TextFormatter; @PreferencesScoped public class DonationKeyPreferencesController implements FxController { @@ -32,6 +34,15 @@ public class DonationKeyPreferencesController implements FxController { public void initialize() { donationKeyField.setText(licenseHolder.getLicenseKey().orElse(null)); donationKeyField.textProperty().addListener(this::registrationKeyChanged); + donationKeyField.setTextFormatter(new TextFormatter<>(this::checkVaultNameLength)); + } + + private TextFormatter.Change checkVaultNameLength(TextFormatter.Change change) { + if (change.isContentChange()) { + var strippedText = CharMatcher.whitespace().removeFrom(change.getText()); + change.setText(strippedText); + } + return change; } private void registrationKeyChanged(@SuppressWarnings("unused") ObservableValue observable, @SuppressWarnings("unused") String oldValue, String newValue) { diff --git a/main/ui/src/main/java/org/cryptomator/ui/quit/QuitComponent.java b/main/ui/src/main/java/org/cryptomator/ui/quit/QuitComponent.java index 23ddf311a..e100c52e9 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/quit/QuitComponent.java +++ b/main/ui/src/main/java/org/cryptomator/ui/quit/QuitComponent.java @@ -5,7 +5,6 @@ *******************************************************************************/ package org.cryptomator.ui.quit; -import dagger.BindsInstance; import dagger.Lazy; import dagger.Subcomponent; import org.cryptomator.ui.common.FxmlFile; @@ -25,7 +24,10 @@ public interface QuitComponent { @FxmlScene(FxmlFile.QUIT) Lazy scene(); - default Stage showQuitWindow() { + QuitController controller(); + + default Stage showQuitWindow(QuitResponse response) { + controller().updateQuitRequest(response); Stage stage = window(); stage.setScene(scene().get()); stage.show(); @@ -36,9 +38,6 @@ public interface QuitComponent { @Subcomponent.Builder interface Builder { - @BindsInstance - Builder quitResponse(QuitResponse response); - QuitComponent build(); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/quit/QuitController.java b/main/ui/src/main/java/org/cryptomator/ui/quit/QuitController.java index 130556ef2..12ddbbca3 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/quit/QuitController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/quit/QuitController.java @@ -16,6 +16,8 @@ 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; @QuitScoped @@ -24,26 +26,40 @@ public class QuitController implements FxController { private static final Logger LOG = LoggerFactory.getLogger(QuitController.class); private final Stage window; - private final QuitResponse response; private final ObservableList unlockedVaults; private final ExecutorService executorService; private final VaultService vaultService; + private final AtomicReference quitResponse = new AtomicReference<>(); public Button lockAndQuitButton; @Inject - QuitController(@QuitWindow Stage window, QuitResponse response, ObservableList vaults, ExecutorService executorService, VaultService vaultService) { + QuitController(@QuitWindow Stage window, ObservableList vaults, ExecutorService executorService, VaultService vaultService) { this.window = window; - this.response = response; 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 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(); - response.cancelQuit(); + respondToQuitRequest(QuitResponse::cancelQuit); } @FXML @@ -56,16 +72,16 @@ public class QuitController implements FxController { LOG.info("Locked {}", lockAllTask.getValue().stream().map(Vault::getDisplayName).collect(Collectors.joining(", "))); if (unlockedVaults.isEmpty()) { window.close(); - response.performQuit(); + respondToQuitRequest(QuitResponse::performQuit); } }); 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/blob/1.4.16/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java#L151-L163 - response.cancelQuit(); + // 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); }); executorService.execute(lockAllTask); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/quit/QuitModule.java b/main/ui/src/main/java/org/cryptomator/ui/quit/QuitModule.java index e73fa9d46..9c779f434 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/quit/QuitModule.java +++ b/main/ui/src/main/java/org/cryptomator/ui/quit/QuitModule.java @@ -43,7 +43,7 @@ abstract class QuitModule { @Provides @FxmlScene(FxmlFile.QUIT) @QuitScoped - static Scene provideUnlockScene(@QuitWindow FXMLLoaderFactory fxmlLoaders) { + static Scene provideQuitScene(@QuitWindow FXMLLoaderFactory fxmlLoaders) { return fxmlLoaders.createScene("/fxml/quit.fxml"); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/vaultoptions/GeneralVaultOptionsController.java b/main/ui/src/main/java/org/cryptomator/ui/vaultoptions/GeneralVaultOptionsController.java index b760ebf9a..e860ee811 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/vaultoptions/GeneralVaultOptionsController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/vaultoptions/GeneralVaultOptionsController.java @@ -39,7 +39,7 @@ public class GeneralVaultOptionsController implements FxController { public void initialize() { vaultName.textProperty().set(vault.getVaultSettings().displayName().get()); vaultName.focusedProperty().addListener(this::trimVaultNameOnFocusLoss); - vaultName.setTextFormatter(new TextFormatter<>(this::checkVaultNameLength)); + vaultName.setTextFormatter(new TextFormatter<>(this::removeWhitespaces)); unlockOnStartupCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().unlockAfterStartup()); actionAfterUnlockChoiceBox.getItems().addAll(WhenUnlocked.values()); actionAfterUnlockChoiceBox.valueProperty().bindBidirectional(vault.getVaultSettings().actionAfterUnlock()); @@ -53,7 +53,7 @@ public class GeneralVaultOptionsController implements FxController { } } - private TextFormatter.Change checkVaultNameLength(TextFormatter.Change change) { + private TextFormatter.Change removeWhitespaces(TextFormatter.Change change) { if (change.isContentChange() && change.getControlNewText().length() > VAULTNAME_TRUNCATE_THRESHOLD) { return null; // reject any change that would lead to a text exceeding threshold } else {