package net.sf.eclipsefp.haskell.ui.internal.backend;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import net.sf.eclipsefp.haskell.core.cabal.CabalImplementation;
import net.sf.eclipsefp.haskell.ui.HaskellUIPlugin;
import net.sf.eclipsefp.haskell.ui.internal.util.UITexts;
import net.sf.eclipsefp.haskell.util.FileUtil;
import net.sf.eclipsefp.haskell.util.NetworkUtil;
import net.sf.eclipsefp.haskell.util.PlatformUtil;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWTException;
import org.eclipse.ui.console.IOConsoleOutputStream;
/**
* This class encapsulates the details of building a package for Scion, such as
* unpacking the internal zip archive to the staging directory and running cabal to
* produce the executable.
*
* Note: most of the contents of this class were previously in ScionBuilder,
* but they were moved here to allow different packages to be built.
*
* @author B. Scott Michel (bscottm@ieee.org)
* @author Alejandro Serrano (trupill@gmail.com)
*/
@Deprecated
public abstract class PackageBuilder {
public static enum PackageBuilderMessages {
UNPACK_ARCHIVE_TITLE,
ARCHIVE_RESOURCE_NOT_FOUND,
ARCHIVE_FILE_EXCEPTION,
ARCHIVE_NON_SPECIFIC_EXTENSION,
BUILD_JOB_TITLE,
INSTALL_FAILED,
INSTALL_ERROR;
}
public abstract InputStream getArchive();
public abstract String getMessage(PackageBuilderMessages message);
/** Unpack the package's archive to its final destination
*/
public ScionBuildStatus unpackScionArchive(final File destdir) {
ScionBuildStatus retval = new ScionBuildStatus();
if( !destdir.exists() || destdir.list().length == 0 ) {
// extract scion from bundled zip file
InputStream is = this.getArchive();
if( is == null ) {
retval.buildFailed( getMessage( PackageBuilderMessages.UNPACK_ARCHIVE_TITLE ),
getMessage( PackageBuilderMessages.ARCHIVE_RESOURCE_NOT_FOUND ) );
return retval;
}
ZipEntry ze = null;
try ( ZipInputStream zis = new ZipInputStream( is )) {
ze = zis.getNextEntry();
while( ze != null ) {
final int BUFFER_SIZE = 2048;
byte[] data = new byte[ BUFFER_SIZE ];
if( !ze.isDirectory() ) {
File f = new File( destdir, ze.getName() );
f.getParentFile().mkdirs();
try (FileOutputStream fos = new FileOutputStream( f );
BufferedOutputStream dest = new BufferedOutputStream( fos, BUFFER_SIZE )) {
int count = 0;
while( ( count = zis.read( data, 0, BUFFER_SIZE ) ) != -1 ) {
dest.write( data, 0, count );
}
}
}
ze = zis.getNextEntry();
}
zis.close();
} catch( Exception e ) {
String message;
if (ze != null) {
message = NLS.bind( getMessage( PackageBuilderMessages.ARCHIVE_FILE_EXCEPTION ), ze.getName(), e.toString() );
} else {
message = NLS.bind( getMessage( PackageBuilderMessages.ARCHIVE_NON_SPECIFIC_EXTENSION ), e.toString() );
}
// delete so we try to unzip next time
FileUtil.deleteRecursively( destdir );
retval.buildFailed( getMessage( PackageBuilderMessages.UNPACK_ARCHIVE_TITLE ), message );
}
}
return retval;
}
public ScionBuildStatus update( final CabalImplementation cabalImpl, final IOConsoleOutputStream conout ) {
final String cabalExecutable = cabalImpl.getCabalExecutableName().toOSString();
ScionBuildStatus retval = new ScionBuildStatus();
if( cabalExecutable.length() <= 0 ) {
retval.buildFailed( UITexts.cabalUpdateJob_title, UITexts.zerolenCabalExecutable_message );
return retval;
}
ArrayList<String> commands = new ArrayList<>();
commands.add( cabalExecutable );
commands.add( "update" );
commands.add( "-v" );
ProcessBuilder pb = new ProcessBuilder( commands );
pb.redirectErrorStream( true );
NetworkUtil.addHTTP_PROXY_env( pb, NetworkUtil.HACKAGE_URL );
String jobPrefix = getClass().getSimpleName();
try {
Process p = pb.start();
BufferedReader is = new BufferedReader( new InputStreamReader( p.getInputStream(), FileUtil.UTF8 ) );
OutputWriter pbWriter = new OutputWriter( jobPrefix + "-OutputWriter", conout );
InputReceiver pbReader = new InputReceiver( jobPrefix + "-InputReader", is, pbWriter);
pbReader.start();
pbWriter.start();
int code = p.waitFor();
if( code != 0 ) {
retval.buildFailed( UITexts.cabalUpdateJob_title, UITexts.cabalUpdateFailed );
}
pbWriter.setTerminate();
pbWriter.interrupt();
pbWriter.join();
pbReader.setTerminate();
pbReader.interrupt();
pbReader.join();
} catch( Exception e ) {
retval.buildFailed( UITexts.cabalUpdateJob_title, UITexts.cabalUpdateError.concat( PlatformUtil.NL + e.toString() ) );
}
return retval;
}
/** Build the built-in Scion server using the Cabal.
*
* @param cabalImpl The Cabal implementation, which specifies the executable to run
* @param destDir The destination directory, where the executable is built
* @param conout The IOConsole output stream to which the build process' output is sent.
*/
public ScionBuildStatus build( final CabalImplementation cabalImpl, final File destDir, final IOConsoleOutputStream conout ) {
ArrayList<String> commands = new ArrayList<>();
ScionBuildStatus retval = new ScionBuildStatus();
final String cabalExecutable = cabalImpl.getCabalExecutableName().toOSString();
if( cabalExecutable.length() <= 0 ) {
retval.buildFailed( getMessage( PackageBuilderMessages.BUILD_JOB_TITLE ), UITexts.zerolenCabalExecutable_message );
return retval;
}
commands.add( cabalExecutable );
HaskellUIPlugin.log( "cabal executable: ".concat( cabalExecutable )
.concat(", cabal-install ")
.concat( cabalImpl.getInstallVersion() )
.concat(", Cabal library version ")
.concat( cabalImpl.getLibraryVersion()),
IStatus.INFO );
String cabalLibVer = cabalImpl.getLibraryVersion();
if( cabalLibVer.startsWith( "1.8." ) ) {
commands.add( "-fcabal_1_8" );
} else if( cabalLibVer.startsWith( "1.10." ) ) {
commands.add( "-fcabal_1_10" );
}
commands.add( "install" );
commands.add( "-v" );
ProcessBuilder pb = new ProcessBuilder( commands );
pb.directory( destDir );
pb.redirectErrorStream( true );
NetworkUtil.addHTTP_PROXY_env( pb, NetworkUtil.HACKAGE_URL );
String jobPrefix = getClass().getSimpleName();
try {
Process p = pb.start();
BufferedReader is = new BufferedReader( new InputStreamReader( p.getInputStream(), FileUtil.UTF8 ) );
OutputWriter pbWriter = new OutputWriter( jobPrefix + "-OutputWriter", conout );
InputReceiver pbReader = new InputReceiver( jobPrefix + "-InputReader", is, pbWriter);
pbReader.start();
pbWriter.start();
int code = p.waitFor();
if( code != 0 ) {
retval.buildFailed( getMessage( PackageBuilderMessages.BUILD_JOB_TITLE ),
getMessage( PackageBuilderMessages.INSTALL_FAILED ) );
}
pbWriter.setTerminate();
pbWriter.interrupt();
pbWriter.join();
pbReader.setTerminate();
pbReader.interrupt();
pbReader.join();
} catch( Exception e ) {
retval.buildFailed( getMessage( PackageBuilderMessages.BUILD_JOB_TITLE ),
getMessage( PackageBuilderMessages.INSTALL_ERROR ).concat( PlatformUtil.NL + e.toString() ) );
}
return retval;
}
/** Does the executable need building? (Note: this really only occurs if the
* actual executable is not present in the staging directory.)
*/
public abstract boolean needsBuilding();
/**
* A separate thread to write the communication with the server see
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=259107
*/
public static class OutputWriter extends Thread {
/** the message list **/
private final LinkedList<String> messages;
/** should we stop? **/
private boolean terminateFlag;
/** The output stream we'll write to. */
private BufferedWriter outStream;
public OutputWriter(final String name, final OutputStream outStream) {
super(name);
messages = new LinkedList<>();
terminateFlag = false;
this.outStream = null;
try {
this.outStream = new BufferedWriter( new OutputStreamWriter(outStream, FileUtil.UTF8) );
} catch (UnsupportedEncodingException exc) {
// Keep Java happy
}
}
public void setTerminate() {
terminateFlag = true;
}
public void addMessage(final String msg) {
synchronized (messages) {
messages.add(msg + PlatformUtil.NL);
messages.notify();
}
}
public void addMessage(final char[] buf, final int start, final int length) {
synchronized (messages) {
messages.add(new String(buf, start, length));
messages.notify();
}
}
public void addMessage(final Exception e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
pw.flush();
synchronized (messages) {
messages.add(sw.toString());
messages.notify();
}
}
@Override
public void run() {
while (!terminateFlag && outStream != null) {
String m = null;
synchronized (messages) {
try {
while (messages.isEmpty()) {
messages.wait();
}
} catch (InterruptedException ignore) {
// noop
}
if (!messages.isEmpty()) {
m = messages.removeFirst();
}
}
if (m != null) {
try {
outStream.write(m);
outStream.flush();
} catch (IOException ex) {
// Nothing to do.
} catch (SWTException se) {
// probably device has been disposed
}
}
}
}
}
/**
* The input receiver thread.
*/
public static class InputReceiver extends Thread {
private boolean terminateFlag;
private BufferedReader inStream;
private final OutputWriter outWriter;
public InputReceiver( final String name, final BufferedReader stream,
final OutputWriter outWriter ) {
super( name );
terminateFlag = false;
this.inStream = stream;
this.outWriter = outWriter;
}
public void setTerminate() {
terminateFlag = true;
}
@Override
public void run() {
while( !terminateFlag && inStream != null ) {
char[] buf = new char[1024];
try {
int nread = inStream.read( buf );
if( nread > 0 ) {
outWriter.addMessage( buf, 0, nread );
} else if (nread < 0) {
terminateFlag = true;
}
} catch( IOException ex ) {
try {
inStream.close();
} catch( IOException ex1 ) {
// Make Java happy.
}
inStream = null;
}
}
}
}
}