diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/command/AsyncStreamCopier.java b/main/ui/src/main/java/org/cryptomator/ui/util/command/AsyncStreamCopier.java index e490646ae..da98bd9a7 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/util/command/AsyncStreamCopier.java +++ b/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; } } - + } diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/command/CommandResult.java b/main/ui/src/main/java/org/cryptomator/ui/util/command/CommandResult.java index 3798f4c7f..d11ccd67e 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/util/command/CommandResult.java +++ b/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(); + } + } + } + } diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/command/CommandRunner.java b/main/ui/src/main/java/org/cryptomator/ui/util/command/CommandRunner.java index b52f56f02..c2e9c2ce7 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/util/command/CommandRunner.java +++ b/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; *
* Runs commands using a system compatible CLI. *
- * 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: *
- * 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. *
- * 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