/**
* Copyright (c) 2005-2006 Aptana, Inc.
*
* 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. If redistributing this code,
* this entire header must remain intact.
*/
package com.aptana.ide.desktop.integration.server;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.osgi.service.datalocation.Location;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.internal.intro.impl.IntroPlugin;
import org.eclipse.ui.progress.UIJob;
import com.aptana.ide.core.CoreStrings;
import com.aptana.ide.core.IdeLog;
import com.aptana.ide.core.MutexJobRule;
import com.aptana.ide.core.StringUtils;
import com.aptana.ide.core.ui.WorkbenchHelper;
import com.aptana.ide.update.Activator;
import com.aptana.ide.update.manager.IPlugin;
import com.aptana.ide.update.manager.PluginManagerException;
/**
* Manages the launching of the workbench files from the command line.
*
* @author Paul Colton
*/
public class LaunchHelper
{
private static final String HANDLE_URL_FLAG = "-handleURL"; //$NON-NLS-1$
private String dotAptanaFile = null;
private String[] initialFiles;
private static class FeatureURL implements IPlugin
{
private final URL url;
private final String id;
FeatureURL(URL url, String id)
{
this.url = url;
this.id = id;
}
public String getId()
{
return id;
}
public String getName()
{
return id;
}
public URL getURL()
{
return url;
}
public String getVersion()
{
return null;
}
}
private List<FeatureURL> featuresToInstall = new LinkedList<FeatureURL>();
/**
* openStartupFiles
*
* @param window
*/
public void openStartupFiles(IWorkbenchWindow window)
{
if (initialFiles != null)
{
if (initialFiles.length > 0)
{
try
{
IntroPlugin.closeIntro();
}
catch (Exception ex)
{
IdeLog.logError(DesktopIntegrationServerActivator.getDefault(), Messages.LaunchHelper_UnableToCLoseWelcome, ex);
}
}
for (int i = 0; i < initialFiles.length; i++)
{
File file = new File(initialFiles[i]);
try
{
if (file.exists())
{
String editorID = getEditorID(file);
if (editorID == null)
{
WorkbenchHelper.openFile(file, window);
}
else
{
WorkbenchHelper.openFile(editorID, file, window);
}
}
}
catch (Exception e)
{
IdeLog.logError(DesktopIntegrationServerActivator.getDefault(), StringUtils.format(
Messages.LaunchHelper_ErrorOpeningFileOnStartup, initialFiles[i]), e);
}
}
initialFiles = null;
}
// Install queued up features
if (featuresToInstall.size() > 0) {
UIJob uiJob = new UIJob("Installing features") { //$NON-NLS-1$
public IStatus runInUIThread(
IProgressMonitor monitor) {
try {
Activator.getDefault().getPluginManager().install(featuresToInstall.toArray(new FeatureURL[0]), new NullProgressMonitor());
} catch (PluginManagerException e) {
IdeLog.logError(DesktopIntegrationServerActivator.getDefault(), e.getMessage());
}
return Status.OK_STATUS;
}
};
uiJob.setSystem(true);
uiJob.setRule(MutexJobRule.getInstance());
uiJob.schedule(10000);
}
}
private String getEditorID(File file)
{
String name = file.getName().toLowerCase();
if (name.endsWith(".js") || name.endsWith(".css")) //$NON-NLS-1$ //$NON-NLS-2$
{
return null;
}
String contents = getFileContents(file);
final String HTML_EDITOR = "com.aptana.ide.editors.HTMLEditor"; //$NON-NLS-1$
if (contents == null)
{
return null;
}
else
{
contents = contents.toLowerCase();
if (contents.indexOf("<!doctype html") != -1 || contents.indexOf("<html") != -1) //$NON-NLS-1$ //$NON-NLS-2$
{
return HTML_EDITOR;
}
}
return null;
}
private String getFileContents(File file)
{
int fileLength = (int) file.length();
if (fileLength == 0)
{
return null;
}
if (fileLength > 100)
{
fileLength = 100;
}
char[] chars = new char[fileLength];
try
{
FileReader fr = new FileReader(file);
fr.read(chars);
fr.close();
}
catch (Exception e)
{
IdeLog.logError(DesktopIntegrationServerActivator.getDefault(), StringUtils.format(Messages.LaunchHelper_UnableToGetFileContents,
file.getAbsolutePath()), e);
return null;
}
return new String(chars);
}
private static LaunchHelper _instance;
/**
* getInstance
*
* @return LaunchHelper
*/
public LaunchHelper getInstance()
{
if (_instance == null)
{
_instance = new LaunchHelper();
}
return _instance;
}
/**
* setLaunchFileCmdLineArgs
*
* @param args
*/
public void setLaunchFileCmdLineArgs(String[] args)
{
int startIndex = 0;
// if any file args were passed to the application, pass them onto the WindowAdvisor
// so that the files can be opened once the window is open.
String[] fileList;
String[] argList = args;
if (argList.length > 0)
{
if (argList[0].toLowerCase().matches(".*?(aptana.exe|aptanastudio.exe)")) //$NON-NLS-1$
{
startIndex = 1;
// String newDotAptanaFile = argList[0].replaceFirst("(?i)(aptana.exe|aptanastudio.exe)", ".aptana"); //$NON-NLS-1$ //$NON-NLS-2$
String newDotAptanaFile = computeDotAptanaFileName();
if (dotAptanaFile == null)
{
dotAptanaFile = newDotAptanaFile;
IdeLog.logInfo(DesktopIntegrationServerActivator.getDefault(), StringUtils.format(
Messages.LaunchHelper_AptanaPortCachedInFile, dotAptanaFile));
}
else
{
IdeLog.logInfo(DesktopIntegrationServerActivator.getDefault(), StringUtils.format(Messages.LaunchHelper_PortCacheFile,
new String[] { dotAptanaFile, newDotAptanaFile }));
}
}
}
featuresToInstall.clear();
List<String> filesList = new ArrayList<String>();
for (int i = startIndex; i < argList.length; i++)
{
// IdeLog.logInfo(DesktopIntegrationServerActivator.getDefault(), "received arg '" + argList[i] + "'");
if (argList[i].startsWith("-")) //$NON-NLS-1$
{
if (argList[i].equals(HANDLE_URL_FLAG)) {
if (i < (argList.length - 1)) {
IdeLog.logInfo(DesktopIntegrationServerActivator.getDefault(), "Received install request : " + argList[i+1]); //$NON-NLS-1$
try {
String urlString = argList[i+1];
// Strip off prefix
if (urlString.startsWith("aptanaplugininstaller:")) { //$NON-NLS-1$
urlString = urlString.substring("aptanaplugininstaller:".length()); //$NON-NLS-1$
}
URL url = new URL(urlString);
final String id = url.getQuery();
if (id != null && id.length() > 0) {
// Skip over next arg
i++;
URL updateSiteUrl = new URL(url.getProtocol(),
url.getHost(), url.getPort(), url
.getPath());
featuresToInstall.add(new FeatureURL(
updateSiteUrl, id));
}
} catch (MalformedURLException e) {
IdeLog.logError(DesktopIntegrationServerActivator.getDefault(), e.getMessage());
}
}
}
} else {
File file = new File(argList[i]);
if (file.exists())
{
filesList.add(argList[i]);
}
}
}
fileList = (String[]) filesList.toArray(new String[0]);
initialFiles = fileList;
}
/**
* checkForRunningInstance
*
* @return the port number the application is running on, or -1 if there is
* no running instance
*/
public int checkForRunningInstance()
{
int port = readCurrentPort();
// If the .aptana file did not exist or contained a bogus port number
// simply return
if (port < 0) {
return port;
}
// Now check if the port is actually in use
ServerSocket serverSocket = null;
try
{
serverSocket = new ServerSocket(port, 0, null);
// Socket not in use. Assume that the .aptana file was left over from
// a abnormal exit the last time.
return -1;
}
catch (IOException e)
{
// Threw an exception. Assume that the port is in use by a running instance of the IDE.
IdeLog.logInfo(DesktopIntegrationServerActivator.getDefault(), e.getMessage());
} finally {
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
}
}
}
return port;
}
public void startServer() {
if (dotAptanaFile == null)
{
dotAptanaFile = computeDotAptanaFileName();
}
try
{
CommandLineArgsServer server = new CommandLineArgsServer(this);
server.start();
}
catch (IOException e)
{
IdeLog.logInfo(DesktopIntegrationServerActivator.getDefault(), Messages.LaunchHelper_ErrorInChdeckingForCurrentInstance, e);
}
}
public void doShutdownCleanup()
{
if (dotAptanaFile != null)
{
(new File(dotAptanaFile)).delete();
}
}
private int readCurrentPort()
{
FileReader fr = null;
if (dotAptanaFile == null)
{
dotAptanaFile = computeDotAptanaFileName();
}
try
{
fr = new FileReader(dotAptanaFile);
BufferedReader br = new BufferedReader(fr);
String sPort = br.readLine().trim();
if (sPort.length() == 0)
{
return -1;
}
return Integer.parseInt(sPort);
}
catch (FileNotFoundException e)
{
return -1;
}
catch (Exception e)
{
IdeLog.logInfo(DesktopIntegrationServerActivator.getDefault(), Messages.LaunchHelper_UnableToFindCurrentPort, e);
return CommandLineArgsServer.STARTING_PORT;
}
finally
{
if (fr != null)
{
try
{
fr.close();
}
catch (IOException e)
{
IdeLog.logInfo(DesktopIntegrationServerActivator.getDefault(), Messages.LaunchHelper_ErrorInClosingFileReader, e);
}
}
}
}
private static String computeDotAptanaFileName() {
// Attempt to locate .aptana file in a predicatable place
// even when launched any directory or folder
File dotAptanaParent = new File(System.getProperty("user.dir")); //$NON-NLS-1$
// Prefer install location
Location location = Platform.getInstallLocation();
if (location == null || location.isReadOnly()) {
// If install location is null (?) or read only - try configuration location
location = Platform.getConfigurationLocation();
if (location == null || location.isReadOnly()) {
// If configuration location is null (?) or read only - try configuration location
location = Platform.getUserLocation();
if (location == null || location.isReadOnly()) {
// If user location is null or read-only - too bad.
location = null;
}
}
}
if (location != null) {
URL locationURL = location.getURL();
if (locationURL != null && "file".equals(locationURL.getProtocol())) { //$NON-NLS-1$
try {
dotAptanaParent = new File(locationURL.toURI());
} catch (URISyntaxException e) {
dotAptanaParent = new File(locationURL.getPath());
}
}
}
String computedDotAptanaFile = new File(dotAptanaParent, ".aptana").getAbsolutePath(); //$NON-NLS-1$
IdeLog.logInfo(DesktopIntegrationServerActivator.getDefault(), ".aptana file " + computedDotAptanaFile); //$NON-NLS-1$
return computedDotAptanaFile;
}
public boolean sendInitialFilesAndInstallFeatures(int port, String[] args)
{
Socket socket = null;
DataOutputStream os = null;
try
{
socket = new Socket(InetAddress.getByName(null), port);
os = new DataOutputStream(socket.getOutputStream());
}
catch (UnknownHostException e)
{
IdeLog.logInfo(DesktopIntegrationServerActivator.getDefault(), Messages.LaunchHelper_UnknownLocalHost);
return false;
}
catch (IOException e)
{
IdeLog.logInfo(DesktopIntegrationServerActivator.getDefault(), Messages.LaunchHelper_CouldNotGetIOConnection);
return false;
}
if (socket != null && os != null)
{
try
{
StringBuilder sb = new StringBuilder();
for (String arg : args) {
if (sb.length() > 0) {
sb.append(StringUtils.SPACE);
}
sb.append("\"" + arg + "\""); //$NON-NLS-1$ //$NON-NLS-2$
}
PrintWriter ps = new PrintWriter(os);
ps.println(sb.toString());
ps.flush();
ps.close();
socket.close();
return true;
}
catch (UnknownHostException e)
{
IdeLog.logInfo(DesktopIntegrationServerActivator.getDefault(), Messages.LaunchHelper_TryingToConnectToUnknownHost);
}
catch (IOException e)
{
IdeLog.logInfo(DesktopIntegrationServerActivator.getDefault(), Messages.LaunchHelper_IOExceptionEncountered);
}
}
return false;
}
/**
* CommandLineArgsServer
*
* @author Ingo Muschenetz
*/
class CommandLineArgsServer extends Thread
{
/**
* STARTING_PORT
*/
public static final int STARTING_PORT = 9980;
LaunchHelper helper;
ServerSocket server = null;
String line;
DataInputStream is;
PrintStream os;
Socket clientSocket = null;
/**
* CommandLineArgsServer
*
* @param helper
* @throws IOException
*/
public CommandLineArgsServer(LaunchHelper helper) throws IOException
{
super("CommandLineArgsServer"); //$NON-NLS-1$
this.helper = helper;
int port = getPort();
if (port == -1)
{
throw new IOException(StringUtils.format(Messages.LaunchHelper_CouldNotFindOpenPort, new String[] {
String.valueOf(STARTING_PORT), String.valueOf(STARTING_PORT + 10) }));
}
else
{
IdeLog.logInfo(DesktopIntegrationServerActivator.getDefault(), StringUtils.format(Messages.LaunchHelper_BoundAptanaToPort,
port));
}
try
{
FileWriter f = new FileWriter(dotAptanaFile);
BufferedWriter out = new BufferedWriter(f);
out.write(StringUtils.EMPTY + port);
out.close();
new File(dotAptanaFile).deleteOnExit();
}
catch (IOException e)
{
}
}
/**
* getPort
*
* @return int
*/
public int getPort()
{
int tries = 10;
int port = STARTING_PORT;
while (tries > 0)
{
try
{
server = new ServerSocket(port, 0, null);
server.setSoTimeout(1000);
return port;
}
catch (IOException e)
{
IdeLog.logInfo(DesktopIntegrationServerActivator.getDefault(), StringUtils.format(
Messages.LaunchHelper_UnableToBindToPort, port));
tries--;
port++;
}
}
return -1;
}
/**
* @see java.lang.Runnable#run()
*/
public void run()
{
while (server.isClosed() == false)
{
try
{
clientSocket = server.accept();
BufferedReader r = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
BufferedWriter w = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()));
line = r.readLine().trim();
w.write("pong"); //$NON-NLS-1$
w.flush();
clientSocket.close();
if (line.length() > 0)
{
helper.startupPerformed(line);
}
}
catch (SocketTimeoutException e)
{
}
catch (Exception e)
{
IdeLog.logInfo(DesktopIntegrationServerActivator.getDefault(), CoreStrings.ERROR, e);
}
}
}
}
/**
* hookStartupListener
*/
public void hookStartupListener()
{
// hook the exe4j launcher using reflection so that our plugin does not
// have a compiler dependency on exe4j. The WorkbenchStartupManager is
// a wrapper for binding to the exe4j interfaces and is injected into the
// IDE's main startup.jar file.= during our build process.
try
{
Class cls = ClassLoader.getSystemClassLoader().loadClass("com.aptana.ide.startup.WorkbenchStartupManager"); //$NON-NLS-1$
Method startupListener;
startupListener = cls.getMethod("setStartupListener", new Class[] { Object.class }); //$NON-NLS-1$
startupListener.invoke(null, new Object[] { this });
}
catch (ClassNotFoundException e)
{
IdeLog.logInfo(DesktopIntegrationServerActivator.getDefault(), Messages.LaunchHelper_TheStartupListenerClassIsNotAvailable);
}
catch (Throwable e)
{
IdeLog.logError(DesktopIntegrationServerActivator.getDefault(), Messages.LaunchHelper_ErrorHookingStartupListener, e);
}
}
/**
* startupPerformed
*
* @param args
*/
public void startupPerformed(String args)
{
String[] startupArgs = parseCommandLineArgs(args);
setLaunchFileCmdLineArgs(startupArgs);
PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable()
{
public void run()
{
openStartupFiles(PlatformUI.getWorkbench().getActiveWorkbenchWindow());
}
});
}
private String[] parseCommandLineArgs(String cmdLine)
{
char quote = '"';
char flagStart = '-';
List<String> args = new ArrayList<String>();
StringBuffer word = new StringBuffer();
try
{
char[] chars = cmdLine.toCharArray();
boolean quoteMode = false;
for (int i = 0; i < chars.length; i++)
{
boolean endWord = false;
char ch = chars[i];
if (ch == quote)
{
if (quoteMode)
{
// this is the end quote, so end the word
quoteMode = false;
endWord = true;
}
else
{
quoteMode = true;
}
}
else if (Character.isWhitespace(ch) && !quoteMode)
{
endWord = true;
}
else
{
word.append(ch);
}
if (endWord)
{
if (word.length() > 0 && (word.toString().equals(HANDLE_URL_FLAG) || word.charAt(0) != flagStart))
{
args.add(word.toString());
}
word.setLength(0);
}
}
}
catch (Exception e)
{
IdeLog.logError(DesktopIntegrationServerActivator.getDefault(), StringUtils.format(
Messages.LaunchHelper_UnableToRecognizeCommandLineLaunchArguments, cmdLine));
}
if (word.length() > 0 && (word.toString().equals(HANDLE_URL_FLAG) || word.charAt(0) != flagStart))
{
args.add(word.toString());
}
String[] argArray = (String[]) args.toArray(new String[0]);
return argArray;
}
}