Prechádzať zdrojové kódy

integration of the fuseNioAdapter, including all the gui (controller & fxml) and settings wiring. english localization fitted and enriched for fuse

infeo 7 rokov pred
rodič
commit
d170e87c1b

+ 14 - 0
main/commons/src/main/java/org/cryptomator/common/settings/Settings.java

@@ -30,6 +30,8 @@ public class Settings {
 	public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
 	public static final String DEFAULT_GVFS_SCHEME = "dav";
 	public static final boolean DEFAULT_DEBUG_MODE = false;
+	public static final String DEFAULT_DEFAULT_MOUNT_DIR = System.getProperty("user.home");
+	public static final String DEFAULT_NIO_ADAPTER = "WEBDAV";
 
 	private final ObservableList<VaultSettings> directories = FXCollections.observableArrayList(VaultSettings::observables);
 	private final BooleanProperty checkForUpdates = new SimpleBooleanProperty(DEFAULT_CHECK_FOR_UDPATES);
@@ -37,6 +39,9 @@ public class Settings {
 	private final IntegerProperty numTrayNotifications = new SimpleIntegerProperty(DEFAULT_NUM_TRAY_NOTIFICATIONS);
 	private final StringProperty preferredGvfsScheme = new SimpleStringProperty(DEFAULT_GVFS_SCHEME);
 	private final BooleanProperty debugMode = new SimpleBooleanProperty(DEFAULT_DEBUG_MODE);
+	private final StringProperty nioAdapterImpl = new SimpleStringProperty(DEFAULT_NIO_ADAPTER);
+	private final StringProperty defaultMountDir = new SimpleStringProperty(DEFAULT_DEFAULT_MOUNT_DIR);
+
 	private Consumer<Settings> saveCmd;
 
 	/**
@@ -49,6 +54,8 @@ public class Settings {
 		numTrayNotifications.addListener(this::somethingChanged);
 		preferredGvfsScheme.addListener(this::somethingChanged);
 		debugMode.addListener(this::somethingChanged);
+		nioAdapterImpl.addListener(this::somethingChanged);
+		defaultMountDir.addListener(this::somethingChanged);
 	}
 
 	void setSaveCmd(Consumer<Settings> saveCmd) {
@@ -91,4 +98,11 @@ public class Settings {
 		return debugMode;
 	}
 
+	public StringProperty usedNioAdapterImpl() {
+		return nioAdapterImpl;
+	}
+
+	public StringProperty defaultMountDir() {
+		return defaultMountDir;
+	}
 }

+ 8 - 0
main/commons/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java

@@ -33,6 +33,8 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
 		out.name("numTrayNotifications").value(value.numTrayNotifications().get());
 		out.name("preferredGvfsScheme").value(value.preferredGvfsScheme().get());
 		out.name("debugMode").value(value.debugMode().get());
+		out.name("nioAdapterImpl").value(value.usedNioAdapterImpl().get());
+		out.name("defaultMountDir").value(value.defaultMountDir().get());
 		out.endObject();
 	}
 
@@ -70,6 +72,12 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
 			case "debugMode":
 				settings.debugMode().set(in.nextBoolean());
 				break;
+			case "nioAdapterImpl":
+				settings.usedNioAdapterImpl().set(in.nextString());
+				break;
+			case "defaultMountDir":
+				settings.defaultMountDir().set(in.nextString());
+				break;
 			default:
 				LOG.warn("Unsupported vault setting found in JSON: " + name);
 				in.skipValue();

+ 5 - 0
main/commons/src/main/java/org/cryptomator/common/settings/VaultSettings.java

@@ -36,6 +36,7 @@ public class VaultSettings {
 	private final BooleanProperty unlockAfterStartup = new SimpleBooleanProperty(DEFAULT_UNLOCK_AFTER_STARTUP);
 	private final BooleanProperty mountAfterUnlock = new SimpleBooleanProperty(DEFAULT_MOUNT_AFTER_UNLOCK);
 	private final BooleanProperty revealAfterMount = new SimpleBooleanProperty(DEFAULT_REAVEAL_AFTER_MOUNT);
+	private final StringProperty mountPath = new SimpleStringProperty(Settings.DEFAULT_DEFAULT_MOUNT_DIR);
 
 	public VaultSettings(String id) {
 		this.id = Objects.requireNonNull(id);
@@ -123,6 +124,10 @@ public class VaultSettings {
 		return revealAfterMount;
 	}
 
+	public StringProperty mountPath() {
+		return mountPath;
+	}
+
 	/* Hashcode/Equals */
 
 	@Override

+ 4 - 0
main/commons/src/main/java/org/cryptomator/common/settings/VaultSettingsJsonAdapter.java

@@ -34,6 +34,7 @@ class VaultSettingsJsonAdapter {
 		String id = null;
 		String path = null;
 		String mountName = null;
+		String mountPath = null;
 		String winDriveLetter = null;
 		boolean unlockAfterStartup = VaultSettings.DEFAULT_UNLOCK_AFTER_STARTUP;
 		boolean mountAfterUnlock = VaultSettings.DEFAULT_MOUNT_AFTER_UNLOCK;
@@ -64,6 +65,8 @@ class VaultSettingsJsonAdapter {
 			case "revealAfterMount":
 				revealAfterMount = in.nextBoolean();
 				break;
+			case "mountPath":
+				mountPath = in.nextString();
 			default:
 				LOG.warn("Unsupported vault setting found in JSON: " + name);
 				in.skipValue();
@@ -78,6 +81,7 @@ class VaultSettingsJsonAdapter {
 		vaultSettings.unlockAfterStartup().set(unlockAfterStartup);
 		vaultSettings.mountAfterUnlock().set(mountAfterUnlock);
 		vaultSettings.revealAfterMount().set(revealAfterMount);
+		vaultSettings.mountPath().set(mountPath);
 		return vaultSettings;
 	}
 

+ 7 - 1
main/pom.xml

@@ -28,7 +28,8 @@
 		<cryptomator.cryptofs.version>1.4.5</cryptomator.cryptofs.version>
 		<cryptomator.webdav.version>1.0.3</cryptomator.webdav.version>
 		<cryptomator.jni.version>1.0.2</cryptomator.jni.version>
-		
+		<cryptomator.fuse.version>0.1.1-SNAPSHOT</cryptomator.fuse.version>
+
 		<commons-io.version>2.5</commons-io.version>
 		<commons-lang3.version>3.6</commons-lang3.version>
 		<httpclient.version>4.5.3</httpclient.version>
@@ -96,6 +97,11 @@
 				<artifactId>cryptofs</artifactId>
 				<version>${cryptomator.cryptofs.version}</version>
 			</dependency>
+			<dependency>
+				<groupId>org.cryptomator</groupId>
+				<artifactId>fuse-nio-adapter</artifactId>
+				<version>${cryptomator.fuse.version}</version>
+			</dependency>
 			<dependency>
 				<groupId>org.cryptomator</groupId>
 				<artifactId>webdav-nio-adapter</artifactId>

+ 5 - 1
main/ui/pom.xml

@@ -30,7 +30,11 @@
 			<groupId>org.cryptomator</groupId>
 			<artifactId>keychain</artifactId>
 		</dependency>
-		
+		<dependency>
+			<groupId>org.cryptomator</groupId>
+			<artifactId>fuse-nio-adapter</artifactId>
+		</dependency>
+
 		<!-- CryptoLib -->
 		<dependency>
 			<groupId>org.cryptomator</groupId>

+ 95 - 3
main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java

@@ -8,15 +8,22 @@
  ******************************************************************************/
 package org.cryptomator.ui.controllers;
 
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.Optional;
 
 import javax.inject.Inject;
 import javax.inject.Named;
 import javax.inject.Singleton;
 
+import javafx.beans.Observable;
+import javafx.beans.value.ObservableValue;
+import javafx.scene.layout.GridPane;
 import org.apache.commons.lang3.SystemUtils;
 import org.cryptomator.common.settings.Settings;
 import org.cryptomator.ui.l10n.Localization;
+import org.cryptomator.ui.model.NioAdapterImpl;
 
 import com.google.common.base.CharMatcher;
 import com.google.common.base.Strings;
@@ -52,6 +59,15 @@ public class SettingsController implements ViewController {
 	@FXML
 	private CheckBox checkForUpdatesCheckbox;
 
+	@FXML
+	private GridPane webdavNioAdapter;
+
+	@FXML
+	private GridPane fuseNioAdapter;
+
+	@FXML
+	private Label portFieldLabel;
+
 	@FXML
 	private TextField portField;
 
@@ -64,9 +80,24 @@ public class SettingsController implements ViewController {
 	@FXML
 	private Label prefGvfsSchemeLabel;
 
+	@FXML
+	private Label defaultMountDirLabel;
+
+	@FXML
+	private TextField defaultMountDir;
+
+	@FXML
+	private Button changeDefaultMountDirButton;
+
 	@FXML
 	private ChoiceBox<String> prefGvfsScheme;
 
+	@FXML
+	private Label nioAdapterLabel;
+
+	@FXML
+	private ChoiceBox<String> nioAdapter;
+
 	@FXML
 	private CheckBox debugModeCheckbox;
 
@@ -75,25 +106,69 @@ public class SettingsController implements ViewController {
 
 	@Override
 	public void initialize() {
+		versionLabel.setText(String.format(localization.getString("settings.version.label"), applicationVersion.orElse("SNAPSHOT")));
 		checkForUpdatesCheckbox.setDisable(areUpdatesManagedExternally());
 		checkForUpdatesCheckbox.setSelected(settings.checkForUpdates().get() && !areUpdatesManagedExternally());
+
+		//NIOADAPTER
+		nioAdapter.getItems().addAll(getSupportedAdapters());
+		nioAdapter.setValue(settings.usedNioAdapterImpl().get());
+		nioAdapter.setVisible(true);
+		nioAdapter.getSelectionModel().selectedItemProperty().addListener( (ObservableValue<? extends String> observable, String oldVal, String newVal) -> changeNioView(newVal) );
+
+
+		//WEBDAV
+		webdavNioAdapter.managedProperty().bind(webdavNioAdapter.visibleProperty());
+		prefGvfsScheme.managedProperty().bind(webdavNioAdapter.visibleProperty());
+		prefGvfsSchemeLabel.managedProperty().bind(webdavNioAdapter.visibleProperty());
+		portFieldLabel.managedProperty().bind(webdavNioAdapter.visibleProperty());
+		changePortButton.managedProperty().bind(webdavNioAdapter.visibleProperty());
+		portField.managedProperty().bind(webdavNioAdapter.visibleProperty());
 		portField.setText(String.valueOf(settings.port().intValue()));
 		portField.addEventFilter(KeyEvent.KEY_TYPED, this::filterNumericKeyEvents);
 		changePortButton.visibleProperty().bind(settings.port().asString().isNotEqualTo(portField.textProperty()));
 		changePortButton.disableProperty().bind(Bindings.createBooleanBinding(this::isPortValid, portField.textProperty()).not());
-		versionLabel.setText(String.format(localization.getString("settings.version.label"), applicationVersion.orElse("SNAPSHOT")));
-		prefGvfsSchemeLabel.setVisible(SystemUtils.IS_OS_LINUX);
-		prefGvfsScheme.setVisible(SystemUtils.IS_OS_LINUX);
 		prefGvfsScheme.getItems().add("dav");
 		prefGvfsScheme.getItems().add("webdav");
 		prefGvfsScheme.setValue(settings.preferredGvfsScheme().get());
+		prefGvfsSchemeLabel.setVisible(SystemUtils.IS_OS_LINUX);
+		prefGvfsScheme.setVisible(SystemUtils.IS_OS_LINUX);
+
+		//FUSE
+		fuseNioAdapter.managedProperty().bind(fuseNioAdapter.visibleProperty());
+		defaultMountDirLabel.managedProperty().bind(fuseNioAdapter.visibleProperty());
+		defaultMountDir.managedProperty().bind(fuseNioAdapter.visibleProperty());
+		defaultMountDirLabel.setVisible(SystemUtils.IS_OS_LINUX);
+		defaultMountDir.setVisible(SystemUtils.IS_OS_LINUX);
+		defaultMountDir.setText(String.valueOf(settings.defaultMountDir().get()));
+		changeDefaultMountDirButton.setVisible(false);
+		changeDefaultMountDirButton.visibleProperty().bind(
+				Bindings.createBooleanBinding(
+						() ->  fuseNioAdapter.visibleProperty().get() && settings.defaultMountDir().isNotEqualTo(Strings.nullToEmpty(defaultMountDir.getText())).get() ,
+						fuseNioAdapter.visibleProperty(),
+						settings.defaultMountDir().isNotEqualTo(defaultMountDir.textProperty())
+						)
+		);
+		changeDefaultMountDirButton.disableProperty().bind(Bindings.createBooleanBinding(this::isDirValid, defaultMountDir.textProperty()).not());
+
 		debugModeCheckbox.setSelected(settings.debugMode().get());
 
 		settings.checkForUpdates().bind(checkForUpdatesCheckbox.selectedProperty());
 		settings.preferredGvfsScheme().bind(prefGvfsScheme.valueProperty());
+		settings.usedNioAdapterImpl().bind(nioAdapter.valueProperty());
 		settings.debugMode().bind(debugModeCheckbox.selectedProperty());
 	}
 
+	//TODO: how to implement this?
+	private String [] getSupportedAdapters() {
+		return new String[]{NioAdapterImpl.FUSE.name(), NioAdapterImpl.WEBDAV.name()};
+	}
+
+	private void changeNioView(String newVal) {
+		fuseNioAdapter.setVisible(newVal.equalsIgnoreCase("FUSE"));
+		webdavNioAdapter.setVisible(newVal.equalsIgnoreCase("WEBDAV"));
+	}
+
 	@Override
 	public Parent getRoot() {
 		return root;
@@ -110,6 +185,23 @@ public class SettingsController implements ViewController {
 		}
 	}
 
+	@FXML
+	private void changeDefaultMountDir(ActionEvent event){
+		assert isDirValid() : "Error. Not a valid Directory. Does and exist and do you have the needed Rights?";
+		settings.defaultMountDir().set(defaultMountDir.getText());
+	}
+
+	private boolean isDirValid(){
+		if(SystemUtils.IS_OS_WINDOWS){
+			//this should never ever happen!
+			return false;
+		}
+		else{
+			Path path = Paths.get(defaultMountDir.getText());
+			return Files.isDirectory(path) && Files.isReadable(path) && Files.isWritable(path) && Files.isExecutable(path);
+		}
+	}
+
 	private boolean isPortValid() {
 		try {
 			int port = Integer.parseInt(portField.getText());

+ 54 - 0
main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java

@@ -8,7 +8,9 @@
  ******************************************************************************/
 package org.cryptomator.ui.controllers;
 
+import java.io.File;
 import java.io.IOException;
+import java.nio.file.*;
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.Objects;
@@ -16,6 +18,8 @@ import java.util.Optional;
 
 import javax.inject.Inject;
 
+import javafx.beans.Observable;
+import javafx.beans.binding.Bindings;
 import org.apache.commons.lang3.CharUtils;
 import org.apache.commons.lang3.SystemUtils;
 import org.cryptomator.common.settings.VaultSettings;
@@ -112,6 +116,15 @@ public class UnlockController implements ViewController {
 	@FXML
 	private ChoiceBox<Character> winDriveLetter;
 
+	@FXML
+	private Label mountPathLabel;
+
+	@FXML
+	private TextField mountPath;
+
+	@FXML
+	private Button changeMountPathButton;
+
 	@FXML
 	private ProgressIndicator progressIndicator;
 
@@ -142,12 +155,26 @@ public class UnlockController implements ViewController {
 		unlockAfterStartup.disableProperty().bind(savePassword.disabledProperty().or(savePassword.selectedProperty().not()));
 		if (SystemUtils.IS_OS_WINDOWS) {
 			winDriveLetter.setConverter(new WinDriveLetterLabelConverter());
+			mountPathLabel.setVisible(false);
+			mountPathLabel.setManaged(false);
+			mountPath.setVisible(false);
+			mountPath.setManaged(false);
+			changeMountPathButton.setVisible(false);
+			changeMountPathButton.setManaged(false);
 		} else {
 			winDriveLetterLabel.setVisible(false);
 			winDriveLetterLabel.setManaged(false);
 			winDriveLetter.setVisible(false);
 			winDriveLetter.setManaged(false);
 		}
+		changeMountPathButton.disableProperty().bind(Bindings.createBooleanBinding(this::isDirVaild, mountPath.textProperty()).not());
+		changeMountPathButton.visibleProperty().bind(
+				Bindings.createBooleanBinding(
+						()-> mountPathLabel.isVisible() && mountPath.textProperty().isEmpty().not().get(),
+						mountPathLabel.visibleProperty(),
+						mountPath.textProperty().isEmpty().not()
+				)
+		);
 	}
 
 	@Override
@@ -208,6 +235,7 @@ public class UnlockController implements ViewController {
 		vaultSubs = vaultSubs.and(EasyBind.subscribe(unlockAfterStartup.selectedProperty(), settings.unlockAfterStartup()::set));
 		vaultSubs = vaultSubs.and(EasyBind.subscribe(mountAfterUnlock.selectedProperty(), settings.mountAfterUnlock()::set));
 		vaultSubs = vaultSubs.and(EasyBind.subscribe(revealAfterMount.selectedProperty(), settings.revealAfterMount()::set));
+
 	}
 
 	// ****************************************
@@ -233,6 +261,32 @@ public class UnlockController implements ViewController {
 		}
 	}
 
+	@FXML
+	private void didClickchangeMountPathButton(ActionEvent event){
+		assert isDirVaild();
+		vault.setMountPath(mountPath.getText());
+	}
+
+	private boolean isDirVaild(){
+		try{
+			if(!mountPath.textProperty().isEmpty().get()){
+				Path p = Paths.get(mountPath.textProperty().get());
+				return Files.isDirectory(p) && Files.isReadable(p) && Files.isWritable(p) && Files.isExecutable(p);
+			}
+			else{
+				//default path will be taken
+				return true;
+			}
+
+		}
+		catch (InvalidPathException e){
+			LOG.info("Invalid path");
+			return false;
+		}
+	}
+
+
+
 	private void filterAlphanumericKeyEvents(KeyEvent t) {
 		if (!Strings.isNullOrEmpty(t.getCharacter()) && !ALPHA_NUMERIC_MATCHER.matchesAllOf(t.getCharacter())) {
 			t.consume();

+ 0 - 1
main/ui/src/main/java/org/cryptomator/ui/model/AutoUnlocker.java

@@ -18,7 +18,6 @@ import javax.inject.Inject;
 import javax.inject.Singleton;
 
 import org.cryptomator.cryptolib.api.CryptoException;
-import org.cryptomator.frontend.webdav.mount.Mounter.CommandFailedException;
 import org.cryptomator.keychain.KeychainAccess;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;

+ 225 - 0
main/ui/src/main/java/org/cryptomator/ui/model/FuseNioAdapter.java

@@ -0,0 +1,225 @@
+package org.cryptomator.ui.model;
+
+import org.cryptomator.common.settings.Settings;
+import org.cryptomator.common.settings.VaultSettings;
+import org.cryptomator.cryptofs.CryptoFileSystem;
+import org.cryptomator.frontend.fuse.AdapterFactory;
+
+import org.apache.commons.lang3.SystemUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.inject.Inject;
+import java.io.IOException;
+import java.nio.file.DirectoryNotEmptyException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Scanner;
+import java.util.concurrent.TimeUnit;
+
+@VaultModule.PerVault
+public class FuseNioAdapter implements NioAdapter {
+
+	private static final Logger LOG = LoggerFactory.getLogger(FuseNioAdapter.class);
+	private static final String AUTOASSIGN_DRRIVE_LETTER = "*";
+
+	private enum OS {
+		WINDOWS,
+		LINUX,
+		MAC;
+
+		public static OS getCurrentOS() {
+			if (SystemUtils.IS_OS_WINDOWS) {
+				return WINDOWS;
+			} else if (SystemUtils.IS_OS_MAC) {
+				return MAC;
+			} else {
+				return LINUX;
+			}
+		}
+
+	}
+
+
+	private final VaultSettings vaultSettings;
+	private final Settings settings;
+	private final WindowsDriveLetters windowsDriveLetters;
+	private final OS os = OS.getCurrentOS();
+	private org.cryptomator.frontend.fuse.FuseNioAdapter ffs;
+	private String mountNameAndId;
+	private String mountURL;
+	private CryptoFileSystem cfs;
+
+	@Inject
+	public FuseNioAdapter(VaultSettings vaultSettings, Settings settings, WindowsDriveLetters windowsDriveLetters) {
+		this.vaultSettings = vaultSettings;
+		this.settings = settings;
+		this.windowsDriveLetters = windowsDriveLetters;
+	}
+
+	@Override
+	public void unlock(CryptoFileSystem fs) {
+		this.cfs = fs;
+		ffs = AdapterFactory.createReadWriteAdapter(fs.getPath("/"));
+	}
+
+	/**
+	 * TODO: should createTempDirectory() be used instead of createDirectory()?
+	 *
+	 * @throws CommandFailedException
+	 */
+	@Override
+	public void mount() throws CommandFailedException {
+		ArrayList<String> mountOptions = new ArrayList<>(8);
+		mountOptions.add(("-oatomic_o_trunc"));
+		Path path;
+		try {
+			switch (os) {
+				case MAC:
+					path = Paths.get(vaultSettings.mountPath().get() + vaultSettings.mountName().get());
+					createVaultDirIfNotExist(path);
+					mountOptions.add("-ouid=" + getUIdOrGID("uid"));
+					mountOptions.add("-ogid=" + getUIdOrGID("gid"));
+					mountOptions.add("-ovolname=" + vaultSettings.mountName().get());
+					mountOptions.add("-oauto_xattr");
+					break;
+				case WINDOWS:
+					if (vaultSettings.winDriveLetter().get().equals(AUTOASSIGN_DRRIVE_LETTER)) {
+						if (!windowsDriveLetters.getAvailableDriveLetters().isEmpty()) {
+							path = Paths.get(windowsDriveLetters.getAvailableDriveLetters().iterator().next() + ":\\");
+						} else {
+							throw new CommandFailedException("No free drive letter to mount.");
+						}
+					} else {
+						path = Paths.get(vaultSettings.winDriveLetter().get() + ":\\");
+					}
+					mountOptions.add("-ouid=-1");
+					mountOptions.add("-ogid=-1");
+					mountOptions.add("-ovolname=" + vaultSettings.mountName().get());
+					mountOptions.add("-oFileInfoTimeout=-1");
+					break;
+				case LINUX:
+					path = Paths.get(vaultSettings.mountPath().get() + vaultSettings.mountName().get());
+					createVaultDirIfNotExist(path);
+					mountOptions.add("-ouid=" + getUIdOrGID("uid"));
+					mountOptions.add("-ogid=" + getUIdOrGID("gid"));
+					mountOptions.add("-oauto_unmount");
+					mountOptions.add("-ofsname=CryptoFs");
+					break;
+				default:
+					throw new IllegalStateException("Not Supported OS.");
+			}
+			ffs.mount(path, false, false, mountOptions.toArray(new String[mountOptions.size()]));
+			mountURL = path.toAbsolutePath().toUri().toURL().toString();
+		} catch (Exception e) {
+			throw new CommandFailedException("Unable to mount Filesystem", e);
+		}
+	}
+
+	private void createVaultDirIfNotExist(Path p) throws IOException {
+		try {
+			if (Files.exists(p)) {
+				if (Files.isDirectory(p)) {
+					if (Files.newDirectoryStream(p).iterator().hasNext()) {
+						return;
+					} else {
+						throw new DirectoryNotEmptyException("Directory not empty.");
+					}
+				}
+			} else {
+				Files.createDirectory(p);
+			}
+		} catch (IOException e) {
+			throw e;
+		}
+	}
+
+	private String getUIdOrGID(String idtype) throws IOException {
+		String id;
+		String parameter;
+		switch (idtype) {
+			case "uid":
+				parameter = "-u";
+				break;
+			case "gid":
+				parameter = "-g";
+				break;
+			default:
+				throw new IllegalArgumentException("Unkown ID type");
+		}
+		Process getId = new ProcessBuilder("sh", "-c", "id " + parameter).start();
+		Scanner s = new Scanner(getId.getInputStream()).useDelimiter("\\A");
+		try {
+			getId.waitFor(1000, TimeUnit.MILLISECONDS);
+		} catch (InterruptedException e) {
+			e.printStackTrace();
+		}
+		id = s.nextLine();
+		return id;
+	}
+
+	@Override
+	public synchronized void unmount() throws CommandFailedException {
+		if (!(cfs.getStats().pollBytesRead() > 0 || cfs.getStats().pollBytesWritten() > 0)) {
+			unmountForced();
+		} else {
+			throw new CommandFailedException("Pending read or write operations.");
+		}
+	}
+
+	@Override
+	public synchronized void unmountForced() throws CommandFailedException {
+		ffs.umount();
+	}
+
+	@Override
+	public void stop() {
+		switch (os) {
+			case WINDOWS:
+				return;
+			case MAC:
+			case LINUX:
+				try {
+					Files.deleteIfExists(Paths.get(vaultSettings.mountPath().get() + vaultSettings.mountName().get()));
+				} catch (IOException e) {
+					LOG.warn("Could not delete mount directory of vault " + vaultSettings.mountName());
+					e.printStackTrace();
+				}
+				return;
+			default:
+				return;
+		}
+
+	}
+
+	@Override
+	public String getFilesystemRootUrl() {
+		return mountURL;
+	}
+
+	/**
+	 * TODO: what should i check here?
+	 */
+	@Override
+	public boolean isSupported() {
+		switch (os) {
+			case LINUX:
+				return true;
+			case WINDOWS:
+				break;
+			case MAC:
+				break;
+			default:
+				return false;
+		}
+		return true;
+	}
+
+	@Override
+	public boolean supportsForcedUnmount() {
+		return true;
+	}
+
+}

+ 2 - 2
main/ui/src/main/java/org/cryptomator/ui/model/VaultModule.java

@@ -43,13 +43,13 @@ public class VaultModule {
 
 	@Provides
 	@PerVault
-	public NioAdapter provideNioAdpater(Settings settings, WebDavNioAdapter webDavNioAdapter) {
+	public NioAdapter provideNioAdpater(Settings settings, WebDavNioAdapter webDavNioAdapter, FuseNioAdapter fuseNioAdapter) {
 		NioAdapterImpl impl = NioAdapterImpl.valueOf(settings.usedNioAdapterImpl().get());
 		switch (impl) {
 			case WEBDAV:
 				return webDavNioAdapter;
 			case FUSE:
-				throw new NotImplementedException();
+				return fuseNioAdapter;
 			default:
 				//this should not happen!
 				throw new IllegalStateException("Unsupported NioAdapter: " + settings.usedNioAdapterImpl().get());

+ 29 - 14
main/ui/src/main/resources/fxml/settings.fxml

@@ -35,22 +35,37 @@
 			<!-- Row 0 -->
 			<Label GridPane.rowIndex="0" GridPane.columnIndex="0" text="%settings.checkForUpdates.label" cacheShape="true" cache="true" />
 			<CheckBox GridPane.rowIndex="0" GridPane.columnIndex="1" fx:id="checkForUpdatesCheckbox" cacheShape="true" cache="true" />
-			
+
 			<!-- Row 1 -->
-			<Label GridPane.rowIndex="1" GridPane.columnIndex="0" text="%settings.port.label" cacheShape="true" cache="true" />
-			<HBox GridPane.rowIndex="1" GridPane.columnIndex="1" spacing="6.0">
-				<TextField  fx:id="portField" cacheShape="true" cache="true" promptText="%settings.port.prompt" />
-				<Button text="%settings.port.apply" fx:id="changePortButton" onAction="#changePort"/>
-			</HBox>
-			
+			<Label GridPane.rowIndex="1" GridPane.columnIndex="0" text="%settings.debugMode.label" cacheShape="true" cache="true" />
+			<CheckBox GridPane.rowIndex="1" GridPane.columnIndex="1" fx:id="debugModeCheckbox" cacheShape="true" cache="true" />
+
 			<!-- Row 2 -->
-			<Label GridPane.rowIndex="2" GridPane.columnIndex="0" fx:id="prefGvfsSchemeLabel" text="%settings.prefGvfsScheme.label" cacheShape="true" cache="true" />
-			<ChoiceBox GridPane.rowIndex="2" GridPane.columnIndex="1" fx:id="prefGvfsScheme" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
-			
-			<!-- Row 3 -->
-			<Label GridPane.rowIndex="3" GridPane.columnIndex="0" text="%settings.debugMode.label" cacheShape="true" cache="true" />
-			<CheckBox GridPane.rowIndex="3" GridPane.columnIndex="1" fx:id="debugModeCheckbox" cacheShape="true" cache="true" />
-			
+			<Label fx:id="nioAdapterLabel" GridPane.rowIndex="2" GridPane.columnIndex="0" text="%settings.nioAdapter.label" cacheShape="true" cache="true" />
+			<ChoiceBox GridPane.rowIndex="2" GridPane.columnIndex="1" fx:id="nioAdapter" cacheShape="true" cache="true" />
+
+			<!-- Row 3 Alt 1-->
+			<GridPane fx:id="webdavNioAdapter" vgap="12.0" hgap="12.0" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2" visible="true" cacheShape="true" cache="true">
+				<Label fx:id="portFieldLabel" GridPane.rowIndex="3" GridPane.columnIndex="0" text="%settings.webdav.port.label" cacheShape="true" cache="true" />
+				<HBox GridPane.rowIndex="3" GridPane.columnIndex="1" spacing="6.0">
+					<TextField  fx:id="portField" cacheShape="true" cache="true" promptText="%settings.webdav.port.prompt" />
+					<Button text="%settings.webdav.port.apply" fx:id="changePortButton" onAction="#changePort"/>
+				</HBox>
+
+				<!-- Row 4 -->
+				<Label GridPane.rowIndex="4" GridPane.columnIndex="0" fx:id="prefGvfsSchemeLabel" text="%settings.webdav.prefGvfsScheme.label" cacheShape="true" cache="true" />
+				<ChoiceBox GridPane.rowIndex="4" GridPane.columnIndex="1" fx:id="prefGvfsScheme" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
+			</GridPane>
+
+			<!-- Row 3 Alt 2-->
+			<GridPane fx:id="fuseNioAdapter" vgap="12.0" hgap="12.0" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2" visible="false" cacheShape="true" cache="true">
+				<Label fx:id="defaultMountDirLabel" GridPane.rowIndex="3" GridPane.columnIndex="0" text="%settings.fuse.defaultMntDir.label" cacheShape="true" cache="true"/>
+				<HBox GridPane.rowIndex="3" GridPane.columnIndex="1" spacing="6.0">
+					<TextField  fx:id="defaultMountDir" cacheShape="true" cache="true" promptText="%settings.fuse.defaultMntDir.defaultVal" />
+					<Button text="%settings.webdav.port.apply" fx:id="changeDefaultMountDirButton" onAction="#changeDefaultMountDir"/>
+				</HBox>
+			</GridPane>
+
 		</children>
 	</GridPane>
 	<Label VBox.vgrow="NEVER" text="%settings.requiresRestartLabel" alignment="CENTER" cacheShape="true" cache="true" />

+ 9 - 1
main/ui/src/main/resources/fxml/unlock.fxml

@@ -83,9 +83,17 @@
 			<!-- Row 3.5 -->
 			<CheckBox GridPane.rowIndex="5" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="revealAfterMount" text="%unlock.label.revealAfterMount" cacheShape="true" cache="true" />
 			
-			<!-- Row 3.6 -->
+			<!-- Row 3.6 Alt1 -->
 			<Label GridPane.rowIndex="6" GridPane.columnIndex="0" fx:id="winDriveLetterLabel" text="%unlock.label.winDriveLetter" cacheShape="true" cache="true" />
 			<ChoiceBox GridPane.rowIndex="6" GridPane.columnIndex="1" fx:id="winDriveLetter" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
+
+			<!-- Row 3.6 Alt2 -->
+			<Label GridPane.rowIndex="6" GridPane.columnIndex="0" fx:id="mountPathLabel" text="%unlock.label.mountPath" cacheShape="true" cache="true" />
+
+			<HBox GridPane.rowIndex="6" GridPane.columnIndex="1" spacing="6.0">
+				<TextField GridPane.rowIndex="6" GridPane.columnIndex="1" fx:id="mountPath"  cacheShape="true" cache="true" />
+				<Button text="%unlock.label.mountPathButton" fx:id="changeMountPathButton" onAction="#didClickchangeMountPathButton"/>
+			</HBox>
 		</GridPane>
 		
 		<!-- Row 4 -->

+ 12 - 5
main/ui/src/main/resources/localization/en.txt

@@ -67,6 +67,8 @@ unlock.label.mountName=Drive Name
 unlock.label.unlockAfterStartup=Auto-Unlock on Start (Experimental)
 unlock.label.revealAfterMount=Reveal Drive
 unlock.label.winDriveLetter=Drive Letter
+unlock.label.mountPath=Mount Path
+unlock.label.mountPathButton=Apply
 unlock.label.downloadsPageLink=All Cryptomator versions
 unlock.label.advancedHeading=Advanced Options
 unlock.button.unlock=Unlock Vault
@@ -97,7 +99,7 @@ unlocked.button.lock=Lock Vault
 unlocked.moreOptions.mount=Mount Drive
 unlocked.moreOptions.unmount=Eject Drive
 unlocked.moreOptions.reveal=Reveal Drive
-unlocked.moreOptions.copyUrl=Copy WebDAV URL
+unlocked.moreOptions.copyUrl=Copy Filesystem URL
 unlocked.label.mountFailed=Connecting drive failed
 unlocked.label.revealFailed=Command failed
 unlocked.label.unmountFailed=Ejecting drive failed
@@ -111,12 +113,17 @@ unlocked.lock.force.confirmation.content=This may be because other programs are
 # settings.fxml
 settings.version.label=Version %s
 settings.checkForUpdates.label=Check for Updates
-settings.port.label=WebDAV Port
-settings.port.prompt=0 = Choose automatically
-settings.port.apply=Apply
-settings.prefGvfsScheme.label=WebDAV Scheme
+settings.webdav.port.label=WebDAV Port
+settings.webdav.port.prompt=0 = Choose automatically
+settings.webdav.port.apply=Apply
+settings.webdav.prefGvfsScheme.label=WebDAV Scheme
 settings.debugMode.label=Debug Mode *
 settings.requiresRestartLabel=* Cryptomator needs to restart
+settings.nioAdapter.label= Mount-Methode *
+settings.nioAdapter.webdav=WebDAV
+settings.nioAdapter.fuse=FUSE
+settings.fuse.defaultMntDir.label=Default mounting point
+settings.fuse.defaultMntDir.defaultVal=/dev/null
 
 # tray icon
 tray.menu.open=Open