瀏覽代碼

- determine available space in NIO file system (fixes #97)
- mount volumes via Finder, not via shell script. this makes creating volumes manually unnecessary

Sebastian Stenzel 9 年之前
父節點
當前提交
2ae5abfc0a

+ 17 - 4
main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFileSystem.java

@@ -1,5 +1,7 @@
 package org.cryptomator.filesystem.nio;
 
+import java.io.IOException;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Optional;
 
@@ -18,14 +20,25 @@ public class NioFileSystem extends NioFolder implements FileSystem {
 
 	@Override
 	public Optional<Long> quotaUsedBytes() {
-		// TODO du -sh
-		return Optional.empty();
+		try {
+			long availableBytes = Files.getFileStore(path).getUsableSpace();
+			long totalBytes = Files.getFileStore(path).getTotalSpace();
+			return Optional.of(totalBytes - availableBytes);
+		} catch (IOException e) {
+			e.printStackTrace();
+			return Optional.empty();
+		}
 	}
 
 	@Override
 	public Optional<Long> quotaAvailableBytes() {
-		// TODO df -lh
-		return Optional.empty();
+		try {
+			long availableBytes = Files.getFileStore(path).getUsableSpace();
+			return Optional.of(availableBytes);
+		} catch (IOException e) {
+			e.printStackTrace();
+			return Optional.empty();
+		}
 	}
 
 }

+ 1 - 1
main/filesystem-stats/src/main/java/org/cryptomator/filesystem/stats/StatsFile.java

@@ -19,7 +19,7 @@ import org.cryptomator.filesystem.delegating.DelegatingFile;
 import org.cryptomator.filesystem.delegating.DelegatingReadableFile;
 import org.cryptomator.filesystem.delegating.DelegatingWritableFile;
 
-public class StatsFile extends DelegatingFile<StatsFolder> {
+class StatsFile extends DelegatingFile<StatsFolder> {
 
 	private final Consumer<Long> readCounter;
 	private final Consumer<Long> writeCounter;

+ 1 - 1
main/filesystem-stats/src/main/java/org/cryptomator/filesystem/stats/StatsFolder.java

@@ -14,7 +14,7 @@ import org.cryptomator.filesystem.File;
 import org.cryptomator.filesystem.Folder;
 import org.cryptomator.filesystem.delegating.DelegatingFolder;
 
-public class StatsFolder extends DelegatingFolder<StatsFolder, StatsFile> {
+class StatsFolder extends DelegatingFolder<StatsFolder, StatsFile> {
 
 	private final Consumer<Long> readCounter;
 	private final Consumer<Long> writeCounter;

+ 62 - 26
main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/MacOsXWebDavMounter.java

@@ -9,20 +9,21 @@
  ******************************************************************************/
 package org.cryptomator.frontend.webdav.mount;
 
+import java.io.IOException;
 import java.net.URI;
-import java.nio.file.FileSystems;
-import java.nio.file.Files;
+import java.nio.charset.StandardCharsets;
 import java.util.Map;
 import java.util.Optional;
-import java.util.UUID;
+import java.util.concurrent.TimeUnit;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.SystemUtils;
 import org.cryptomator.frontend.CommandFailedException;
 import org.cryptomator.frontend.Frontend.MountParam;
-import org.cryptomator.frontend.webdav.mount.command.Script;
 
 @Singleton
 final class MacOsXWebDavMounter implements WebDavMounterStrategy {
@@ -43,42 +44,77 @@ final class MacOsXWebDavMounter implements WebDavMounterStrategy {
 
 	@Override
 	public WebDavMount mount(URI uri, Map<MountParam, Optional<String>> mountParams) throws CommandFailedException {
-		final String mountName = mountParams.get(MountParam.MOUNT_NAME).orElseThrow(() -> {
-			return new IllegalArgumentException("Missing mount parameter MOUNT_NAME.");
-		});
-
-		// we don't use the uri to derive a path, as it *could* be longer than 255 chars.
-		final String path = "/Volumes/Cryptomator_" + UUID.randomUUID().toString();
-		final Script mountScript = Script.fromLines("mkdir \"$MOUNT_PATH\"", "mount_webdav -S -v $MOUNT_NAME \"$DAV_AUTHORITY$DAV_PATH\" \"$MOUNT_PATH\"").addEnv("DAV_AUTHORITY", uri.getRawAuthority())
-				.addEnv("DAV_PATH", uri.getRawPath()).addEnv("MOUNT_PATH", path).addEnv("MOUNT_NAME", mountName);
-		mountScript.execute();
-		return new MacWebDavMount(path);
+		try {
+			String mountAppleScript = String.format("mount volume \"%s\"", uri.toString());
+			ProcessBuilder mount = new ProcessBuilder("/usr/bin/osascript", "-e", mountAppleScript);
+			Process mountProcess = mount.start();
+			String stdout = IOUtils.toString(mountProcess.getInputStream(), StandardCharsets.UTF_8);
+			waitForProcessAndCheckSuccess(mountProcess, 1, TimeUnit.SECONDS);
+			String volumeIdentifier = StringUtils.trim(StringUtils.removeStart(stdout, "file "));
+			String waitAppleScript1 = String.format("tell application \"Finder\" to repeat while not (\"%s\" exists)", volumeIdentifier);
+			String waitAppleScript2 = "delay 0.1";
+			String waitAppleScript3 = "end repeat";
+			ProcessBuilder wait = new ProcessBuilder("/usr/bin/osascript", "-e", waitAppleScript1, "-e", waitAppleScript2, "-e", waitAppleScript3);
+			Process waitProcess = wait.start();
+			waitForProcessAndCheckSuccess(waitProcess, 5, TimeUnit.SECONDS);
+			return new MacWebDavMount(volumeIdentifier);
+		} catch (IOException e) {
+			throw new CommandFailedException(e);
+		}
 	}
 
 	private static class MacWebDavMount extends AbstractWebDavMount {
-		private final String mountPath;
-		private final Script revealScript;
-		private final Script unmountScript;
-
-		private MacWebDavMount(String mountPath) {
-			this.mountPath = mountPath;
-			this.revealScript = Script.fromLines("open \"$MOUNT_PATH\"").addEnv("MOUNT_PATH", mountPath);
-			this.unmountScript = Script.fromLines("diskutil umount $MOUNT_PATH").addEnv("MOUNT_PATH", mountPath);
+		private final ProcessBuilder revealCommand;
+		private final ProcessBuilder unmountCommand;
+
+		private MacWebDavMount(String volumeIdentifier) {
+			String openAppleScript = String.format("tell application \"Finder\" to open \"%s\"", volumeIdentifier);
+			String activateAppleScript = String.format("tell application \"Finder\" to activate \"%s\"", volumeIdentifier);
+			String ejectAppleScript = String.format("tell application \"Finder\" to if \"%s\" exists then eject \"%s\"", volumeIdentifier, volumeIdentifier);
+
+			System.err.println("open: " + openAppleScript + "\nactivate: " + activateAppleScript + "\neject: " + ejectAppleScript);
+
+			this.revealCommand = new ProcessBuilder("/usr/bin/osascript", "-e", openAppleScript, "-e", activateAppleScript);
+			this.unmountCommand = new ProcessBuilder("/usr/bin/osascript", "-e", ejectAppleScript);
 		}
 
 		@Override
 		public void unmount() throws CommandFailedException {
-			// only attempt unmount if user didn't unmount manually:
-			if (Files.exists(FileSystems.getDefault().getPath(mountPath))) {
-				unmountScript.execute();
+			try {
+				Process proc = unmountCommand.start();
+				waitForProcessAndCheckSuccess(proc, 1, TimeUnit.SECONDS);
+			} catch (IOException e) {
+				throw new CommandFailedException(e);
 			}
 		}
 
 		@Override
 		public void reveal() throws CommandFailedException {
-			revealScript.execute();
+			try {
+				Process proc = revealCommand.start();
+				waitForProcessAndCheckSuccess(proc, 1, TimeUnit.SECONDS);
+			} catch (IOException e) {
+				throw new CommandFailedException(e);
+			}
 		}
 
 	}
 
+	private static void waitForProcessAndCheckSuccess(Process proc, long timeout, TimeUnit unit) throws CommandFailedException, IOException {
+		try {
+			boolean finishedInTime = proc.waitFor(timeout, unit);
+			if (!finishedInTime) {
+				proc.destroyForcibly();
+				throw new CommandFailedException("Command timed out.");
+			}
+			int exitCode = proc.exitValue();
+			if (exitCode != 0) {
+				String error = IOUtils.toString(proc.getErrorStream(), StandardCharsets.UTF_8);
+				throw new CommandFailedException("Command failed with exit code " + exitCode + ". Stderr: " + error);
+			}
+		} catch (InterruptedException e) {
+			Thread.currentThread().interrupt();
+		}
+	}
+
 }