/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: hxbackend.cpp,v 1.32.2.8 2004/11/23 00:24:25 rggammon Exp $
 * 
 * * Portions Copyright (c) 1995-2002 RealNetworks, Inc. All Rights Reserved.
 * 
 * The contents of this file, and the files included with this file,
 * are subject to the current version of the RealNetworks Public
 * Source License (the "RPSL") available at
 * http://www.helixcommunity.org/content/rpsl unless you have licensed
 * the file under the current version of the RealNetworks Community
 * Source License (the "RCSL") available at
 * http://www.helixcommunity.org/content/rcsl, in which case the RCSL
 * will apply. You may also obtain the license terms directly from
 * RealNetworks.  You may not use this file except in compliance with
 * the RPSL or, if you have a valid RCSL with RealNetworks applicable
 * to this file, the RCSL.  Please see the applicable RPSL or RCSL for
 * the rights, obligations and limitations governing use of the
 * contents of the file.
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL") in which case the provisions of the GPL are applicable
 * instead of those above. If you wish to allow use of your version of
 * this file only under the terms of the GPL, and not to allow others
 * to use your version of this file under the terms of either the RPSL
 * or RCSL, indicate your decision by deleting the provisions above
 * and replace them with the notice and other provisions required by
 * the GPL. If you do not delete the provisions above, a recipient may
 * use your version of this file under the terms of any one of the
 * RPSL, the RCSL or the GPL.
 * 
 * This file is part of the Helix DNA Technology. RealNetworks is the
 * developer of the Original Code and owns the copyrights in the
 * portions it created.
 * 
 * This file, and the files included with this file, is distributed
 * and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS
 * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET
 * ENJOYMENT OR NON-INFRINGEMENT.
 * 
 * Technology Compatibility Kit Test Suite(s) Location:
 *    http://www.helixcommunity.org/content/tck
 * 
 * Contributor(s):
 * 
 * ***** END LICENSE BLOCK ***** */

/* XXXRGG: Undefine DEBUG so we don't pull in nsDebug stuff */
#ifdef DEBUG
#  undef DEBUG
#endif

#ifdef _UNIX
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#endif

#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>

#include <signal.h>

#include "npplat.h"
#include "hxbackend.h"
#include "asprintf.h"

#include "tokenizer.h"

#include "nsIServiceManager.h"
#include "nsIComponentManager.h"
#include "nsIDOMWindow.h"

#define EXIT_CODE_UNKNOWN          0
#define EXIT_CODE_HXPLAY_NOT_FOUND 10

const int CHXPlayerBackend::m_iBackendVersion = 1;
const int CHXPlayerBackend::m_iPlayerVersion = 1;

typedef enum
{
    FIND_PLAYER_STATE_INIT,
    FIND_PLAYER_STATE_OVERRIDE,
    FIND_PLAYER_STATE_REALPLAY,
    FIND_PLAYER_STATE_HXPLAY,
    FIND_PLAYER_STATE_FAILED
} FindPlayerState;


/* This is a dummy class that we pass off any outstanding timer references
   to when we shut down.

   It has two reasons for existing:
   1. Mozilla bug 215163 (affecting versions up to 1.4) means that CHXPlayerBackend
      can have dangling timer references that prevent a clean shutdown. The CHXTimerFob
      takes ownership of these references.
   2. The timer can release its reference from another thread that can run after
      NS_PluginShutdown gets called. There's nothing wrong with this per se, but I'm
      more comfortable if the backend shuts down at the same time as the plugin, rather
      than sticking around until all timer references are released.
*/
class CHXTimerFob : public nsIObserver
{
    NS_DECL_ISUPPORTS

    CHXTimerFob() { };
    virtual ~CHXTimerFob() { };

    // nsIObserver methods
    NS_IMETHOD Observe(nsISupports *pSubject, const char *szTopic, const PRUnichar *pData)
    {
        printf("This should not be called\n");
        return NS_OK;
    }
};

// nsIObserver
NS_IMPL_ISUPPORTS1(CHXTimerFob, nsIObserver)

// nsIObserver
NS_IMPL_ISUPPORTS1(CHXPlayerBackend, nsIObserver)
    
