Browse Source

Merge branch 'master' into develop

Sebastian Stenzel 4 years ago
parent
commit
a074450452

+ 1 - 2
.idea/runConfigurations/Cryptomator_macOS.xml

@@ -5,8 +5,7 @@
     </envs>
     <option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
     <module name="launcher" />
-    <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
-    <option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath=&quot;~/Library/Application Support/Cryptomator/settings.json&quot; -Dcryptomator.ipcPortPath=&quot;~/Library/Application Support/Cryptomator/ipcPort.bin&quot; -Dcryptomator.logDir=&quot;~/Library/Logs/Cryptomator&quot; -Dcryptomator.mountPointsDir=&quot;/Volumes/&quot; -Xss2m -Xmx512m -ea" />
+    <option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath=&quot;~/Library/Application Support/Cryptomator/settings.json&quot; -Dcryptomator.ipcPortPath=&quot;~/Library/Application Support/Cryptomator/ipcPort.bin&quot; -Dcryptomator.logDir=&quot;~/Library/Logs/Cryptomator&quot; -Xss2m -Xmx512m -ea" />
     <method v="2">
       <option name="Make" enabled="true" />
     </method>

+ 1 - 1
.idea/runConfigurations/Cryptomator_macOS_Dev.xml

@@ -5,7 +5,7 @@
     </envs>
     <option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
     <module name="launcher" />
-    <option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath=&quot;~/Library/Application Support/Cryptomator-Dev/settings.json&quot; -Dcryptomator.ipcPortPath=&quot;~/Library/Application Support/Cryptomator-Dev/ipcPort.bin&quot; -Dcryptomator.logDir=&quot;~/Library/Logs/Cryptomator-Dev&quot; -Dcryptomator.mountPointsDir=&quot;/Volumes/&quot; -Dfuse.experimental=&quot;true&quot; -Xss2m -Xmx512m -ea" />
+    <option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath=&quot;~/Library/Application Support/Cryptomator-Dev/settings.json&quot; -Dcryptomator.ipcPortPath=&quot;~/Library/Application Support/Cryptomator-Dev/ipcPort.bin&quot; -Dcryptomator.logDir=&quot;~/Library/Logs/Cryptomator-Dev&quot; -Xss2m -Xmx512m -ea" />
     <method v="2">
       <option name="Make" enabled="true" />
     </method>

+ 1 - 8
main/commons/src/main/java/org/cryptomator/common/mountpoint/AvailableDriveLetterChooser.java

@@ -8,9 +8,7 @@ import javax.inject.Inject;
 import java.nio.file.Path;
 import java.util.Optional;
 
