001: /*
002: * This is free software, licensed under the Gnu Public License (GPL)
003: * get a copy from <http://www.gnu.org/licenses/gpl.html>
004: *
005: * author: Henner Zeller <H.Zeller@acm.org>
006: */
007: package henplus.commands;
008:
009: import java.io.InputStream;
010: import java.io.IOException;
011: import java.util.Iterator;
012:
013: import henplus.HenPlus;
014: import henplus.OutputDevice;
015: import henplus.Interruptable;
016: import henplus.SQLSession;
017: import henplus.AbstractCommand;
018: import henplus.CommandDispatcher;
019: import henplus.SigIntHandler;
020:
021: /**
022: * This command executes stuff on the shell. Supports the most common
023: * shell commands for convenience.
024: */
025: public class ShellCommand extends AbstractCommand implements
026: Interruptable {
027: Thread _myThread;
028:
029: /**
030: * returns the command-strings this command can handle.
031: */
032: public String[] getCommandList() {
033: return new String[] { "system", "!" };
034: }
035:
036: /**
037: * shell commands always have the semicolon as special character.
038: */
039: /*
040: public boolean isComplete(String command) {
041: return (!command.endsWith(";"));
042: }
043: */
044:
045: public boolean requiresValidSession(String cmd) {
046: return false;
047: }
048:
049: /**
050: * filename completion by default.
051: */
052: public Iterator complete(CommandDispatcher disp,
053: String partialCommand, String lastWord) {
054: return new FileCompletionIterator(partialCommand, lastWord);
055: }
056:
057: public void interrupt() {
058: _myThread.interrupt();
059: }
060:
061: /**
062: * execute the command given.
063: */
064: public int execute(SQLSession session, String cmd, String param) {
065: if (param.trim().length() == 0) {
066: return SYNTAX_ERROR;
067: }
068: Process p = null;
069: IOHandler ioHandler = null;
070: int exitStatus = -1;
071: _myThread = Thread.currentThread();
072: SigIntHandler.getInstance().pushInterruptable(this );
073: try {
074: try {
075: p = Runtime.getRuntime().exec(
076: new String[] { "sh", "-c", param });
077: ioHandler = new IOHandler(p);
078: } catch (IOException e) {
079: ioHandler.stop();
080: return EXEC_FAILED;
081: }
082:
083: exitStatus = p.waitFor();
084: } catch (InterruptedException e) {
085: p.destroy();
086: HenPlus.msg().println("Shell command interrupted.");
087: }
088: ioHandler.stop();
089: HenPlus.msg().attributeGrey();
090: HenPlus.msg().println("[exit " + exitStatus + "]");
091: HenPlus.msg().attributeReset();
092: return SUCCESS;
093: }
094:
095: // -- description
096: public String getShortDescription() {
097: return "execute system commands";
098: }
099:
100: public String getSynopsis(String cmd) {
101: return cmd + " <system-shell-commandline>";
102: }
103:
104: /**
105: * provide a long description just in case the user types
106: * 'help ls'.
107: */
108: public String getLongDescription(String cmd) {
109: return "\tExecute a system command in the shell. You can only invoke\n"
110: + "\tcommands, that do not expect anything from stdin: the\n"
111: + "\tinteractive input from HenPlus is disconnected from the\n"
112: + "\tsubprocess' stdin. But this is useful to call some small\n"
113: + "\tcommands in the middle of the session. There are two syntaxes\n"
114: + "\tsupported: system <command> or even shorter with the\n"
115: + "\texclamation mark: !<command>.\n"
116: + "\tExample:\n"
117: + "\t!ls";
118: }
119:
120: //-------- Helper class to handle the output of an process.
121:
122: /**
123: * The output handler handles the output streams from the process.
124: */
125: private static class IOHandler {
126: //private final Thread stdinThread;
127: private final Thread stdoutThread;
128: private final Thread stderrThread;
129: private final Process process;
130: private volatile boolean running;
131:
132: public IOHandler(Process p) throws IOException {
133: this .process = p;
134: stdoutThread = new Thread(new CopyWorker(
135: p.getInputStream(), HenPlus.out()));
136: stdoutThread.setDaemon(true);
137: stderrThread = new Thread(new CopyWorker(
138: p.getErrorStream(), HenPlus.msg()));
139: stderrThread.setDaemon(true);
140: /*
141: stdinThread = new Thread(new CopyWorker(System.in,
142: p.getOutputStream()));
143: stdinThread.setDaemon(true);
144: */
145: p.getOutputStream().close();
146: running = true;
147: start();
148: }
149:
150: private void start() {
151: stdoutThread.start();
152: stderrThread.start();
153: //stdinThread.start();
154: }
155:
156: public void stop() {
157: synchronized (this ) {
158: running = false;
159: }
160: //stdinThread.interrupt(); // this does not work for blocked IO!
161: try {
162: stdoutThread.join();
163: } catch (InterruptedException e) {
164: }
165: try {
166: stderrThread.join();
167: } catch (InterruptedException e) {
168: }
169: //try { stdinThread.join(); } catch(InterruptedException e) {}
170: try {
171: process.getInputStream().close();
172: } catch (IOException e) {
173: }
174: try {
175: process.getErrorStream().close();
176: } catch (IOException e) {
177: }
178: }
179:
180: /**
181: * Thread, that copies from an input stream to an output stream
182: * until EOF is reached.
183: */
184: private class CopyWorker implements Runnable {
185: InputStream source;
186: OutputDevice dest;
187:
188: public CopyWorker(InputStream source, OutputDevice dest) {
189: this .source = source;
190: this .dest = dest;
191: }
192:
193: public void run() {
194: byte[] buf = new byte[256];
195: int r;
196: try {
197: /*
198: * some sort of 'select' would be good here.
199: */
200: while ((running || source.available() > 0)
201: && (r = source.read(buf)) > 0) {
202: dest.write(buf, 0, r);
203: }
204: dest.flush();
205: } catch (IOException ignore_me) {
206: }
207: }
208: }
209: }
210: }
211:
212: /*
213: * Local variables:
214: * c-basic-offset: 4
215: * compile-command: "ant -emacs -find build.xml"
216: * End:
217: */
|