Skip to content

Commit

Permalink
Merge pull request #7 from d4rken/pr-javadoc
Browse files Browse the repository at this point in the history
Javadocs + removal of tryGetters
  • Loading branch information
d4rken authored Dec 20, 2017
2 parents 6306bd1 + 7db5ce4 commit 25c36e9
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 35 deletions.
96 changes: 86 additions & 10 deletions core/src/main/java/eu/darken/rxshell/cmd/Cmd.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,34 +96,49 @@ public Result(Cmd cmd, int exitCode, @Nullable List<String> output, @Nullable Li
this.errors = errors;
}

/**
* The {@link Cmd} that was executed.
*/
public Cmd getCmd() {
return cmd;
}

/**
* The last exitcode emitted by the executed commands.
* <p>Think:
* {@code
* YOUR_COMMAND;YOUR_COMMAND;echo $?
* }
* <p>For convenience {@link ExitCode}
*/
public int getExitCode() {
return exitCode;
}

/**
* Your command's output.
* <p>The shell processes' {@code STDOUT} during the execution your commands.
* <p>Maybe null depending on {@link Builder#outputBuffer(boolean)}
*/
@Nullable
public List<String> getOutput() {
return output;
}

/**
* Your command's errors.
* <p>The shell processes' {@code STDERR} during the execution your commands.
* <p>Maybe null depending on {@link Builder#errorBuffer(boolean)} (boolean)}
*/
@Nullable
public List<String> getErrors() {
return errors;
}

public List<String> tryGetOutput() {
if (getOutput() == null) return new ArrayList<>();
return getOutput();
}

public List<String> tryGetErrors() {
if (getErrors() == null) return new ArrayList<>();
return getErrors();
}

/**
* Merges {@link #getOutput()} and {@link #getErrors()}.
* <p>Output first, then errors.
*/
public Collection<String> merge() {
List<String> merged = new ArrayList<>();
if (getOutput() != null) merged.addAll(getOutput());
Expand Down Expand Up @@ -169,54 +184,112 @@ public Builder(Cmd source) {
timeout(source.getTimeout());
}

/**
* The commands you want to execute.
*/
public Builder input(String... commands) {
this.commands.addAll(Arrays.asList(commands));
return this;
}

/**
* @see #input(String...)
*/
public Builder input(Collection<String> commands) {
this.commands.addAll(commands);
return this;
}

/**
* Whether the output should be stored and returned to you in {@link Result} after the command finished.
* <p>If you run into memory issues you can combine this with {@link #outputProcessor(FlowableProcessor)} to process output "on-the-fly".
* <p>If this is set to {@code false} {@link Result#getOutput()} will return {@code null}.
*
* @param enabled defaults to {@code true}
*/
public Builder outputBuffer(boolean enabled) {
this.outputBuffer = enabled;
return this;
}

/**
* @param enabled defaults to {@code true}
* @see #outputBuffer(boolean)
*/
public Builder errorBuffer(boolean enabled) {
this.errorBuffer = enabled;
return this;
}

/**
* The shell will call {@link FlowableProcessor#onNext(Object)} on this for each line of {@code STDOUT}.
* <p>Mind the backpressure!
* Blocking this processor can block the {@code STDOUT} {@link Harvester}
* Blocking the harvester can block the shell process if buffers run full.
* <p>The processor emits {@code onComplete} if the command finishes normally and emit an error otherwise.
* <p>Note: This does NOT emit control sequences used internally by {@link RxCmdShell}
*
* @param outputProcessor the processor to use
*/
public Builder outputProcessor(FlowableProcessor<String> outputProcessor) {
this.outputProcessor = outputProcessor;
return this;
}

/**
* @see #outputProcessor(FlowableProcessor)
*/
public Builder errorProcessor(FlowableProcessor<String> errorProcessor) {
this.errorProcessor = errorProcessor;
return this;
}

/**
* A timeout for this command. If the timeout is reached the whole {@link RxCmdShell.Session} is forcibly killed.
* <p>A command that timed out returns {@link Cmd.ExitCode#TIMEOUT} from {@link Result#getExitCode()}.
*
* @param timeout in milliseconds
*/
public Builder timeout(long timeout) {
this.timeout = timeout;
return this;
}

/**
* Builds the command.
*/
public Cmd build() {
if (commands.isEmpty()) throw new IllegalArgumentException("Trying to create a Command without commands.");
return new Cmd(this);
}

/**
* Convenience method for {@link RxCmdShell.Session#submit(Cmd)}
* <p> Submission happens on subscription.
*
* @param session the session to use.
* @return a {@link Single} that will emit a {@link Cmd.Result} when the command has terminated.
*/
public Single<Result> submit(RxCmdShell.Session session) {
return session.submit(build());
}

/**
* Convenience method for {@link #submit(RxCmdShell.Session)} using {@link Single#blockingGet()}
*/
public Result execute(RxCmdShell.Session session) {
return submit(session).blockingGet();
}

/**
* This is a convenience method for single-shot execution.
* <p>It's behavior depends on {@link RxCmdShell#isAlive()}.
* <br>If the shell is alive, then the existing session is used and kept open.
* <br>If the shell wasn't alive, a new session is created and closed after the command has terminated.
*
* @param shell the {@link RxCmdShell} to use.
* @return a {@link Single} that will emit a {@link Cmd.Result} when the command has terminated.
*/
public Single<Result> submit(RxCmdShell shell) {
final Cmd cmd = build();
return shell.isAlive().flatMap(wasAlive ->
Expand All @@ -227,6 +300,9 @@ public Single<Result> submit(RxCmdShell shell) {
})));
}

