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 6b4264547..4b54076bd 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 @@ -69,7 +69,7 @@ public class DokanyVolume implements Volume { } else { //auto assign drive letter if (!windowsDriveLetters.getAvailableDriveLetters().isEmpty()) { - return windowsDriveLetters.getAvailableDriveLetters().iterator().next(); + return Path.of(windowsDriveLetters.getAvailableDriveLetters().iterator().next() + ":\\"); } else { //TODO: Error Handling throw new VolumeException("No free drive letter available."); diff --git a/main/commons/src/main/java/org/cryptomator/common/vaults/WindowsDriveLetters.java b/main/commons/src/main/java/org/cryptomator/common/vaults/WindowsDriveLetters.java index dd7f94fe9..be4b00fd6 100644 --- a/main/commons/src/main/java/org/cryptomator/common/vaults/WindowsDriveLetters.java +++ b/main/commons/src/main/java/org/cryptomator/common/vaults/WindowsDriveLetters.java @@ -5,6 +5,7 @@ *******************************************************************************/ package org.cryptomator.common.vaults; +import com.google.common.collect.Sets; import org.apache.commons.lang3.SystemUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -14,7 +15,6 @@ import javax.inject.Singleton; import java.nio.file.FileSystems; import java.nio.file.Path; import java.util.Set; -import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.StreamSupport; @@ -23,11 +23,11 @@ import java.util.stream.StreamSupport; public final class WindowsDriveLetters { private static final Logger LOG = LoggerFactory.getLogger(WindowsDriveLetters.class); - private static final Set A_TO_Z; + private static final Set A_TO_Z; static { try (IntStream stream = IntStream.rangeClosed('A', 'Z')) { - A_TO_Z = stream.mapToObj(i -> Path.of(((char) i)+":\\")).collect(Collectors.toSet()); + A_TO_Z = stream.mapToObj(i -> String.valueOf((char) i)).collect(Collectors.toSet()); } } @@ -35,20 +35,22 @@ public final class WindowsDriveLetters { public WindowsDriveLetters() { } - public Set getOccupiedDriveLetters() { + public Set getAllDriveLetters() { + return A_TO_Z; + } + + public Set getOccupiedDriveLetters() { if (!SystemUtils.IS_OS_WINDOWS) { LOG.warn("Attempted to get occupied drive letters on non-Windows machine."); return Set.of(); } else { Iterable rootDirs = FileSystems.getDefault().getRootDirectories(); - return StreamSupport.stream(rootDirs.spliterator(), false).collect(Collectors.toSet()); + return StreamSupport.stream(rootDirs.spliterator(), false).map(Path::toString).collect(Collectors.toSet()); } } - public Set getAvailableDriveLetters() { - Set occupiedDriveLetters = getOccupiedDriveLetters(); - Predicate isOccupiedDriveLetter = occupiedDriveLetters::contains; - return A_TO_Z.stream().filter(isOccupiedDriveLetter.negate()).collect(Collectors.toSet()); + public Set getAvailableDriveLetters() { + return Sets.difference(A_TO_Z, getOccupiedDriveLetters()); } } diff --git a/main/ui/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java b/main/ui/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java index aadf9c939..cdd2934aa 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java @@ -1,14 +1,18 @@ package org.cryptomator.ui.vaultoptions; +import com.google.common.base.Strings; +import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.StringProperty; +import javafx.beans.value.ObservableValue; import javafx.fxml.FXML; import javafx.scene.control.CheckBox; import javafx.scene.control.ChoiceBox; import javafx.scene.control.RadioButton; import javafx.scene.control.TextField; +import javafx.scene.control.Toggle; import javafx.scene.control.ToggleGroup; import javafx.stage.DirectoryChooser; import javafx.stage.Stage; @@ -23,7 +27,6 @@ import org.cryptomator.ui.common.FxController; import javax.inject.Inject; import java.io.File; import java.nio.file.Path; -import java.util.Comparator; import java.util.ResourceBundle; import java.util.Set; @@ -36,15 +39,15 @@ public class MountOptionsController implements FxController { private final BooleanBinding adapterIsDokan; private final WindowsDriveLetters windowsDriveLetters; private final ResourceBundle resourceBundle; - private final ToggleGroup toggleGroup; public TextField driveName; public CheckBox readOnlyCheckbox; public CheckBox customMountFlagsCheckbox; public TextField mountFlags; - public RadioButton automaticDriveLetter; - public RadioButton specificDriveLetter; - public RadioButton specificDirectory; - public ChoiceBox driveLetterSelection; + public ToggleGroup mountPoint; + public RadioButton mountPointAuto; + public RadioButton mountPointWinDriveLetter; + public RadioButton mountPointCustomDir; + public ChoiceBox driveLetterSelection; @Inject MountOptionsController(@VaultOptionsWindow Stage window, @VaultOptionsWindow Vault vault, Settings settings, WindowsDriveLetters windowsDriveLetters, ResourceBundle resourceBundle) { @@ -53,16 +56,18 @@ public class MountOptionsController implements FxController { this.adapterIsDokan = settings.preferredVolumeImpl().isEqualTo(VolumeImpl.DOKANY); this.windowsDriveLetters = windowsDriveLetters; this.resourceBundle = resourceBundle; - this.toggleGroup = new ToggleGroup(); } @FXML public void initialize() { driveName.textProperty().bindBidirectional(vault.getVaultSettings().mountName()); + + // readonly: readOnlyCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().usesReadOnlyMode()); - mountFlags.disableProperty().bind(customMountFlagsCheckbox.selectedProperty().not()); readOnlyCheckbox.disableProperty().bind(customMountFlagsCheckbox.selectedProperty()); + // custom mount flags: + mountFlags.disableProperty().bind(customMountFlagsCheckbox.selectedProperty().not()); customMountFlagsCheckbox.setSelected(vault.isHavingCustomMountFlags()); if (vault.isHavingCustomMountFlags()) { mountFlags.textProperty().bindBidirectional(vault.getVaultSettings().mountFlags()); @@ -71,20 +76,24 @@ public class MountOptionsController implements FxController { mountFlags.textProperty().bind(vault.defaultMountFlagsProperty()); } - toggleGroup.getToggles().addAll(automaticDriveLetter, specificDriveLetter, specificDirectory); - initDriveLetterSelection(); - - //TODO: set the toggleGroup correctly at init - } - - private void initDriveLetterSelection() { - driveLetterSelection.setConverter(new WinDriveLetterLabelConverter()); - Set freeLetters = windowsDriveLetters.getAvailableDriveLetters(); - driveLetterSelection.getItems().addAll(freeLetters); - driveLetterSelection.getItems().sort(new WinDriveLetterComparator()); - chooseSelectedDriveLetter(); - //TODO: check if we should write only the letter or the path to the settings!! - driveLetterSelection.getSelectionModel().selectedItemProperty().addListener(p -> vault.getVaultSettings().winDriveLetter().set(p.toString())); + // mount point options: + mountPoint.selectedToggleProperty().addListener(this::toggleMountPoint); + driveLetterSelection.getItems().addAll(windowsDriveLetters.getAllDriveLetters()); + driveLetterSelection.setConverter(new WinDriveLetterLabelConverter(windowsDriveLetters)); + driveLetterSelection.setValue(vault.getVaultSettings().winDriveLetter().get()); + vault.getVaultSettings().usesIndividualMountPath().bind(mountPoint.selectedToggleProperty().isEqualTo(mountPointCustomDir)); + vault.getVaultSettings().winDriveLetter().bind( // + Bindings.when(mountPoint.selectedToggleProperty().isEqualTo(mountPointWinDriveLetter)) // + .then(driveLetterSelection.getSelectionModel().selectedItemProperty()) // + .otherwise((String) null) // + ); + if (vault.getVaultSettings().usesIndividualMountPath().get()) { + mountPoint.selectToggle(mountPointCustomDir); + } else if (!Strings.isNullOrEmpty(vault.getVaultSettings().winDriveLetter().get())) { + mountPoint.selectToggle(mountPointWinDriveLetter); + } else { + mountPoint.selectToggle(mountPointAuto); + } } @FXML @@ -102,36 +111,7 @@ public class MountOptionsController implements FxController { } @FXML - public void changeMountPointForWindows() { - assert osIsWindows.get(); - if (specificDriveLetter.isSelected()) { - vault.getVaultSettings().usesIndividualMountPath().set(true); - vault.getVaultSettings().winDriveLetter().set(driveLetterSelection.getSelectionModel().getSelectedItem().toString()); - vault.getVaultSettings().individualMountPath().set(null); - } else if (specificDirectory.isSelected()) { - final File file = chooseDirectory(); - if (file != null) { - //TODO: should we check wether the directory is empty or not? - vault.getVaultSettings().usesIndividualMountPath().set(true); - vault.getVaultSettings().individualMountPath().set(file.getAbsolutePath()); - vault.getVaultSettings().winDriveLetter().set(null); - } else { - //NO-OP - //TODO: deduplicate code - toggleGroup.selectToggle(automaticDriveLetter); - vault.getVaultSettings().usesIndividualMountPath().set(false); - vault.getVaultSettings().winDriveLetter().set(null); - vault.getVaultSettings().individualMountPath().set(null); - } - } else { - //set property - vault.getVaultSettings().usesIndividualMountPath().set(false); - vault.getVaultSettings().winDriveLetter().set(null); - vault.getVaultSettings().individualMountPath().set(null); - } - } - - private File chooseDirectory() { + private void chooseCustomMountPoint() { DirectoryChooser directoryChooser = new DirectoryChooser(); directoryChooser.setTitle(resourceBundle.getString("vaultOptions.mount.winDirChooser")); try { @@ -139,65 +119,43 @@ public class MountOptionsController implements FxController { } catch (Exception e) { //NO-OP } - return directoryChooser.showDialog(window); + File file = directoryChooser.showDialog(window); + if (file != null) { + vault.getVaultSettings().individualMountPath().set(file.getAbsolutePath()); + } else { + vault.getVaultSettings().individualMountPath().set(null); + } + } + + private void toggleMountPoint(@SuppressWarnings("unused") ObservableValue observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) { + if (mountPointCustomDir.equals(newValue) && Strings.isNullOrEmpty(vault.getVaultSettings().individualMountPath().get())) { + chooseCustomMountPoint(); + } } /** * Converts 'C' to "C:" to translate between model and GUI. */ - private class WinDriveLetterLabelConverter extends StringConverter { + private static class WinDriveLetterLabelConverter extends StringConverter { + + private final Set occupiedDriveLetters; + + WinDriveLetterLabelConverter(WindowsDriveLetters windowsDriveLetters) { + this.occupiedDriveLetters = windowsDriveLetters.getOccupiedDriveLetters(); + } @Override - public String toString(Path root) { - if (root == null) { - //TODO: none drive letter is selected - return ""; - } else if (root.endsWith("occupied")) { - return root.getRoot().toString().substring(0, 1) + " (" + resourceBundle.getString("vaultOptions.mount.winDriveLetterOccupied") + ")"; + public String toString(String driveLetter) { + if (occupiedDriveLetters.contains(driveLetter)) { + return driveLetter + ": (occupied)"; // TODO localize? } else { - return root.toString().substring(0, 1); + return driveLetter + ":"; } } @Override - public Path fromString(String string) { - return Path.of(string); - } - - } - - /** - * Natural sorting of ASCII letters, but null always on first, as this is "auto-assign". - */ - private static class WinDriveLetterComparator implements Comparator { - - @Override - public int compare(Path c1, Path c2) { - if (c1 == null) { - return -1; - } else if (c2 == null) { - return 1; - } else { - return c1.compareTo(c2); - } - } - } - - private void chooseSelectedDriveLetter() { - assert SystemUtils.IS_OS_WINDOWS; - // if the vault prefers a drive letter, that is currently occupied, this is our last chance to reset this: - if (vault.getVaultSettings().winDriveLetter().isNotEmpty().get()) { - final Path pickedRoot = Path.of(vault.getVaultSettings().winDriveLetter().get()); - if (windowsDriveLetters.getOccupiedDriveLetters().contains(pickedRoot)) { - Path alteredPath = pickedRoot.resolve("occupied"); - driveLetterSelection.getItems().add(alteredPath); - driveLetterSelection.getSelectionModel().select(alteredPath); - } else { - driveLetterSelection.getSelectionModel().select(pickedRoot); - } - } else { - // first option is known to be 'auto-assign' due to #WinDriveLetterComparator. - driveLetterSelection.getSelectionModel().selectFirst(); + public String fromString(String string) { + throw new UnsupportedOperationException(); } } @@ -220,11 +178,11 @@ public class MountOptionsController implements FxController { return adapterIsDokan.get(); } - public StringProperty customMountPathProperty(){ + public StringProperty customMountPathProperty() { return vault.getVaultSettings().individualMountPath(); } - public String getCustomMountPath(){ + public String getCustomMountPath() { return vault.getVaultSettings().individualMountPath().get(); } diff --git a/main/ui/src/main/resources/fxml/vault_options_mount.fxml b/main/ui/src/main/resources/fxml/vault_options_mount.fxml index ea2859019..557798e92 100644 --- a/main/ui/src/main/resources/fxml/vault_options_mount.fxml +++ b/main/ui/src/main/resources/fxml/vault_options_mount.fxml @@ -1,19 +1,25 @@ + + + + + + @@ -34,23 +40,25 @@ - - + - - - - - - + + + + - - + + - + - + +