package net.sf.eclipsefp.haskell.util;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ProcessRunner implements IProcessRunner {
// private final IProcessFactory fProcessFactory;
private final static String STDOUT_REDIRECT = "output_redirect";
private final static String STDERR_REDIRECT = "error_redirect";
public static final String LIBRARY_VERSION="using version ((\\d|\\.)*) of the Cabal library";
public ProcessRunner() {
//this( new ProcessFactory() );
}
// public ProcessRunner( final IProcessFactory factory ) {
// fProcessFactory = factory;
// }
@Override
public int executeBlocking( final File workingDir, final Writer out,
final Writer err, final String ... args ) throws IOException {
Process proc = doExecute( workingDir,err==null, args );
Thread outRedirect = redirect( new InputStreamReader( proc.getInputStream() ), out, STDOUT_REDIRECT );
Thread errRedirect = null;
if (err!=null){
errRedirect = redirect( new InputStreamReader( proc.getErrorStream() ), err, STDERR_REDIRECT );
}
int code=-1;
try {
code=proc.waitFor(); // wait for process to finish
outRedirect.join(); // wait until out stream content is redirected
if (errRedirect!=null){
errRedirect.join(); // wait until err stream content is redirected
}
} catch (InterruptedException ex) {
// ignore
}
return code;
}
@Override
public Process executeNonblocking( final File workingDir, final Writer out,
Writer err, final String ... args ) throws IOException {
Process proc = doExecute( workingDir,err==null, args );
redirect( new InputStreamReader( proc.getInputStream() ), out, STDOUT_REDIRECT );
if (err==null){
err=new StringWriter();
}
redirect( new InputStreamReader( proc.getErrorStream() ), err, STDERR_REDIRECT );
return proc;
}
private Process doExecute( final File workingDir, final boolean redirect,final String ... args )
throws IOException {
//Process proc = fProcessFactory.startProcess( workingDir, args );
ProcessBuilder builder = new ProcessBuilder(args);
builder.directory( workingDir );
builder.redirectErrorStream(redirect);
return builder.start();
}
private static Thread redirect( final Reader in, final Writer out, String name ) {
Thread outRedirect = new StreamRedirect( name, in, out );
outRedirect.start();
return outRedirect;
}
public static Thread[] consume(Process proc){
Thread t1=redirect( new InputStreamReader( proc.getInputStream() ), new StringWriter(), STDOUT_REDIRECT );
Thread t2=redirect( new InputStreamReader( proc.getErrorStream() ), new StringWriter(), STDERR_REDIRECT );
return new Thread[]{t1,t2};
}
/**
* get both the version of the executabl
* @param path the path
* @param wait should we wait for the executable or do we NOT trust it to return and shut down (older versions of scion-browser did not return on the --version flag
* @return the version of the executable or null if we could not get it
* @throws IOException
*/
public static String getExecutableVersion(String path,boolean wait) throws IOException{
List<String> ls=getExecutableAndCabalVersion(path, wait);
if (ls!=null && ls.size()>0){
return ls.get(0);
}
return null;
}
/**
* get both the version of the executable and the version of the Cabal library used to build it
* @param path the path
* @param wait should we wait for the executable or do we NOT trust it to return and shut down (older versions of scion-browser did not return on the --version flag
* @return a list, potentially null or empty, containing first the version of the executable, then the version of Cabal
* @throws IOException
*/
public static List<String> getExecutableAndCabalVersion(String path,boolean wait) throws IOException{
File f=new File(path);
if (f.exists()){
StringWriter sw=new StringWriter();
// get the process
Process p = new ProcessRunner().doExecute( f.getParentFile(), false,f.getAbsolutePath(),"--version" );
// redirect output into string writer
Thread t1= redirect( new InputStreamReader( p.getInputStream() ), sw, STDOUT_REDIRECT );
// ignore error
redirect( new InputStreamReader( p.getErrorStream() ), new StringWriter(), STDERR_REDIRECT );
try {
// we trust the process to be short lived
if (wait){
try {
p.waitFor();
p=null;
} catch (InterruptedException ie){
//
}
} else {
// we do not trust the process to be short lived
for (int a=0;a<200;a++){ // 200 * 100 -> 20 seconds maxi
try {
p.exitValue();
p=null;
break;
} catch (IllegalThreadStateException ise){
// still running
}
try {
Thread.sleep(100);
} catch (InterruptedException ie){
//
}
}
}
} finally {
if (p!=null){
p.destroy();
}
}
// we wait for the redirect thread to finish reading/writing
try {
t1.join(); // wait for thread to finish correctly, old code was: 10*1000 10 seconds max waiting for write
} catch (InterruptedException ignore){
// noop
}
List<String> ret=new ArrayList<>();
// now we should have the proper result
BufferedReader br=new BufferedReader(new StringReader(sw.toString().trim()));
String line=br.readLine();
if (line!=null){
int ix=line.lastIndexOf(' ');
if (ix>-1 && ix<line.length()){
line=line.substring(ix+1);
if (Character.isDigit(line.charAt(0))){
ret.add(line);
}
}
}
// we read the second line to see if we have the cabal version
line=br.readLine();
if (line!=null){
Matcher m=Pattern.compile(LIBRARY_VERSION).matcher(line);
if (m.matches()){
ret.add(m.group(1));
}
}
return ret;
}
return null;
}
public static String getGHCArgument(String s){
if (PlatformUtil.runningOnWindows()){
String escaped=s.replace("\"", "\\\"");
return "\""+escaped+"\"";
}
return s;
}
}