Parcourir la source

Merge pull request #2789 from cryptomator/feature/2786-only-restart-macfuset

Feature: Only require app restart if switching between macFUSE and FUSE-T
Armin Schrenk il y a 2 ans
Parent
commit
dfe17569e1

+ 12 - 0
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 <T, U> ObservableValue<U> mapWithDefault(ObservableValue<T> observable, Function<? super T, ? extends U> mapper, Supplier<U> defaultValue) {
+		return Bindings.createObjectBinding(() -> {
+			if (observable.getValue() == null) {
+				return defaultValue.get();
+			} else {
+				return mapper.apply(observable.getValue());
+			}
+		}, observable);
+	}
 }

+ 40 - 19
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<MountService> formerSelectedMountService = new AtomicReference<>(null);
+	private static final List<String> problematicFuseMountServices = List.of("org.cryptomator.frontend.fuse.mount.MacFuseMountProvider", "org.cryptomator.frontend.fuse.mount.FuseTMountProvider");
+
 	@Provides
 	@Singleton
 	static List<MountService> provideSupportedMountServices() {
 		return MountService.get().toList();
 	}
 
-	//currently not used, because macFUSE and FUSE-T cannot be used in the same JVM
-	/*
 	@Provides
 	@Singleton
-	static ObservableValue<ActualMountService> provideMountService(Settings settings, List<MountService> serviceImpls) {
-		var fallbackProvider = serviceImpls.stream().findFirst().orElse(null);
-		return 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()); //
-				}, //
-				new ActualMountService(fallbackProvider, true));
+	@Named("FUPFMS")
+	static AtomicReference<MountService> provideFirstUsedProblematicFuseMountService() {
+		return new AtomicReference<>(null);
 	}
-	 */
 
 	@Provides
 	@Singleton
-	static ActualMountService provideActualMountService(Settings settings, List<MountService> serviceImpls) {
+	static ObservableValue<ActualMountService> provideMountService(Settings settings, List<MountService> serviceImpls, @Named("FUPFMS") AtomicReference<MountService> fupfms) {
 		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()); //
+
+		var observableMountService = ObservableUtil.mapWithDefault(settings.mountService(), //
+				desiredServiceImpl -> { //
+					var serviceFromSettings = serviceImpls.stream().filter(serviceImpl -> serviceImpl.getClass().getName().equals(desiredServiceImpl)).findAny(); //
+					var targetedService = serviceFromSettings.orElse(fallbackProvider);
+					return applyWorkaroundForProblematicFuse(targetedService, serviceFromSettings.isPresent(), fupfms);
+				}, //
+				() -> { //
+					return applyWorkaroundForProblematicFuse(fallbackProvider, true, fupfms);
+				});
+		return observableMountService;
 	}
 
-	@Provides
-	@Singleton
-	static ObservableValue<ActualMountService> 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<MountService> 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());
+	}
 }

+ 19 - 1
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<Boolean> mountToDriveLetterSupported;
 	private final ObservableValue<Boolean> mountFlagsSupported;
 	private final ObservableValue<Boolean> readonlySupported;
+	private final ObservableValue<Boolean> fuseRestartRequired;
 	private final Lazy<Application> application;
 	private final List<MountService> mountProviders;
 	public ChoiceBox<MountService> volumeTypeChoiceBox;
@@ -40,7 +44,7 @@ public class VolumePreferencesController implements FxController {
 	public Button loopbackPortApplyButton;
 
 	@Inject
-	VolumePreferencesController(Settings settings, Lazy<Application> application, List<MountService> mountProviders, ResourceBundle resourceBundle) {
+	VolumePreferencesController(Settings settings, Lazy<Application> application, List<MountService> mountProviders, @Named("FUPFMS") AtomicReference<MountService> 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<Boolean> fuseRestartRequiredProperty() {
+		return fuseRestartRequired;
+	}
+
+	public boolean getFuseRestartRequired() {
+		return fuseRestartRequired.getValue();
+	}
+
 	/* Helpers */
 
 	private class MountServiceConverter extends StringConverter<MountService> {

+ 4 - 0
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;
 }

+ 4 - 0
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;
 }

+ 3 - 1
src/main/resources/fxml/preferences_volume.fxml

@@ -8,9 +8,9 @@
 <?import javafx.scene.control.Hyperlink?>
 <?import javafx.scene.control.Label?>
 <?import javafx.scene.control.Separator?>
+<?import javafx.scene.control.Tooltip?>
 <?import javafx.scene.layout.HBox?>
 <?import javafx.scene.layout.VBox?>
-<?import javafx.scene.control.Tooltip?>
 <VBox xmlns:fx="http://javafx.com/fxml"
 	  xmlns="http://javafx.com/javafx"
 	  fx:controller="org.cryptomator.ui.preferences.VolumePreferencesController"
@@ -32,6 +32,8 @@
 			</Hyperlink>
 		</HBox>
 
+		<Label styleClass="label-red" text="%preferences.volume.fuseRestartRequired" visible="${controller.fuseRestartRequired}" managed="${controller.fuseRestartRequired}"/>
+
 		<HBox spacing="12" alignment="CENTER_LEFT" visible="${controller.loopbackPortSupported}" managed="${controller.loopbackPortSupported}">
 			<Label text="%preferences.volume.tcp.port"/>
 			<NumericTextField fx:id="loopbackPortField"/>

+ 2 - 1
src/main/resources/i18n/strings.properties

@@ -275,9 +275,10 @@ preferences.interface.showMinimizeButton=Show minimize button
 preferences.interface.showTrayIcon=Show tray icon (requires restart)
 ## Volume
 preferences.volume=Virtual Drive
-preferences.volume.type=Volume Type (requires restart)
+preferences.volume.type=Volume Type
 preferences.volume.type.automatic=Automatic
 preferences.volume.docsTooltip=Open the documentation to learn more about the different volume types.
+preferences.volume.fuseRestartRequired=To apply the changes, Cryptomator needs to be restarted.
 preferences.volume.tcp.port=TCP Port
 preferences.volume.supportedFeatures=The chosen volume type supports the following features:
 preferences.volume.feature.mountAuto=Automatic mount point selection