/*
* Copyright (C) 2013 Andrey Chaschev.
*
* 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 bear.core;
import bear.console.ConsoleCallbackResult;
import bear.console.ConsoleCallbackResultType;
import bear.plugins.sh.GenericUnixRemoteEnvironmentPlugin;
import bear.ssh.MyStreamCopier;
import chaschev.util.Exceptions;
import com.google.common.base.Optional;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
/**
* Generally, this is a big mess. What could be improved? Copiers could be run in a single thread.
*
* @author Andrey Chaschev chaschev@gmail.com
*/
public abstract class AbstractConsole extends bear.console.AbstractConsole.Terminal {
private static final Logger logger = LoggerFactory.getLogger(GenericUnixRemoteEnvironmentPlugin.RemoteConsole.class);
public static abstract class Listener {
protected AbstractConsole console;
@Nonnull
protected abstract ConsoleCallbackResult textAdded(String textAdded, MarkedBuffer buffer) throws Exception;
}
Listener listener;
OutputStream out;
Closeable shutdownTrigger;
/**
* There are basically two copiers: for stdout and stderr which copy everything to out.
*/
List<MyStreamCopier> copiers = new ArrayList<MyStreamCopier>();
List<MarkedBuffer> buffers = new ArrayList<MarkedBuffer>();
List<Future> futures = new ArrayList<Future>();
protected volatile boolean finished = false;
protected volatile transient ConsoleCallbackResult lastCallbackResult;
protected volatile transient ConsoleCallbackResult lastError;
protected AbstractConsole(Listener listener, Closeable shutdownTrigger) {
this.listener = listener;
this.shutdownTrigger = shutdownTrigger;
listener.console = this;
}
public void println(String s) {
print(s + "\n");
}
public void print(String s) {
try {
out.write(s.getBytes());
out.flush();
} catch (IOException e) {
throw Exceptions.runtime(e);
}
}
public AbstractConsole addInputStream(InputStream is) {
return addInputStream(is, false);
}
public static final class OpenBAOS extends ByteArrayOutputStream{
public byte[] getBuffer(){
return buf;
}
public int getLength(){
return count;
}
}
public AbstractConsole addInputStream(InputStream is, boolean stdErr) {
final OpenBAOS baos = new OpenBAOS();
final MyStreamCopier copier = new MyStreamCopier(is, baos, stdErr);
final MarkedBuffer buffer = new MarkedBuffer(stdErr);
copiers.add(copier);
buffers.add(buffer);
copier.listener(new MyStreamCopier.Listener() {
@Override
public void reportProgress(long transferred, byte[] buf, int read) throws Exception {
synchronized (baos){
buffer.progress(baos.getBuffer(), baos.getLength());
}
LoggerFactory.getLogger("log").trace("appended to buffer: {}", buffer.interimText());
lastCallbackResult = listener.textAdded(buffer.interimText(), buffer);
if(lastCallbackResult.type == ConsoleCallbackResultType.EXCEPTION){
lastError = lastCallbackResult;
// PlayPlugin.logger.debug("OOOOOOOOOOOOPS - set error!!");
}
switch (lastCallbackResult.type) {
case DONE:
case EXCEPTION:
case FINISHED:
stopStreamCopiersGracefully();
IOUtils.closeQuietly(shutdownTrigger);
break;
}
buffer.markInterim();
}
});
return this;
}
@Override
public void finishWithResult(ConsoleCallbackResult callbackResult){
lastCallbackResult = callbackResult;
stopStreamCopiersGracefully();
IOUtils.closeQuietly(shutdownTrigger);
if(callbackResult.type.isError()){
lastError = callbackResult;
}
}
@Override
public boolean isDone(){
return finished;
}
public void stopStreamCopiersGracefully() {
// logger.debug("OOOOOOOOOOOOPS - stopStreamCopiersGracefully");
finished = true;
for (MyStreamCopier copier : copiers) {
copier.stop();
}
}
public void stopStreamCopiers() {
// logger.debug("OOOOOOOOOOOOPS - stopStreamCopiers", new Exception());
for (int i = 0; i < copiers.size(); i++) {
copiers.get(i).stop();
final Future future = futures.get(i);
if (!future.isDone()) {
future.cancel(true);
}
}
}
public boolean awaitStreamCopiers(long duration, TimeUnit unit) {
// logger.debug("OOOOOOOOOOOOPS - awaitStreamCopiers");
long periodNs = NANOSECONDS.convert(duration, unit) / 9;
if (periodNs == 0) {
periodNs = 1;
}
long sleepMs = MILLISECONDS.convert(periodNs, NANOSECONDS);
long sleepNano = periodNs - NANOSECONDS.convert(sleepMs, MILLISECONDS);
final long durationMs = unit.toMillis(duration);
long startedAt = System.currentTimeMillis();
for (MyStreamCopier copier : copiers) {
copier.setFinishAtMs(startedAt + durationMs);
}
while (true) {
try {
for (MyStreamCopier copier : copiers) {
copier.triggerCopy();
}
final long now = System.currentTimeMillis();
long timeElapsedMs = now - startedAt;
if (allFinished()) {
return true;
}
if (timeElapsedMs > durationMs) {
return false;
}
Thread.sleep(sleepMs, (int) sleepNano);
} catch (InterruptedException e) {
throw Exceptions.runtime(e);
}
}
}
public boolean allFinished() {
boolean allFinished = true;
for (MyStreamCopier copier : copiers) {
if (!copier.isFinished()) {
allFinished = false;
break;
}
}
return allFinished;
}
public void setOut(OutputStream out) {
this.out = out;
}
public AbstractConsole bufSize(int size) {
for (MyStreamCopier copier : copiers) {
copier.bufSize(size);
}
return this;
}
public AbstractConsole spawn(ExecutorService service) {
for (MyStreamCopier copier : copiers) {
futures.add(copier.spawn(service, -1));
}
return this;
}
public AbstractConsole spawn(ExecutorService service, int timeout, TimeUnit unit) {
for (MyStreamCopier copier : copiers) {
futures.add(copier.spawn(service, System.currentTimeMillis() + unit.toMillis(timeout)));
}
return this;
}
public StringBuilder concatOutputs() {
StringBuilder sb = new StringBuilder(buffers.get(0).length() + 20);
for (int i = 0; i < buffers.size(); i++) {
MarkedBuffer buffer = buffers.get(i);
sb.append(buffer.wholeText());
if (i != buffers.size() - 1) {
return sb.append("\n");
}
}
return sb;
}
public Optional<ConsoleCallbackResult> getLastCallbackResult() {
return Optional.fromNullable(lastCallbackResult);
}
public Optional<ConsoleCallbackResult> getLastError() {
return Optional.fromNullable(lastError);
}
}