package com.trilead.ssh2; | |
import com.googlecode.android_scripting.Log; | |
import java.io.File; | |
import java.io.FileOutputStream; | |
import java.io.IOException; | |
import java.io.InputStream; | |
/** | |
* A <code>StreamGobbler</code> is an InputStream that uses an internal worker thread to constantly | |
* consume input from another InputStream. It uses a buffer to store the consumed data. The buffer | |
* size is automatically adjusted, if needed. | |
* <p> | |
* This class is sometimes very convenient - if you wrap a session's STDOUT and STDERR InputStreams | |
* with instances of this class, then you don't have to bother about the shared window of STDOUT and | |
* STDERR in the low level SSH-2 protocol, since all arriving data will be immediatelly consumed by | |
* the worker threads. Also, as a side effect, the streams will be buffered (e.g., single byte | |
* read() operations are faster). | |
* <p> | |
* Other SSH for Java libraries include this functionality by default in their STDOUT and STDERR | |
* InputStream implementations, however, please be aware that this approach has also a downside: | |
* <p> | |
* If you do not call the StreamGobbler's <code>read()</code> method often enough and the peer is | |
* constantly sending huge amounts of data, then you will sooner or later encounter a low memory | |
* situation due to the aggregated data (well, it also depends on the Java heap size). Joe Average | |
* will like this class anyway - a paranoid programmer would never use such an approach. | |
* <p> | |
* The term "StreamGobbler" was taken from an article called "When Runtime.exec() won't", see | |
* http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html. | |
* | |
* @author Christian Plattner, plattner@trilead.com | |
* @version $Id: StreamGobbler.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ | |
*/ | |
public class StreamGobbler extends InputStream { | |
class GobblerThread extends Thread { | |
@Override | |
public void run() { | |
while (true) { | |
try { | |
byte[] saveBuffer = null; | |
int avail = is.read(buffer, write_pos, buffer.length - write_pos); | |
synchronized (synchronizer) { | |
if (avail <= 0) { | |
isEOF = true; | |
synchronizer.notifyAll(); | |
break; | |
} | |
write_pos += avail; | |
int space_available = buffer.length - write_pos; | |
if (space_available == 0) { | |
if (read_pos > 0) { | |
saveBuffer = new byte[read_pos]; | |
System.arraycopy(buffer, 0, saveBuffer, 0, read_pos); | |
System.arraycopy(buffer, read_pos, buffer, 0, buffer.length - read_pos); | |
write_pos -= read_pos; | |
read_pos = 0; | |
} else { | |
write_pos = 0; | |
saveBuffer = buffer; | |
} | |
} | |
synchronizer.notifyAll(); | |
} | |
writeToFile(saveBuffer); | |
} catch (IOException e) { | |
synchronized (synchronizer) { | |
exception = e; | |
synchronizer.notifyAll(); | |
break; | |
} | |
} | |
} | |
} | |
} | |
private InputStream is; | |
private GobblerThread t; | |
private Object synchronizer = new Object(); | |
private boolean isEOF = false; | |
private boolean isClosed = false; | |
private IOException exception = null; | |
private byte[] buffer; | |
private int read_pos = 0; | |
private int write_pos = 0; | |
private final FileOutputStream mLogStream; | |
private final int mBufferSize; | |
public StreamGobbler(InputStream is, File log, int buffer_size) { | |
this.is = is; | |
mBufferSize = buffer_size; | |
FileOutputStream out = null; | |
try { | |
out = new FileOutputStream(log, false); | |
} catch (IOException e) { | |
Log.e(e); | |
} | |
mLogStream = out; | |
buffer = new byte[mBufferSize]; | |
t = new GobblerThread(); | |
t.setDaemon(true); | |
t.start(); | |
} | |
public void writeToFile(byte[] buffer) { | |
if (mLogStream != null && buffer != null) { | |
try { | |
mLogStream.write(buffer); | |
} catch (IOException e) { | |
Log.e(e); | |
} | |
} | |
} | |
@Override | |
public int read() throws IOException { | |
synchronized (synchronizer) { | |
if (isClosed) { | |
throw new IOException("This StreamGobbler is closed."); | |
} | |
while (read_pos == write_pos) { | |
if (exception != null) { | |
throw exception; | |
} | |
if (isEOF) { | |
return -1; | |
} | |
try { | |
synchronizer.wait(); | |
} catch (InterruptedException e) { | |
} | |
} | |
int b = buffer[read_pos++] & 0xff; | |
return b; | |
} | |
} | |
@Override | |
public int available() throws IOException { | |
synchronized (synchronizer) { | |
if (isClosed) { | |
throw new IOException("This StreamGobbler is closed."); | |
} | |
return write_pos - read_pos; | |
} | |
} | |
@Override | |
public int read(byte[] b) throws IOException { | |
return read(b, 0, b.length); | |
} | |
@Override | |
public void close() throws IOException { | |
synchronized (synchronizer) { | |
if (isClosed) { | |
return; | |
} | |
isClosed = true; | |
isEOF = true; | |
synchronizer.notifyAll(); | |
is.close(); | |
} | |
} | |
@Override | |
public int read(byte[] b, int off, int len) throws IOException { | |
if (b == null) { | |
throw new NullPointerException(); | |
} | |
if ((off < 0) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0) || (off > b.length)) { | |
throw new IndexOutOfBoundsException(); | |
} | |
if (len == 0) { | |
return 0; | |
} | |
synchronized (synchronizer) { | |
if (isClosed) { | |
throw new IOException("This StreamGobbler is closed."); | |
} | |
while (read_pos == write_pos) { | |
if (exception != null) { | |
throw exception; | |
} | |
if (isEOF) { | |
return -1; | |
} | |
try { | |
synchronizer.wait(); | |
} catch (InterruptedException e) { | |
} | |
} | |
int avail = write_pos - read_pos; | |
avail = (avail > len) ? len : avail; | |
System.arraycopy(buffer, read_pos, b, off, avail); | |
read_pos += avail; | |
return avail; | |
} | |
} | |
} |