/*
* Copyright (C) 2017 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 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 StreamGobbler
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.
*
* 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). *
* 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: *
* If you do not call the StreamGobbler's read()
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.
*
* 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. * * @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; } } }