NS_IMETHODIMP CHXPlayerBackend::Observe(nsISupports * /* pSubject */,
                                        const char *szTopic,
                                        const PRUnichar * /*pData*/)
{
    if(strcmp(szTopic, NS_TIMER_CALLBACK_TOPIC) != 0)
    {
        return NS_ERROR_FAILURE;
    }
    
    if(m_bPlayerShutdown)
    {
        return NS_ERROR_FAILURE;
    }
    
    return PollForCallbacks();
}

CHXPlayerBackend::CHXPlayerBackend()
    :   m_bCallbackTimerRunning(FALSE),
        m_fdApi(-1),
    	m_fdCallback(-1),
    	m_nChildPid(0),
    	m_bPlayerShutdown(PR_FALSE),
        m_bEmbeddedPlayerOpen(PR_FALSE),
        m_pCallbackBuf(NULL),
        m_nCallbackBufLen(0),
        m_nCallbackBufPos(0),
        m_pTimer(NULL),
        m_pObsoleteTimer(NULL),
        m_pUConv(NULL),
        m_pPromptService(NULL),
        m_pMemory(NULL),
        m_pPlayersList(NULL),
        m_nPlayerCount(0),
        m_nMaxPlayers(0)
{
}

CHXPlayerBackend::~CHXPlayerBackend()
{
    if(m_nPlayerCount == 0)
    {
        printf("Shutting down with plugins still existing\n");
    }
}

nsresult CHXPlayerBackend::SendMessage(const char *pBuf, int nSize)
{
    ssize_t nPos = 0;
    ssize_t nResult;
    fd_set fdsWrite, fdsException;
    struct timeval timeout;
    int nDescriptors;
    nsresult result = NS_OK;

    if(!m_bEmbeddedPlayerOpen)
    {
        return NS_ERROR_FAILURE;
    }
    
    do
    {
        FD_ZERO(&fdsWrite);
        FD_ZERO(&fdsException);

        FD_SET(m_fdApi, &fdsWrite);
        FD_SET(m_fdApi, &fdsException);

        timeout.tv_sec = IPC_TIMEOUT;
        timeout.tv_usec = 0;

        nDescriptors = select(m_fdApi + 1, NULL, &fdsWrite, &fdsException, &timeout);
        if(nDescriptors < 0)
        {
            perror("select");
            break;
        }
        else if(nDescriptors == 0)
        {
            /* We timed out. The current ipc protocol doesn't deal with this
               well, as responses can't be mapped to requests. */
            printf("Timed out in SendMessage\n");
            result = NS_ERROR_FAILURE;
            break;
        }
        else if(FD_ISSET(m_fdApi, &fdsException))
        {
            /* This should never happen. */
            printf("Exception in SendMessage\n");
            result = NS_ERROR_FAILURE;
            break;
        }
        else if(FD_ISSET(m_fdApi, &fdsWrite))
        {
            /* We can read */
            nResult = write(m_fdApi, pBuf + nPos, nSize - nPos);
            if(nResult <= 0)
            {
                if(nResult < 0)
                {
                    if((errno != EINTR) && (errno != EAGAIN))
                    {
                        perror("write");
                        result = NS_ERROR_FAILURE;
                    }
                }

                /* Connection closed */
                result = NS_ERROR_FAILURE;
                CloseEmbeddedPlayer(PR_FALSE);
                break;
            }
            else
            {
                nPos += nResult;
            }
        }
        else
        {
            /* This is unreachable. */
            printf("Unknown state in select()\n");
            result = NS_ERROR_FAILURE;
            break;
        }
                
    } while(nPos < nSize);

    return result;
}

