diff --git a/main/commons/src/main/java/org/cryptomator/common/mountpoint/AvailableDriveLetterChooser.java b/main/commons/src/main/java/org/cryptomator/common/mountpoint/AvailableDriveLetterChooser.java index 22fc8f4af..7634601fa 100644 --- a/main/commons/src/main/java/org/cryptomator/common/mountpoint/AvailableDriveLetterChooser.java +++ b/main/commons/src/main/java/org/cryptomator/common/mountpoint/AvailableDriveLetterChooser.java @@ -1,6 +1,7 @@ package org.cryptomator.common.mountpoint; import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.common.vaults.Volume; import org.cryptomator.common.vaults.WindowsDriveLetters; import javax.inject.Inject; @@ -19,12 +20,12 @@ public class AvailableDriveLetterChooser implements MountPointChooser { } @Override - public boolean isApplicable() { + public boolean isApplicable(Volume caller) { return SystemUtils.IS_OS_WINDOWS; } @Override - public Optional chooseMountPoint() { + public Optional chooseMountPoint(Volume caller) { return this.windowsDriveLetters.getAvailableDriveLetterPath(); } diff --git a/main/commons/src/main/java/org/cryptomator/common/mountpoint/CustomDriveLetterChooser.java b/main/commons/src/main/java/org/cryptomator/common/mountpoint/CustomDriveLetterChooser.java index 0d0eae225..ac9e5b716 100644 --- a/main/commons/src/main/java/org/cryptomator/common/mountpoint/CustomDriveLetterChooser.java +++ b/main/commons/src/main/java/org/cryptomator/common/mountpoint/CustomDriveLetterChooser.java @@ -2,6 +2,7 @@ package org.cryptomator.common.mountpoint; import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.settings.VaultSettings; +import org.cryptomator.common.vaults.Volume; import javax.inject.Inject; import java.nio.file.Path; @@ -20,12 +21,12 @@ public class CustomDriveLetterChooser implements MountPointChooser { } @Override - public boolean isApplicable() { + public boolean isApplicable(Volume caller) { return SystemUtils.IS_OS_WINDOWS; } @Override - public Optional chooseMountPoint() { + public Optional chooseMountPoint(Volume caller) { return this.vaultSettings.getWinDriveLetter().map(letter -> letter.charAt(0) + ":\\").map(Paths::get); } diff --git a/main/commons/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java b/main/commons/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java index 3cad72e9d..b39baba23 100644 --- a/main/commons/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java +++ b/main/commons/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java @@ -1,6 +1,7 @@ package org.cryptomator.common.mountpoint; import org.cryptomator.common.vaults.Vault; +import org.cryptomator.common.vaults.Volume; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,18 +31,18 @@ public class CustomMountPointChooser implements MountPointChooser { } @Override - public boolean isApplicable() { + public boolean isApplicable(Volume caller) { return true; } @Override - public Optional chooseMountPoint() { + public Optional chooseMountPoint(Volume caller) { //VaultSettings#getCustomMountPath already checks whether the saved custom mountpoint should be used return this.vault.getVaultSettings().getCustomMountPath().map(Paths::get); } @Override - public boolean prepare(Path mountPoint) throws InvalidMountPointException { + public boolean prepare(Volume caller, Path mountPoint) throws InvalidMountPointException { switch (this.vault.getMountPointRequirement()) { case PARENT_NO_MOUNT_POINT -> prepareParentNoMountPoint(mountPoint); case EMPTY_MOUNT_POINT -> prepareEmptyMountPoint(mountPoint); diff --git a/main/commons/src/main/java/org/cryptomator/common/mountpoint/MountPointChooser.java b/main/commons/src/main/java/org/cryptomator/common/mountpoint/MountPointChooser.java index 662eed490..b2e0798ac 100644 --- a/main/commons/src/main/java/org/cryptomator/common/mountpoint/MountPointChooser.java +++ b/main/commons/src/main/java/org/cryptomator/common/mountpoint/MountPointChooser.java @@ -16,70 +16,76 @@ import java.util.SortedSet; * The priority must be defined by the developer to reflect a useful execution order; * the order of execution of MPCs with equal priority is undefined. * - *

MPCs are executed by a {@link Volume} in ascedning order of their priority to find - * and prepare a suitable mountpoint for the volume. The volume has access to a - * {@link SortedSet} of MPCs in this specific order, that is provided by the Module. - * The Set only contains Choosers that were deemed {@link #isApplicable() applicable} - * by the Module. + *

MPCs are executed by a {@link Volume} in ascending order of their priority + * (smaller priorities are tried first) to find and prepare a suitable mountpoint for the volume. + * The volume has access to a {@link SortedSet} of MPCs in this specific order, + * that is provided by the Module. The Set contains all available Choosers, even if they + * are not {@link #isApplicable(Volume) applicable} for the Vault/Volume. The Volume must + * check whether a MPC is applicable by invoking {@code #isApplicable(Volume)} on it + * before calling {@code #chooseMountPoint(Volume)}. * - *

At execution of a MPC {@link #chooseMountPoint()} is called to choose a mountpoint + *

At execution of a MPC {@link #chooseMountPoint(Volume)} is called to choose a mountpoint * according to the MPC's strategy. The strategy can involve reading configs, * searching the filesystem, processing user-input or similar operations. - * If {@code #chooseMountPoint()} returns a non-null path (everthing but - * {@linkplain Optional#empty()}) the MPC's {@link #prepare(Path)}-Method is called and the - * MountPoint is verfied and/or prepared. In this case no other MPC's will be called for - * this volume, even if {@code #prepare(Path)} fails. + * If {@code #chooseMountPoint(Volume)} returns a non-null path (everything but + * {@linkplain Optional#empty()}) the MPC's {@link #prepare(Volume, Path)} method is called and the + * MountPoint is verified and/or prepared. In this case no other MPC's will be called for + * this volume, even if {@code #prepare(Volume, Path)} fails. * - *

If {@code #chooseMountPoint()} yields no result, the next MPC is executed - * without first calling the {@code #prepare(Path)}-Method of the current MPC. + *

If {@code #chooseMountPoint(Volume)} yields no result, the next MPC is executed + * without first calling the {@code #prepare(Volume, Path)} method of the current MPC. * This is repeated until
*

    - *
  • either a mountpoint is returned by {@code #chooseMountPoint()} - * and {@code #prepare(Path)} succeeds or fails, ending the entire operation
  • + *
  • either a mountpoint is returned by {@code #chooseMountPoint(Volume)} + * and {@code #prepare(Volume, Path)} succeeds or fails, ending the entire operation
  • *
  • or no MPC remains and an {@link InvalidMountPointException} is thrown.
  • *
- * If the {@code #prepare(Path)}-Method of a MPC fails, the entire Mountpoint-Choosing-Operation - * is aborted and the method should do all necessary cleanup before throwing the exception. - * If the preparation succeeds {@link #cleanup(Path)} can be used after unmount to do any + * If the {@code #prepare(Volume, Path)} method of a MPC fails, the entire + * Mountpoint-Choosing-Operation is aborted and the method should do all necessary cleanup + * before throwing the exception. + * If the preparation succeeds {@link #cleanup(Volume, Path)} can be used after unmount to do any * remaining cleanup. */ public interface MountPointChooser extends Comparable { /** - * Called by the {@link MountPointChooserModule} to determine whether this MountPointChooser is - * applicable for the given Systemconfiguration. + * Called by the {@link Volume} to determine whether this MountPointChooser is + * applicable for mounting the Vault/Volume, especially with regard to the + * current system configuration and particularities of the Volume type. * - *

Developers should override this method to - * check for Systemconfigurations that are unsuitable for this MPC. + *

Developers should override this method to check for system configurations + * that are unsuitable for this MPC. * + * @param caller The Volume that is calling the method to determine applicability of the MPC * @return a boolean flag; true if applicable, else false. - * @see #chooseMountPoint() + * @see #chooseMountPoint(Volume) */ - boolean isApplicable(); + boolean isApplicable(Volume caller); /** * Called by a {@link Volume} to choose a mountpoint according to the * MountPointChoosers strategy. * - *

This method is only called for MPCs that were deemed {@link #isApplicable() applicable} - * by the {@link MountPointChooserModule MountPointChooserModule.} + *

This method must only be called for MPCs that were deemed + * {@link #isApplicable(Volume) applicable} by the {@link Volume Volume.} * Developers should override this method to find or extract a mountpoint for * the volume without preparing it. Preparation should be done by - * {@link #prepare(Path)} instead. + * {@link #prepare(Volume, Path)} instead. * Exceptions in this method should be handled gracefully and result in returning * {@link Optional#empty()} instead of throwing an exception. * + * @param caller The Volume that is calling the method to choose a mountpoint * @return the chosen path or {@link Optional#empty()} if an exception occurred * or no mountpoint could be found. - * @see #isApplicable() - * @see #prepare(Path) + * @see #isApplicable(Volume) + * @see #prepare(Volume, Path) */ - Optional chooseMountPoint(); + Optional chooseMountPoint(Volume caller); /** * Called by a {@link Volume} to prepare and/or verify the chosen mountpoint.
- * This method is only called if the {@link #chooseMountPoint()}-Method of the same - * MountPointChooser returned a path. + * This method is only called if the {@link #chooseMountPoint(Volume)} method + * of the same MountPointChooser returned a path. * *

Developers should override this method to prepare the mountpoint for * the volume and check for any obstacles that could hinder the mount operation. @@ -92,37 +98,39 @@ public interface MountPointChooser extends Comparable { * *

Often the preparation of a mountpoint involves creating files or others * actions that require cleaning up after the volume is unmounted. - * In this case developers should override the {@link #cleanup(Path)}-Method - * and return {@code true} to the volume to indicate that the - * {@code #cleanup}-Method of this MPC should be called after unmount. + * In this case developers should override the {@link #cleanup(Volume, Path)} + * method and return {@code true} to the volume to indicate that the + * {@code #cleanup} method of this MPC should be called after unmount. * *

Please note: If this method fails the entire - * Mountpoint-Choosing-Operation is aborted without calling {@link #cleanup(Path)} - * or any other MPCs. Therefore this method should do all necessary cleanup - * before throwing the exception. + * Mountpoint-Choosing-Operation is aborted without calling + * {@link #cleanup(Volume, Path)} or any other MPCs. Therefore this method should + * do all necessary cleanup before throwing the exception. * - * @param mountPoint the mountpoint chosen by {@link #chooseMountPoint()} + * @param caller The Volume that is calling the method to prepare a mountpoint + * @param mountPoint the mountpoint chosen by {@link #chooseMountPoint(Volume)} * @return a boolean flag; true if cleanup is needed, false otherwise * @throws InvalidMountPointException if the preparation fails - * @see #chooseMountPoint() - * @see #cleanup(Path) + * @see #chooseMountPoint(Volume) + * @see #cleanup(Volume, Path) */ - default boolean prepare(Path mountPoint) throws InvalidMountPointException { + default boolean prepare(Volume caller, Path mountPoint) throws InvalidMountPointException { return false; //NO-OP } /** * Called by a {@link Volume} to do any cleanup needed after unmount. * - *

This method is only called if the {@link #prepare(Path)}-Method of the same - * MountPointChooser returned {@code true}. Typically developers want to + *

This method is only called if the {@link #prepare(Volume, Path)} method + * of the same MountPointChooser returned {@code true}. Typically developers want to * delete any files created prior to mount or do similar tasks.
* Exceptions in this method should be handled gracefully. * - * @param mountPoint the mountpoint that was prepared by {@link #prepare(Path)} - * @see #prepare(Path) + * @param caller The Volume that is calling the method to cleanup the prepared mountpoint + * @param mountPoint the mountpoint that was prepared by {@link #prepare(Volume, Path)} + * @see #prepare(Volume, Path) */ - default void cleanup(Path mountPoint) { + default void cleanup(Volume caller, Path mountPoint) { //NO-OP } @@ -139,10 +147,9 @@ public interface MountPointChooser extends Comparable { int getPriority(); /** - * Called by the {@link MountPointChooserModule} to determine the execution order - * of the registered MPCs. Implementations usually should not override this - * method. This default implementation sorts the MPCs in ascending order - * of their {@link #getPriority() priority.}
+ * Called by the {@link Volume} to determine the execution order of the registered MPCs. + * Implementations usually may not override this method. This default implementation + * sorts the MPCs in ascending order of their {@link #getPriority() priority.}
*
* Original description: *

{@inheritDoc} diff --git a/main/commons/src/main/java/org/cryptomator/common/mountpoint/MountPointChooserModule.java b/main/commons/src/main/java/org/cryptomator/common/mountpoint/MountPointChooserModule.java index 3e29d8265..09789dacb 100644 --- a/main/commons/src/main/java/org/cryptomator/common/mountpoint/MountPointChooserModule.java +++ b/main/commons/src/main/java/org/cryptomator/common/mountpoint/MountPointChooserModule.java @@ -8,7 +8,6 @@ import dagger.multibindings.IntoSet; import org.cryptomator.common.vaults.PerVault; import javax.inject.Named; -import java.util.Comparator; import java.util.Set; import java.util.SortedSet; @@ -43,9 +42,9 @@ public abstract class MountPointChooserModule { @Provides @PerVault - @Named("orderedValidMountPointChoosers") - public static SortedSet provideOrderedValidMountPointChoosers(Set choosers) { - //The natural order is defined by MountPointChooser#compareTo - return choosers.stream().filter(MountPointChooser::isApplicable).collect(ImmutableSortedSet.toImmutableSortedSet(Comparator.naturalOrder())); + @Named("orderedMountPointChoosers") + public static SortedSet provideOrderedMountPointChoosers(Set choosers) { + //Sort by natural order. The natural order is defined by MountPointChooser#compareTo + return ImmutableSortedSet.copyOf(choosers); } } diff --git a/main/commons/src/main/java/org/cryptomator/common/mountpoint/TemporaryMountPointChooser.java b/main/commons/src/main/java/org/cryptomator/common/mountpoint/TemporaryMountPointChooser.java index 501c7e8f1..fa3ab5ab2 100644 --- a/main/commons/src/main/java/org/cryptomator/common/mountpoint/TemporaryMountPointChooser.java +++ b/main/commons/src/main/java/org/cryptomator/common/mountpoint/TemporaryMountPointChooser.java @@ -3,6 +3,7 @@ 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; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,7 +32,7 @@ public class TemporaryMountPointChooser implements MountPointChooser { } @Override - public boolean isApplicable() { + public boolean isApplicable(Volume caller) { if (this.environment.getMountPointsDir().isEmpty()) { LOG.warn("\"cryptomator.mountPointsDir\" is not set to a valid path!"); return false; @@ -40,7 +41,7 @@ public class TemporaryMountPointChooser implements MountPointChooser { } @Override - public Optional chooseMountPoint() { + public Optional chooseMountPoint(Volume caller) { return this.environment.getMountPointsDir().map(this::choose); } @@ -57,7 +58,7 @@ public class TemporaryMountPointChooser implements MountPointChooser { } @Override - public boolean prepare(Path mountPoint) throws InvalidMountPointException { + 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. @@ -83,7 +84,7 @@ public class TemporaryMountPointChooser implements MountPointChooser { } @Override - public void cleanup(Path mountPoint) { + public void cleanup(Volume caller, Path mountPoint) { try { Files.delete(mountPoint); LOG.debug("Successfully deleted mount point: {}", mountPoint); diff --git a/main/commons/src/main/java/org/cryptomator/common/vaults/AbstractVolume.java b/main/commons/src/main/java/org/cryptomator/common/vaults/AbstractVolume.java index 69b68c7ff..199c576db 100644 --- a/main/commons/src/main/java/org/cryptomator/common/vaults/AbstractVolume.java +++ b/main/commons/src/main/java/org/cryptomator/common/vaults/AbstractVolume.java @@ -8,6 +8,7 @@ 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 { @@ -24,25 +25,31 @@ public abstract class AbstractVolume implements Volume { } protected Path determineMountPoint() throws InvalidMountPointException { + SortedSet checkedChoosers = new TreeSet<>(); //Natural order for (MountPointChooser chooser : this.choosers) { - Optional chosenPath = chooser.chooseMountPoint(); + if(!chooser.isApplicable(this)) { + continue; + } + + Optional 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 continue; } - this.cleanupRequired = chooser.prepare(chosenPath.get()); //Fail entirely if an Exception occurs + this.cleanupRequired = chooser.prepare(this, chosenPath.get()); //Fail entirely if an Exception occurs 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 tried = Joiner.on(", ").join(this.choosers.stream().map((mpc) -> mpc.getClass().getTypeName()).collect(ImmutableSet.toImmutableSet())); - throw new InvalidMountPointException(String.format("No feasible MountPoint found! Tried %s", tried)); + 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)); } protected void cleanupMountPoint() { if (this.cleanupRequired) { - this.usedChooser.cleanup(this.mountPoint); + this.usedChooser.cleanup(this, this.mountPoint); } } diff --git a/main/commons/src/main/java/org/cryptomator/common/vaults/DokanyVolume.java b/main/commons/src/main/java/org/cryptomator/common/vaults/DokanyVolume.java index bafecdac9..fce231e40 100644 --- a/main/commons/src/main/java/org/cryptomator/common/vaults/DokanyVolume.java +++ b/main/commons/src/main/java/org/cryptomator/common/vaults/DokanyVolume.java @@ -27,7 +27,7 @@ public class DokanyVolume extends AbstractVolume { private Mount mount; @Inject - public DokanyVolume(VaultSettings vaultSettings, ExecutorService executorService, @Named("orderedValidMountPointChoosers") SortedSet choosers) { + public DokanyVolume(VaultSettings vaultSettings, ExecutorService executorService, @Named("orderedMountPointChoosers") SortedSet choosers) { super(choosers); this.vaultSettings = vaultSettings; this.mountFactory = new MountFactory(executorService); diff --git a/main/commons/src/main/java/org/cryptomator/common/vaults/FuseVolume.java b/main/commons/src/main/java/org/cryptomator/common/vaults/FuseVolume.java index 400cbc379..895e2100a 100644 --- a/main/commons/src/main/java/org/cryptomator/common/vaults/FuseVolume.java +++ b/main/commons/src/main/java/org/cryptomator/common/vaults/FuseVolume.java @@ -26,7 +26,7 @@ public class FuseVolume extends AbstractVolume { private Mount mount; @Inject - public FuseVolume(@Named("orderedValidMountPointChoosers") SortedSet choosers) { + public FuseVolume(@Named("orderedMountPointChoosers") SortedSet choosers) { super(choosers); }