diff --git a/src/main/java/org/cryptomator/common/ObservableUtil.java b/src/main/java/org/cryptomator/common/ObservableUtil.java index 289f6e929..7927b6e54 100644 --- a/src/main/java/org/cryptomator/common/ObservableUtil.java +++ b/src/main/java/org/cryptomator/common/ObservableUtil.java @@ -2,7 +2,9 @@ package org.cryptomator.common; import javafx.beans.binding.Bindings; import javafx.beans.value.ObservableValue; +import java.util.Objects; import java.util.function.Function; +import java.util.function.Supplier; public class ObservableUtil { @@ -15,4 +17,14 @@ public class ObservableUtil { } }, observable); } + + public static ObservableValue mapWithDefault(ObservableValue observable, Function mapper, Supplier defaultValue) { + return Bindings.createObjectBinding(() -> { + if (observable.getValue() == null) { + return defaultValue.get(); + } else { + return mapper.apply(observable.getValue()); + } + }, observable); + } } diff --git a/src/main/java/org/cryptomator/common/mount/MountModule.java b/src/main/java/org/cryptomator/common/mount/MountModule.java index d78cc3216..0978c44db 100644 --- a/src/main/java/org/cryptomator/common/mount/MountModule.java +++ b/src/main/java/org/cryptomator/common/mount/MountModule.java @@ -2,50 +2,71 @@ package org.cryptomator.common.mount; import dagger.Module; import dagger.Provides; +import org.cryptomator.common.ObservableUtil; import org.cryptomator.common.settings.Settings; +import org.cryptomator.integrations.mount.Mount; import org.cryptomator.integrations.mount.MountService; +import javax.inject.Named; import javax.inject.Singleton; -import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableValue; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; @Module public class MountModule { + private static final AtomicReference formerSelectedMountService = new AtomicReference<>(null); + private static final List problematicFuseMountServices = List.of("org.cryptomator.frontend.fuse.mount.MacFuseMountProvider", "org.cryptomator.frontend.fuse.mount.FuseTMountProvider"); + @Provides @Singleton static List provideSupportedMountServices() { return MountService.get().toList(); } - //currently not used, because macFUSE and FUSE-T cannot be used in the same JVM - /* @Provides @Singleton - static ObservableValue provideMountService(Settings settings, List serviceImpls) { + @Named("FUPFMS") + static AtomicReference provideFirstUsedProblematicFuseMountService() { + return new AtomicReference<>(null); + } + + @Provides + @Singleton + static ObservableValue provideMountService(Settings settings, List serviceImpls, @Named("FUPFMS") AtomicReference fupfms) { var fallbackProvider = serviceImpls.stream().findFirst().orElse(null); - return ObservableUtil.mapWithDefault(settings.mountService(), // + + var observableMountService = ObservableUtil.mapWithDefault(settings.mountService(), // desiredServiceImpl -> { // - var desiredService = serviceImpls.stream().filter(serviceImpl -> serviceImpl.getClass().getName().equals(desiredServiceImpl)).findAny(); // - return new ActualMountService(desiredService.orElse(fallbackProvider), desiredService.isPresent()); // + var serviceFromSettings = serviceImpls.stream().filter(serviceImpl -> serviceImpl.getClass().getName().equals(desiredServiceImpl)).findAny(); // + var targetedService = serviceFromSettings.orElse(fallbackProvider); + return applyWorkaroundForProblematicFuse(targetedService, serviceFromSettings.isPresent(), fupfms); }, // - new ActualMountService(fallbackProvider, true)); - } - */ - - @Provides - @Singleton - static ActualMountService provideActualMountService(Settings settings, List serviceImpls) { - var fallbackProvider = serviceImpls.stream().findFirst().orElse(null); - var desiredService = serviceImpls.stream().filter(serviceImpl -> serviceImpl.getClass().getName().equals(settings.mountService().getValue())).findFirst(); // - return new ActualMountService(desiredService.orElse(fallbackProvider), desiredService.isPresent()); // + () -> { // + return applyWorkaroundForProblematicFuse(fallbackProvider, true, fupfms); + }); + return observableMountService; } - @Provides - @Singleton - static ObservableValue provideMountService(ActualMountService service) { - return new SimpleObjectProperty<>(service); + //see https://github.com/cryptomator/cryptomator/issues/2786 + private synchronized static ActualMountService applyWorkaroundForProblematicFuse(MountService targetedService, boolean isDesired, AtomicReference firstUsedProblematicFuseMountService) { + //set the first used problematic fuse service if applicable + var targetIsProblematicFuse = isProblematicFuseService(targetedService); + if (targetIsProblematicFuse && firstUsedProblematicFuseMountService.get() == null) { + firstUsedProblematicFuseMountService.set(targetedService); + } + + //do not use the targeted mount service and fallback to former one, if the service is problematic _and_ not the first problematic one used. + if (targetIsProblematicFuse && !firstUsedProblematicFuseMountService.get().equals(targetedService)) { + return new ActualMountService(formerSelectedMountService.get(), false); + } else { + formerSelectedMountService.set(targetedService); + return new ActualMountService(targetedService, isDesired); + } } + public static boolean isProblematicFuseService(MountService service) { + return problematicFuseMountServices.contains(service.getClass().getName()); + } } diff --git a/src/main/java/org/cryptomator/ui/preferences/VolumePreferencesController.java b/src/main/java/org/cryptomator/ui/preferences/VolumePreferencesController.java index 61a54ba22..6c60e74b7 100644 --- a/src/main/java/org/cryptomator/ui/preferences/VolumePreferencesController.java +++ b/src/main/java/org/cryptomator/ui/preferences/VolumePreferencesController.java @@ -2,12 +2,14 @@ package org.cryptomator.ui.preferences; import dagger.Lazy; import org.cryptomator.common.ObservableUtil; +import org.cryptomator.common.mount.MountModule; import org.cryptomator.common.settings.Settings; import org.cryptomator.integrations.mount.MountCapability; import org.cryptomator.integrations.mount.MountService; import org.cryptomator.ui.common.FxController; import javax.inject.Inject; +import javax.inject.Named; import javafx.application.Application; import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanExpression; @@ -19,6 +21,7 @@ import javafx.util.StringConverter; import java.util.List; import java.util.Optional; import java.util.ResourceBundle; +import java.util.concurrent.atomic.AtomicReference; @PreferencesScoped public class VolumePreferencesController implements FxController { @@ -33,6 +36,7 @@ public class VolumePreferencesController implements FxController { private final ObservableValue mountToDriveLetterSupported; private final ObservableValue mountFlagsSupported; private final ObservableValue readonlySupported; + private final ObservableValue fuseRestartRequired; private final Lazy application; private final List mountProviders; public ChoiceBox volumeTypeChoiceBox; @@ -40,7 +44,7 @@ public class VolumePreferencesController implements FxController { public Button loopbackPortApplyButton; @Inject - VolumePreferencesController(Settings settings, Lazy application, List mountProviders, ResourceBundle resourceBundle) { + VolumePreferencesController(Settings settings, Lazy application, List mountProviders, @Named("FUPFMS") AtomicReference firstUsedProblematicFuseMountService, ResourceBundle resourceBundle) { this.settings = settings; this.application = application; this.mountProviders = mountProviders; @@ -53,6 +57,12 @@ public class VolumePreferencesController implements FxController { this.mountToDriveLetterSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_AS_DRIVE_LETTER)); this.mountFlagsSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_FLAGS)); this.readonlySupported = selectedMountService.map(s -> s.hasCapability(MountCapability.READ_ONLY)); + this.fuseRestartRequired = selectedMountService.map(s -> {// + return firstUsedProblematicFuseMountService.get() != null // + && MountModule.isProblematicFuseService(s) // + && !firstUsedProblematicFuseMountService.get().equals(s); + }); + } public void initialize() { @@ -129,6 +139,14 @@ public class VolumePreferencesController implements FxController { return mountFlagsSupported.getValue(); } + public ObservableValue fuseRestartRequiredProperty() { + return fuseRestartRequired; + } + + public boolean getFuseRestartRequired() { + return fuseRestartRequired.getValue(); + } + /* Helpers */ private class MountServiceConverter extends StringConverter { diff --git a/src/main/resources/css/dark_theme.css b/src/main/resources/css/dark_theme.css index 5f0877842..45cadba93 100644 --- a/src/main/resources/css/dark_theme.css +++ b/src/main/resources/css/dark_theme.css @@ -116,6 +116,10 @@ -fx-font-size: 0.64em; } +.label-red { + -fx-text-fill: RED_5; +} + .text-flow > * { -fx-fill: TEXT_FILL; } diff --git a/src/main/resources/css/light_theme.css b/src/main/resources/css/light_theme.css index decf64b64..c3c0faaa9 100644 --- a/src/main/resources/css/light_theme.css +++ b/src/main/resources/css/light_theme.css @@ -116,6 +116,10 @@ -fx-font-size: 0.64em; } +.label-red { + -fx-text-fill: RED_5; +} + .text-flow > * { -fx-fill: TEXT_FILL; } diff --git a/src/main/resources/fxml/preferences_volume.fxml b/src/main/resources/fxml/preferences_volume.fxml index 16ccc2b52..f48b1c1c8 100644 --- a/src/main/resources/fxml/preferences_volume.fxml +++ b/src/main/resources/fxml/preferences_volume.fxml @@ -8,9 +8,9 @@ + - +