nsresult CHXPlayerBackend::ReceiveMessage(char **ppBuf)
{
    ssize_t nPos = 0;
    ssize_t nResult;
    fd_set fdsRead, fdsException;
    struct timeval timeout;
    int nDescriptors;
    int nSize = 1024;
    char *pBuf;
    nsresult result = NS_ERROR_FAILURE;

    if(!m_bEmbeddedPlayerOpen)
    {
        return NS_ERROR_FAILURE;
    }
    
    pBuf = (char*)malloc(nSize);
    *ppBuf = pBuf;

    for(;;)
    {
        FD_ZERO(&fdsRead);
        FD_ZERO(&fdsException);

        FD_SET(m_fdApi, &fdsRead);                                                                                
        FD_SET(m_fdApi, &fdsException);

        timeout.tv_sec = IPC_TIMEOUT;
        timeout.tv_usec = 0;

        nDescriptors = select(m_fdApi + 1, &fdsRead, NULL, &fdsException, &timeout);
        if(nDescriptors < 0)
        {
            if(errno != EINTR)
            {
                perror("select");
            }
            break;
        }
        else if(nDescriptors == 0)
        {
            /* We timed out. The current ipc protocol doesn't deal with this
               well, as responses can't be mapped to requests. */
            printf("Timed out in ReceiveMessage\n");
            break;
        }
        else if(FD_ISSET(m_fdApi, &fdsException))
        {
            /* This should never happen. */
            printf("Exception in ReceiveMessage\n");
            break;
        }
        else if(FD_ISSET(m_fdApi, &fdsRead))
        {
            /* We can read */
            nResult = read(m_fdApi, pBuf + nPos, nSize - nPos);
            if(nResult <= 0)
            {
                if(nResult < 0)
                {
                    if((errno != EINTR) && (errno != EAGAIN))
                    {
                        perror("read");
                    }
                }
                
                /* Connection closed */
                CloseEmbeddedPlayer(PR_FALSE);
                break;
            }
            else
            {
                char* pNewLine;
                
                nPos += nResult;
                if(nPos >= nSize)
                {
                    /* We need more buffer space */
                    nSize *= 2;
                    pBuf = (char*)realloc(pBuf, nSize);
                }
                pBuf[nPos] = '\0';
                pNewLine = strchr(pBuf, '\n');
                if(pNewLine)
                {
                    if((pNewLine - pBuf) != (nPos - 1))
                    {
                        /* Newline should have been the last thing we read */
                        printf("Got data after newline\n");
                    }
                    result = NS_OK;
                    break;
                }
            }
        }
        else
        {
            /* This is unreachable. */
            printf("Unknown state in select()\n");
            break;
        }
    }                

    if(result == NS_OK)
    {
        /* Trim back the buffer to the size we actually used (plus NULL) */
        pBuf = (char*)realloc(pBuf, nPos + 1);
    }
    else
    {
        free(pBuf);
        *ppBuf = NULL;
    }

    return result;
}

nsresult CHXPlayerBackend::Init(NPP instance)
{    
    // Query for services    
    nsIServiceManager *pServiceManager = NULL;
    nsIComponentManager *pComponentManager = NULL;
    NPN_GetValue(NULL, NPNVserviceManager, &pServiceManager);
    nsresult rv;

    if(!pServiceManager)
    {        
        /* There are two possibilities here:
           1. We are not being used by mozilla
           2. We are being used by mozilla, but we were compiled
              with the wrong version of mozilla.
           
           We check for the latter here. */

        nsIServiceManager *pUnusable = NULL;

        NPN_GetValue(NULL, (NPNVariable)(NPNVserviceManager ^ NP_ABI_GCC3_MASK), &pUnusable);
        if(pUnusable)
        {
            /* This is not a fatal error -- we can still run, but callbacks
               will not function properly. */
            ShowError("The HelixPlayer plugin and mozilla were compiled with different versions of gcc.", instance);
        }
    }
    
    if(pServiceManager)
    {
        pServiceManager->GetServiceByContractID("@mozilla.org/embedcomp/prompt-service;1",
                                                NS_GET_IID(nsIPromptService),
                                                (void**)&m_pPromptService);                

        pServiceManager->GetServiceByContractID("@mozilla.org/xpcom/memory-service;1",
                                                NS_GET_IID(nsIMemory),
                                                (void**)&m_pMemory);

        /* Get the component manager from the service manager */
        rv = pServiceManager->QueryInterface(NS_GET_IID(nsIComponentManager), (void**) &pComponentManager);
        if(pComponentManager && NS_SUCCEEDED(rv))
        {
            pComponentManager->CreateInstanceByContractID(NS_ISCRIPTABLEUNICODECONVERTER_CONTRACTID,
                                                          NULL,
                                                          NS_GET_IID(nsIScriptableUnicodeConverter),
                                                          (void**)&m_pUConv);
            
            pComponentManager->CreateInstanceByContractID(NS_TIMER_CONTRACTID,
                                                          NULL,
                                                          NS_GET_IID(nsITimer),
                                                          (void**)&m_pTimer);
            
            if(!m_pTimer)
            {
                /* Try to get an old-style mozilla 1.0-1.2 timer */
                pComponentManager->CreateInstanceByContractID(NS_TIMER_CONTRACTID,
                                                              NULL,
                                                              NS_GET_IID(nsIScriptableTimer),
                                                              (void**)&m_pObsoleteTimer);
            }
            
            NS_RELEASE(pComponentManager);
        }

        NS_RELEASE(pServiceManager);
    }
    
    // Step 2: Initialize services
    if(m_pUConv)
    {
        m_pUConv->SetCharset("us-ascii");
    }

    m_bPlayerShutdown = PR_FALSE;

    return NS_OK;
}