-public class AvailableDriveLetterChooser implements MountPointChooser {
-
-	public static final int PRIORITY = 200;
+class AvailableDriveLetterChooser implements MountPointChooser {
 
 	private final WindowsDriveLetters windowsDriveLetters;
 
@@ -28,9 +26,4 @@ public class AvailableDriveLetterChooser implements MountPointChooser {
 	public Optional<Path> chooseMountPoint(Volume caller) {
 		return this.windowsDriveLetters.getAvailableDriveLetterPath();
 	}
-
-	@Override
-	public int getPriority() {
-		return PRIORITY;
-	}
 }

+ 1 - 8
main/commons/src/main/java/org/cryptomator/common/mountpoint/CustomDriveLetterChooser.java

@@ -9,9 +9,7 @@ import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Optional;
 
-public class CustomDriveLetterChooser implements MountPointChooser {
-
-	public static final int PRIORITY = 100;
+class CustomDriveLetterChooser implements MountPointChooser {
 
 	private final VaultSettings vaultSettings;
 
@@ -29,9 +27,4 @@ public class CustomDriveLetterChooser implements MountPointChooser {
 	public Optional<Path> chooseMountPoint(Volume caller) {
 		return this.vaultSettings.getWinDriveLetter().map(letter -> letter.charAt(0) + ":\\").map(Paths::get);
 	}
-
-	@Override
-	public int getPriority() {
-		return PRIORITY;
-	}
 }

+ 1 - 7
main/commons/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java

@@ -20,9 +20,7 @@ import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Optional;
 
-public class CustomMountPointChooser implements MountPointChooser {
-
-	public static final int PRIORITY = 0;
+class CustomMountPointChooser implements MountPointChooser {
 
 	private static final Logger LOG = LoggerFactory.getLogger(CustomMountPointChooser.class);
 
@@ -94,8 +92,4 @@ public class CustomMountPointChooser implements MountPointChooser {
 		}
 	}
 
-	@Override
-	public int getPriority() {
-		return PRIORITY;
-	}
 }

+ 30 - 5
main/commons/src/main/java/org/cryptomator/common/mountpoint/IrregularUnmountCleaner.java

@@ -1,20 +1,44 @@
 package org.cryptomator.common.mountpoint;
 
+import org.cryptomator.common.Environment;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.inject.Inject;
+import javax.inject.Singleton;
 import java.io.IOException;
 import java.nio.file.DirectoryNotEmptyException;
 import java.nio.file.Files;
 import java.nio.file.LinkOption;
 import java.nio.file.Path;
 import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Optional;
 
-public class IrregularUnmountCleaner {
+@Singleton
+class IrregularUnmountCleaner {
 
 	public static Logger LOG = LoggerFactory.getLogger(IrregularUnmountCleaner.class);
 
-	public static void removeIrregularUnmountDebris(Path dirContainingMountPoints) {
+	private final Optional<Path> tmpMountPointDir;
+	private volatile boolean alreadyChecked = false;
+
+	@Inject
+	public IrregularUnmountCleaner(Environment env) {
+		this.tmpMountPointDir = env.getMountPointsDir();
+	}
+
+
+	public synchronized void clearIrregularUnmountDebrisIfNeeded() {
+		if (alreadyChecked || tmpMountPointDir.isEmpty()) {
+			return; //nuthin to do
+		}
+		if (Files.exists(tmpMountPointDir.get(), LinkOption.NOFOLLOW_LINKS)) {
+			clearIrregularUnmountDebris(tmpMountPointDir.get());
+		}
+		alreadyChecked = true;
+	}
+
+	private void clearIrregularUnmountDebris(Path dirContainingMountPoints) {
 		IOException cleanupFailed = new IOException("Cleanup failed");
 
 		try {
@@ -41,11 +65,12 @@ public class IrregularUnmountCleaner {
 			}
 		} catch (IOException e) {
 			LOG.warn("Unable to perform cleanup of mountpoint dir {}.", dirContainingMountPoints, e);
+		} finally {
+			alreadyChecked = true;
 		}
-
 	}
 
-	private static void deleteEmptyDir(Path dir) throws IOException {
+	private void deleteEmptyDir(Path dir) throws IOException {
 		assert Files.isDirectory(dir, LinkOption.NOFOLLOW_LINKS);
 		try {
 			Files.delete(dir); // attempt to delete dir non-recursively (will fail, if there are contents)
@@ -54,7 +79,7 @@ public class IrregularUnmountCleaner {
 		}
 	}
 
-	private static void deleteDeadLink(Path symlink) throws IOException {
+	private void deleteDeadLink(Path symlink) throws IOException {
 		assert Files.isSymbolicLink(symlink);
 		if (Files.notExists(symlink)) { // following link: target does not exist
 			Files.delete(symlink);

+ 64 - 0
main/commons/src/main/java/org/cryptomator/common/mountpoint/MacVolumeMountChooser.java

@@ -0,0 +1,64 @@
+package org.cryptomator.common.mountpoint;
+
+import org.apache.commons.lang3.SystemUtils;
+import org.cryptomator.common.settings.VaultSettings;
+import org.cryptomator.common.vaults.Volume;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.inject.Inject;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Optional;
+
+class MacVolumeMountChooser implements MountPointChooser {
+
+	private static final Logger LOG = LoggerFactory.getLogger(MacVolumeMountChooser.class);
+	private static final int MAX_MOUNTPOINT_CREATION_RETRIES = 10;
+	private static final Path VOLUME_PATH = Path.of("/Volumes");
+
+	private final VaultSettings vaultSettings;
+
+	@Inject
+	public MacVolumeMountChooser(VaultSettings vaultSettings) {
+		this.vaultSettings = vaultSettings;
+	}
+
+	@Override
+	public boolean isApplicable(Volume caller) {
+		return SystemUtils.IS_OS_MAC;
+	}
+
+	@Override
+	public Optional<Path> chooseMountPoint(Volume caller) {
+		String basename = this.vaultSettings.mountName().get();
+		// regular
+		Path mountPoint = VOLUME_PATH.resolve(basename);
+		if (Files.notExists(mountPoint)) {
+			return Optional.of(mountPoint);
+		}
+		// with id
+		mountPoint = VOLUME_PATH.resolve(basename + " (" + vaultSettings.getId() + ")");
+		if (Files.notExists(mountPoint)) {
+			return Optional.of(mountPoint);
+		}
+		// with id and count
+		for (int i = 1; i < MAX_MOUNTPOINT_CREATION_RETRIES; i++) {
+			mountPoint = VOLUME_PATH.resolve(basename + "_(" + vaultSettings.getId() + ")_" + i);
+			if (Files.notExists(mountPoint)) {
+				return Optional.of(mountPoint);
+			}
+		}
+		LOG.error("Failed to find feasible mountpoint at /Volumes/{}_x. Giving up after {} attempts.", basename, MAX_MOUNTPOINT_CREATION_RETRIES);
+		return Optional.empty();
+	}
+
+	@Override
+	public boolean prepare(Volume caller, Path mountPoint) {
+		// https://github.com/osxfuse/osxfuse/issues/306#issuecomment-245114592:
+		// In order to allow non-admin users to mount FUSE volumes in `/Volumes`,
+		// starting with version 3.5.0, FUSE will create non-existent mount points automatically.
+		// Therefore we don't need to prepare anything.
+		return false;
+	}
+}

+ 1 - 32
main/commons/src/main/java/org/cryptomator/common/mountpoint/MountPointChooser.java

@@ -47,7 +47,7 @@ import java.util.SortedSet;
  * If the preparation succeeds {@link #cleanup(Volume, Path)} can be used after unmount to do any
  * remaining cleanup.
  */
-public interface MountPointChooser extends Comparable<MountPointChooser> {
+public interface MountPointChooser {
 
 	/**
 	 * Called by the {@link Volume} to determine whether this MountPointChooser is
@@ -135,35 +135,4 @@ public interface MountPointChooser extends Comparable<MountPointChooser> {
 		//NO-OP
 	}
 
-	/**
-	 * Called by the {@link MountPointChooserModule} to sort the available MPCs
-	 * and determine their execution order.
-	 * The priority must be defined by the developer to reflect a useful execution order.
-	 * MPCs with lower priorities will be placed at lower indices in the resulting
-	 * {@link SortedSet} and will be executed with higher probability.<br>
-	 * A specific priority <b>must not</b> be assigned to more than one MPC at a time;
-	 * the result of having two MPCs with equal priority is undefined.
-	 *
-	 * @return the priority of this MPC.
-	 */
-	int getPriority();
-
-	/**
-	 * Called by the {@link Volume} to determine the execution order of the registered MPCs.
-	 * <b>Implementations usually may not override this method.</b> This default implementation
-	 * sorts the MPCs in ascending order of their {@link #getPriority() priority.}<br>
-	 * <br>
-	 * <b>Original description:</b>
-	 * <p>{@inheritDoc}
-	 *
-	 * @implNote This default implementation sorts the MPCs in ascending order
-	 * of their {@link #getPriority() priority.}
-	 */
-	@Override
-	default int compareTo(MountPointChooser other) {
-		Preconditions.checkNotNull(other, "Other must not be null!");
-
-		//Sort by priority (ascending order)
-		return Integer.compare(this.getPriority(), other.getPriority());
-	}
 }

+ 23 - 11
main/commons/src/main/java/org/cryptomator/common/mountpoint/MountPointChooserModule.java

@@ -1,15 +1,17 @@
 package org.cryptomator.common.mountpoint;
 
-import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.Iterables;
 import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
-import dagger.multibindings.IntoSet;
+import dagger.multibindings.IntKey;
+import dagger.multibindings.IntoMap;
 import org.cryptomator.common.vaults.PerVault;
 
 import javax.inject.Named;
-import java.util.Set;
-import java.util.SortedSet;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
 
 /**
  * Dagger-Module for {@link MountPointChooser MountPointChoosers.}<br>
@@ -21,30 +23,40 @@ import java.util.SortedSet;
 public abstract class MountPointChooserModule {
 
 	@Binds
-	@IntoSet
+	@IntoMap
+	@IntKey(0)
 	@PerVault
 	public abstract MountPointChooser bindCustomMountPointChooser(CustomMountPointChooser chooser);
 
 	@Binds
-	@IntoSet
+	@IntoMap
+	@IntKey(100)
 	@PerVault
 	public abstract MountPointChooser bindCustomDriveLetterChooser(CustomDriveLetterChooser chooser);
 
 	@Binds
-	@IntoSet
+	@IntoMap
+	@IntKey(101)
+	@PerVault
+	public abstract MountPointChooser bindMacVolumeMountChooser(MacVolumeMountChooser chooser);
+
+	@Binds
+	@IntoMap
+	@IntKey(200)
 	@PerVault
 	public abstract MountPointChooser bindAvailableDriveLetterChooser(AvailableDriveLetterChooser chooser);
 
 	@Binds
-	@IntoSet
+	@IntoMap
+	@IntKey(999)
 	@PerVault
 	public abstract MountPointChooser bindTemporaryMountPointChooser(TemporaryMountPointChooser chooser);
 
 	@Provides
 	@PerVault
 	@Named("orderedMountPointChoosers")
-	public static SortedSet<MountPointChooser> provideOrderedMountPointChoosers(Set<MountPointChooser> choosers) {
-		//Sort by natural order. The natural order is defined by MountPointChooser#compareTo
-		return ImmutableSortedSet.copyOf(choosers);
+	public static Iterable<MountPointChooser> provideOrderedMountPointChoosers(Map<Integer, MountPointChooser> choosers) {
+		SortedMap<Integer, MountPointChooser> sortedChoosers = new TreeMap<>(choosers);
+		return Iterables.unmodifiableIterable(sortedChoosers.values());
 	}
 }

+ 12 - 19
main/commons/src/main/java/org/cryptomator/common/mountpoint/TemporaryMountPointChooser.java

@@ -1,6 +1,5 @@
 package org.cryptomator.common.mountpoint;
 
-import org.apache.commons.lang3.SystemUtils;
 import org.cryptomator.common.Environment;
 import org.cryptomator.common.settings.VaultSettings;
 import org.cryptomator.common.vaults.Volume;
@@ -12,23 +11,23 @@ import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.Optional;
 
-public class TemporaryMountPointChooser implements MountPointChooser {
-
-	public static final int PRIORITY = 300;
+class TemporaryMountPointChooser implements MountPointChooser {
 
 	private static final Logger LOG = LoggerFactory.getLogger(TemporaryMountPointChooser.class);
 	private static final int MAX_TMPMOUNTPOINT_CREATION_RETRIES = 10;
 
 	private final VaultSettings vaultSettings;
 	private final Environment environment;
+	private final IrregularUnmountCleaner cleaner;
+	private volatile boolean clearedDebris;
 
 	@Inject
-	public TemporaryMountPointChooser(VaultSettings vaultSettings, Environment environment) {
+	public TemporaryMountPointChooser(VaultSettings vaultSettings, Environment environment, IrregularUnmountCleaner cleaner) {
 		this.vaultSettings = vaultSettings;
 		this.environment = environment;
+		this.cleaner = cleaner;
 	}
 
 	@Override
@@ -42,9 +41,14 @@ public class TemporaryMountPointChooser implements MountPointChooser {
 
 	@Override
 	public Optional<Path> chooseMountPoint(Volume caller) {
+		assert environment.getMountPointsDir().isPresent();
+		//clean leftovers of not-regularly unmounted vaults
+		//see https://github.com/cryptomator/cryptomator/issues/1013 and https://github.com/cryptomator/cryptomator/issues/1061
+		cleaner.clearIrregularUnmountDebrisIfNeeded();
 		return this.environment.getMountPointsDir().map(this::choose);
 	}
 
+
 	private Path choose(Path parent) {
 		String basename = this.vaultSettings.mountName().get();
 		//regular
@@ -53,13 +57,13 @@ public class TemporaryMountPointChooser implements MountPointChooser {
 			return mountPoint;
 		}
 		//with id
-		mountPoint = parent.resolve(basename + " (" +vaultSettings.getId() + ")");
+		mountPoint = parent.resolve(basename + " (" + vaultSettings.getId() + ")");
 		if (Files.notExists(mountPoint)) {
 			return mountPoint;
 		}
 		//with id and count
 		for (int i = 1; i < MAX_TMPMOUNTPOINT_CREATION_RETRIES; i++) {
-			mountPoint = parent.resolve(basename + "_(" +vaultSettings.getId() + ")_"+i);
+			mountPoint = parent.resolve(basename + "_(" + vaultSettings.getId() + ")_" + i);
 			if (Files.notExists(mountPoint)) {
 				return mountPoint;
 			}
@@ -70,13 +74,6 @@ public class TemporaryMountPointChooser implements MountPointChooser {
 
 	@Override
 	public boolean prepare(Volume caller, Path mountPoint) throws InvalidMountPointException {
-		// https://github.com/osxfuse/osxfuse/issues/306#issuecomment-245114592:
-		// In order to allow non-admin users to mount FUSE volumes in `/Volumes`,
-		// starting with version 3.5.0, FUSE will create non-existent mount points automatically.
-		if (SystemUtils.IS_OS_MAC && mountPoint.getParent().equals(Paths.get("/Volumes"))) {
-			return false;
-		}
-
 		try {
 			switch (caller.getMountPointRequirement()) {
 				case PARENT_NO_MOUNT_POINT -> {
@@ -114,8 +111,4 @@ public class TemporaryMountPointChooser implements MountPointChooser {
 		}
 	}
 
-	@Override
-	public int getPriority() {
-		return PRIORITY;
-	}
 }

+ 7 - 22
main/commons/src/main/java/org/cryptomator/common/vaults/AbstractVolume.java

@@ -1,50 +1,35 @@
 package org.cryptomator.common.vaults;
 
-import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
 import org.cryptomator.common.mountpoint.InvalidMountPointException;
 import org.cryptomator.common.mountpoint.MountPointChooser;
 
 import java.nio.file.Path;
 import java.util.Optional;
-import java.util.SortedSet;
-import java.util.TreeSet;
 
 public abstract class AbstractVolume implements Volume {
 
-	private final SortedSet<MountPointChooser> choosers;
+	private final Iterable<MountPointChooser> choosers;
 
 	protected Path mountPoint;
-
-	//Cleanup
 	private boolean cleanupRequired;
 	private MountPointChooser usedChooser;
 
-	public AbstractVolume(SortedSet<MountPointChooser> choosers) {
+	public AbstractVolume(Iterable<MountPointChooser> choosers) {
 		this.choosers = choosers;
 	}
 
 	protected Path determineMountPoint() throws InvalidMountPointException {
-		SortedSet<MountPointChooser> checkedChoosers = new TreeSet<>(); //Natural order
-		for (MountPointChooser chooser : this.choosers) {
-			if (!chooser.isApplicable(this)) {
-				continue;
-			}
-
+		for (var chooser : Iterables.filter(choosers, c -> c.isApplicable(this))) {
 			Optional<Path> chosenPath = chooser.chooseMountPoint(this);
-			checkedChoosers.add(chooser); //Consider a chooser checked if it's #chooseMountPoint() method was called
-			if (chosenPath.isEmpty()) {
-				//Chooser was applicable, but couldn't find a feasible mountpoint
+			if (chosenPath.isEmpty()) { // chooser couldn't find a feasible mountpoint
 				continue;
 			}
-			this.cleanupRequired = chooser.prepare(this, chosenPath.get()); //Fail entirely if an Exception occurs
+			this.cleanupRequired = chooser.prepare(this, chosenPath.get());
 			this.usedChooser = chooser;
 			return chosenPath.get();
 		}
-		//SortedSet#stream() should return a sorted stream (that's what it's docs and the docs of #spliterator() say, even if they are not 100% clear for me.)
-		//We want to keep that order, that's why we use ImmutableSet#toImmutableSet() to collect (even if it doesn't implement SortedSet, it's docs promise use encounter ordering.)
-		String checked = Joiner.on(", ").join(checkedChoosers.stream().map((mpc) -> mpc.getClass().getTypeName()).collect(ImmutableSet.toImmutableSet()));
-		throw new InvalidMountPointException(String.format("No feasible MountPoint found! Checked %s", checked.isBlank() ? "<No applicable MPC>" : checked));
+		throw new InvalidMountPointException("No feasible MountPoint found!");
 	}
 
 	protected void cleanupMountPoint() {

+ 1 - 1
main/commons/src/main/java/org/cryptomator/common/vaults/DokanyVolume.java

@@ -28,7 +28,7 @@ public class DokanyVolume extends AbstractVolume {
 	private Mount mount;
 
 	@Inject
-	public DokanyVolume(VaultSettings vaultSettings, ExecutorService executorService, @Named("orderedMountPointChoosers") SortedSet<MountPointChooser> choosers) {
+	public DokanyVolume(VaultSettings vaultSettings, ExecutorService executorService, @Named("orderedMountPointChoosers") Iterable<MountPointChooser> choosers) {
 		super(choosers);
 		this.vaultSettings = vaultSettings;
 		this.mountFactory = new MountFactory(executorService);

+ 21 - 3
main/commons/src/main/java/org/cryptomator/common/vaults/FuseVolume.java

@@ -1,6 +1,6 @@
 package org.cryptomator.common.vaults;
 
-import com.google.common.base.Splitter;
+import com.google.common.collect.Iterators;
 import org.apache.commons.lang3.SystemUtils;
 import org.cryptomator.common.mountpoint.InvalidMountPointException;
 import org.cryptomator.common.mountpoint.MountPointChooser;
@@ -18,16 +18,20 @@ import org.slf4j.LoggerFactory;
 import javax.inject.Inject;
 import javax.inject.Named;
 import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.SortedSet;
+import java.util.regex.Pattern;
 
 public class FuseVolume extends AbstractVolume {
 
 	private static final Logger LOG = LoggerFactory.getLogger(FuseVolume.class);
+	private static final Pattern NON_WHITESPACE_OR_QUOTED = Pattern.compile("[^\\s\"']+|\"([^\"]*)\"|'([^']*)'"); // Thanks to https://stackoverflow.com/a/366532
 
 	private Mount mount;
 
 	@Inject
-	public FuseVolume(@Named("orderedMountPointChoosers") SortedSet<MountPointChooser> choosers) {
+	public FuseVolume(@Named("orderedMountPointChoosers") Iterable<MountPointChooser> choosers) {
 		super(choosers);
 	}
 
@@ -51,7 +55,21 @@ public class FuseVolume extends AbstractVolume {
 	}
 
 	private String[] splitFlags(String str) {
-		return Splitter.on(' ').splitToList(str).toArray(String[]::new);
+		List<String> flags = new ArrayList<>();
+		var matches = Iterators.peekingIterator(NON_WHITESPACE_OR_QUOTED.matcher(str).results().iterator());
+		while (matches.hasNext()) {
+			String flag = matches.next().group();
+			// check if flag is missing its argument:
+			if (flag.endsWith("=") && matches.hasNext() && matches.peek().group(1) != null) { // next is "double quoted"
+				// next is "double quoted" and flag is missing its argument
+				flag += matches.next().group(1);
+			} else if (flag.endsWith("=") && matches.hasNext() && matches.peek().group(2) != null) {
+				// next is 'single quoted' and flag is missing its argument
+				flag += matches.next().group(2);
+			}
+			flags.add(flag);
+		}
+		return flags.toArray(String[]::new);
 	}
 
 	@Override

+ 2 - 2
main/commons/src/main/java/org/cryptomator/common/vaults/VaultModule.java

@@ -101,7 +101,7 @@ public class VaultModule {
 		if (readOnly.get()) {
 			flags.append(" -ordonly");
 		}
-		flags.append(" -ovolname=").append(mountName.get());
+		flags.append(" -ovolname=").append('"').append(mountName.get()).append('"');
 		flags.append(" -oatomic_o_trunc");
 		flags.append(" -oauto_xattr");
 		flags.append(" -oauto_cache");
@@ -158,7 +158,7 @@ public class VaultModule {
 			flags.append(" -ouid=-1");
 			flags.append(" -ogid=-1");
 		}
-		flags.append(" -ovolname=").append(mountName.get());
+		flags.append(" -ovolname=").append('"').append(mountName.get()).append('"');
 		//Dokany requires this option to be set, WinFSP doesn't seem to share this peculiarity,
 		//but the option exists. Let's keep this here in case we need it.
 //		flags.append(" -oThreadCount=").append(5);

+ 1 - 11
main/ui/src/main/java/org/cryptomator/ui/launcher/UiLauncher.java

@@ -1,7 +1,5 @@
 package org.cryptomator.ui.launcher;
 
-import org.cryptomator.common.Environment;
-import org.cryptomator.common.mountpoint.IrregularUnmountCleaner;
 import org.cryptomator.common.settings.Settings;
 import org.cryptomator.common.vaults.Vault;
 import org.cryptomator.integrations.tray.TrayIntegrationProvider;
@@ -16,8 +14,6 @@ import javafx.collections.ObservableList;
 import java.awt.Desktop;
 import java.awt.SystemTray;
 import java.awt.desktop.AppReopenedListener;
-import java.nio.file.Files;
-import java.nio.file.LinkOption;
 import java.util.Collection;
 import java.util.Optional;
 
@@ -32,17 +28,15 @@ public class UiLauncher {
 	private final FxApplicationStarter fxApplicationStarter;
 	private final AppLaunchEventHandler launchEventHandler;
 	private final Optional<TrayIntegrationProvider> trayIntegration;
-	private final Environment env;
 
 	@Inject
-	public UiLauncher(Settings settings, ObservableList<Vault> vaults, TrayMenuComponent.Builder trayComponent, FxApplicationStarter fxApplicationStarter, AppLaunchEventHandler launchEventHandler, Optional<TrayIntegrationProvider> trayIntegration, Environment env) {
+	public UiLauncher(Settings settings, ObservableList<Vault> vaults, TrayMenuComponent.Builder trayComponent, FxApplicationStarter fxApplicationStarter, AppLaunchEventHandler launchEventHandler, Optional<TrayIntegrationProvider> trayIntegration) {
 		this.settings = settings;
 		this.vaults = vaults;
 		this.trayComponent = trayComponent;
 		this.fxApplicationStarter = fxApplicationStarter;
 		this.launchEventHandler = launchEventHandler;
 		this.trayIntegration = trayIntegration;
-		this.env = env;
 	}
 
 	public void launch() {
@@ -65,10 +59,6 @@ public class UiLauncher {
 		// register app reopen listener
 		Desktop.getDesktop().addAppEventListener((AppReopenedListener) e -> showMainWindowAsync(hasTrayIcon));
 
-		//clean leftovers of not-regularly unmounted vaults
-		//see https://github.com/cryptomator/cryptomator/issues/1013 and https://github.com/cryptomator/cryptomator/issues/1061
-		env.getMountPointsDir().filter(path -> Files.exists(path, LinkOption.NOFOLLOW_LINKS)).ifPresent(IrregularUnmountCleaner::removeIrregularUnmountDebris);
-
 		// auto unlock
 		Collection<Vault> vaultsToAutoUnlock = vaults.filtered(this::shouldAttemptAutoUnlock);
 		if (!vaultsToAutoUnlock.isEmpty()) {