/**
* Convenience method for {@link #submit(RxCmdShell)} using {@link Single#blockingGet()}
*/
public Result execute(RxCmdShell shell) {
return submit(shell).blockingGet();
}
Expand Down
9 changes: 7 additions & 2 deletions core/src/main/java/eu/darken/rxshell/cmd/CmdProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import android.util.Log;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -49,7 +50,7 @@ public Single<Cmd.Result> submit(Cmd cmd) {
}
}).doOnSuccess(item -> {
if (RXSDebug.isDebug()) {
Timber.tag(TAG).log(item.tryGetErrors().size() > 0 ? Log.WARN : Log.INFO, "Processed: %s", item);
Timber.tag(TAG).log(item.getErrors() != null && item.getErrors().size() > 0 ? Log.WARN : Log.INFO, "Processed: %s", item);
}
});
}
Expand Down Expand Up @@ -199,7 +200,11 @@ QueueCmd errors(List<String> errors) {
}

Cmd.Result buildResult() {
return new Cmd.Result(cmd, exitCode, output, errors);
return new Cmd.Result(
cmd, exitCode,
output == null && cmd.isOutputBufferEnabled() ? new ArrayList<>() : output,
errors == null && cmd.isErrorBufferEnabled() ? new ArrayList<>() : errors
);
}

