Merge branch 'fix/1409' into hotfix/1.5.10

fixes #1409
This commit is contained in:
Sebastian Stenzel
2020-11-18 13:16:36 +01:00
14 changed files with 124 additions and 117 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -10,7 +10,7 @@ import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
public class IrregularUnmountCleaner {
class IrregularUnmountCleaner {
public static Logger LOG = LoggerFactory.getLogger(IrregularUnmountCleaner.class);

View File

@@ -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;
}
}

View File

@@ -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());
}
}

View File

@@ -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());
}
}

View File

@@ -11,19 +11,20 @@ import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
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 volatile boolean clearedDebris;
@Inject
public TemporaryMountPointChooser(VaultSettings vaultSettings, Environment environment) {
@@ -42,9 +43,24 @@ public class TemporaryMountPointChooser implements MountPointChooser {
@Override
public Optional<Path> chooseMountPoint(Volume caller) {
clearDebrisIfNeeded();
return this.environment.getMountPointsDir().map(this::choose);
}
//clean leftovers of not-regularly unmounted vaults
//see https://github.com/cryptomator/cryptomator/issues/1013 and https://github.com/cryptomator/cryptomator/issues/1061
private synchronized void clearDebrisIfNeeded() {
assert environment.getMountPointsDir().isPresent();
if (clearedDebris) {
return; // already cleared
}
Path mountPointDir = environment.getMountPointsDir().get();
if (Files.exists(mountPointDir, LinkOption.NOFOLLOW_LINKS)) {
IrregularUnmountCleaner.removeIrregularUnmountDebris(mountPointDir);
}
clearedDebris = true;
}
private Path choose(Path parent) {
String basename = this.vaultSettings.mountName().get();
//regular
@@ -53,13 +69,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 +86,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 +123,4 @@ public class TemporaryMountPointChooser implements MountPointChooser {
}
}
@Override
public int getPriority() {
return PRIORITY;
}
}

View File

@@ -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() {

View File

@@ -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);

View File

@@ -31,7 +31,7 @@ public class FuseVolume extends AbstractVolume {
private Mount mount;
@Inject
public FuseVolume(@Named("orderedMountPointChoosers") SortedSet<MountPointChooser> choosers) {
public FuseVolume(@Named("orderedMountPointChoosers") Iterable<MountPointChooser> choosers) {
super(choosers);
}

View File

@@ -1,7 +1,6 @@
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 +15,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;
@@ -65,10 +62,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()) {