blob: 6b2fb71bee91dc72328b37e9092b2c1ae9c740b9 [file] [log] [blame]
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;
}
}
}