diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/Directory.java b/main/ui/src/main/java/org/cryptomator/ui/model/Directory.java index d1245188e..e172ac994 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/Directory.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/Directory.java @@ -70,7 +70,7 @@ public class Directory implements Serializable { public boolean mount() { try { - URI shareUri = URI.create(String.format("dav://localhost:", server.getPort())); + URI shareUri = URI.create(String.format("dav://localhost:%d", server.getPort())); webDavMount = WebDavMounter.mount(shareUri); return true; } catch (CommandFailedException e) { diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/CommandUtil.java b/main/ui/src/main/java/org/cryptomator/ui/util/CommandUtil.java deleted file mode 100644 index 536909bee..000000000 --- a/main/ui/src/main/java/org/cryptomator/ui/util/CommandUtil.java +++ /dev/null @@ -1,44 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 Sebastian Stenzel, Markus Kreusch - * This file is licensed under the terms of the MIT license. - * See the LICENSE.txt file for more info. - * - * Contributors: - * Sebastian Stenzel - initial API and implementation - * Markus Kreusch - ******************************************************************************/ -package org.cryptomator.ui.util; - -import static java.lang.String.join; - -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -import org.apache.commons.io.IOUtils; -import org.cryptomator.ui.util.webdav.CommandFailedException; - -public final class CommandUtil { - - private static final int DEFAULT_TIMEOUT_SECONDS = 3; - - public static String exec(String ... command) throws CommandFailedException { - try { - final Process proc = Runtime.getRuntime().exec(command); - if (!proc.waitFor(DEFAULT_TIMEOUT_SECONDS, TimeUnit.SECONDS)) { - proc.destroy(); - throw new CommandFailedException("Timeout executing command " + join(" ", command)); - } - if (proc.exitValue() != 0) { - throw new CommandFailedException(IOUtils.toString(proc.getErrorStream())); - } - return IOUtils.toString(proc.getInputStream()); - } catch (IOException | InterruptedException | IllegalThreadStateException e) { - throw new CommandFailedException(e); - } - } - - private CommandUtil() { - throw new IllegalStateException("Class is not instantiable"); - } - -} diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/command/AsyncLineWriter.java b/main/ui/src/main/java/org/cryptomator/ui/util/command/AsyncLineWriter.java new file mode 100644 index 000000000..64a202ffb --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/util/command/AsyncLineWriter.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2014 Markus Kreusch + * This file is licensed under the terms of the MIT license. + * See the LICENSE.txt file for more info. + * + * Contributors: + * Markus Kreusch + ******************************************************************************/ +package org.cryptomator.ui.util.command; + +import java.io.IOException; +import java.io.OutputStream; + +final class AsyncLineWriter extends Thread { + + private final String[] lines; + private final OutputStream output; + + private IOException exception; + + public AsyncLineWriter(String[] lines, OutputStream output) { + this.lines = lines; + this.output = output; + start(); + } + + @Override + public void run() { + try (OutputStream outputToBeClosed = output) { + for (String line : lines) { + output.write(line.getBytes()); + output.write("\n".getBytes()); + output.flush(); + } + } catch (IOException e) { + exception = e; + } + } + + public void assertOk() throws IOException { + try { + join(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + if (exception != null) { + throw exception; + } + } + +} 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 new file mode 100644 index 000000000..e490646ae --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/util/command/AsyncStreamCopier.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2014 Markus Kreusch + * This file is licensed under the terms of the MIT license. + * See the LICENSE.txt file for more info. + * + * Contributors: + * Markus Kreusch + ******************************************************************************/ +package org.cryptomator.ui.util.command; + +import static org.apache.commons.io.IOUtils.copy; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +final class AsyncStreamCopier extends Thread { + + private final InputStream input; + private final OutputStream output; + + private IOException exception; + + public AsyncStreamCopier(InputStream input, OutputStream output) { + this.input = input; + this.output = output; + start(); + } + + @Override + public void run() { + try (InputStream inputToBeClosed = input; + OutputStream outputToBeClosed = output) { + copy(input, output); + } catch (IOException e) { + exception = e; + } + } + + public void assertOk() throws IOException { + try { + join(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + 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 new file mode 100644 index 000000000..d5c347c47 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/util/command/CommandResult.java @@ -0,0 +1,113 @@ +/******************************************************************************* + * Copyright (c) 2014 Markus Kreusch + * This file is licensed under the terms of the MIT license. + * See the LICENSE.txt file for more info. + * + * Contributors: + * Markus Kreusch + ******************************************************************************/ +package org.cryptomator.ui.util.command; + +import static java.lang.String.format; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import org.cryptomator.ui.util.webdav.CommandFailedException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CommandResult { + + private static final int DEFAULT_TIMEOUT_MILLISECONDS = 10000; + + private static final Logger LOG = LoggerFactory.getLogger(CommandResult.class); + + private final ByteArrayOutputStream output = new ByteArrayOutputStream(); + private final ByteArrayOutputStream error = new ByteArrayOutputStream(); + private final Process process; + + private final AsyncStreamCopier processOutputCopier; + private final AsyncStreamCopier processErrorCopier; + + private boolean finished; + + public CommandResult(Process process, String[] lines) { + this.process = process; + new AsyncLineWriter(lines, process.getOutputStream()); + processOutputCopier = new AsyncStreamCopier(process.getInputStream(), output); + processErrorCopier = new AsyncStreamCopier(process.getErrorStream(), error); + } + + public String getOutput() throws CommandFailedException { + return getOutput(DEFAULT_TIMEOUT_MILLISECONDS, TimeUnit.MICROSECONDS); + } + + public String getError() throws CommandFailedException { + return getError(DEFAULT_TIMEOUT_MILLISECONDS, TimeUnit.MICROSECONDS); + } + + public String getOutput(long timeout, TimeUnit unit) throws CommandFailedException { + waitAndAssertOk(timeout, unit); + return new String(output.toByteArray()); + } + + public String getError(long timeout, TimeUnit unit) throws CommandFailedException { + waitAndAssertOk(timeout, unit); + return new String(error.toByteArray()); + } + + public int getExitValue(long timeout, TimeUnit unit) throws CommandFailedException { + waitAndAssertOk(timeout, unit); + return process.exitValue(); + } + + private void waitAndAssertOk(long timeout, TimeUnit unit) throws CommandFailedException { + if (finished) return; + try { + if (!process.waitFor(timeout, unit)) { + throw new CommandFailedException("Waiting time elapsed before command execution finished"); + } + processOutputCopier.assertOk(); + processErrorCopier.assertOk(); + finished = true; + logDebugInfo(); + } catch (IOException | InterruptedException e) { + throw new CommandFailedException(e); + } + } + + 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())); + } + } + + public void assertOk() throws CommandFailedException { + assertOk(DEFAULT_TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS); + } + + public void assertOk(long timeout, TimeUnit unit) throws CommandFailedException { + int exitValue = getExitValue(timeout, unit); + 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()), + new String(error.toByteArray()))); + } + } + +} 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 new file mode 100644 index 000000000..9ccadd7da --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/util/command/CommandRunner.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * Copyright (c) 2014 Markus Kreusch + * This file is licensed under the terms of the MIT license. + * See the LICENSE.txt file for more info. + * + * Contributors: + * Markus Kreusch + ******************************************************************************/ +package org.cryptomator.ui.util.command; + +import static java.lang.String.format; +import static org.apache.commons.lang3.SystemUtils.IS_OS_UNIX; +import static org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS; + +import java.io.IOException; + +import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.ui.util.webdav.CommandFailedException; + +/** + *
+ * Runs commands using a system compatible CLI. + *
+ * 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 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 {
+
+ public static final String CLI_EXECUTABLE_PROPERTY = "cryptomator.cli";
+
+ public static final String WINDOWS_DEFAULT_CLI[] = {"cmd"};
+ public static final String UNIX_DEFAULT_CLI[] = {"/bin/sh", "-e"};
+
+ static CommandResult execute(Script script) throws CommandFailedException {
+ ProcessBuilder builder = new ProcessBuilder(determineCli());
+ builder.environment().clear();
+ builder.environment().putAll(script.environment());
+ try {
+ return run(builder.start(), script.getLines());
+ } catch (IOException e) {
+ throw new CommandFailedException(e);
+ }
+ }
+
+ private static CommandResult run(Process process, String[] lines) {
+ return new CommandResult(process, lines);
+ }
+
+ private static String[] determineCli() {
+ final String cliFromProperty = System.getProperty(CLI_EXECUTABLE_PROPERTY);
+ if (cliFromProperty != null) {
+ return cliFromProperty.split("");
+ } else if (IS_OS_WINDOWS) {
+ return WINDOWS_DEFAULT_CLI;
+ } 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));
+ }
+ }
+
+}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/command/Script.java b/main/ui/src/main/java/org/cryptomator/ui/util/command/Script.java
new file mode 100644
index 000000000..4417f0281
--- /dev/null
+++ b/main/ui/src/main/java/org/cryptomator/ui/util/command/Script.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2014 Markus Kreusch
+ * This file is licensed under the terms of the MIT license.
+ * See the LICENSE.txt file for more info.
+ *
+ * Contributors:
+ * Markus Kreusch
+ ******************************************************************************/
+package org.cryptomator.ui.util.command;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.cryptomator.ui.util.webdav.CommandFailedException;
+
+public final class Script {
+
+ public static Script fromLines(String ... commands) {
+ return new Script(commands);
+ }
+
+ private final String[] lines;
+ private final Map