nsresult CHXPlayerBackend::OpenEmbeddedPlayer(NPP instance)
{
    int apisockets[2], cbsockets[2];
    FindPlayerState findPlayerState = FIND_PLAYER_STATE_INIT;

    while(findPlayerState != FIND_PLAYER_STATE_FAILED)
    {
        const char* szAppName;
        switch(findPlayerState)
        {
            case FIND_PLAYER_STATE_INIT:
                /* New state */
                findPlayerState = FIND_PLAYER_STATE_OVERRIDE;
                szAppName = getenv("HELIX_PLUGIN_PLAYER_OVERRIDE");
                if(szAppName)
                {                    
                    break;
                }
                /* failed to find an override, fall through */
                
            case FIND_PLAYER_STATE_OVERRIDE:
                findPlayerState = FIND_PLAYER_STATE_REALPLAY;
                szAppName = "realplay";
                break;
                
            case FIND_PLAYER_STATE_REALPLAY:
                findPlayerState = FIND_PLAYER_STATE_HXPLAY;
                szAppName = "hxplay";
                break;
                
            case FIND_PLAYER_STATE_HXPLAY:
            case FIND_PLAYER_STATE_FAILED:
            default:
                findPlayerState = FIND_PLAYER_STATE_FAILED;
                szAppName = NULL;
                break;
        }

        if(findPlayerState == FIND_PLAYER_STATE_FAILED)
        {
            break;
        }
        
        if (socketpair(AF_UNIX, SOCK_STREAM, 0, apisockets) < 0)
        {
            perror("socketpair");
            return NPERR_GENERIC_ERROR;
        }
        if (socketpair(AF_UNIX, SOCK_STREAM, 0, cbsockets) < 0)
        {
            perror("socketpair");
            
            close(apisockets[0]);
            close(apisockets[1]);
            
            return NPERR_GENERIC_ERROR;
        }

        // make sure these fds stay open across exec
        fcntl(apisockets[1], F_SETFD, 0);
        fcntl(cbsockets[1], F_SETFD, 0);

        m_nChildPid = fork();
        if(m_nChildPid < 0)
        {
            perror("fork");
            close(apisockets[0]);
            close(apisockets[1]);
            
            close(cbsockets[0]);
            close(cbsockets[1]);

            return NS_ERROR_FAILURE;
        }
        else if(m_nChildPid == 0)
        {
            // we are the child.
            int result;
            printf("Calling %s\n", szAppName);
#if 0
            char c = 0;
            printf("Attach to child %d\n", getpid());
            while(c == 0)
            {
            }
#endif            
            close(apisockets[0]);
            close(cbsockets[0]);
        
            char apiSd[20];
            sprintf(apiSd, "%d", apisockets[1]);
        
            char cbSd[20];
            sprintf(cbSd, "%d", cbsockets[1]);
                
            if (m_pTimer || m_pObsoleteTimer)
            {
                /* We have a timer callback, include --callbacks flag */
                result = execlp(szAppName, szAppName, "--embedded", apiSd, "--callbacks", cbSd, NULL);
            }
            else
            {
                /* No timer service -- disable callbacks */
                result = execlp(szAppName, szAppName, "--embedded", apiSd, NULL);
            }
        
            if (result < 0)
            {
                int nExitCode = 0;
                switch(errno)
                {
                    case ENOENT:
                        nExitCode = EXIT_CODE_HXPLAY_NOT_FOUND;
                        break;

                    default:
                        nExitCode = EXIT_CODE_UNKNOWN;
                        break;
                }
            
                // we couldn't exec hxplay.  
                close(apisockets[1]);
                close(cbsockets[1]);
            
                // Use _exit instead of exit to avoid affecting open
                // mozilla file descriptors
                _exit(nExitCode);
            }
        }
    
        // we are the parent    
        m_fdApi = apisockets[0];
        m_fdCallback = cbsockets[0];

        /* The embedded player, if any, now owns these. Close our side. */
        close(apisockets[1]);
        close(cbsockets[1]);

        /* Embedded player is now open until proved otherwise */
        m_bEmbeddedPlayerOpen = PR_TRUE;

        // Check for protocol version 
        char szRequest[32];
        nsresult result;
        int iPlayerVersion = -1;
        int nLen;
    
        nLen = snprintf(szRequest, sizeof(szRequest), "Version %d\n", m_iBackendVersion);
        result = SendMessage(szRequest, nLen);
        if(NS_SUCCEEDED(result))
        {
            int iReturnCode;
            char *pMsg = NULL;
            result = ReceiveMessage(&pMsg);
            if(NS_SUCCEEDED(result))
            {
                sscanf(pMsg, "%d, %d", &iReturnCode, &iPlayerVersion);
                free(pMsg);
                if(NS_SUCCEEDED(iReturnCode))
                {
                    if(iPlayerVersion == m_iBackendVersion)
                    {
                        /* found a suitable player */
                        break;
                    }
                    else
                    {
                        CloseEmbeddedPlayer(PR_TRUE);                        
                    }      
                }
                else
                {
                    CloseEmbeddedPlayer(PR_TRUE);
                }
            }
            else
            {
                CloseEmbeddedPlayer(PR_FALSE);
            }
        }
        else
        {
            CloseEmbeddedPlayer(PR_FALSE);
        }
        
    } // while(m_findPlayerState != FIND_PLAYER_STATE_FAILED)

    if(findPlayerState == FIND_PLAYER_STATE_FAILED)
    {                
        ShowError("Could not find an appropriate hxplay or realplay in the "
                  "system path to use as an embedded player", instance);
        
        /* Shutdown will clean up after Init() */
        Shutdown(PR_FALSE);
        
        return NS_ERROR_FAILURE;        
    }    
    
    /*  Allocate initial buffer space */
    m_nCallbackBufLen = 1024;
    m_pCallbackBuf = (char*)malloc(m_nCallbackBufLen * sizeof(char));

    StartCallbackTimer();
    
    return NS_OK;    
}
 
