Browse Source

Merge branch 'develop' into feature/convert-hub-to-local

Armin Schrenk 2 years ago
parent
commit
2b391a6ee3

+ 25 - 9
.github/workflows/win-debug.yml

@@ -9,8 +9,10 @@ on:
 
 env:
   JAVA_VERSION: 19
-  JAVA_DIST: 'zulu'
+  JAVA_DIST: 'temurin'
   JAVA_CACHE: 'maven'
+  JFX_JMODS_URL: 'https://download2.gluonhq.com/openjfx/19.0.2.1/openjfx-19.0.2.1_windows-x64_bin-jmods.zip'
+  JFX_JMODS_HASH: 'B7CF2CAD2468842B3B78D99F6C0555771499A36FA1F1EE3DD1B9A4597F1FAB86'
 
 defaults:
   run:
@@ -35,17 +37,31 @@ jobs:
         with:
           distribution: ${{ env.JAVA_DIST }}
           java-version: ${{ env.JAVA_VERSION }}
-          java-package: 'jdk+fx'
+          java-package: 'jdk'
           cache: ${{ env.JAVA_CACHE }}
-      - name: Ensure major jfx version in pom equals in jdk
+      - name: Download and extract JavaFX jmods from Gluon
+        #In the last step we move all jmods files a dir level up because jmods are placed inside a directory in the zip
+        run: |
+          curl --output jfxjmods.zip -L "${{ env.JFX_JMODS_URL }}"
+          if(!(Get-FileHash -Path jfxjmods.zip -Algorithm SHA256).Hash.equals("${{ env.JFX_JMODS_HASH }}")) {
+            exit 1;
+          }
+          Expand-Archive -Path jfxjmods.zip -DestinationPath jfxjmods
+          Get-ChildItem -Path jfxjmods -Recurse -Filter "*.jmod" | ForEach-Object { Move-Item -Path $_ -Destination $_.Directory.Parent}
         shell: pwsh
+      - name: Ensure major jfx version in pom and in jmods is the same
         run: |
-          $jfxPomVersion = (&mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout) -split "\."
-          $jfxJdkVersion = ((Get-Content -path "${env:JAVA_HOME}/lib/javafx.properties" | Where-Object {$_ -like 'javafx.version=*' }) -replace '.*=','') -split "\."
-          if ($jfxPomVersion[0] -ne $jfxJdkVersion[0]) {
-            Write-Error "Major part of JavaFX version in pom($($jfxPomVersion[0])) does not match the version in JDK($($jfxJdkVersion[0])) "
+          JMOD_VERSION_AMD64=$(jmod describe jfxjmods/javafx.base.jmod | head -1)
+          JMOD_VERSION_AMD64=${JMOD_VERSION_AMD64#*@}
+          JMOD_VERSION_AMD64=${JMOD_VERSION_AMD64%%.*}
+          POM_JFX_VERSION=$(mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout)
+          POM_JFX_VERSION=${POM_JFX_VERSION#*@}
+          POM_JFX_VERSION=${POM_JFX_VERSION%%.*}
+
+          if [ $POM_JFX_VERSION -ne $JMOD_VERSION_AMD64 ]; then
+            >&2 echo "Major JavaFX version in pom.xml (${POM_JFX_VERSION}) != amd64 jmod version (${JMOD_VERSION_AMD64})"
             exit 1
-          }
+          fi
       - name: Set version
         run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
       - name: Run maven
@@ -59,7 +75,7 @@ jobs:
           ${JAVA_HOME}/bin/jlink
           --verbose
           --output runtime
-          --module-path "${JAVA_HOME}/jmods"
+          --module-path "jfxjmods;${JAVA_HOME}/jmods"
           --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr
           --strip-native-commands
           --no-header-files

+ 1 - 0
README.md

@@ -31,6 +31,7 @@ Cryptomator is provided free of charge as an open-source project despite the hig
   <tbody>
     <tr>
       <td><a href="https://mowcapital.com/"><img src="https://cryptomator.org/img/sponsors/mowcapital.svg" alt="Mow Capital" height="40"></a></td>
+      <td><a href="https://www.easeus.com/"><img src="https://cryptomator.org/img/sponsors/easeus.png" alt="EaseUS" height="40"></a></td>
       <td><a href="https://www.hassmann-it-forensik.de/"><img src="https://cryptomator.org/img/sponsors/hassmannitforensik.png" alt="Hassmann IT-Forensik" height="40"></a></td>
     </tr>
   </tbody>

+ 1 - 36
pom.xml

@@ -38,7 +38,7 @@
 		<cryptomator.integrations.win.version>1.2.0</cryptomator.integrations.win.version>
 		<cryptomator.integrations.mac.version>1.2.0</cryptomator.integrations.mac.version>
 		<cryptomator.integrations.linux.version>1.2.0</cryptomator.integrations.linux.version>
-		<cryptomator.fuse.version>2.0.4</cryptomator.fuse.version>
+		<cryptomator.fuse.version>2.0.5</cryptomator.fuse.version>
 		<cryptomator.dokany.version>2.0.0</cryptomator.dokany.version>
 		<cryptomator.webdav.version>2.0.2</cryptomator.webdav.version>
 
@@ -317,41 +317,6 @@
 					</compilerArgs>
 				</configuration>
 			</plugin>
-			<plugin>
-				<groupId>org.codehaus.mojo</groupId>
-				<artifactId>exec-maven-plugin</artifactId>
-				<version>3.1.0</version>
-				<executions>
-					<execution>
-						<id>compile-light-theme</id>
-						<phase>compile</phase>
-						<goals>
-							<goal>java</goal>
-						</goals>
-						<configuration>
-							<mainClass>javafx.graphics/com.sun.javafx.css.parser.Css2Bin</mainClass>
-							<arguments>
-								<arg>${project.basedir}/src/main/resources/css/light_theme.css</arg>
-								<arg>${project.build.outputDirectory}/css/light_theme.bss</arg>
-							</arguments>
-						</configuration>
-					</execution>
-					<execution>
-						<id>compile-dark-theme</id>
-						<phase>compile</phase>
-						<goals>
-							<goal>java</goal>
-						</goals>
-						<configuration>
-							<mainClass>javafx.graphics/com.sun.javafx.css.parser.Css2Bin</mainClass>
-							<arguments>
-								<arg>${project.basedir}/src/main/resources/css/dark_theme.css</arg>
-								<arg>${project.build.outputDirectory}/css/dark_theme.bss</arg>
-							</arguments>
-						</configuration>
-					</execution>
-				</executions>
-			</plugin>
 			<plugin>
 				<groupId>org.apache.maven.plugins</groupId>
 				<artifactId>maven-jar-plugin</artifactId>

+ 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