/* Copyright (C) 2009 Mobile Sorcery AB
This program is free software; you can redistribute it and/or modify it
under the terms of the Eclipse Public License v1.0.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the Eclipse Public License v1.0 for
more details.
You should have received a copy of the Eclipse Public License v1.0 along
with this program. It is also available at http://www.eclipse.org/legal/epl-v10.html
*/
package com.mobilesorcery.sdk.core;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import org.eclipse.core.runtime.ListenerList;
public class SpawnedProcess extends Process {
public interface IProcessListener {
public final static int START = 0;
public final static int EXIT = 1;
public final static int DESTROY_CALLED = 2;
public void handleEvent(SpawnedProcess process, int type);
}
private static final int INITIAL_EXIT_VALUE = Integer.MIN_VALUE;
private final String cmd;
private final String[] args;
private final File dir;
private int handle = 0;
private int exitValue = INITIAL_EXIT_VALUE;
private InputStream errStream = new ByteArrayInputStream(new byte[0]);
private InputStream inpStream = new ByteArrayInputStream(new byte[0]);
private final OutputStream outputStream = new ByteArrayOutputStream();
private final CountDownLatch waitFor;
private final ListenerList processListeners = new ListenerList();
private Runnable shutdownHook = null;
public SpawnedProcess(String cmd, String[] args, File dir) {
waitFor = new CountDownLatch(1);
this.cmd = cmd;
this.args = args;
this.dir = dir;
}
public void start() throws IOException {
notifyListeners(IProcessListener.START);
final IProcessUtil pu = CoreMoSyncPlugin.getDefault().getProcessUtil();
if (!dir.exists()) {
throw new IllegalStateException("The directory to launch in does not exist");
}
String args = Util.join(Util.ensureQuoted(this.args), " ");
handle = pu.proc_spawn((cmd + '\0').getBytes(), (args + '\0').getBytes(), (dir.getAbsolutePath() + '\0')
.getBytes());
Thread waitForThread = new Thread(new Runnable() {
@Override
public void run() {
int tmpExitValue = pu.proc_wait_for(handle);
runShutdownHook();
exitValue = tmpExitValue;
notifyListeners(IProcessListener.EXIT);
waitFor.countDown();
}
});
waitForThread.setName("Waiting for process " + cmd);
waitForThread.setDaemon(true);
waitForThread.start();
if (handle < 0) {
throw new IllegalStateException("Could not spawn process");
}
}
public static int[] createPipe() {
int[] fds = new int[2];
CoreMoSyncPlugin.getDefault().getProcessUtil().pipe_create(fds);
return fds;
}
public static void close(int fd) {
CoreMoSyncPlugin.getDefault().getProcessUtil().pipe_close(fd);
}
public void setErrorStream(InputStream errStream) {
if (errStream != null) {
this.errStream = errStream;
}
}
public void setInputStream(InputStream inpStream) {
if (inpStream != null) {
this.inpStream = inpStream;
}
}
public void setOutputFd(int outputFd) {
throw new UnsupportedOperationException("Only need read in this app");
}
@Override
public void destroy() {
notifyListeners(IProcessListener.DESTROY_CALLED);
if (exitValue != INITIAL_EXIT_VALUE) {
closeStreams();
return;
}
int result = CoreMoSyncPlugin.getDefault().getProcessUtil().proc_kill(handle, 0);
if (result != 0) {
throw new IllegalStateException("Could not terminate application");
}
}
private void closeStreams() {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
// Ignore.
}
}
if (errStream != null) {
try {
errStream.close();
} catch (IOException e) {
// Ignore.
}
}
if (inpStream != null) {
try {
inpStream.close();
} catch (IOException e) {
// Ignore.
}
}
}
@Override
public int exitValue() {
if (exitValue == INITIAL_EXIT_VALUE) {
throw new IllegalThreadStateException("Process not terminated");
}
return exitValue;
}
@Override
public InputStream getErrorStream() {
return errStream;
}
@Override
public InputStream getInputStream() {
return inpStream;
}
@Override
public OutputStream getOutputStream() {
return outputStream;
}
public void setShutdownHook(Runnable shutdownHook) {
this.shutdownHook = shutdownHook;
}
private void runShutdownHook() {
if (shutdownHook != null) {
shutdownHook.run();
}
}
@Override
public String toString() {
return "[" + dir + ":] " + cmd + " " + args;
}
@Override
public int waitFor() throws InterruptedException {
waitFor.await();
return exitValue;
}
public void addProcessListener(IProcessListener listener) {
processListeners.add(listener);
}
public void removeProcessListener(IProcessListener listener) {
processListeners.remove(listener);
}
protected void notifyListeners(int type) {
Object[] listeners = processListeners.getListeners();
for (int i = 0; i < listeners.length; i++) {
((IProcessListener) listeners[i]).handleEvent(this, type);
}
}
}