void emit() {
Expand Down
78 changes: 75 additions & 3 deletions core/src/main/java/eu/darken/rxshell/cmd/RxCmdShell.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ private RxCmdShell() throws InstantiationException {
processorFactory = builder.getProcessorFactory();
}

/**
* Calling this repeatedly will keep returning the same {@link Session} while it is alive.
* <p> Session creation may be blocking for longer durations, e.g. if the user is shown a root access prompt.
*
* @return a {@link Single} that when subscribed to creates a new session.
*/
public synchronized Single<Session> open() {
if (RXSDebug.isDebug()) Timber.tag(TAG).v("open()");
if (session == null) {
Expand Down Expand Up @@ -98,21 +104,34 @@ public void onError(Throwable e) {
return session;
}

/**
* @see Session#isAlive()
*/
public Single<Boolean> isAlive() {
if (RXSDebug.isDebug()) Timber.tag(TAG).v("isAlive()");
if (session == null) return Single.just(false);
else return session.flatMap(Session::isAlive);
}

/**
* If there is no active session this just completes
*
* @see Session#cancel()
*/
public synchronized Completable cancel() {
if (RXSDebug.isDebug()) Timber.tag(TAG).v("cancel()");
if (session == null) return Completable.complete();
else return session.flatMapCompletable(Session::cancel);
}

/**
* If there is no active session this returns immediately with exitcode {@link Cmd.ExitCode#OK}
*
* @see Session#close()
*/
public synchronized Single<Integer> close() {
if (RXSDebug.isDebug()) Timber.tag(TAG).v("close()");
if (session == null) return Single.just(0);
if (session == null) return Single.just(Cmd.ExitCode.OK);
else return session.flatMap(Session::close);
}

Expand Down Expand Up @@ -141,25 +160,46 @@ public Session(RxShell.Session session, CmdProcessor cmdProcessor) {
.cache();
}

/**
* @param cmd the command to execute
* @return a {@link Single} that when subscribed to will submit the command and return it's results.
*/
public Single<Cmd.Result> submit(Cmd cmd) {
return cmdProcessor.submit(cmd);
}

/**
* @return {@code true} if the current {@link Session} is alive and usable for command submission.
*/
public Single<Boolean> isAlive() {
if (RXSDebug.isDebug()) Timber.tag(TAG).v("isAlive()");
return session.isAlive();
}

/**
* Blocks until the {@link RxCmdShell.Session} terminates.
*
* @return A blocking single emitting the shell exitCode.
*/
public Single<Integer> waitFor() {
if (RXSDebug.isDebug()) Timber.tag(TAG).v("waitFor()");
return waitFor;
}

/**
* Cancels the current session, terminating the current command and all queued commands.
* <p>Canceled commands will return {@link Cmd.ExitCode#SHELL_DIED} as exitcode.
*/
public Completable cancel() {
if (RXSDebug.isDebug()) Timber.tag(TAG).v("cancel()");
return cancel;
}

/**
* Closes the current session after all commands have executed.
*
* @return a {@link Single} that blocks until the session completes and emits the shell processes exitcode.
*/
public Single<Integer> close() {
if (RXSDebug.isDebug()) Timber.tag(TAG).v("close()");
return close;
Expand All @@ -186,18 +226,35 @@ RxShell getRxShell() {
return rxShell;
}

/**
* Environment variables that will be set when opening the shell session.
* <p>
* Think `PATH=$PATH:/something`
* </p>
* Calling this the same key will overwrite the previous value.
*
* @param variable variable name
* @param value variable value
*/
public Builder shellEnvironment(String variable, String value) {
this.environment.put(variable, value);
return this;
}

/**
* @see #shellEnvironment(String, String)
*/
public Builder shellEnvironment(Collection<Pair<String, String>> vars) {
for (Pair<String, String> pair : vars) {
shellEnvironment(pair.first, pair.second);
}
return this;
}

/**
* @param envVars the class which provides environment variables, will be called when calling {@link #build()}
* @see #shellEnvironment(String, String)
*/
public Builder shellEnvironment(HasEnvironmentVariables envVars) {
this.envVarSources.add(envVars);
return this;
Expand All @@ -207,11 +264,21 @@ Map<String, String> getEnvironment() {
return environment;
}

public Builder root(boolean root) {
this.useRoot = root;
/**
* A root shell is opened by executing `su` otherwise `sh` is executed.
*
* @param useRoot whether to create a root shell. Defaults to `false`.
*/
public Builder root(boolean useRoot) {
this.useRoot = useRoot;
return this;
}

/**
* Each call creates a new instance.
*
* @return a new {@link RxCmdShell} instance.
*/
public RxCmdShell build() {
for (HasEnvironmentVariables envVars : envVarSources) {
shellEnvironment(envVars.getEnvironmentVariables(useRoot));
Expand All @@ -225,6 +292,11 @@ public RxCmdShell build() {
return new RxCmdShell(this);
}

/**
* Equal to {@code builder.build().open()}
*
* @see RxCmdShell#open()
*/
public Single<Session> open() {
return build().open();
}
Expand Down
Loading

0 comments on commit 25c36e9

Please sign in to comment.