void CHXPlayerBackend::ShowError(const char *szError, NPP instance)
{
    nsIDOMWindow *pDomWindow = NULL;

#ifdef DEBUG
    /* Sanity check to make sure the error doesn't contain newlines.
       nsIPromptService can handle 'em, but javascript alert cannot. */
    assert(strchr(szError, '\n') == NULL);
#endif
    
    if(!instance)
    {
        if(m_nPlayerCount > 0)
        {
            /* Get the instance of the first player plugin */
            instance = m_pPlayersList[0]->GetInstance();
        }
    }

    if(instance)
    {
        PRUnichar *pUnicodeTitle = NULL;
        PRUnichar *pUnicodeMessage = NULL;

        NPN_GetValue(instance, NPNVDOMWindow, &pDomWindow);

        if(m_pPromptService && m_pUConv && m_pMemory && pDomWindow)
        {
            m_pUConv->ConvertToUnicode("Helix DNA Plugin Error", &pUnicodeTitle);
            m_pUConv->ConvertToUnicode(szError, &pUnicodeMessage);
        }

        if(pUnicodeTitle && pUnicodeMessage && m_pPromptService && m_pMemory && pDomWindow)
        {        
            /* Cancel the callback timer while the alert dialog is up to
               prevent a race (eg, the timer callback can call Shutdown(),
               which would destroy resources that are used below) */
            PRBool bRestartCallbackTimer = m_bCallbackTimerRunning;

            StopCallbackTimer();
            
            m_pPromptService->Alert(pDomWindow, pUnicodeTitle, pUnicodeMessage);

            if(bRestartCallbackTimer)
            {
                StartCallbackTimer();
            }
        }
        else
        {
            /* Fall back to printing error to standard out, and attempting a
               javascript alert */
            char *szJavaScriptAlert;

            asprintf(&szJavaScriptAlert, "javascript:alert(\"%s\")", szError);

            NPN_GetURL(instance, szJavaScriptAlert, "_self");
            free(szJavaScriptAlert);        
        }

        if(pUnicodeTitle)
        {
            m_pMemory->Free(pUnicodeTitle);
        }
        if(pUnicodeMessage)
        {
            m_pMemory->Free(pUnicodeMessage);
        }
    }
    
    fprintf(stderr, "%s\n", szError);

    if(pDomWindow)
    {
        NS_IF_RELEASE(pDomWindow);
    }
}

