/*
Copyright 2009 Hauke Rehfeld
This file is part of QuakeInjector.
QuakeInjector is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
QuakeInjector 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
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QuakeInjector. If not, see <http://www.gnu.org/licenses/>.
*/
package de.haukerehfeld.quakeinjector;
import java.io.File;
import java.util.ArrayList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
public class EngineStarter {
private File workingDir;
private File quakeExe;
private String quakeCmdline;
private static boolean isMacOSX() {
return System.getProperty("os.name").startsWith("Mac OS X");
}
/**
* Checks whether the given File is a Mac OS X application bundle.
*/
private static boolean isMacApplication(File app) {
return app.isDirectory()
&& app.getName().endsWith(".app");
}
/**
* Checks whether exe is an executable file.
*/
private static boolean isExecutable(File exe) {
return !exe.isDirectory() && exe.canExecute();
}
/**
* Checks whether app is a valid application. On Mac OS X it can
* either be an executable or an app bundle, on other platforms
* it must be an executable.
*/
public static boolean isValidApplication(File app) {
if (!app.exists() || !app.canRead())
return false;
if (isMacOSX() && isMacApplication(app))
return true;
return isExecutable(app);
}
/**
* Returns an error message to display when the user picks app
* as the engine, or returns null if it is a valid application.
*
* @see #isValidApplication(File)
*/
public static String errorMessageForApplication(File app) {
if (!app.exists()) {
return "Doesn't exist!";
}
if (isMacOSX()) {
if (!isMacApplication(app) && !isExecutable(app)) {
return "Must be an application or executable!";
}
return null;
} else {
if (app.isDirectory()) {
return "Must be an executable file!";
}
else if (!app.canExecute()) {
return "Cannot be executed!";
}
return null;
}
}
/**
* If app is a Mac OS X app bundle (e.g. ~/quake/QuakeSpasm.app), returns
* the executable inside the app bundle (e.g. ~/quake/QuakeSpasm.app/Contents/MacOS/QuakeSpasm).
* Otherwise, returns app.
*/
private static File executableForApplication(File app) {
if (app != null && isMacApplication(app)) {
try {
File contents = new File(app, "Contents");
File plist = new File(contents, "Info.plist");
File macOS = new File(contents, "MacOS");
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document document = builder.parse(plist);
XPath xpath = XPathFactory.newInstance().newXPath();
XPathExpression expr = xpath.compile("/plist/dict/key/text()[.='CFBundleExecutable']/../following-sibling::string[1]/text()");
String cfBundleExecutable = (String) expr.evaluate(document);
File executable = new File(macOS, cfBundleExecutable);
return executable;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
return app;
}
public EngineStarter(File workingDir, File quakeApp, Configuration.EngineCommandLine quakeCmdline) {
this.workingDir = workingDir;
setQuakeApplication(quakeApp);
this.quakeCmdline = quakeCmdline.get();
}
public Process start(String mapCmdline, String startmap) throws java.io.IOException {
ArrayList<String> cmd = new ArrayList<String>(5);
cmd.add(quakeExe.getAbsolutePath());
//processbuilder doesn't like arguments with spaces
if (quakeCmdline != null) {
for (String s: quakeCmdline.split(" ")) { cmd.add(s); }
}
if (mapCmdline != null) {
for (String s: mapCmdline.split(" ")) { cmd.add(s); }
}
cmd.add("+map");
cmd.add(startmap);
ProcessBuilder pb = new ProcessBuilder(cmd);
pb.directory(workingDir);
pb.redirectErrorStream(true);
System.out.println(cmd);
Process p = pb.start();
return p;
}
public void setWorkingDirectory(File dir) {
this.workingDir = dir;
}
public void setQuakeApplication(File quakeApp) {
this.quakeExe = executableForApplication(quakeApp);
}
public void setQuakeCommandline(Configuration.EngineCommandLine cmdline) {
this.quakeCmdline = cmdline.get();
}
public boolean checkPaths() {
return (quakeExe.exists()
&& !quakeExe.isDirectory()
&& quakeExe.canRead()
&& quakeExe.canExecute());
}
}