mirror of https://github.com/termux/termux-app
143 lines
5.6 KiB
Java
143 lines
5.6 KiB
Java
package com.termux.app.terminal;
|
|
|
|
import androidx.annotation.NonNull;
|
|
|
|
import com.termux.R;
|
|
import com.termux.shared.termux.TermuxConstants;
|
|
import com.termux.app.TermuxService;
|
|
import com.termux.shared.shell.StreamGobbler;
|
|
import com.termux.shared.logger.Logger;
|
|
import com.termux.app.utils.PluginUtils;
|
|
import com.termux.shared.shell.ShellUtils;
|
|
import com.termux.app.models.ExecutionCommand;
|
|
import com.termux.app.models.ExecutionCommand.ExecutionState;
|
|
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
|
|
/**
|
|
* A class that maintains info for background Termux tasks.
|
|
* It also provides a way to link each {@link Process} with the {@link ExecutionCommand}
|
|
* that started it.
|
|
*/
|
|
public final class TermuxTask {
|
|
|
|
private final Process mProcess;
|
|
private final ExecutionCommand mExecutionCommand;
|
|
|
|
private static final String LOG_TAG = "TermuxTask";
|
|
|
|
private TermuxTask(Process process, ExecutionCommand executionCommand) {
|
|
this.mProcess = process;
|
|
this.mExecutionCommand = executionCommand;
|
|
}
|
|
|
|
public static TermuxTask create(@NonNull final TermuxService service, @NonNull ExecutionCommand executionCommand) {
|
|
if (executionCommand.workingDirectory == null || executionCommand.workingDirectory.isEmpty()) executionCommand.workingDirectory = TermuxConstants.TERMUX_HOME_DIR_PATH;
|
|
|
|
String[] env = ShellUtils.buildEnvironment(service, false, executionCommand.workingDirectory);
|
|
|
|
final String[] commandArray = ShellUtils.setupProcessArgs(executionCommand.executable, executionCommand.arguments);
|
|
// final String commandDescription = Arrays.toString(commandArray);
|
|
|
|
if(!executionCommand.setState(ExecutionState.EXECUTING))
|
|
return null;
|
|
|
|
Logger.logDebug(LOG_TAG, executionCommand.toString());
|
|
|
|
String taskName = ShellUtils.getExecutableBasename(executionCommand.executable);
|
|
|
|
if(executionCommand.commandLabel == null)
|
|
executionCommand.commandLabel = taskName;
|
|
|
|
// Exec the process
|
|
final Process process;
|
|
try {
|
|
process = Runtime.getRuntime().exec(commandArray, env, new File(executionCommand.workingDirectory));
|
|
} catch (IOException e) {
|
|
executionCommand.setStateFailed(ExecutionCommand.RESULT_CODE_FAILED, service.getString(R.string.error_failed_to_execute_termux_task_command, executionCommand.getCommandIdAndLabelLogString()), e);
|
|
TermuxSession.processTermuxSessionResult(service, null, executionCommand);
|
|
return null;
|
|
}
|
|
|
|
final int pid = ShellUtils.getPid(process);
|
|
|
|
Logger.logDebug(LOG_TAG, "Running \"" + executionCommand.commandLabel + "\" background task with pid " + pid);
|
|
|
|
final TermuxTask termuxTask = new TermuxTask(process, executionCommand);
|
|
|
|
StringBuilder stdout = new StringBuilder();
|
|
StringBuilder stderr = new StringBuilder();
|
|
|
|
new Thread() {
|
|
@Override
|
|
public void run() {
|
|
try {
|
|
// setup stdout and stderr gobblers
|
|
StreamGobbler STDOUT = new StreamGobbler(pid + "-stdout", process.getInputStream(), stdout);
|
|
StreamGobbler STDERR = new StreamGobbler(pid + "-stderr", process.getErrorStream(), stderr);
|
|
|
|
// start gobbling
|
|
STDOUT.start();
|
|
STDERR.start();
|
|
|
|
// wait for our process to finish, while we gobble away in the
|
|
// background
|
|
int exitCode = process.waitFor();
|
|
|
|
// make sure our threads are done gobbling
|
|
// and the process is destroyed - while the latter shouldn't be
|
|
// needed in theory, and may even produce warnings, in "normal" Java
|
|
// they are required for guaranteed cleanup of resources, so lets be
|
|
// safe and do this on Android as well
|
|
STDOUT.join();
|
|
STDERR.join();
|
|
process.destroy();
|
|
|
|
|
|
// Process result
|
|
if (exitCode == 0)
|
|
Logger.logDebug(LOG_TAG, "The \"" + executionCommand.commandLabel + "\" background task with pid " + pid + " exited normally");
|
|
else
|
|
Logger.logDebug(LOG_TAG, "The \"" + executionCommand.commandLabel + "\" background task with pid " + pid + " exited with code: " + exitCode);
|
|
|
|
executionCommand.stdout = stdout.toString();
|
|
executionCommand.stderr = stderr.toString();
|
|
executionCommand.exitCode = exitCode;
|
|
|
|
if(!executionCommand.setState(ExecutionState.EXECUTED))
|
|
return;
|
|
|
|
TermuxTask.processTermuxTaskResult(service, termuxTask, null);
|
|
|
|
} catch (IllegalThreadStateException | InterruptedException e) {
|
|
// TODO: Should either of these be handled or returned?
|
|
}
|
|
}
|
|
}.start();
|
|
|
|
return termuxTask;
|
|
}
|
|
|
|
public static void processTermuxTaskResult(@NonNull final TermuxService service, final TermuxTask termuxTask, ExecutionCommand executionCommand) {
|
|
if(termuxTask != null)
|
|
executionCommand = termuxTask.mExecutionCommand;
|
|
|
|
if(executionCommand == null) return;
|
|
|
|
PluginUtils.processPluginExecutionCommandResult(service.getApplicationContext(), LOG_TAG, executionCommand);
|
|
|
|
if(termuxTask != null)
|
|
service.onTermuxTaskExited(termuxTask);
|
|
}
|
|
|
|
public Process getTerminalSession() {
|
|
return mProcess;
|
|
}
|
|
|
|
public ExecutionCommand getExecutionCommand() {
|
|
return mExecutionCommand;
|
|
}
|
|
|
|
}
|