/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package vogar; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import java.io.File; import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.List; import java.util.Map; import vogar.tasks.Task; /** * A target runtime environment such as a remote device or the local host */ public abstract class Target { protected abstract ImmutableList targetProcessPrefix(); public abstract String getDeviceUserName(); public abstract List ls(File directory) throws FileNotFoundException; public abstract void await(File nonEmptyDirectory); public abstract void rm(File file); public abstract void mkdirs(File file); public abstract void forwardTcp(int port); public abstract void push(File local, File remote); public abstract void pull(File remote, File local); public final Task pushTask(final File local, final File remote) { return new Task("push " + remote) { @Override protected Result execute() throws Exception { push(local, remote); return Result.SUCCESS; } }; } public final Task rmTask(final File remote) { return new Task("rm " + remote) { @Override protected Result execute() throws Exception { rm(remote); return Result.SUCCESS; } }; } /** * Create a {@link ScriptBuilder} appropriate for this target. */ public ScriptBuilder newScriptBuilder() { return new ScriptBuilder(targetProcessPrefix()); } /** * Responsible for constructing a one line script appropriate for a specific target. */ public static class ScriptBuilder { private static final Joiner SCRIPT_JOINER = Joiner.on(" "); /** * Escape any special shell characters so that the target shell treats them as literal * characters. * *

e.g. an escaped space will not be treated as a token separator, an escaped dollar will * not cause shell substitution. */ @VisibleForTesting static String escape(String token) { int length = token.length(); StringBuilder builder = new StringBuilder(length); for (int i = 0; i < length; ++i) { char c = token.charAt(i); if (Character.isWhitespace(c) || c == '\'' || c == '\"' || c == '|' || c == '&' || c == '<' || c == '>' || c == '$' || c == '!' || c == '(' || c == ')') { builder.append('\\'); } builder.append(c); } return builder.toString(); } /** * The prefix to insert before the script to produce a command line that will execute the * script within a shell. */ private final ImmutableList commandLinePrefix; /** * The list of tokens making up the script, they were escaped where necessary before they * were added to the list. */ private final List escapedTokens; private ScriptBuilder(ImmutableList commandLinePrefix) { this.commandLinePrefix = commandLinePrefix; escapedTokens = new ArrayList<>(); } /** * Set the working directory in the target shell before running the command. */ public ScriptBuilder workingDirectory(File workingDirectory) { escapedTokens.add("cd"); escapedTokens.add(escape(workingDirectory.getPath())); escapedTokens.add("&&"); return this; } /** * Set inline environment variables on the target shell that will affect the command. */ public void env(Map env) { for (Map.Entry entry : env.entrySet()) { String name = entry.getKey(); String value = entry.getValue(); escapedTokens.add(name + "=" + escape(value)); } } /** * Add tokens to the script. * *

Each token is escaped before adding it to the list. This method is suitable for adding * the command line name and arguments. */ public ScriptBuilder tokens(List tokens) { for (String token : tokens) { escapedTokens.add(escape(token)); } return this; } /** * Add tokens to the script. * *

Each token is escaped before adding it to the list. This method is suitable for adding * the command line name and arguments. */ public ScriptBuilder tokens(String... tokens) { for (String token : tokens) { escapedTokens.add(escape(token)); } return this; } /** * Construct a command line to execute the script in the target shell. */ public List commandLine() { // Group the tokens into a single String argument. This is necessary for running in // a local shell where the first argument after the -c option is the script and the // remainder are treated as arguments to the script. This has no effect on either // adb or ssh shells as they both concatenate all their arguments into one single // string before parsing. String grouped = SCRIPT_JOINER.join(escapedTokens); return new ImmutableList.Builder() .addAll(commandLinePrefix) .add(grouped) .build(); } } }