termux-app
691 строка · 26.6 Кб
1package com.termux.shared.shell.command;2
3import android.content.Intent;4import android.net.Uri;5
6import androidx.annotation.NonNull;7import androidx.annotation.Nullable;8
9import com.termux.shared.data.IntentUtils;10import com.termux.shared.shell.command.result.ResultConfig;11import com.termux.shared.shell.command.result.ResultData;12import com.termux.shared.errors.Error;13import com.termux.shared.logger.Logger;14import com.termux.shared.markdown.MarkdownUtils;15import com.termux.shared.data.DataUtils;16import com.termux.shared.shell.command.runner.app.AppShell;17import com.termux.terminal.TerminalSession;18
19import java.util.Collections;20import java.util.List;21
22public class ExecutionCommand {23
24/*25The {@link ExecutionState#SUCCESS} and {@link ExecutionState#FAILED} is defined based on
26successful execution of command without any internal errors or exceptions being raised.
27The shell command {@link #exitCode} being non-zero **does not** mean that execution command failed.
28Only the {@link #errCode} being non-zero means that execution command failed from the Termux app
29perspective.
30*/
31
32/** The {@link Enum} that defines {@link ExecutionCommand} state. */33public enum ExecutionState {34
35PRE_EXECUTION("Pre-Execution", 0),36EXECUTING("Executing", 1),37EXECUTED("Executed", 2),38SUCCESS("Success", 3),39FAILED("Failed", 4);40
41private final String name;42private final int value;43
44ExecutionState(final String name, final int value) {45this.name = name;46this.value = value;47}48
49public String getName() {50return name;51}52
53public int getValue() {54return value;55}56
57
58}59
60public enum Runner {61
62/** Run command in {@link TerminalSession}. */63TERMINAL_SESSION("terminal-session"),64
65/** Run command in {@link AppShell}. */66APP_SHELL("app-shell");67
68///** Run command in {@link AdbShell}. */69//ADB_SHELL("adb-shell"),70
71///** Run command in {@link RootShell}. */72//ROOT_SHELL("root-shell");73
74private final String name;75
76Runner(final String name) {77this.name = name;78}79
80public String getName() {81return name;82}83
84public boolean equalsRunner(String runner) {85return runner != null && runner.equals(this.name);86}87
88/** Get {@link Runner} for {@code name} if found, otherwise {@code null}. */89@Nullable90public static Runner runnerOf(String name) {91for (Runner v : Runner.values()) {92if (v.name.equals(name)) {93return v;94}95}96return null;97}98
99/** Get {@link Runner} for {@code name} if found, otherwise {@code def}. */100@NonNull101public static Runner runnerOf(@Nullable String name, @NonNull Runner def) {102Runner runner = runnerOf(name);103return runner != null ? runner : def;104}105
106}107
108public enum ShellCreateMode {109
110/** Always create {@link TerminalSession}. */111ALWAYS("always"),112
113/** Create shell only if no shell with {@link #shellName} found. */114NO_SHELL_WITH_NAME("no-shell-with-name");115
116private final String mode;117
118ShellCreateMode(final String mode) {119this.mode = mode;120}121
122public String getMode() {123return mode;124}125
126public boolean equalsMode(String sessionCreateMode) {127return sessionCreateMode != null && sessionCreateMode.equals(this.mode);128}129
130/** Get {@link ShellCreateMode} for {@code mode} if found, otherwise {@code null}. */131@Nullable132public static ShellCreateMode modeOf(String mode) {133for (ShellCreateMode v : ShellCreateMode.values()) {134if (v.mode.equals(mode)) {135return v;136}137}138return null;139}140
141}142
143/** The optional unique id for the {@link ExecutionCommand}. This should equal -1 if execution144* command is not going to be managed by a shell manager. */
145public Integer id;146
147/** The process id of command. */148public int mPid = -1;149
150/** The current state of the {@link ExecutionCommand}. */151private ExecutionState currentState = ExecutionState.PRE_EXECUTION;152/** The previous state of the {@link ExecutionCommand}. */153private ExecutionState previousState = ExecutionState.PRE_EXECUTION;154
155
156/** The executable for the {@link ExecutionCommand}. */157public String executable;158/** The executable Uri for the {@link ExecutionCommand}. */159public Uri executableUri;160/** The executable arguments array for the {@link ExecutionCommand}. */161public String[] arguments;162/** The stdin string for the {@link ExecutionCommand}. */163public String stdin;164/** The current working directory for the {@link ExecutionCommand}. */165public String workingDirectory;166
167
168/** The terminal transcript rows for the {@link ExecutionCommand}. */169public Integer terminalTranscriptRows;170
171
172/** The {@link Runner} for the {@link ExecutionCommand}. */173public String runner;174
175/** If the {@link ExecutionCommand} is meant to start a failsafe terminal session. */176public boolean isFailsafe;177
178/**179* The {@link ExecutionCommand} custom log level for background {@link AppShell}
180* commands. By default, @link com.termux.shared.shell.StreamGobbler} only logs stdout and
181* stderr if {@link Logger} `CURRENT_LOG_LEVEL` is >= {@link Logger#LOG_LEVEL_VERBOSE} and
182* {@link AppShell} only logs stdin if `CURRENT_LOG_LEVEL` is >=
183* {@link Logger#LOG_LEVEL_DEBUG}.
184*/
185public Integer backgroundCustomLogLevel;186
187
188/** The session action of {@link Runner#TERMINAL_SESSION} commands. */189public String sessionAction;190
191
192/** The shell name of commands. */193public String shellName;194
195/** The {@link ShellCreateMode} of commands. */196public String shellCreateMode;197
198/** Whether to set {@link ExecutionCommand} shell environment. */199public boolean setShellCommandShellEnvironment;200
201
202
203
204/** The command label for the {@link ExecutionCommand}. */205public String commandLabel;206/** The markdown text for the command description for the {@link ExecutionCommand}. */207public String commandDescription;208/** The markdown text for the help of command for the {@link ExecutionCommand}. This can be used209* to provide useful info to the user if an internal error is raised. */
210public String commandHelp;211
212
213/** Defines the markdown text for the help of the Termux plugin API that was used to start the214* {@link ExecutionCommand}. This can be used to provide useful info to the user if an internal
215* error is raised. */
216public String pluginAPIHelp;217
218
219/** Defines the {@link Intent} received which started the command. */220public Intent commandIntent;221
222/** Defines if {@link ExecutionCommand} was started because of an external plugin request223* like with an intent or from within Termux app itself. */
224public boolean isPluginExecutionCommand;225
226/** Defines the {@link ResultConfig} for the {@link ExecutionCommand} containing information227* on how to handle the result. */
228public final ResultConfig resultConfig = new ResultConfig();229
230/** Defines the {@link ResultData} for the {@link ExecutionCommand} containing information231* of the result. */
232public final ResultData resultData = new ResultData();233
234
235/** Defines if processing results already called for this {@link ExecutionCommand}. */236public boolean processingResultsAlreadyCalled;237
238private static final String LOG_TAG = "ExecutionCommand";239
240
241public ExecutionCommand() {242}243
244public ExecutionCommand(Integer id) {245this.id = id;246}247
248public ExecutionCommand(Integer id, String executable, String[] arguments, String stdin, String workingDirectory, String runner, boolean isFailsafe) {249this.id = id;250this.executable = executable;251this.arguments = arguments;252this.stdin = stdin;253this.workingDirectory = workingDirectory;254this.runner = runner;255this.isFailsafe = isFailsafe;256}257
258
259public boolean isPluginExecutionCommandWithPendingResult() {260return isPluginExecutionCommand && resultConfig.isCommandWithPendingResult();261}262
263
264public synchronized boolean setState(ExecutionState newState) {265// The state transition cannot go back or change if already at {@link ExecutionState#SUCCESS}266if (newState.getValue() < currentState.getValue() || currentState == ExecutionState.SUCCESS) {267Logger.logError(LOG_TAG, "Invalid "+ getCommandIdAndLabelLogString() + " state transition from \"" + currentState.getName() + "\" to " + "\"" + newState.getName() + "\"");268return false;269}270
271// The {@link ExecutionState#FAILED} can be set again, like to add more errors, but we don't update272// {@link #previousState} with the {@link #currentState} value if its at {@link ExecutionState#FAILED} to273// preserve the last valid state274if (currentState != ExecutionState.FAILED)275previousState = currentState;276
277currentState = newState;278return true;279}280
281public synchronized boolean hasExecuted() {282return currentState.getValue() >= ExecutionState.EXECUTED.getValue();283}284
285public synchronized boolean isExecuting() {286return currentState == ExecutionState.EXECUTING;287}288
289public synchronized boolean isSuccessful() {290return currentState == ExecutionState.SUCCESS;291}292
293
294public synchronized boolean setStateFailed(@NonNull Error error) {295return setStateFailed(error.getType(), error.getCode(), error.getMessage(), null);296}297
298public synchronized boolean setStateFailed(@NonNull Error error, Throwable throwable) {299return setStateFailed(error.getType(), error.getCode(), error.getMessage(), Collections.singletonList(throwable));300}301public synchronized boolean setStateFailed(@NonNull Error error, List<Throwable> throwablesList) {302return setStateFailed(error.getType(), error.getCode(), error.getMessage(), throwablesList);303}304
305public synchronized boolean setStateFailed(int code, String message) {306return setStateFailed(null, code, message, null);307}308
309public synchronized boolean setStateFailed(int code, String message, Throwable throwable) {310return setStateFailed(null, code, message, Collections.singletonList(throwable));311}312
313public synchronized boolean setStateFailed(int code, String message, List<Throwable> throwablesList) {314return setStateFailed(null, code, message, throwablesList);315}316public synchronized boolean setStateFailed(String type, int code, String message, List<Throwable> throwablesList) {317if (!this.resultData.setStateFailed(type, code, message, throwablesList)) {318Logger.logWarn(LOG_TAG, "setStateFailed for " + getCommandIdAndLabelLogString() + " resultData encountered an error.");319}320
321return setState(ExecutionState.FAILED);322}323
324public synchronized boolean shouldNotProcessResults() {325if (processingResultsAlreadyCalled) {326return true;327} else {328processingResultsAlreadyCalled = true;329return false;330}331}332
333public synchronized boolean isStateFailed() {334if (currentState != ExecutionState.FAILED)335return false;336
337if (!resultData.isStateFailed()) {338Logger.logWarn(LOG_TAG, "The " + getCommandIdAndLabelLogString() + " has an invalid errCode value set in errors list while having ExecutionState.FAILED state.\n" + resultData.errorsList);339return false;340} else {341return true;342}343}344
345
346@NonNull347@Override348public String toString() {349if (!hasExecuted())350return getExecutionInputLogString(this, true, true);351else {352return getExecutionOutputLogString(this, true, true, true);353}354}355
356/**357* Get a log friendly {@link String} for {@link ExecutionCommand} execution input parameters.
358*
359* @param executionCommand The {@link ExecutionCommand} to convert.
360* @param ignoreNull Set to {@code true} if non-critical {@code null} values are to be ignored.
361* @param logStdin Set to {@code true} if {@link #stdin} should be logged.
362* @return Returns the log friendly {@link String}.
363*/
364public static String getExecutionInputLogString(final ExecutionCommand executionCommand, boolean ignoreNull, boolean logStdin) {365if (executionCommand == null) return "null";366
367StringBuilder logString = new StringBuilder();368
369logString.append(executionCommand.getCommandIdAndLabelLogString()).append(":");370
371if (executionCommand.mPid != -1)372logString.append("\n").append(executionCommand.getPidLogString());373
374if (executionCommand.previousState != ExecutionState.PRE_EXECUTION)375logString.append("\n").append(executionCommand.getPreviousStateLogString());376logString.append("\n").append(executionCommand.getCurrentStateLogString());377
378logString.append("\n").append(executionCommand.getExecutableLogString());379logString.append("\n").append(executionCommand.getArgumentsLogString());380logString.append("\n").append(executionCommand.getWorkingDirectoryLogString());381logString.append("\n").append(executionCommand.getRunnerLogString());382logString.append("\n").append(executionCommand.getIsFailsafeLogString());383
384if (Runner.APP_SHELL.equalsRunner(executionCommand.runner)) {385if (logStdin && (!ignoreNull || !DataUtils.isNullOrEmpty(executionCommand.stdin)))386logString.append("\n").append(executionCommand.getStdinLogString());387
388if (!ignoreNull || executionCommand.backgroundCustomLogLevel != null)389logString.append("\n").append(executionCommand.getBackgroundCustomLogLevelLogString());390}391
392if (!ignoreNull || executionCommand.sessionAction != null)393logString.append("\n").append(executionCommand.getSessionActionLogString());394
395if (!ignoreNull || executionCommand.shellName != null) {396logString.append("\n").append(executionCommand.getShellNameLogString());397}398
399if (!ignoreNull || executionCommand.shellCreateMode != null) {400logString.append("\n").append(executionCommand.getShellCreateModeLogString());401}402
403logString.append("\n").append(executionCommand.getSetRunnerShellEnvironmentLogString());404
405if (!ignoreNull || executionCommand.commandIntent != null)406logString.append("\n").append(executionCommand.getCommandIntentLogString());407
408logString.append("\n").append(executionCommand.getIsPluginExecutionCommandLogString());409if (executionCommand.isPluginExecutionCommand)410logString.append("\n").append(ResultConfig.getResultConfigLogString(executionCommand.resultConfig, ignoreNull));411
412return logString.toString();413}414
415/**416* Get a log friendly {@link String} for {@link ExecutionCommand} execution output parameters.
417*
418* @param executionCommand The {@link ExecutionCommand} to convert.
419* @param ignoreNull Set to {@code true} if non-critical {@code null} values are to be ignored.
420* @param logResultData Set to {@code true} if {@link #resultData} should be logged.
421* @param logStdoutAndStderr Set to {@code true} if {@link ResultData#stdout} and {@link ResultData#stderr} should be logged.
422* @return Returns the log friendly {@link String}.
423*/
424public static String getExecutionOutputLogString(final ExecutionCommand executionCommand, boolean ignoreNull, boolean logResultData, boolean logStdoutAndStderr) {425if (executionCommand == null) return "null";426
427StringBuilder logString = new StringBuilder();428
429logString.append(executionCommand.getCommandIdAndLabelLogString()).append(":");430
431logString.append("\n").append(executionCommand.getPreviousStateLogString());432logString.append("\n").append(executionCommand.getCurrentStateLogString());433
434if (logResultData)435logString.append("\n").append(ResultData.getResultDataLogString(executionCommand.resultData, logStdoutAndStderr));436
437return logString.toString();438}439
440/**441* Get a log friendly {@link String} for {@link ExecutionCommand} with more details.
442*
443* @param executionCommand The {@link ExecutionCommand} to convert.
444* @return Returns the log friendly {@link String}.
445*/
446public static String getDetailedLogString(final ExecutionCommand executionCommand) {447if (executionCommand == null) return "null";448
449StringBuilder logString = new StringBuilder();450
451logString.append(getExecutionInputLogString(executionCommand, false, true));452logString.append(getExecutionOutputLogString(executionCommand, false, true, true));453
454logString.append("\n").append(executionCommand.getCommandDescriptionLogString());455logString.append("\n").append(executionCommand.getCommandHelpLogString());456logString.append("\n").append(executionCommand.getPluginAPIHelpLogString());457
458return logString.toString();459}460
461/**462* Get a markdown {@link String} for {@link ExecutionCommand}.
463*
464* @param executionCommand The {@link ExecutionCommand} to convert.
465* @return Returns the markdown {@link String}.
466*/
467public static String getExecutionCommandMarkdownString(final ExecutionCommand executionCommand) {468if (executionCommand == null) return "null";469
470if (executionCommand.commandLabel == null) executionCommand.commandLabel = "Execution Command";471
472StringBuilder markdownString = new StringBuilder();473
474markdownString.append("## ").append(executionCommand.commandLabel).append("\n");475
476if (executionCommand.mPid != -1)477markdownString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("Pid", executionCommand.mPid, "-"));478
479markdownString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("Previous State", executionCommand.previousState.getName(), "-"));480markdownString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("Current State", executionCommand.currentState.getName(), "-"));481
482markdownString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("Executable", executionCommand.executable, "-"));483markdownString.append("\n").append(getArgumentsMarkdownString("Arguments", executionCommand.arguments));484markdownString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("Working Directory", executionCommand.workingDirectory, "-"));485markdownString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("Runner", executionCommand.runner, "-"));486markdownString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("isFailsafe", executionCommand.isFailsafe, "-"));487
488if (Runner.APP_SHELL.equalsRunner(executionCommand.runner)) {489if (!DataUtils.isNullOrEmpty(executionCommand.stdin))490markdownString.append("\n").append(MarkdownUtils.getMultiLineMarkdownStringEntry("Stdin", executionCommand.stdin, "-"));491if (executionCommand.backgroundCustomLogLevel != null)492markdownString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("Background Custom Log Level", executionCommand.backgroundCustomLogLevel, "-"));493}494
495markdownString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("Session Action", executionCommand.sessionAction, "-"));496
497markdownString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("Shell Name", executionCommand.shellName, "-"));498markdownString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("Shell Create Mode", executionCommand.shellCreateMode, "-"));499markdownString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("Set Shell Command Shell Environment", executionCommand.setShellCommandShellEnvironment, "-"));500
501markdownString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("isPluginExecutionCommand", executionCommand.isPluginExecutionCommand, "-"));502
503markdownString.append("\n\n").append(ResultConfig.getResultConfigMarkdownString(executionCommand.resultConfig));504
505markdownString.append("\n\n").append(ResultData.getResultDataMarkdownString(executionCommand.resultData));506
507if (executionCommand.commandDescription != null || executionCommand.commandHelp != null) {508if (executionCommand.commandDescription != null)509markdownString.append("\n\n### Command Description\n\n").append(executionCommand.commandDescription).append("\n");510if (executionCommand.commandHelp != null)511markdownString.append("\n\n### Command Help\n\n").append(executionCommand.commandHelp).append("\n");512markdownString.append("\n##\n");513}514
515if (executionCommand.pluginAPIHelp != null) {516markdownString.append("\n\n### Plugin API Help\n\n").append(executionCommand.pluginAPIHelp);517markdownString.append("\n##\n");518}519
520return markdownString.toString();521}522
523
524public String getIdLogString() {525if (id != null)526return "(" + id + ") ";527else528return "";529}530
531public String getPidLogString() {532return "Pid: `" + mPid + "`";533}534
535public String getCurrentStateLogString() {536return "Current State: `" + currentState.getName() + "`";537}538
539public String getPreviousStateLogString() {540return "Previous State: `" + previousState.getName() + "`";541}542
543public String getCommandLabelLogString() {544if (commandLabel != null && !commandLabel.isEmpty())545return commandLabel;546else547return "Execution Command";548}549
550public String getCommandIdAndLabelLogString() {551return getIdLogString() + getCommandLabelLogString();552}553
554public String getExecutableLogString() {555return "Executable: `" + executable + "`";556}557
558public String getArgumentsLogString() {559return getArgumentsLogString("Arguments", arguments);560}561
562public String getWorkingDirectoryLogString() {563return "Working Directory: `" + workingDirectory + "`";564}565
566public String getRunnerLogString() {567return Logger.getSingleLineLogStringEntry("Runner", runner, "-");568}569
570public String getIsFailsafeLogString() {571return "isFailsafe: `" + isFailsafe + "`";572}573
574public String getStdinLogString() {575if (DataUtils.isNullOrEmpty(stdin))576return "Stdin: -";577else578return Logger.getMultiLineLogStringEntry("Stdin", stdin, "-");579}580
581public String getBackgroundCustomLogLevelLogString() {582return "Background Custom Log Level: `" + backgroundCustomLogLevel + "`";583}584
585public String getSessionActionLogString() {586return Logger.getSingleLineLogStringEntry("Session Action", sessionAction, "-");587}588
589public String getShellNameLogString() {590return Logger.getSingleLineLogStringEntry("Shell Name", shellName, "-");591}592
593public String getShellCreateModeLogString() {594return Logger.getSingleLineLogStringEntry("Shell Create Mode", shellCreateMode, "-");595}596
597public String getSetRunnerShellEnvironmentLogString() {598return "Set Shell Command Shell Environment: `" + setShellCommandShellEnvironment + "`";599}600
601public String getCommandDescriptionLogString() {602return Logger.getSingleLineLogStringEntry("Command Description", commandDescription, "-");603}604
605public String getCommandHelpLogString() {606return Logger.getSingleLineLogStringEntry("Command Help", commandHelp, "-");607}608
609public String getPluginAPIHelpLogString() {610return Logger.getSingleLineLogStringEntry("Plugin API Help", pluginAPIHelp, "-");611}612
613public String getCommandIntentLogString() {614if (commandIntent == null)615return "Command Intent: -";616else617return Logger.getMultiLineLogStringEntry("Command Intent", IntentUtils.getIntentString(commandIntent), "-");618}619
620public String getIsPluginExecutionCommandLogString() {621return "isPluginExecutionCommand: `" + isPluginExecutionCommand + "`";622}623
624
625/**626* Get a log friendly {@link String} for {@link List<String>} argumentsArray.
627* If argumentsArray are null or of size 0, then `Arguments: -` is returned. Otherwise
628* following format is returned:
629*
630* Arguments:
631* ```
632* Arg 1: `value`
633* Arg 2: 'value`
634* ```
635*
636* @param argumentsArray The {@link String[]} argumentsArray to convert.
637* @return Returns the log friendly {@link String}.
638*/
639public static String getArgumentsLogString(String label, final String[] argumentsArray) {640StringBuilder argumentsString = new StringBuilder(label + ":");641
642if (argumentsArray != null && argumentsArray.length != 0) {643argumentsString.append("\n```\n");644for (int i = 0; i != argumentsArray.length; i++) {645argumentsString.append(Logger.getSingleLineLogStringEntry("Arg " + (i + 1),646DataUtils.getTruncatedCommandOutput(argumentsArray[i], Logger.LOGGER_ENTRY_MAX_SAFE_PAYLOAD / 5, true, false, true),647"-")).append("\n");648}649argumentsString.append("```");650} else{651argumentsString.append(" -");652}653
654return argumentsString.toString();655}656
657/**658* Get a markdown {@link String} for {@link String[]} argumentsArray.
659* If argumentsArray are null or of size 0, then `**Arguments:** -` is returned. Otherwise
660* following format is returned:
661*
662* **Arguments:**
663*
664* **Arg 1:**
665* ```
666* value
667* ```
668* **Arg 2:**
669* ```
670* value
671*```
672*
673* @param argumentsArray The {@link String[]} argumentsArray to convert.
674* @return Returns the markdown {@link String}.
675*/
676public static String getArgumentsMarkdownString(String label, final String[] argumentsArray) {677StringBuilder argumentsString = new StringBuilder("**" + label + ":**");678
679if (argumentsArray != null && argumentsArray.length != 0) {680argumentsString.append("\n");681for (int i = 0; i != argumentsArray.length; i++) {682argumentsString.append(MarkdownUtils.getMultiLineMarkdownStringEntry("Arg " + (i + 1), argumentsArray[i], "-")).append("\n");683}684} else{685argumentsString.append(" - ");686}687
688return argumentsString.toString();689}690
691}
692