nsresult CHXPlayerBackend::ReadGenericResponse(PRBool* retval)
{
    // just reads a generic response code
    char *pMsg;
    nsresult result;
    
    *retval = PR_FALSE;
    
    result = ReceiveMessage(&pMsg);
    if(NS_FAILED(result))
    {
        return result;
    }
    
    if(sscanf(pMsg, "%d", retval) != 1)
    {
        result = NS_ERROR_FAILURE;
    }

    free(pMsg);
       
    return result;
}

void CHXPlayerBackend::CloseEmbeddedPlayer(PRBool bSendShutdownCommand)
{
    nsresult result;
    PRBool retval;    
    char *pMsg;
    int nLen;
    int i;

    if(!m_bEmbeddedPlayerOpen)
    {
        return;
    }
    
    if(bSendShutdownCommand)
    {
        /* Tell the player to quit */
        pMsg = "Shutdown\n";
        nLen = strlen(pMsg);
        result = SendMessage(pMsg, nLen);

        if(NS_SUCCEEDED(result))
        {
            result = ReadGenericResponse(&retval);

            /* Ignore retval... */
        }
    }
    
    /* Make sure the player exits */
    int nStatus = 0;
    PRBool bExited = PR_FALSE;
    
    if(m_nChildPid)
    {
        for(i = 0; i < 10; i++)
        {
            /* Give the player 2 seconds to exit */
            pid_t pidExited = 0;

            pidExited = waitpid(m_nChildPid, &nStatus, WNOHANG);
            if(WIFEXITED(nStatus) && pidExited)
            {
                bExited = PR_TRUE;
                break;
            }
            usleep(200);
        }

        if(!bExited)
        {
            /* Player failed to exit, kill it */
#ifdef DEBUG
            ShowError("Player failed to exit");
#endif
            kill(m_nChildPid, SIGTERM);
        }

        if(bExited)
        {
            /* Keep in mind that our child process here is a bourne shell.
               sh will return the exit code of the last process to exit,
               or 128 + the signal number if it was killed by a signal. */
            
            int nTermSig = WTERMSIG(nStatus);
            int nExitStatus = WEXITSTATUS(nStatus);

            if(nTermSig != 0 || nExitStatus != 0)
            {
                if(nTermSig == 0 && nExitStatus == EXIT_CODE_HXPLAY_NOT_FOUND)
                {
                    /* Not found */
                }
                else
                {
                    char *pMsg;

                    if(nTermSig)
                    {
                        /* Our shell has crashed... */
                        asprintf(&pMsg, "Player's shell exited with signal %d",
                                 nTermSig);
                    }
                    else if(nExitStatus > 128)
                    {
                        // sh will return 128 + signal number
                        nTermSig = nExitStatus - 128; 
                        asprintf(&pMsg, "Player exited with signal %d",
                                 nTermSig);
                    }
                    else
                    {
                        // player exited abnormally. The shell executing the
                        // hxplay script will propagate this error code to us.
                        asprintf(&pMsg, "Player exited with code %d",
                                 nExitStatus);
                    }
                    
                    ShowError(pMsg);
                    free(pMsg);
                }
            }
        }

        m_nChildPid = 0;
    }    

    m_bEmbeddedPlayerOpen = FALSE;
}


