OSDN Git Service

New command: ExecCommand (for execute commands)
authorjruesga <jorge@ruesga.com>
Fri, 12 Oct 2012 00:19:52 +0000 (02:19 +0200)
committerjruesga <jorge@ruesga.com>
Fri, 12 Oct 2012 00:19:52 +0000 (02:19 +0200)
13 files changed:
res/xml/command_list.xml
src/com/cyanogenmod/explorer/commands/ExecExecutable.java [new file with mode: 0644]
src/com/cyanogenmod/explorer/commands/ExecutableCreator.java
src/com/cyanogenmod/explorer/commands/shell/AsyncResultProgram.java
src/com/cyanogenmod/explorer/commands/shell/AsyncResultProgramListener.java
src/com/cyanogenmod/explorer/commands/shell/ExecCommand.java [new file with mode: 0644]
src/com/cyanogenmod/explorer/commands/shell/FindCommand.java
src/com/cyanogenmod/explorer/commands/shell/FolderUsageCommand.java
src/com/cyanogenmod/explorer/commands/shell/ShellExecutableCreator.java
src/com/cyanogenmod/explorer/console/shell/ShellConsole.java
src/com/cyanogenmod/explorer/util/CommandHelper.java
tests/src/com/cyanogenmod/explorer/commands/shell/ExecCommandTest.java [new file with mode: 0644]
tests/src/com/cyanogenmod/explorer/commands/shell/LinkCommandTest.java

index 8161115..ef91382 100644 (file)
@@ -44,6 +44,7 @@
   <command commandId="diskusageall" commandPath="/system/bin/df" commandArgs="" />
   <command commandId="dirname" commandPath="/system/xbin/dirname" commandArgs="%1$s" />
   <command commandId="echo" commandPath="/system/xbin/echo" commandArgs="%1$s" />
+  <command commandId="exec" commandPath="/system/bin/sh" commandArgs="-c %1$s" />
   <command commandId="fileinfo" commandPath="/system/bin/ls" commandArgs="-ald %1$s" />
   <command commandId="find" commandPath="/system/xbin/find" commandArgs="%1$s \\( -name %2$s -o -name %3$s -o -name %4$s -o -name %5$s -o -name %6$s \\) -exec /system/xbin/echo {} \\; -exec /system/bin/ls -ald {} \\;" />
   <command commandId="folderusage" commandPath="/system/bin/ls" commandArgs="-alR %1$s" />
