瀏覽代碼

- General Simplification
- Refactoring: Using Concurrency API now.
- TODO: Use Futures instead of blocking methods

Sebastian Stenzel 10 年之前
父節點
當前提交
dbadf54893

+ 29 - 11
main/ui/src/main/java/org/cryptomator/ui/util/command/AsyncStreamCopier.java

@@ -13,39 +13,57 @@ import static org.apache.commons.io.IOUtils.copy;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
-final class AsyncStreamCopier extends Thread {
+final class AsyncStreamCopier implements Runnable {
 
+	private final Lock lock = new ReentrantLock();
+	private final Condition doneCondition = lock.newCondition();
 	private final InputStream input;
 	private final OutputStream output;
-	
+
 	private IOException exception;
-	
+	private boolean done;
+
 	public AsyncStreamCopier(InputStream input, OutputStream output) {
 		this.input = input;
 		this.output = output;
-		start();
 	}
-	
+
 	@Override
 	public void run() {
-		try (InputStream inputToBeClosed = input;
-				OutputStream outputToBeClosed = output) {
+		lock.lock();
+		try (InputStream inputToBeClosed = input; OutputStream outputToBeClosed = output) {
 			copy(input, output);
+			done = true;
+			doneCondition.signal();
 		} catch (IOException e) {
 			exception = e;
+		} finally {
+			lock.unlock();
 		}
 	}
-	
-	public void assertOk() throws IOException {
+
+	private void waitUntilDone() {
+		lock.lock();
 		try {
-			join();
+			while (!done) {
+				doneCondition.await();
+			}
 		} catch (InterruptedException e) {
 			Thread.currentThread().interrupt();
+		} finally {
+			lock.unlock();
 		}
+	}
+
+	public void assertOk() throws IOException {
+		this.waitUntilDone();
 		if (exception != null) {
 			throw exception;
 		}
 	}
-	
+
 }

+ 68 - 46
main/ui/src/main/java/org/cryptomator/ui/util/command/CommandResult.java

@@ -12,92 +12,114 @@ import static java.lang.String.format;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 import org.cryptomator.ui.util.mount.CommandFailedException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 public class CommandResult {
-	
+
 	private static final Logger LOG = LoggerFactory.getLogger(CommandResult.class);
 
 	private final ByteArrayOutputStream output = new ByteArrayOutputStream();
 	private final ByteArrayOutputStream error = new ByteArrayOutputStream();
+	private final Lock lock = new ReentrantLock();
+	private final Condition finishedCondition = lock.newCondition();
 	private final Process process;
-	
+
 	private final AsyncStreamCopier processOutputCopier;
 	private final AsyncStreamCopier processErrorCopier;
-	
+
 	private boolean finished;
-	
-	public CommandResult(Process process, String[] lines) {
+	private CommandFailedException exception;
+
+	public CommandResult(Process process, String[] lines, Executor cmdExecutor, long timeout, TimeUnit unit) {
 		this.process = process;
-		new AsyncLineWriter(lines, process.getOutputStream());
 		processOutputCopier = new AsyncStreamCopier(process.getInputStream(), output);
 		processErrorCopier = new AsyncStreamCopier(process.getErrorStream(), error);
+		cmdExecutor.execute(processOutputCopier);
+		cmdExecutor.execute(processErrorCopier);
+		cmdExecutor.execute(new CommandTask(timeout, unit));
 	}
-	
+
 	public String getOutput() throws CommandFailedException {
-		if (!finished) {
-			throw new IllegalStateException("Command not yet finished.");
-		}
+		this.waitUntilFinished();
 		return new String(output.toByteArray());
 	}
-	
+
 	public String getError() throws CommandFailedException {
-		if (!finished) {
-			throw new IllegalStateException("Command not yet finished.");
-		}
+		this.waitUntilFinished();
 		return new String(error.toByteArray());
 	}
-	
-	public int getExitValue(long timeout, TimeUnit unit) throws CommandFailedException {
-		waitAndAssertOk(timeout, unit);
+
+	public int getExitValue() throws CommandFailedException {
+		this.waitUntilFinished();
 		return process.exitValue();
 	}
 
-	private void waitAndAssertOk(long timeout, TimeUnit unit) throws CommandFailedException {
-		if (finished) return;
+	private void waitUntilFinished() throws CommandFailedException {
+		lock.lock();
 		try {
-			if (!process.waitFor(timeout, unit)) {
-				throw new CommandFailedException("Waiting time elapsed before command execution finished");
+			while (!finished) {
+				finishedCondition.await();
 			}
-			processOutputCopier.assertOk();
-			processErrorCopier.assertOk();
-			finished = true;
-			logDebugInfo();
-		} catch (IOException | InterruptedException e) {
-			throw new CommandFailedException(e);
+		} catch (InterruptedException e) {
+			Thread.currentThread().interrupt();
+		} finally {
+			lock.unlock();
+		}
+		if (exception != null) {
+			throw exception;
 		}
 	}
 
 	private void logDebugInfo() {
 		if (LOG.isDebugEnabled()) {
-			LOG.debug("Command execution finished. Exit code: {}\n"
-					+ "Output:\n"
-					+ "{}\n"
-					+ "Error:\n"
-					+ "{}\n",
-					process.exitValue(),
-					new String(output.toByteArray()),
-					new String(error.toByteArray()));
+			LOG.debug("Command execution finished. Exit code: {}\n" + "Output:\n" + "{}\n" + "Error:\n" + "{}\n", process.exitValue(), new String(output.toByteArray()), new String(error.toByteArray()));
 		}
 	}
-	
-	public void assertOk(long timeout, TimeUnit unit) throws CommandFailedException {
-		int exitValue = getExitValue(timeout, unit);
+
+	void assertOk() throws CommandFailedException {
+		int exitValue = getExitValue();
 		if (exitValue != 0) {
-			throw new CommandFailedException(format(
-					"Command execution failed. Exit code: %d\n"
-					+ "# Output:\n"
-					+ "%s\n"
-					+ "# Error:\n"
-					+ "%s",
-					exitValue,
-					new String(output.toByteArray()),
+			throw new CommandFailedException(format("Command execution failed. Exit code: %d\n" + "# Output:\n" + "%s\n" + "# Error:\n" + "%s", exitValue, new String(output.toByteArray()),
 					new String(error.toByteArray())));
 		}
 	}
 
+	private class CommandTask implements Runnable {
+
+		private final long timeout;
+		private final TimeUnit unit;
+
+		private CommandTask(long timeout, TimeUnit unit) {
+			this.timeout = timeout;
+			this.unit = unit;
+		}
+
+		@Override
+		public void run() {
+			try {
+				if (!process.waitFor(timeout, unit)) {
+					exception = new CommandFailedException("Waiting time elapsed before command execution finished");
+				}
+				processOutputCopier.assertOk();
+				processErrorCopier.assertOk();
+				logDebugInfo();
+			} catch (IOException | InterruptedException e) {
+				exception = new CommandFailedException(e);
+			} finally {
+				lock.lock();
+				finished = true;
+				finishedCondition.signal();
+				lock.unlock();
+			}
+		}
+	}
+
 }

+ 19 - 19
main/ui/src/main/java/org/cryptomator/ui/util/command/CommandRunner.java

@@ -14,6 +14,9 @@ import static org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS;
 
 import java.io.IOException;
 import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 import org.apache.commons.lang3.ArrayUtils;
@@ -24,44 +27,43 @@ import org.cryptomator.ui.util.mount.CommandFailedException;
  * <p>
  * Runs commands using a system compatible CLI.
  * <p>
- * To detect the system type {@link SystemUtils} is used. The following CLIs are
- * used by default:
+ * To detect the system type {@link SystemUtils} is used. The following CLIs are used by default:
  * <ul>
  * <li><i>{@link #WINDOWS_DEFAULT_CLI}</i> if {@link SystemUtils#IS_OS_WINDOWS}
  * <li><i>{@link #UNIX_DEFAULT_CLI}</i> if {@link SystemUtils#IS_OS_UNIX}
  * </ul>
  * <p>
- * If the path to the executables differs from the default or the system can not
- * be detected the Java system property {@value #CLI_EXECUTABLE_PROPERTY} can be
- * set to define it.
+ * If the path to the executables differs from the default or the system can not be detected the Java system property
+ * {@value #CLI_EXECUTABLE_PROPERTY} can be set to define it.
  * <p>
- * If a CLI executable can not be determined using these methods operation of
- * {@link CommandRunner} will fail with {@link IllegalStateException}s.
+ * If a CLI executable can not be determined using these methods operation of {@link CommandRunner} will fail with
+ * {@link IllegalStateException}s.
  *
  * @author Markus Kreusch
  */
-class CommandRunner {
+final class CommandRunner {
 
 	public static final String CLI_EXECUTABLE_PROPERTY = "cryptomator.cli";
-	
 	public static final String WINDOWS_DEFAULT_CLI[] = {"cmd", "/C"};
 	public static final String UNIX_DEFAULT_CLI[] = {"/bin/sh", "-c"};
-	
+	private static final Executor CMD_EXECUTOR = Executors.newCachedThreadPool();
+
 	/**
 	 * Executes all lines in the given script in the specified order. Stops as soon as the first command fails.
+	 * 
 	 * @param script Script containing command lines and environment variables.
 	 * @return Result of the last command, if it exited successfully.
 	 * @throws CommandFailedException If one of the command lines in the given script fails.
 	 */
-	static CommandResult execute(Script script) throws CommandFailedException {
+	static CommandResult execute(Script script, long timeout, TimeUnit unit) throws CommandFailedException {
 		try {
 			final List<String> env = script.environment().entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.toList());
 			CommandResult result = null;
 			for (final String line : script.getLines()) {
 				final String[] cmds = ArrayUtils.add(determineCli(), line);
 				final Process proc = Runtime.getRuntime().exec(cmds, env.toArray(new String[0]));
-				result = run(proc, script.getLines());
-				result.assertOk(script.getTimeout(), script.getTimeoutUnit());
+				result = run(proc, script.getLines(), timeout, unit);
+				result.assertOk();
 			}
 			return result;
 		} catch (IOException e) {
@@ -69,8 +71,8 @@ class CommandRunner {
 		}
 	}
 
-	private static CommandResult run(Process process, String[] lines) {
-		return new CommandResult(process, lines);
+	private static CommandResult run(Process process, String[] lines, long timeout, TimeUnit unit) {
+		return new CommandResult(process, lines, CMD_EXECUTOR, timeout, unit);
 	}
 
 	private static String[] determineCli() {
@@ -82,10 +84,8 @@ class CommandRunner {
 		} else if (IS_OS_UNIX) {
 			return UNIX_DEFAULT_CLI;
 		} else {
-			throw new IllegalStateException(format(
-					"Failed to determine cli to use. Set Java system property %s to the executable.",
-					CLI_EXECUTABLE_PROPERTY));
+			throw new IllegalStateException(format("Failed to determine cli to use. Set Java system property %s to the executable.", CLI_EXECUTABLE_PROPERTY));
 		}
 	}
-	
+
 }

+ 20 - 34
main/ui/src/main/java/org/cryptomator/ui/util/command/Script.java

@@ -15,65 +15,51 @@ import java.util.concurrent.TimeUnit;
 import org.cryptomator.ui.util.mount.CommandFailedException;
 
 public final class Script {
-	
+
 	private static final int DEFAULT_TIMEOUT_MILLISECONDS = 3000;
-	
-	public static Script fromLines(String ... commands) {
+
+	public static Script fromLines(String... commands) {
 		return new Script(commands);
 	}
-	
+
 	private final String[] lines;
-	private final Map<String,String> environment = new HashMap<>();
-	private long timeout = DEFAULT_TIMEOUT_MILLISECONDS;
-	private TimeUnit timeoutUnit = TimeUnit.MILLISECONDS;
-	
+	private final Map<String, String> environment = new HashMap<>();
+
 	private Script(String[] lines) {
 		this.lines = lines;
 		setEnv(System.getenv());
 	}
-	
+
 	public String[] getLines() {
 		return lines;
 	}
-	
+
 	public CommandResult execute() throws CommandFailedException {
-		return CommandRunner.execute(this);
+		return CommandRunner.execute(this, DEFAULT_TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS);
+	}
+
+	public CommandResult execute(long timeout, TimeUnit unit) throws CommandFailedException {
+		return CommandRunner.execute(this, timeout, unit);
 	}
-	
-	Map<String,String> environment() {
+
+	Map<String, String> environment() {
 		return environment;
 	}
-	
-	public Script setEnv(Map<String,String> environment) {
+
+	public Script setEnv(Map<String, String> environment) {
 		this.environment.clear();
 		addEnv(environment);
 		return this;
 	}
-	
-	public Script addEnv(Map<String,String> environment) {
+
+	public Script addEnv(Map<String, String> environment) {
 		this.environment.putAll(environment);
 		return this;
 	}
-	
+
 	public Script addEnv(String name, String value) {
 		environment.put(name, value);
 		return this;
 	}
 
-	public long getTimeout() {
-		return timeout;
-	}
-
-	public void setTimeout(long timeout) {
-		this.timeout = timeout;
-	}
-
-	public TimeUnit getTimeoutUnit() {
-		return timeoutUnit;
-	}
-
-	public void setTimeoutUnit(TimeUnit timeoutUnit) {
-		this.timeoutUnit = timeoutUnit;
-	}
-	
 }

+ 4 - 8
main/ui/src/main/java/org/cryptomator/ui/util/mount/WindowsWebDavMounter.java

@@ -29,7 +29,7 @@ import org.cryptomator.ui.util.command.Script;
 final class WindowsWebDavMounter implements WebDavMounterStrategy {
 
 	private static final Pattern WIN_MOUNT_DRIVELETTER_PATTERN = Pattern.compile("\\s*([A-Z]:)\\s*");
-	
+
 	@Override
 	public boolean shouldWork() {
 		return SystemUtils.IS_OS_WINDOWS;
@@ -37,14 +37,10 @@ final class WindowsWebDavMounter implements WebDavMounterStrategy {
 
 	@Override
 	public WebDavMount mount(int localPort) throws CommandFailedException {
-		final Script mountScript = fromLines("net use * http://0--1.ipv6-literal.net:%PORT% /persistent:no")
-				.addEnv("PORT", String.valueOf(localPort));
-		mountScript.setTimeout(30);
-		mountScript.setTimeoutUnit(TimeUnit.SECONDS);
-		final CommandResult mountResult = mountScript.execute();
+		final Script mountScript = fromLines("net use * http://0--1.ipv6-literal.net:%PORT% /persistent:no").addEnv("PORT", String.valueOf(localPort));
+		final CommandResult mountResult = mountScript.execute(30, TimeUnit.SECONDS);
 		final String driveLetter = getDriveLetter(mountResult.getOutput());
-		final Script unmountScript = fromLines("net use "+driveLetter+" /delete")
-				.addEnv("DRIVE_LETTER", driveLetter);
+		final Script unmountScript = fromLines("net use " + driveLetter + " /delete").addEnv("DRIVE_LETTER", driveLetter);
 		return new WebDavMount() {
 			@Override
 			public void unmount() throws CommandFailedException {