/******************************************************************************* * Copyright (c) 2009 Cloudsmith Inc. and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Cloudsmith Inc. - initial API and implementation *******************************************************************************/ package org.eclipse.buckminster.util.progress; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.InetAddress; import java.net.Socket; import java.net.UnknownHostException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IProgressMonitorWithBlocking; import org.eclipse.core.runtime.IStatus; /** * Implementation of the {@link IProgressMonitorWithBlocking} interface that communicates with an instance * of an {@link ExternalProgressMonitor} via a communication protocol. This implementation can be used * either via arbitrary streams that has to be setup (e.g. can be used with System.out, System.in), or via * a socket port. The executing process must get the port from the parent process via the command line. * * If a prefix is used in the matching {@link ExternalProgressMonitor} the same prefix must be specified * when creating the proxy. * * <h4>Usage</h4> * Instantiate with a port, or two streams, and optionally set the prefix. * Then use this monitor as any other {@link IProgressMonitorWithBlocking} * * All operations on the proxy are send via the communication channel to the matching external monitor. * The external monitor can be canceled, and the running task should (as always) check {@link IProgressMonitor#isCanceled()} * and stop work when it returns true. The proxy will get the cancel flag updated each time the proxy reports * progress, but due to lag in the communication the flag may not have been set until the next time progress is * reported. * * @author henrik.lindberg@cloudsmith.com * */ public class ExternalProgressProxyMonitor implements IProgressMonitor, IProgressMonitorWithBlocking, ExternalProgressMonitorConstants { private boolean canceled; private PrintStream out; private Socket server; private BufferedReader reader; private String prefix; private boolean closeStreamsOnDone; /** * Create a socket based proxy on the port (obtained from the matching external monitor). * Does not use a prefix in the communication. * @param port * @throws UnknownHostException * @throws IOException */ public ExternalProgressProxyMonitor(int port) throws UnknownHostException, IOException { this(port, DEFAULT_PREFIX); } /** * Create a socket based proxy on the port (obtained from the matching external monitor), and * sets a prefix to use in communication. The same prefix must be used in the matching external monitor. * @param port * @throws UnknownHostException * @throws IOException */ public ExternalProgressProxyMonitor(int port, String prefixString) throws UnknownHostException, IOException { canceled = false; prefix = prefixString; server = new Socket(InetAddress.getLocalHost(), port); out = new PrintStream(server.getOutputStream()); closeStreamsOnDone = true; reader = new BufferedReader(new InputStreamReader(server.getInputStream())); process(); } /** * Creates a proxy that communicates over the two streams passed as arguments. * No prefix is used. * @param outStream * @param inStream */ public ExternalProgressProxyMonitor(PrintStream outStream, InputStream inStream) { this(outStream, inStream, DEFAULT_PREFIX); } /** * Creates a proxy that communicates over the two streams passed as arguments, and sets the * defined prefix. Note that the same prefix must be used in the matching external monitor. * @param outStream * @param inStream */ public ExternalProgressProxyMonitor(PrintStream outStream, final InputStream inStream, String prefixString) { canceled = false; prefix = prefixString; out = outStream; closeStreamsOnDone = false; reader = new BufferedReader(new InputStreamReader(inStream)); process(); } /** * Called internally to handle the communication. Runs in a separate thread. */ private void process() { // Read the input stream to listen for cancel message Runnable canceler = new Runnable() { public void run() { // System.err.print("Proxy command reader started\n"); String cancelTrue = prefix + SET_CANCELED + "t"; //$NON-NLS-1$ String cancelFalse = prefix + SET_CANCELED + "f"; //$NON-NLS-1$ try { String data; while ((data = reader.readLine()) != null) { // System.err.print("Proxy got: " + data + "\n"); if (data.equals(cancelTrue)) internalCancel(true); else if (data.equals(cancelFalse)) internalCancel(false); } } catch (IOException e) { // ignore } finally { try { reader.close(); } catch (IOException e) { // ignore } } // System.err.print("Proxy command reader stopped\n"); } }; new Thread(canceler).start(); } protected void internalCancel(boolean value) { canceled = value; } public void beginTask(String name, int totalWork) { report(BEGIN, totalWork, name); } public void done() { report(DONE); if (!closeStreamsOnDone) return; out.close(); try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } public void internalWorked(double work) { report(INTERNAL_WORKED, work); } public boolean isCanceled() { return canceled; } /** * Sends set cancel to parent process and sets this monitor to canceled. */ public void setCanceled(boolean value) { if (value != canceled) { canceled = value; report(SET_CANCELED, value); } } public void setTaskName(String name) { report(SET_TASK_NAME, name); } public void subTask(String name) { report(SUB_TASK, name); } public void worked(int work) { if (work == 1) report(WORKED_ONE); else report(WORKED, work); } public void clearBlocked() { report(CLEAR_BLOCKED); } public void setBlocked(IStatus reason) { report(SET_BLOCKED, reason.getMessage()); } private void report(char type, int value, String message) { message = message == null ? "" : message; //$NON-NLS-1$ synchronized (out) { out.print(prefix); out.print(type + Integer.toString(value) + "," + message + '\n'); //$NON-NLS-1$ out.flush(); } } private void report(char type) { synchronized (out) { out.print(prefix); out.print(type + "\n"); //$NON-NLS-1$ out.flush(); } } private void report(char type, double value) { synchronized (out) { out.print(prefix); out.print(type + Double.toString(value) + '\n'); out.flush(); } } private void report(char type, int value) { synchronized (out) { out.print(prefix); out.print(type + Integer.toString(value) + '\n'); out.flush(); } } private void report(char type, String value) { value = value == null ? "" : value; //$NON-NLS-1$ synchronized (out) { out.print(prefix); out.print(type + value + '\n'); out.flush(); } } private void report(char type, boolean value) { synchronized (out) { out.print(prefix); out.print(type + (value ? "t" : "f")); out.flush(); } } }