void CHXPlayerBackend::Shutdown(PRBool bSendShutdownCommand)
{
    if(m_bPlayerShutdown)
    {
        return;
    }

    m_bPlayerShutdown = PR_TRUE;

    /* Kill the timer -- it can call Shutdown while we're in some
       of the dialogs below, which confuses us. */

    StopCallbackTimer();
    
    CHXTimerFob* pTimerFob = new CHXTimerFob;
    NS_ADDREF(pTimerFob);
    
    if(m_pTimer)
    {
        m_pTimer->Cancel();

        /* Re-initing causes the timer to unref this and switch its ref
           to pTimerFob instead */
        m_pTimer->Init(pTimerFob,
                       500,
                       nsIScriptableTimer::TYPE_REPEATING_SLACK);
        m_pTimer->Cancel();

        NS_RELEASE(m_pTimer);
        m_pTimer = NULL;
    }

    if(m_pObsoleteTimer)
    {
        m_pObsoleteTimer->Cancel();

        /* Re-initing causes the timer to unref this and switch its ref
           to pTimerFob instead */
        m_pObsoleteTimer->Init(pTimerFob,
                               500,
                               nsIScriptableTimer::PRIORITY_NORMAL,
                               nsIScriptableTimer::TYPE_REPEATING_SLACK);
        m_pObsoleteTimer->Cancel();

        NS_RELEASE(m_pObsoleteTimer);
        m_pObsoleteTimer = NULL;
    }

    NS_RELEASE(pTimerFob);
    
    CloseEmbeddedPlayer(bSendShutdownCommand);
    
    /* Release components and services */
    NS_IF_RELEASE(m_pPromptService);
    NS_IF_RELEASE(m_pUConv);
    NS_IF_RELEASE(m_pMemory);

    if(m_fdApi != -1)
    {
        close(m_fdApi);
    }
    
    if(m_fdCallback != -1)
    {
        close(m_fdCallback);
    }
        
    m_fdApi = -1;
    m_fdCallback = -1;

    if(m_pCallbackBuf)
    {
        free(m_pCallbackBuf);
        m_nCallbackBufLen = 0;
        m_nCallbackBufPos = 0;
    }

    if(m_pPlayersList)
    {
        free(m_pPlayersList);
        m_nPlayerCount = 0;
        m_nMaxPlayers = 0;
    }
}

void CHXPlayerBackend::AddHXPlayer(nsHXPlayer *pPlayer)
{
    /* We could add reference counting here. */
    if(!m_pPlayersList)
    {
        m_nMaxPlayers = 10;
        m_pPlayersList = (nsHXPlayer**)calloc(m_nMaxPlayers, m_nMaxPlayers * sizeof(nsHXPlayer*));
    }
    
    m_pPlayersList[m_nPlayerCount++] = pPlayer;

    if(m_nPlayerCount <= m_nMaxPlayers)
    {
        m_nMaxPlayers *= 2;
        m_pPlayersList = (nsHXPlayer**)realloc(m_pPlayersList, m_nMaxPlayers * sizeof(*m_pPlayersList));
    }
}

void CHXPlayerBackend::RemoveHXPlayer(nsHXPlayer *pPlayer)
{
    int i;

    for(i = 0; i < m_nPlayerCount; i++)
    {
        if(pPlayer == m_pPlayersList[i])
        {
            m_pPlayersList[i] = m_pPlayersList[m_nPlayerCount - 1];
            m_nPlayerCount--;
            return;
        }        
    }

    printf("Cannot find player in RemoveHXPlayer\n");
}

void CHXPlayerBackend::ProcessCallback(const char *szCallback)
{
    /*
      callbacks are of this format:
      Callback name, callback, parameterlist
    */
    CStringTokenizer tokenizer(szCallback);
    char *szType = NULL;
    char *szUrl = NULL;
    char *szTarget = NULL;
    nsHXPlayer* pPlayer = NULL;
    int i;
    
    szType = tokenizer.NextToken();
    
    if(strcmp(szType, "Callback") == 0)
    {
        char *szName;
        char *szCallback;
        char *szParams;
        
        szName = tokenizer.NextToken();
        szCallback = tokenizer.NextToken();
        szParams = tokenizer.RemainingChars();
        asprintf(&szUrl, "javascript:%s_%s(%s)", szName, szCallback, szParams);
        szTarget = strdup("_self");

        /* find a player with matching name */
        for(i = 0; i < m_nPlayerCount; i++)
        {
            if(strcasecmp(m_pPlayersList[i]->GetName(), szName) == 0)
            {
                pPlayer = m_pPlayersList[i];
                break;
            }
        }
        
        free(szName);
        free(szCallback);
        free(szParams);
    }
    else if(strcmp(szType, "GetURL") == 0)
    {
        char *szPlayerId;
        int nPlayerId;

        szPlayerId = tokenizer.NextToken(); // must be the player ID
        nPlayerId = atoi(szPlayerId);
        free(szPlayerId);

        for(i = 0; i < m_nPlayerCount; i++)
        {
            if(m_pPlayersList[i]->GetId() == nPlayerId)
            {
                pPlayer = m_pPlayersList[i];
                break;
            }
        }
        
        szUrl    = tokenizer.NextToken(); // must be the URL
        szTarget = tokenizer.NextToken(); // must be the target
    }
    else
    {
        printf("Unknown command %s\n", szType);
    }

    if(szType)
    {
        free(szType);
    }
    
    if(szUrl && szTarget && pPlayer)
    {
        pPlayer->OnGetURL(szUrl, szTarget);
    }

    if(szUrl)
    {
        free(szUrl);
    }

    if(szTarget)
    {
        free(szTarget);
    }
}