diff --git a/src/com/cyanogenmod/explorer/commands/ExecExecutable.java b/src/com/cyanogenmod/explorer/commands/ExecExecutable.java
new file mode 100644 (file)
index 0000000..d743ace
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2012 The CyanogenMod 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 com.cyanogenmod.explorer.commands;
+
+/**
+ * An interface that represents an executable for execute a command
+ */
+public interface ExecExecutable extends AsyncResultExecutable {
+
+    /**
+     * Method that returns the exit code of the executed program
+     * 
+     * @return int The exit code
+     */
+    public int getExitCode();
+}
index 5a4f2f3..4e61f57 100644 (file)
@@ -158,6 +158,17 @@ public interface ExecutableCreator {
     EchoExecutable createEchoExecutable(String msg) throws CommandNotFoundException;
 
     /**
+     * Method that execute a command
+     *
+     * @param cmd The command to execute
+     * @param asyncResultListener The listener where to return partial results
+     * @return ExecExecutable A {@link ExecExecutable} executable implementation reference
+     * @throws CommandNotFoundException If the executable can't be created
+     */
+    ExecExecutable createExecExecutable(
+            String cmd, AsyncResultListener asyncResultListener) throws CommandNotFoundException;
+
+    /**
      * Method that creates an executable for make searches over the filesystem.
      *
      * @param directory The directory where to search
index e765369..480f7f1 100644 (file)
@@ -31,12 +31,25 @@ import java.util.List;
 public abstract class AsyncResultProgram
     extends Program implements AsyncResultExecutable, AsyncResultProgramListener {
 
+    /**
+     * @hide
+     */
+    static final Byte STDIN = new Byte((byte)0);
+    /**
+     * @hide
+     */
+    static final Byte STDERR = new Byte((byte)1); 
+
     private final AsyncResultListener mAsyncResultListener;
     private AsyncResultProgramThread mWorkerThread;
     /**
      * @hide
      */
     final List<String> mPartialData;
+    /**
+     * @hide
+     */
+    final List<Byte> mPartialDataType;
     private final Object mSync = new Object();
     /**
      * @hide
@@ -79,6 +92,7 @@ public abstract class AsyncResultProgram
         super(id, prepare, args);
         this.mAsyncResultListener = asyncResultListener;
         this.mPartialData = Collections.synchronizedList(new ArrayList<String>());
+        this.mPartialDataType = Collections.synchronizedList(new ArrayList<Byte>());
         this.mTempBuffer = new StringBuffer();
         this.mOnCancelListener = null;
         this.mCanceled = false;
@@ -158,6 +172,35 @@ public abstract class AsyncResultProgram
                 data = this.mTempBuffer.append(partialIn.substring(0, pos + 1)).toString();
             }
 
+            this.mPartialDataType.add(STDIN);
+            this.mPartialData.add(data);
+            this.mTempBuffer = new StringBuffer();
+            this.mSync.notify();
+        }
+    }
+    
+    /**
+     * Method that parse the error result of a program invocation.
+     *
+     * @param partialErr A partial standard err buffer (incremental buffer)
+     * @hide
+     */
+    public final void parsePartialErrResult(String partialErr) {
+        synchronized (this.mSync) {
+            String data = partialErr;
+            if (parseOnlyCompleteLines()) {
+                int pos = partialErr.lastIndexOf(FileHelper.NEWLINE);
+                if (pos == -1) {
+                    //Save partial data
+                    this.mTempBuffer.append(partialErr);
+                    return;
+                }
+
+                //Retrieve the data
+                data = this.mTempBuffer.append(partialErr.substring(0, pos + 1)).toString();
+            }
+
+            this.mPartialDataType.add(STDERR);
             this.mPartialData.add(data);
             this.mTempBuffer = new StringBuffer();
             this.mSync.notify();
@@ -265,9 +308,14 @@ public abstract class AsyncResultProgram
                            if (!this.mAlive) {
                                return;
                            }
+                           Byte type = AsyncResultProgram.this.mPartialDataType.remove(0);
                            String data = AsyncResultProgram.this.mPartialData.remove(0);
                            try {
-                               AsyncResultProgram.this.onParsePartialResult(data);
+                               if (type.compareTo(STDIN) == 0) {
+                                   AsyncResultProgram.this.onParsePartialResult(data);
+                               } else {
+                                   AsyncResultProgram.this.onParseErrorPartialResult(data);
+                               }
                            } catch (Throwable ex) {
                                /**NON BLOCK**/
                            }
index 9e4e058..dc57cfc 100644 (file)
@@ -40,4 +40,11 @@ public interface AsyncResultProgramListener {
      * @param partialIn A partial standard input buffer (incremental buffer)
      */
     void onParsePartialResult(String partialIn);
+    
+    /**
+     * Method invoked when a parse of new error results are needed.
+     *
+     * @param partialErr A partial standard err buffer (incremental buffer)
+     */
+    void onParseErrorPartialResult(String partialErr);
 }
diff --git a/src/com/cyanogenmod/explorer/commands/shell/ExecCommand.java b/src/com/cyanogenmod/explorer/commands/shell/ExecCommand.java
new file mode 100644 (file)
index 0000000..dff4c36
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2012 The CyanogenMod 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 com.cyanogenmod.explorer.commands.shell;
+
+import com.cyanogenmod.explorer.commands.AsyncResultListener;
+import com.cyanogenmod.explorer.commands.ExecExecutable;
+import com.cyanogenmod.explorer.console.CommandNotFoundException;
+import com.cyanogenmod.explorer.console.ExecutionException;
+import com.cyanogenmod.explorer.console.InsufficientPermissionsException;
+
+/**
+ * A class for execute a command
+ *
+ * {@link "http://unixhelp.ed.ac.uk/CGI/man-cgi?sh"}
+ */
+public class ExecCommand extends AsyncResultProgram implements ExecExecutable {
+
+    private static final String ID = "exec"; //$NON-NLS-1$
+
+    private int mExitCode;
+
+    /**
+     * Constructor of <code>ExecCommand</code>.
+     *
+     * @param cmd The "absolute" directory to compute
+     * @param asyncResultListener The partial result listener
+     * @throws InvalidCommandDefinitionException If the command has an invalid definition
+     */
+    public ExecCommand(
+            String cmd, AsyncResultListener asyncResultListener)
+            throws InvalidCommandDefinitionException {
+        super(ID, asyncResultListener, new String[]{cmd});
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onStartParsePartialResult() {
+        this.mExitCode = 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onEndParsePartialResult(boolean cancelled) {/** NON BLOCK **/}
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onParsePartialResult(final String partialIn) {
+        //If a listener is defined, then send the partial result
+        if (partialIn != null && partialIn.length() > 0) {
+            if (getAsyncResultListener() != null) {
+                getAsyncResultListener().onPartialResult(partialIn);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onParseErrorPartialResult(String partialErr) {
+        //If a listener is defined, then send the partial result
+        if (partialErr != null && partialErr.length() > 0) {
+            if (getAsyncResultListener() != null) {
+                getAsyncResultListener().onPartialResult(partialErr);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getExitCode() {
+        return this.mExitCode;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isIgnoreShellStdErrCheck() {
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void checkExitCode(int exitCode)
+            throws InsufficientPermissionsException, CommandNotFoundException, ExecutionException {
+        this.mExitCode = exitCode;
+    }
+}
index 7a2e3d0..11c237b 100644 (file)
@@ -193,6 +193,12 @@ public class FindCommand extends AsyncResultProgram implements FindExecutable {
      * {@inheritDoc}
      */
     @Override
+    public void onParseErrorPartialResult(String partialErr) {/**NON BLOCK**/}
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
     public boolean isIgnoreShellStdErrCheck() {
         return true;
     }
index b662c57..d2084c2 100644 (file)
@@ -46,7 +46,7 @@ public class FolderUsageCommand extends AsyncResultProgram implements FolderUsag
 
     private static final String TAG = "FolderUsageCommand"; //$NON-NLS-1$
 
-    private static final String ID_FOLDER_USAGE_DIRECTORY = "folderusage"; //$NON-NLS-1$
+    private static final String ID = "folderusage"; //$NON-NLS-1$
 
     private final String mDirectory;
     private FolderUsage mFolderUsage;
@@ -62,7 +62,7 @@ public class FolderUsageCommand extends AsyncResultProgram implements FolderUsag
     public FolderUsageCommand(
             String directory, AsyncResultListener asyncResultListener)
             throws InvalidCommandDefinitionException {
-        super(ID_FOLDER_USAGE_DIRECTORY, asyncResultListener, new String[]{directory});
+        super(ID, asyncResultListener, new String[]{directory});
         this.mFolderUsage = new FolderUsage(directory);
         this.mPartial = ""; //$NON-NLS-1$
         this.mDirectory = directory;
@@ -185,6 +185,12 @@ public class FolderUsageCommand extends AsyncResultProgram implements FolderUsag
      * {@inheritDoc}
      */
     @Override
+    public void onParseErrorPartialResult(String partialErr) {/**NON BLOCK**/}
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
     public boolean isIgnoreShellStdErrCheck() {
         return true;
     }
index 8e308e8..909f4bc 100644 (file)
@@ -28,6 +28,7 @@ import com.cyanogenmod.explorer.commands.DeleteDirExecutable;
 import com.cyanogenmod.explorer.commands.DeleteFileExecutable;
 import com.cyanogenmod.explorer.commands.DiskUsageExecutable;
 import com.cyanogenmod.explorer.commands.EchoExecutable;
+import com.cyanogenmod.explorer.commands.ExecExecutable;
 import com.cyanogenmod.explorer.commands.ExecutableCreator;
 import com.cyanogenmod.explorer.commands.FindExecutable;
 import com.cyanogenmod.explorer.commands.FolderUsageExecutable;
@@ -224,6 +225,19 @@ public class ShellExecutableCreator implements ExecutableCreator {
      * {@inheritDoc}
      */
     @Override
+    public ExecExecutable createExecExecutable(
+            String cmd, AsyncResultListener asyncResultListener) throws CommandNotFoundException {
+        try {
+            return new ExecCommand(cmd, asyncResultListener);
+        } catch (InvalidCommandDefinitionException icdEx) {
+            throw new CommandNotFoundException("ExecCommand", icdEx); //$NON-NLS-1$
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
     public FindExecutable createFindExecutable(
             String directory, Query query, AsyncResultListener asyncResultListener)
             throws CommandNotFoundException {
index 0f4c5e9..8e4b7e2 100644 (file)
@@ -724,6 +724,15 @@ public abstract class ShellConsole extends Console {
                         if (!ShellConsole.this.mCancelled) {
                             ShellConsole.this.mSbErr.append((char)r);
                             sb.append((char)r);
+                            
+                            //Notify asynchronous partial data
+                            if (ShellConsole.this.mStarted &&
+                                ShellConsole.this.mActiveCommand != null &&
+                                ShellConsole.this.mActiveCommand instanceof AsyncResultProgram) {
+                                AsyncResultProgram program =
+                                        ((AsyncResultProgram)ShellConsole.this.mActiveCommand);
+                                program.parsePartialErrResult(new String(new char[]{(char)r}));
+                            }
                         }
 
                         //Has more data? Read with available as more as exists
@@ -743,6 +752,14 @@ public abstract class ShellConsole extends Console {
                             ShellConsole.this.mSbErr.append(s);
                             sb.append(s);
 
+                            //Notify asynchronous partial data
+                            if (ShellConsole.this.mActiveCommand != null &&
+                                ShellConsole.this.mActiveCommand  instanceof AsyncResultProgram) {
+                                AsyncResultProgram program =
+                                        ((AsyncResultProgram)ShellConsole.this.mActiveCommand);
+                                program.parsePartialErrResult(s);
+                            }
+
                             //Wait for buffer to be filled
                             try {
                                 Thread.sleep(50L);
index f9793b8..4ea9dfa 100644 (file)
@@ -31,6 +31,7 @@ import com.cyanogenmod.explorer.commands.DeleteDirExecutable;
 import com.cyanogenmod.explorer.commands.DeleteFileExecutable;
 import com.cyanogenmod.explorer.commands.DiskUsageExecutable;
 import com.cyanogenmod.explorer.commands.EchoExecutable;
+import com.cyanogenmod.explorer.commands.ExecExecutable;
 import com.cyanogenmod.explorer.commands.Executable;
 import com.cyanogenmod.explorer.commands.FindExecutable;
 import com.cyanogenmod.explorer.commands.FolderUsageExecutable;
@@ -734,6 +735,40 @@ public final class CommandHelper {
     }
 
     /**
+     * Method that executes a command.
+     *
+     * @param context The current context (needed if console == null)
+     * @param cmd The command to execute
+     * @param asyncResultListener The partial result listener
+     * @param console The console in which execute the program.
+     * <code>null</code> to attach to the default console
+     * @return AsyncResultProgram The command executed in background
+     * @throws FileNotFoundException If the initial directory not exists
+     * @throws IOException If initial directory can't not be checked
+     * @throws InvalidCommandDefinitionException If the command has an invalid definition
+     * @throws NoSuchFileOrDirectory If the file or directory was not found
+     * @throws ConsoleAllocException If the console can't be allocated
+     * @throws InsufficientPermissionsException If an operation requires elevated permissions
+     * @throws CommandNotFoundException If the command was not found
+     * @throws OperationTimeoutException If the operation exceeded the maximum time of wait
+     * @throws ExecutionException If the operation returns a invalid exit code
+     * @see AsyncResultExecutable
+     */
+    public static AsyncResultExecutable exec(
+            Context context, String cmd, AsyncResultListener asyncResultListener, Console console)
+            throws FileNotFoundException, IOException, ConsoleAllocException,
+            NoSuchFileOrDirectory, InsufficientPermissionsException,
+            CommandNotFoundException, OperationTimeoutException,
+            ExecutionException, InvalidCommandDefinitionException {
+        Console c = ensureConsole(context, console);
+        ExecExecutable executable =
+                c.getExecutableFactory().newCreator().
+                    createExecExecutable(cmd, asyncResultListener);
+        execute(context, executable, c);
+        return executable;
+    }
+
+    /**
      * Method that does a search in a directory tree seeking for some terms.
      *
      * @param context The current context (needed if console == null)
diff --git a/tests/src/com/cyanogenmod/explorer/commands/shell/ExecCommandTest.java b/tests/src/com/cyanogenmod/explorer/commands/shell/ExecCommandTest.java
new file mode 100644 (file)
index 0000000..518630a
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2012 The CyanogenMod 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 com.cyanogenmod.explorer.commands.shell;
+
+import java.io.File;
+import java.io.FileWriter;
+
+import com.cyanogenmod.explorer.commands.AsyncResultListener;
+import com.cyanogenmod.explorer.util.CommandHelper;
+
+/**
+ * A class for testing exec command.
+ *
+ * @see ExecCommand
+ */
+public class ExecCommandTest extends AbstractConsoleTest {
+
+    private static final String EXEC_CMD = "/sdcard/source.sh"; //$NON-NLS-1$
+    private static final String EXEC_PROGRAM =
+            "#!/system/bin/sh\necho \"List of files:\"\nls -la\n"; //$NON-NLS-1$
+
+    /**
+     * @hide
+     */
+    final Object mSync = new Object();
+    /**
+     * @hide
+     */
+    boolean mNewPartialData;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isRootConsoleNeeded() {
+        return true;
+    }
+
+    /**
+     * Method that performs a test over known search results.
+     *
+     * @throws Exception If test failed
+     */
+    public void testFindWithPartialResult() throws Exception {
+        try {
+            // Create the test program
+            FileWriter fw = new FileWriter(new File(EXEC_CMD));
+            fw.write(EXEC_PROGRAM);
+            fw.close();
+
+            // Execute the test program
+            this.mNewPartialData = false;
+            CommandHelper.exec(getContext(), EXEC_CMD, new AsyncResultListener() {
+                public void onAsyncStart() {
+                    /**NON BLOCK**/
+                }
+                public void onAsyncEnd(boolean cancelled) {
+                    synchronized (ExecCommandTest.this.mSync) {
+                        ExecCommandTest.this.mSync.notifyAll();
+                    }
+                }
+                public void onException(Exception cause) {
+                    fail(cause.toString());
+                }
+                public void onPartialResult(Object results) {
+                    ExecCommandTest.this.mNewPartialData = true;
+                }
+            }, getConsole());
+            synchronized (ExecCommandTest.this.mSync) {
+                ExecCommandTest.this.mSync.wait(15000L);
+            }
+            assertTrue("no new partial data", this.mNewPartialData); //$NON-NLS-1$
+            
+        } finally {
+            try {
+                CommandHelper.deleteFile(getContext(), EXEC_CMD, getConsole());
+            } catch (Exception e) {/**NON BLOCK**/}
+        }
+    }
+
+}
index 318475f..f8ef322 100644 (file)
@@ -24,7 +24,7 @@ import com.cyanogenmod.explorer.util.CommandHelper;
 /**
  * A class for testing the {@link LinkCommandTest} command.
  *
- * @see DeleteFileCommand
+ * @see LinkCommand
  */
 public class LinkCommandTest extends AbstractConsoleTest {
 
@@ -42,7 +42,7 @@ public class LinkCommandTest extends AbstractConsoleTest {
     }
 
     /**
-     * Method that performs a test to delete a file.
+     * Method that performs a test to link to a file.
      *
      * @throws Exception If test failed
      */
@@ -66,7 +66,7 @@ public class LinkCommandTest extends AbstractConsoleTest {
     }
 
     /**
-     * Method that performs a test to delete an invalid file.
+     * Method that performs a test to link to an invalid file.
      *
      * @throws Exception If test failed
      */