mirror of
https://github.com/cryptomator/cryptomator.git
synced 2025-02-01 07:52:17 +00:00
- General Simplification
- Refactoring: Using Concurrency API now. - TODO: Use Futures instead of blocking methods
This commit is contained in:
parent
38a0cfb2eb
commit
dbadf54893
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
Map<String,String> environment() {
|
||||
|
||||
public CommandResult execute(long timeout, TimeUnit unit) throws CommandFailedException {
|
||||
return CommandRunner.execute(this, timeout, unit);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user