blob: 8f8f755b302ff9baa3a7d8c12bb71cdd014fcbe6 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package flash.tools.debugger.concrete;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.BindException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import flash.localization.LocalizationManager;
import flash.tools.debugger.AIRLaunchInfo;
import flash.tools.debugger.DebuggerLocalizer;
import flash.tools.debugger.DefaultDebuggerCallbacks;
import flash.tools.debugger.IDebuggerCallbacks;
import flash.tools.debugger.ILaunchNotification;
import flash.tools.debugger.ILauncher;
import flash.tools.debugger.IProgress;
import flash.tools.debugger.Player;
import flash.tools.debugger.Session;
import flash.tools.debugger.SessionManager2;
import flash.tools.debugger.VersionException;
import flash.util.URLHelper;
public class PlayerSessionManager implements SessionManager2
{
private ServerSocket m_serverSocket;
private HashMap<String, Object> m_prefs;
private IDebuggerCallbacks m_debuggerCallbacks;
private static LocalizationManager m_localizationManager;
private Socket m_connectSocket;
private boolean m_cancelConnect;
static
{
// set up for localizing messages
m_localizationManager = new LocalizationManager();
m_localizationManager.addLocalizer( new DebuggerLocalizer("flash.tools.debugger.concrete.djapi.") ); //$NON-NLS-1$
}
public PlayerSessionManager()
{
m_debuggerCallbacks = new DefaultDebuggerCallbacks();
m_serverSocket = null;
m_connectSocket = null;
m_cancelConnect = false;
m_prefs = new HashMap<String, Object>();
// manager
setPreference(PREF_ACCEPT_TIMEOUT, 120000); // 2 minutes
setPreference(PREF_URI_MODIFICATION, 1);
setPreference(PREF_CONNECT_TIMEOUT, 120000); // 2 minutes
setPreference(PREF_CONNECT_WAIT_INTERVAL, 250); // 0.25 seconds
setPreference(PREF_CONNECT_RETRY_ATTEMPTS, -1); // Retry till timeout
// session
// response to requests
setPreference(PREF_SOCKET_TIMEOUT, -1); // no timeout by default
setPreference(PREF_RESPONSE_TIMEOUT, 750); // 0.75s
setPreference(PREF_CONTEXT_RESPONSE_TIMEOUT, 1000); // 1s
setPreference(PREF_GETVAR_RESPONSE_TIMEOUT, 1500); // 1.5s
setPreference(PREF_SETVAR_RESPONSE_TIMEOUT, 5000); // 5s
setPreference(PREF_SWFSWD_LOAD_TIMEOUT, 5000); // 5s
// wait for a suspend to occur after a halt
setPreference(PREF_SUSPEND_WAIT, 7000);
// invoke getters by default
setPreference(PREF_INVOKE_GETTERS, 1);
// hierarchical variables view
setPreference(PREF_HIERARCHICAL_VARIABLES, 0);
}
/**
* Set preference
* If an invalid preference is passed, it will be silently ignored.
*/
public void setPreference(String pref, int value) { m_prefs.put(pref, new Integer(value)); }
public void setPreference(String pref, String value){ m_prefs.put(pref, value); }
public Set<String> keySet() { return m_prefs.keySet(); }
public Object getPreferenceAsObject(String pref) { return m_prefs.get(pref); }
/*
* @see flash.tools.debugger.SessionManager#getPreference(java.lang.String)
*/
public int getPreference(String pref)
{
int val = 0;
Integer i = (Integer)m_prefs.get(pref);
if (i == null)
throw new NullPointerException();
val = i.intValue();
return val;
}
/*
* @see flash.tools.debugger.SessionManager#startListening()
*/
public void startListening() throws IOException
{
if (m_serverSocket == null)
m_serverSocket = new ServerSocket(DProtocol.DEBUG_PORT);
}
/*
* @see flash.tools.debugger.SessionManager#stopListening()
*/
public void stopListening() throws IOException
{
if (m_serverSocket != null)
{
m_serverSocket.close();
m_serverSocket = null;
}
}
/*
* @see flash.tools.debugger.SessionManager#isListening()
*/
public boolean isListening()
{
return (m_serverSocket == null) ? false : true;
}
private class LaunchInfo
{
private String m_uri;
public LaunchInfo(String uri)
{
m_uri = uri;
}
public boolean isAbout()
{
return m_uri.startsWith("about:"); //$NON-NLS-1$
}
public boolean isHttpOrAbout()
{
return m_uri.startsWith("http:") || m_uri.startsWith("https:") || isAbout(); //$NON-NLS-1$ //$NON-NLS-2$
}
public boolean isWebPage()
{
return isHttpOrAbout() || m_uri.endsWith(".htm") || m_uri.endsWith(".html"); //$NON-NLS-1$ //$NON-NLS-2$
}
public boolean isWebBrowserNativeLaunch()
{
return isWebPage() && (m_debuggerCallbacks.getHttpExe() != null);
}
public boolean isPlayerNativeLaunch()
{
return m_uri.length() > 0 && !isWebPage() && (m_debuggerCallbacks.getPlayerExe() != null);
}
public boolean isAIRLaunch()
{
return m_uri.startsWith("file:") && (m_uri.endsWith("-app.xml") || m_uri.endsWith("application.xml")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
}
private enum OS {
Mac,
Windows,
Unix
}
private OS getOS()
{
String osName = System.getProperty("os.name").toLowerCase(); //$NON-NLS-1$
if (osName.startsWith("mac os x")) //$NON-NLS-1$
{
return OS.Mac;
}
else if (osName.startsWith("windows")) //$NON-NLS-1$
{
return OS.Windows;
}
else
{
return OS.Unix;
}
}
/*
* @see flash.tools.debugger.SessionManager#launch(java.lang.String, flash.tools.debugger.AIRLaunchInfo, boolean, flash.tools.debugger.IProgress)
*/
public Session launch(String uri, AIRLaunchInfo airLaunchInfo, boolean forDebugging, IProgress waitReporter, ILaunchNotification launchNotification) throws IOException
{
String[] launchCommand = getLaunchCommand(uri, airLaunchInfo,forDebugging);
// create the process and attach a thread to watch it during our accept phase
Process proc = m_debuggerCallbacks.launchDebugTarget(launchCommand);
ProcessListener processListener = startProcessListener(airLaunchInfo,forDebugging, launchNotification, launchCommand, proc,false);
PlayerSession session = null;
if (forDebugging)
{
session = waitForConnection(uri, airLaunchInfo, waitReporter, proc, processListener);
}
return session;
}
private PlayerSession waitForConnection(String uri,
AIRLaunchInfo airLaunchInfo, IProgress waitReporter, Process proc,
ProcessListener pl) throws IOException {
/* now wait for a connection */
PlayerSession session = (PlayerSession)accept(pl, waitReporter);
session.setProcess(proc);
session.setLaunchUrl(uri);
session.setAIRLaunchInfo(airLaunchInfo);
return session;
}
/**
* Tweaks the launch URI if necessary, e.g. may append "?debug=true"
*/
private String tweakNativeLaunchUri(String uri, boolean forDebugging,
LaunchInfo launchInfo) throws IOException, FileNotFoundException
{
// first let's see if it's an HTTP URL or not
if (launchInfo.isHttpOrAbout())
{
boolean modify = (getPreference(PREF_URI_MODIFICATION) != 0);
if (modify && forDebugging && !launchInfo.isAbout())
{
// escape spaces if we have any
uri = URLHelper.escapeSpace(uri);
// be sure that ?debug=true is included in query string
URLHelper urlHelper = new URLHelper(uri);
Map<String, String> params = urlHelper.getParameterMap();
params.put("debug", "true"); //$NON-NLS-1$ //$NON-NLS-2$
urlHelper.setParameterMap(params);
uri = urlHelper.getURL();
}
}
else
{
// ok, its not an http: type request therefore we should be able to see
// it on the file system, right? If not then it's probably not valid
File f = null;
if (uri.startsWith("file:")) //$NON-NLS-1$
{
try
{
f = new File(new URI(uri));
}
catch (URISyntaxException e)
{
IOException ioe = new IOException(e.getMessage());
ioe.initCause(e);
throw ioe;
}
}
else
{
f = new File(uri);
}
if (f != null && f.exists()) {
// Do not use getCanonicalPath() -- see FB-24595
uri = f.getAbsolutePath();
} else {
throw new FileNotFoundException(uri);
}
}
return uri;
}
/**
* Gets the arguments needed for launching a swf that needs to run
* in a web browser or the standalone player.
*/
private String[] getFlashLaunchArgs(String uri, LaunchInfo launchInfo) throws FileNotFoundException
{
String[] launchCommand;
OS os = getOS();
/**
* Various ways to launch this stupid thing. If we have the exe
* values for the player, then we can launch it directly, monitor
* it and kill it when we die; otherwise we launch it through
* a command shell (cmd.exe, open, or bash) and our Process object
* dies right away since it spawned another process to run the
* Player within.
*/
if (os == OS.Mac)
{
if (launchInfo.isWebBrowserNativeLaunch())
{
File httpExe = m_debuggerCallbacks.getHttpExe();
String[] customParams = m_debuggerCallbacks.getBrowserParameters(uri);
if (customParams == null) {
launchCommand = new String[] { "/usr/bin/open", "-a", httpExe.toString(), uri }; //$NON-NLS-1$ //$NON-NLS-2$
}
else {
final int prependLen = 4;
launchCommand = new String[customParams.length + prependLen ];
launchCommand[0] = "/usr/bin/open"; //$NON-NLS-1$
launchCommand[1] = "-a"; //$NON-NLS-1$
launchCommand[2] = httpExe.toString();
launchCommand[3] = "--args"; //$NON-NLS-1$
for ( int i = 0; i < customParams.length; i++) {
launchCommand[i + prependLen] = customParams[i];
}
}
}
else if (launchInfo.isPlayerNativeLaunch())
{
File playerExe = m_debuggerCallbacks.getPlayerExe();
launchCommand = new String[] { "/usr/bin/open", "-a", playerExe.toString(), uri }; //$NON-NLS-1$ //$NON-NLS-2$
}
else
{
launchCommand = new String[] { "/usr/bin/open", uri }; //$NON-NLS-1$
}
}
else // Windows or *nix
{
if (launchInfo.isWebBrowserNativeLaunch())
{
File httpExe = m_debuggerCallbacks.getHttpExe();
String[] customParams = m_debuggerCallbacks.getBrowserParameters(uri);
if (customParams == null) {
if (os == OS.Windows)
launchCommand = getWindowsBrowserLaunchArgs(httpExe, uri);
else
launchCommand = new String[] { httpExe.toString(), uri };
}
else {
final int prependLen = 1;
launchCommand = new String[customParams.length + prependLen];
launchCommand[0] = httpExe.toString();
for ( int i = 0; i < customParams.length; i++) {
launchCommand[i + prependLen] = customParams[i];
}
}
}
else if (launchInfo.isPlayerNativeLaunch())
{
File playerExe = m_debuggerCallbacks.getPlayerExe();
launchCommand = new String[] { playerExe.toString(), uri };
}
else
{
if (os == OS.Windows)
{
// We must quote all ampersands in the URL; if we don't, then
// cmd.exe will interpret the ampersand as a command separator.
uri = uri.replaceAll("&", "\"&\""); //$NON-NLS-1$ //$NON-NLS-2$
launchCommand = new String[] { "cmd", "/c", "start", uri }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
else
{
String exeName;
if (launchInfo.isWebPage())
exeName = m_debuggerCallbacks.getHttpExeName();
else
exeName = m_debuggerCallbacks.getPlayerExeName();
throw new FileNotFoundException(exeName);
}
}
}
return launchCommand;
}
/**
* Gets the arguments needed for launching a web browser on Windows.
*/
private String[] getWindowsBrowserLaunchArgs(File httpExe, String uri)
{
if (httpExe.getName().equalsIgnoreCase("chrome.exe")) //$NON-NLS-1$
{
// FB-16779: Adding "--disable-hang-monitor" to prevent Chrome
// from warning us that a plug-inappears to be hung; it does
// that when the user hits a breakpoint.
return new String[] { httpExe.toString(), "--disable-hang-monitor", uri }; //$NON-NLS-1$
}
else if (httpExe.getName().equalsIgnoreCase("iexplore.exe")) //$NON-NLS-1$
{
boolean isIE8 = false;
try
{
int[] ieVersion = m_debuggerCallbacks.getAppVersion(httpExe);
if (ieVersion != null)
isIE8 = (ieVersion[0] >= 8);
} catch (IOException e) {
// ignore
}
if (isIE8)
{
// FB-22107: Tell IE to keep using the new process we are
// launching, rather than merging the new process into the
// old one. This allows us to terminate the new IE
// debugging session.
return new String[] { httpExe.toString(), "-noframemerging", uri }; //$NON-NLS-1$
}
}
return new String[] { httpExe.toString(), uri };
}
/**
* Gets the arguments needed for launching a swf that needs to run
* in AIR.
*/
private String[] getAIRLaunchArgs(String uri, AIRLaunchInfo airLaunchInfo)
throws IOException
{
List<String> cmdList = new LinkedList<String>();
cmdList.add(airLaunchInfo.airDebugLauncher.getPath());
if (airLaunchInfo.airRuntimeDir != null)
{
cmdList.add("-runtime"); //$NON-NLS-1$
cmdList.add(airLaunchInfo.airRuntimeDir.getPath());
}
if (airLaunchInfo.airSecurityPolicy != null)
{
cmdList.add("-security-policy"); //$NON-NLS-1$
cmdList.add(airLaunchInfo.airSecurityPolicy.getPath());
}
if (airLaunchInfo.airPublisherID != null && airLaunchInfo.airPublisherID.length() > 0)
{
cmdList.add("-pubid"); //$NON-NLS-1$
cmdList.add(airLaunchInfo.airPublisherID);
}
if (airLaunchInfo.profile != null && airLaunchInfo.profile.length() > 0)
{
cmdList.add("-profile"); //$NON-NLS-1$
cmdList.add(airLaunchInfo.profile);
}
if (airLaunchInfo.screenSize != null && airLaunchInfo.screenSize.length() > 0)
{
cmdList.add("-screensize"); //$NON-NLS-1$
cmdList.add(airLaunchInfo.screenSize);
}
if (airLaunchInfo.dpi > 0)
{
//TODO: this is apparently only going to be used in AIR 2.5.
//Figure out permanent solution when AIR 3.0 comes along.
cmdList.add("-XscreenDPI"); //$NON-NLS-1$
cmdList.add(String.valueOf(airLaunchInfo.dpi));
}
if (airLaunchInfo.versionPlatform != null && airLaunchInfo.versionPlatform.length() > 0)
{
cmdList.add("-XversionPlatform"); //$NON-NLS-1$
cmdList.add(airLaunchInfo.versionPlatform);
}
if (airLaunchInfo.extDir != null && airLaunchInfo.extDir.length() > 0) {
cmdList.add("-extdir"); //$NON-NLS-1$
cmdList.add(airLaunchInfo.extDir);
}
if (airLaunchInfo.deviceExtDir != null && airLaunchInfo.deviceExtDir.length() > 0) {
cmdList.add("-XdeviceExtDir"); //$NON-NLS-1$
cmdList.add(airLaunchInfo.deviceExtDir);
}
// If it's a "file:" URL, then pass the actual filename; otherwise, use the URL
// ok, its not an http: type request therefore we should be able to see
// it on the file system, right? If not then it's probably not valid
File f = null;
if (uri.startsWith("file:")) //$NON-NLS-1$
{
try
{
f = new File(new URI(uri));
cmdList.add(f.getPath());
}
catch (URISyntaxException e)
{
IOException ioe = new IOException(e.getMessage());
ioe.initCause(e);
throw ioe;
}
}
else
{
cmdList.add(uri);
}
if (airLaunchInfo.applicationContentRootDir != null)
{
cmdList.add(airLaunchInfo.applicationContentRootDir.getAbsolutePath());
}
List<String> args = null;
if (airLaunchInfo.applicationArgumentsArray != null)
{
args = Arrays.asList(airLaunchInfo.applicationArgumentsArray);
}
else if (airLaunchInfo.applicationArguments != null)
{
args = splitArgs(airLaunchInfo.applicationArguments);
}
if (args != null && args.size() > 0)
{
cmdList.add("--"); //$NON-NLS-1$
cmdList.addAll(args);
}
return cmdList.toArray(new String[cmdList.size()]);
}
/**
* This is annoying: We must duplicate the operating system's behavior
* with regard to splitting arguments.
*
* @param arguments A single string of arguments that are intended to
* be passed to an AIR application. The tricky part is that some
* of the arguments may be quoted, and if they are, then the quoting
* will be in a way that is specific to the current platform. For
* example, on Windows, strings are quoted with the double-quote character
* ("); on Mac and Unix, strings can be quoted with either double-quote
* or single-quote.
* @return The equivalent
*/
private List<String> splitArgs(String arguments)
{
List<String> retval = new ArrayList<String>();
arguments = arguments.trim();
// Windows quotes only with double-quote; Mac and Unix also allow single-quote.
boolean isWindows = System.getProperty("os.name").toLowerCase().startsWith("win"); //$NON-NLS-1$ //$NON-NLS-2$
boolean isMacOrUnix = !isWindows;
int i=0;
while (i<arguments.length()) {
char ch = arguments.charAt(i);
if (ch == ' ' || ch == '\t') {
// keep looping
i++;
} else if (ch == '"' || (isMacOrUnix && ch == '\'')) {
char quote = ch;
int nextQuote = arguments.indexOf(quote, i+1);
if (nextQuote == -1) {
retval.add(arguments.substring(i+1));
return retval;
} else {
retval.add(arguments.substring(i+1, nextQuote));
i = nextQuote+1;
}
} else {
int startPos = i;
while (i<arguments.length()) {
ch = arguments.charAt(i);
if (ch == ' ' || ch == '\t') {
break;
}
i++;
}
retval.add(arguments.substring(startPos, i));
}
}
return retval;
}
/*
* @see flash.tools.debugger.SessionManager#playerForUri(java.lang.String, flash.tools.debugger.AIRLaunchInfo)
*/
public Player playerForUri(String url, AIRLaunchInfo airLaunchInfo)
{
LaunchInfo launchInfo = new LaunchInfo(url);
if (airLaunchInfo != null)
{
return new AIRPlayer(airLaunchInfo.airDebugLauncher);
}
else if (launchInfo.isAIRLaunch())
{
return new AIRPlayer(null);
}
else
{
// Find the Netscape plugin
if (getOS() == OS.Mac)
{
if (!launchInfo.isWebBrowserNativeLaunch())
{
File playerExe = m_debuggerCallbacks.getPlayerExe();
return new StandalonePlayer(playerExe);
}
File flashPlugin = new File("/Library/Internet Plug-Ins/Flash Player.plugin"); //$NON-NLS-1$
return new NetscapePluginPlayer(m_debuggerCallbacks.getHttpExe(), flashPlugin);
}
else
{
if (launchInfo.isWebBrowserNativeLaunch())
{
File httpExe = m_debuggerCallbacks.getHttpExe();
if (httpExe.getName().equalsIgnoreCase("iexplore.exe")) //$NON-NLS-1$
{
// IE on Windows: Find the ActiveX control
String activeXFile = null;
final String registryKey = "HKEY_CLASSES_ROOT\\CLSID\\{D27CDB6E-AE6D-11cf-96B8-444553540000}\\InprocServer32"; //$NON-NLS-1$
//check if this is a 64-bit machine
boolean is64Bit = (System.getenv("ProgramFiles(x86)") != null); //$NON-NLS-1$
try
{
if (is64Bit) {
//now we have to ensure that we only query the registry
//that is 32 or 64-bit depending upon whether we are
//launching 32 or 64-bit IE.
if (httpExe.getCanonicalPath().contains("(x86)")) { //$NON-NLS-1$
activeXFile = m_debuggerCallbacks.queryWindowsRegistry(registryKey, null, 1);
}
else {
activeXFile = m_debuggerCallbacks.queryWindowsRegistry(registryKey, null, 2);
}
}
else {
activeXFile = m_debuggerCallbacks.queryWindowsRegistry(registryKey, null, 1);
}
}
catch (IOException e)
{
// ignore
}
if (activeXFile == null)
return null; // we couldn't find the player
File file = new File(activeXFile);
return new ActiveXPlayer(httpExe, file);
}
else
{
// Find the Netscape plugin
File browserDir = httpExe.getParentFile();
// Opera puts plugins under "program\plugins" rather than under "plugins"
if (httpExe.getName().equalsIgnoreCase("opera.exe")) //$NON-NLS-1$
browserDir = new File(browserDir, "program"); //$NON-NLS-1$
File pluginsDir = new File(browserDir, "plugins"); //$NON-NLS-1$
File flashPlugin = new File(pluginsDir, "NPSWF32.dll"); // WARNING, Windows-specific //$NON-NLS-1$
// Bug FB-4691: The player is now installed via a registry key, not
// in the "plugins" directory.
//
// Although Mozilla does not document this, the actual behavior of
// the browser seems to be that it looks first in the "plugins" directory,
// and then, if the file is not found there, it looks in the registry.
// So, we mimic that behavior.
if (!flashPlugin.exists())
{
File pathFromRegistry = getWindowsMozillaPlayerPathFromRegistry();
if (pathFromRegistry != null)
flashPlugin = pathFromRegistry;
}
return new NetscapePluginPlayer(httpExe, flashPlugin);
}
}
else if (launchInfo.isPlayerNativeLaunch())
{
File playerExe = m_debuggerCallbacks.getPlayerExe();
return new StandalonePlayer(playerExe);
}
}
}
return null;
}
/**
* Look in the Windows registry for the Mozilla version of the Flash player.
*/
private File getWindowsMozillaPlayerPathFromRegistry()
{
final String KEY = "\\SOFTWARE\\MozillaPlugins\\@adobe.com/FlashPlayer"; //$NON-NLS-1$
final String PATH = "Path"; //$NON-NLS-1$
// According to
//
// http://developer.mozilla.org/en/docs/Plugins:_The_first_install_problem
//
// the MozillaPlugins key can be written to either HKEY_CURRENT_USER or
// HKEY_LOCAL_MACHINE. Unfortunately, as of this writing, Firefox
// (version 2.0.0.2) doesn't actually work that way -- it only checks
// HKEY_LOCAL_MACHINE, but not HKEY_CURRENT_USER.
//
// But in hopeful anticipation of a fix for that, we are going to check both
// locations. On current builds, that won't do any harm, because the
// current Flash Player installer only writes to HKEY_LOCAL_MACHINE. In the
// future, if Mozilla gets fixed and then the Flash player installer gets
// updated, then our code will already work correctly.
//
// Another quirk: In my opinion, it would be better for Mozilla to look first
// in HKEY_CURRENT_USER, and then in HKEY_LOCAL_MACHINE. However, according to
//
// http://developer.mozilla.org/en/docs/Installing_plugins_to_Gecko_embedding_browsers_on_Windows
//
// they don't agree with that -- they want HKEY_LOCAL_MACHINE first.
String[] roots = { "HKEY_LOCAL_MACHINE", "HKEY_CURRENT_USER" }; //$NON-NLS-1$ //$NON-NLS-2$
for (int i=0; i<roots.length; ++i)
{
try
{
String path = m_debuggerCallbacks.queryWindowsRegistry(roots[i] + KEY, PATH, 1);
if (path != null)
return new File(path);
}
catch (IOException e)
{
// ignore
}
}
return null;
}
/*
* @see flash.tools.debugger.SessionManager#supportsLaunch()
*/
public boolean supportsLaunch()
{
return true;
}
/*
* @see flash.tools.debugger.SessionManager#accept(flash.tools.debugger.IProgress)
*/
public Session accept(IProgress waitReporter) throws IOException
{
return accept(null, waitReporter);
}
/**
* A private variation on <code>accept()</code> that also has an argument
* indicating that the process we are waiting for has terminated.
*
* @param pl
* Optional process listener. If non-null, this is used to detect
* if a process that was launched has terminated unexpectedly.
* For example, if launch() launches adl, but adl exits, then we
* don't want to continue to wait for a socket connection.
*/
private Session accept(ProcessListener pl, IProgress waitReporter) throws IOException
{
// get timeout
int timeout = getPreference(PREF_ACCEPT_TIMEOUT);
int totalTimeout = timeout;
int iterateOn = 100;
PlayerSession session = null;
try
{
m_serverSocket.setSoTimeout(iterateOn);
// Wait 100ms per iteration. We have to do that so that we can report how long
// we have been waiting.
Socket s = null;
while (s == null && !airAppTerminated(pl))
{
try
{
s = m_serverSocket.accept();
}
catch(IOException ste)
{
timeout -= iterateOn;
if (timeout < 0 || m_serverSocket == null || m_serverSocket.isClosed())
throw ste; // we reached the timeout, or someome called stopListening()
}
// Tell the progress monitor we've waited a little while longer,
// so that the Eclipse progress bar can keep chugging along
if (waitReporter != null)
waitReporter.setProgress(totalTimeout - timeout, totalTimeout);
}
if (s == null && airAppTerminated(pl))
{
throw pl.createLaunchFailureException();
}
/* create a new session around this socket */
session = PlayerSession.createFromSocketWithOptions(s, m_debuggerCallbacks, this);
// transfer preferences
session.setPreferences(m_prefs);
}
catch(NullPointerException npe)
{
throw new BindException(getLocalizationManager().getLocalizedTextString("serverSocketNotListening")); //$NON-NLS-1$
}
return session;
}
/**
* Returns true if the passed-in process listener is for an AIR application
* that has terminated. This is used by accept() in order to detect that it
* should give up listening on the socket.
*
* The reason we can't do this for Flash player-based apps is that unlike
* AIR apps, the process that we launched sometimes acts as just sort of a
* "launcher" process that terminates quickly, and the actual Flash player
* is in some other process. For example, on Mac, we often invoke the "open"
* program to open a web browser; and on Windows, if you launch firefox.exe
* but it detects that there is already a running instance of firefox.exe,
* the new instance will just pass a message to the old instance, and then
* the new instance will terminate.
*
* @param pl
* a process listener, or <code>null</code>
* @return true if pl refers to an AIR app that has terminated.
*/
private boolean airAppTerminated(ProcessListener pl)
{
if (pl != null)
{
if (pl.isAIRApp())
{
if (pl.isProcessDead())
{
return true;
}
}
}
return false;
}
/*
* @see flash.tools.debugger.SessionManager#getDebuggerCallbacks()
*/
public IDebuggerCallbacks getDebuggerCallbacks()
{
return m_debuggerCallbacks;
}
/*
* @see flash.tools.debugger.SessionManager#setDebuggerCallbacks(flash.tools.debugger.IDebuggerCallbacks)
*/
public void setDebuggerCallbacks(IDebuggerCallbacks debuggerCallbacks)
{
m_debuggerCallbacks = debuggerCallbacks;
}
/**
* A private variation on <code>connect()</code> that also has an argument
* indicating that the process we are waiting for has terminated.
*
* @param pl
* Optional process listener. If non-null, this is used to detect
* if a process that was launched has terminated unexpectedly.
* For example, if launch() launches adl, but adl exits, then we
* don't want to continue to wait for a socket connection.
*/
public Session connect(int port, IProgress waitReporter) throws IOException
{
final int waitTime = getPreference(PREF_CONNECT_WAIT_INTERVAL);
final int maxRetryAttempts = getPreference(PREF_CONNECT_RETRY_ATTEMPTS);
final int totalTimeout = getPreference(PREF_CONNECT_TIMEOUT);
final long timeForConnectStart = System.currentTimeMillis();
long elapsedTime = 0;
int retryAttempts = -1;
PlayerSession session = null;
Socket s = null;
m_cancelConnect = false;
// Try to see if a connect happens till totalTimeout
// If the connection was refused in between, retry
// again after waitTime until totalTimeout is elapsed.
// Retry mechanism is disabled if PREF_CONNECT_RETRY_ATTEMPTS
// is 0.
while (s == null)
{
try
{
InetSocketAddress localAddress = new InetSocketAddress(InetAddress.getByName(null), port);
s = new Socket();
//save the socket for canceling connect
m_connectSocket = s;
//connect to loopback address at the specified port
s.connect(localAddress, totalTimeout);
}
catch(IOException ste)
{
if (ste instanceof SocketTimeoutException) {
//if we timed out, abort connect
abortConnect(ste);
}
safeCloseSocket(s);
s = null;
retryAttempts++;
//if we should not retry, checkConnectTimeout
//throws an exception
elapsedTime = checkConnectTimeout(waitTime, maxRetryAttempts,
totalTimeout, retryAttempts, timeForConnectStart, ste);
}
// Tell the progress monitor we've waited a little while longer,
// so that the Eclipse progress bar can keep chugging along
if (waitReporter != null)
waitReporter.setProgress((int)elapsedTime, totalTimeout);
if (s != null) {
/** If we connected, make sure that we get some response
* back after sending the handshake. This is required because
* of the way port forwarding works. A connect will be successful
* if port forwarding is set up, but we won't get any response
* unless the application is actually listening.
*/
/* create a new session around this socket */
session = PlayerSession.createFromSocketWithOptions(s, m_debuggerCallbacks, this);
// transfer preferences
session.setPreferences(m_prefs);
try {
session.bind();
}
catch (VersionException ex) {
session.unbind();
safeCloseSocket(s);
s = null;
retryAttempts++;
/** The VersionException here is considered as an IOException
* because we do not know if there was even a valid application
* listening on the port. Once the port is forwarded, connect
* succeeds and we get a VersionException even if player is not
* listening on that port.
*/
elapsedTime = checkConnectTimeout(waitTime, maxRetryAttempts,
totalTimeout, retryAttempts, timeForConnectStart,
new IOException(ex.getLocalizedMessage()));
}
}
}
m_connectSocket = null;
return session;
}
/**
* @param waitTime
* @param maxRetryAttempts
* @param totalTimeout
* @param retryAttempts
* @param startTime
* @param caughtException
* @return
* @throws IOException
*/
private long checkConnectTimeout(final int waitTime,
final int maxRetryAttempts, final int totalTimeout,
int retryAttempts, final long startTime, IOException caughtException)
throws IOException {
long elapsedTime;
long endTime = System.currentTimeMillis();
elapsedTime = endTime - startTime;
// check if we should retry
boolean retryFinished = (maxRetryAttempts != -1 && retryAttempts >= maxRetryAttempts);
// check if we timed out or somebody called stopConnecting()
if (retryFinished ||
elapsedTime > totalTimeout ||
m_cancelConnect ) {
abortConnect(caughtException);
}
//wait a bit before retrying
try {
Thread.sleep(waitTime);
} catch (InterruptedException e) {
abortConnect(caughtException);
}
//check cancel before resuming
if (m_cancelConnect ) {
abortConnect(caughtException);
}
return elapsedTime;
}
/**
* @param ste
* @throws IOException
*/
private void abortConnect(IOException ste) throws IOException {
m_connectSocket = null;
m_cancelConnect = false;
throw ste;
}
/**
* @param s
*/
private void safeCloseSocket(Socket s) {
//clean up the socket
if (s != null && !s.isClosed()) {
try {
s.close();
}
catch (IOException closeException) {
//ignore
}
}
}
/*
* @see flash.tools.debugger.SessionManager#stopConnecting()
*/
public void stopConnecting() throws IOException
{
if (!m_cancelConnect) {
m_cancelConnect = true;
if (m_connectSocket != null)
{
m_connectSocket.close();
m_connectSocket = null;
}
}
}
/*
* @see flash.tools.debugger.SessionManager#isConnecting()
*/
public boolean isConnecting()
{
return (m_connectSocket == null) ? false : true;
}
/**
* Returns the localization manager. Use this for all localized strings.
*/
public static LocalizationManager getLocalizationManager()
{
return m_localizationManager;
}
@Override
public Process launchForRun(String uri, AIRLaunchInfo airLaunchInfo,
IProgress waitReporter, ILaunchNotification launchNotification)
throws IOException {
String[] launchCommand = getLaunchCommand(uri, airLaunchInfo, false);
// create the process and attach a thread to watch it during our accept phase
Process proc = m_debuggerCallbacks.launchDebugTarget(launchCommand);
//forDebugging = false
// If launching an AIR app, and forDebugging=false (meaning we are just running it,
// not debugging it), start a background thread that will call the launchNotification
// when the launch is complete.
startProcessListener(airLaunchInfo, false,launchNotification,
launchCommand, proc,true);
return proc;
}
private ProcessListener startProcessListener(AIRLaunchInfo airLaunchInfo, boolean forDebugging,
ILaunchNotification launchNotification, String[] launchCommand,
Process proc, boolean isRunLaunch) {
ProcessListener processListener = new ProcessListener(launchCommand, proc, launchNotification, forDebugging, airLaunchInfo != null); // BUG FB-9874: launchNotifier added
processListener.setIsRunLaunch(isRunLaunch);
// If launching an AIR app, and forDebugging=false (meaning we are just running it,
// not debugging it), start a background thread that will call the launchNotification
// when the launch is complete.
if (!forDebugging && airLaunchInfo != null && launchNotification != null)
processListener.startLaunchNotifier();
return processListener;
}
private String[] getLaunchCommand(String uri, AIRLaunchInfo airLaunchInfo, boolean forDebugging)
throws IOException, FileNotFoundException {
String[] launchCommand;
uri = uri.trim();
if (airLaunchInfo == null)
{
LaunchInfo launchInfo = new LaunchInfo(uri);
uri = tweakNativeLaunchUri(uri, forDebugging, launchInfo);
launchCommand = getFlashLaunchArgs(uri, launchInfo);
}
else // else, AIR
{
launchCommand = getAIRLaunchArgs(uri, airLaunchInfo);
}
return launchCommand;
}
@Override
public Process launchForRun(String uri, AIRLaunchInfo airLaunchInfo, IProgress waitReporter, ILaunchNotification launchNotification,
ILauncher launcher) throws IOException {
String[] launchCommand = getLaunchCommandForLauncher(uri, airLaunchInfo,false);
// create the process and attach a thread to watch it during our accept phase
Process proc = m_debuggerCallbacks.launchDebugTarget(launchCommand,launcher);
//forDebugging = false
// If launching an AIR app, and forDebugging=false (meaning we are just running it,
// not debugging it), start a background thread that will call the launchNotification
// when the launch is complete.
startProcessListener(airLaunchInfo, false,launchNotification,
launchCommand, proc, true);;
return proc;
}
private String[] getLaunchCommandForLauncher(String uri, AIRLaunchInfo airLaunchInfo,boolean forDebugging)
throws IOException, FileNotFoundException {
String[] launchCommand;
if (airLaunchInfo == null)
{
LaunchInfo launchInfo = new LaunchInfo(uri);
uri = tweakNativeLaunchUri(uri, forDebugging, launchInfo);
launchCommand = new String[]{uri};
}
else // else, AIR
{
launchCommand = getAIRLaunchArgs(uri, airLaunchInfo);
}
return launchCommand;
}
@Override
public Session launch(String uri, AIRLaunchInfo airLaunchInfo, boolean forDebugging, IProgress waitReporter,
ILaunchNotification launchNotification, ILauncher launcher) throws IOException {
String[] launchCommand = getLaunchCommandForLauncher(uri, airLaunchInfo,forDebugging);
// create the process and attach a thread to watch it during our accept phase
Process proc = m_debuggerCallbacks.launchDebugTarget(launchCommand, launcher);
ProcessListener processListener = startProcessListener(airLaunchInfo,forDebugging, launchNotification, launchCommand, proc,false);
PlayerSession session = null;
if (forDebugging)
{
session = waitForConnection(uri, airLaunchInfo, waitReporter, proc, processListener);
session.setLauncher(launcher);
}
return session;
}
}