nsresult CHXPlayerBackend::PollForCallbacks(void)
{
    /* Poll for data on the callback descriptor */
    ssize_t nResult;
    fd_set fdsRead, fdsException;
    struct timeval timeout;
    int nDescriptors;
    nsresult result = NS_OK;
    char c;

    if(m_bPlayerShutdown || !m_bEmbeddedPlayerOpen)
    {
        return NS_ERROR_FAILURE;
    }
    
    for(;;)
    {
        FD_ZERO(&fdsRead);
        FD_ZERO(&fdsException);

        FD_SET(m_fdCallback, &fdsRead);
        FD_SET(m_fdCallback, &fdsException);

        /* Return immediately */
        timeout.tv_sec = 0;
        timeout.tv_usec = 0;

        nDescriptors = select(m_fdCallback + 1, &fdsRead, NULL, &fdsException, &timeout);
        if(nDescriptors < 0)
        {
            perror("select");
            break;
        }
        else if(nDescriptors == 0)
        {
            /* No more data available */
            break;
        }
        else 
        {
            if(FD_ISSET(m_fdCallback, &fdsException))
            {
                /* This should never happen. */
                printf("Exception in playeripc_parse_commands");
                result = NS_ERROR_FAILURE;
                break;
            }
            else if(FD_ISSET(m_fdCallback, &fdsRead))
            {
                /* We can read */
                nResult = read(m_fdCallback, &c, 1);
                if(nResult <= 0)
                {
                    if(nResult < 0)
                    {
                        if((errno != EINTR) && (errno != EAGAIN))
                        {
                            perror("read");
                            result = NS_ERROR_FAILURE;
                        }
                    }

                    /* Connection closed */
                    result = NS_ERROR_FAILURE;
                    CloseEmbeddedPlayer(PR_FALSE);
                    break;
                }
                else
                {
                    if(c == '\n')
                    {
                        m_pCallbackBuf[m_nCallbackBufPos] = '\0';
                        ProcessCallback(m_pCallbackBuf);
                        m_nCallbackBufPos = 0;
                    }
                    else
                    {
                        m_pCallbackBuf[m_nCallbackBufPos++] = c;
                        
                        if(m_nCallbackBufPos >= m_nCallbackBufLen)
                        {
                            /* We need more buffer space */
                            m_nCallbackBufLen *= 2;
                            m_pCallbackBuf = (char*)realloc(m_pCallbackBuf, m_nCallbackBufLen * sizeof(char));
                        }
                    }
                }
            }
            else
            {
                /* This is unreachable. */
                printf("Unknown state in select()\n");
                result = NS_ERROR_FAILURE;
                break;
            }
        }
    }                

    return NS_OK;
}

void CHXPlayerBackend::StartCallbackTimer()
{
    if (m_pTimer)
    {
        m_bCallbackTimerRunning = PR_TRUE;
        m_pTimer->Init(this,
                       500,
                       nsITimer::TYPE_REPEATING_SLACK);
    }
    else if(m_pObsoleteTimer)
    {
        m_bCallbackTimerRunning = PR_TRUE;
        m_pObsoleteTimer->Init(this,
                               500,
                               nsIScriptableTimer::PRIORITY_NORMAL,
                               nsIScriptableTimer::TYPE_REPEATING_SLACK);
    }
}


void CHXPlayerBackend::StopCallbackTimer()
{
    if(m_pTimer)
    {
        m_bCallbackTimerRunning = PR_FALSE;
        m_pTimer->Cancel();
    }
    if(m_pObsoleteTimer)
    {
        m_bCallbackTimerRunning = PR_FALSE;
        m_pObsoleteTimer->Cancel();
    }
}


