/*
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * Copyright (c) 1999-2003 Apple Computer, Inc.  All Rights Reserved.
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 *
 */

/*
    8.11.99 rt  - changed references to "PlaylistBroadcaster Setup" to Broadcast Description File

    8.4.99 rt   - changed references to "PlaylistBroadcaster Description" to Broadcast Setup File
                - addded error messages
                - prefilght config file access
                - require log file creation
                
                
    7.27.99 rt  - removed license from about display
                - updated credit names
                - fixed mapping of --stop to 's' from 'l'
                - added about to help
                

    8.2.99 rt   - changed reference to "channel setup" to "PlaylistBroadcaster Description"
                - changed &d's in qtss_printf's to %d
                
*/



#include <stdio.h>
#include <stdlib.h>
#include "SafeStdLib.h"
#include <signal.h>
#ifndef __MacOSX__
#include "getopt.h"
#endif

#ifndef __Win32__
    #if defined (__solaris__) || defined (__osf__) || defined (__sgi__) || defined (__hpux__)
        #include "daemon.h"
    #else
        #ifndef __FreeBSD__
            #include <sys/sysctl.h>
        #endif
    #endif
#endif

#ifndef kVersionString
#include "revision.h"
#endif

#include <errno.h>
#include "QTRTPFile.h"

#include "OSHeaders.h"
#include "OS.h"
#include "OSMemory.h"
#include "SocketUtils.h"
#include "Socket.h"
#include "Task.h"
#include "TimeoutTask.h"
#include "BroadcasterSession.h"
#include "OSArrayObjectDeleter.h"
#include "PickerFromFile.h"
#include "PLBroadcastDef.h"
#include "BroadcastLog.h"
#include "StringTranslator.h"

#ifndef __Win32__
    #include "BCasterTracker.h"
#endif

#ifdef __Win32__
    #include "getopt.h"
#endif  

// must now inlcude this from the project level using the -i switch in the compiler
#ifndef __MacOSX__
    #include "revision.h"
#endif

#include "playlist_SDPGen.h"
#include "playlist_broadcaster.h"


#include "MyAssert.h"

/*
    local functions
*/

static void usage();
static void version();
static void help();
static PlaylistPicker*  MakePickerFromConfig( PLBroadcastDef* broadcastParms );
static void SignalEventHandler( int signalID );
static int EvalBroadcasterErr(int err);
static const char* GetMovieFileErrString( int err );

static void RegisterEventHandlers();
static void ShowPlaylistStatus();
static void StopABroadcast( const char* arg );
static bool DoSDPGen( PLBroadcastDef *broadcastParms, bool preflight, bool overWriteSDP, bool generateNewSDP, int* numErrorsPtr, char* refMovie);

static bool AddOurPIDToTracker( const char* bcastSetupFilePath );

static void Cleanup();

/* -- preflighting --*/

static int MyAccess( const char* path, int mode );
static int PreflightSDPFileAccess( const char * sdpPath );
static int PreflightDestinationAddress( const char *destAddress );
static int PreflightBasePort( const char *basePort );
static int PreflightReferenceMovieAccess( const char *refMoviePath );
static bool PreflightTrackerFileAccess( int mode );
static bool PreFlightSetupFile( const char * bcastSetupFilePath );
static int PreflightClientBufferDelay( const char * delay, Float32 *ioDelay);
static void ShowSetupParams(PLBroadcastDef* broadcastParms, const char *file);
static int PreflightDestSDPFileAccess( const char * sdpPath );
static void PreFlightOrBroadcast( const char *bcastSetupFilePath, bool preflight, bool daemonize, bool showMovieList, bool writeCurrentMovieFile, char *destinationIP,bool writeNewSDP, const char* errorFilePath);

/* changed by emil@popwire.com */
static bool FileCreateAndCheckAccess(char *theFileName);
static void PrintPlaylistElement(PLDoubleLinkedListNode<SimplePlayListElement> *node,void *file);
static void ShowPlaylistElements(PlaylistPicker *picker,FILE *file);
static void RemoveFiles(PLBroadcastDef* broadcastParms);
/* ***************************************************** */

/*
    local variables
*/
static char*                sgProgramName; // the actual program name as input at commmand line
static char*                sgTrackerDirPath = "/var/run";
static char*                sgTrackerFilePath = "/var/run/broadcastlist";
static BroadcastLog*        sgLogger = NULL;
static bool             sgTrackingSucceeded = false;
static BroadcasterSession* sBroadcasterSession = NULL;
static  StrPtrLen           sSetupDirPath;
static  PlaylistPicker*     sTempPicker = NULL;
static SInt32               sElementCount = 0;
static SInt32               sMaxUpcomingListSize = 5;
static PLBroadcastDef*      sBroadcastParms = NULL;

enum {maxSDPbuffSize = 10000};
static	char	sSDPBuffer[maxSDPbuffSize] = {0};
static	bool	sQuitImmediate = false;
static  bool    sAnnounceBroadcast = false;
static  bool    sPushOverTCP = false;
static  int		sRTSPClientError = 0;
static  bool    sErrorEvaluted = false;
static  bool    sDeepDebug = false;

static int  sNumWarnings = 0;
static int  sNumErrors = 0;
static bool sPreflight = false;
static bool sCleanupDone = false;
static bool sBurst = false;

//+ main is getting too big.. need to clean up and separate the command actions 
// into a separate file.


int main(int argc, char *argv[]) {

    char    *bcastSetupFilePath = NULL;
    bool    daemonize = true;
    int anOption = 0;
    bool    preflight = false;
    bool    showMovieList = false;
    bool    writeCurrentMovieFile = false;
    int displayedOptions = 0; // count number of command line options we displayed
    bool    needsTracker = false;   // set to true when PLB needs tracker access
    bool    needsLogfile = false;   // set to true when PLB needs tracker access
    char*   destinationIP = NULL;
    bool    writeNewSDP = false;
    char*   errorlog = NULL;    
    extern char* optarg;
    extern int optind;

#ifdef __Win32__        
    //
    // Start Win32 DLLs
    WORD wsVersion = MAKEWORD(1, 1);
    WSADATA wsData;
    (void)::WSAStartup(wsVersion, &wsData);
#endif


    QTRTPFile::Initialize(); // do only once
    OS::Initialize();
    OSThread::Initialize();
    OSMemory::SetMemoryError(ENOMEM);
    Socket::Initialize();
    SocketUtils::Initialize(false);
    
    if (SocketUtils::GetNumIPAddrs() == 0)
    {
        qtss_printf("Network initialization failed. IP must be enabled to run PlaylistBroadcaster\n");
        ::exit(0);
    }

    sgProgramName = argv[0];
#ifdef __Win32__
    while ((anOption = getopt(argc, argv, "vhdcpbDtai:fe:" )) != EOF)
#else
    while ((anOption = getopt(argc, argv, "vhdcpbDls:tai:fe:" )) != EOF)
#endif
    {
        
        switch(anOption)
        {
            case 'b':
                sBurst = true;
                break;
                
            case 'v':
                ::version();
                ::usage();
                return 0;

            case 'h':
                ::help();
                displayedOptions++;
                break;
            
            case 'd':
                daemonize = false;
                break;
                                    
            case 'c':
                writeCurrentMovieFile = true;
                break;              
                
/*
            case 'm':
                showMovieList = true;
                daemonize = false;
                break;
*/
                            
            case 'p':
                preflight = true;
                daemonize = false;
                needsTracker = true;
                needsLogfile = true;
                break;
                
            case 'D':
                sDeepDebug = true;
                daemonize = false;
                break;

#ifndef __Win32__

            
            case 'l':
                // show playlist broadcast status           
                if (!PreflightTrackerFileAccess( R_OK ))    // check for read access. 
                    ::exit(-1);
                ShowPlaylistStatus();               // <- exits() on failure
                displayedOptions++;
                break;
                
            case 's':               
                // stop a playlist broadcast            
                // is there one to kill?
                if (!PreflightTrackerFileAccess( R_OK | W_OK ))     // check for read access. 
                    ::exit(-1);
                StopABroadcast( optarg );                   // <- exits() on failure
                displayedOptions++;
                break;
#endif
            
            case 't':
                sAnnounceBroadcast = true;
                sPushOverTCP = true;
                break;

            case 'a':
                sAnnounceBroadcast = true;
                break;              
            
            case 'i':
                destinationIP = (char*)malloc(strlen(optarg)+1);
                strcpy(destinationIP, optarg);
                qtss_printf("destinationIP =%s\n",destinationIP);
                break;
                
            case 'f':
                writeNewSDP = true;
                break;
                
            case 'e':
                errorlog = (char*)malloc(strlen(optarg)+1);
                strcpy(errorlog, optarg);
                break;
                
            default:
                ::usage();
                ::exit(-1);
        }
    }

    if (argv[optind] != NULL)
    {
        bcastSetupFilePath = (char*)malloc(strlen(argv[optind])+1);
        strcpy(bcastSetupFilePath, argv[optind]);
    }

	sPreflight = preflight;
         
    if (errorlog != NULL)
    {
            if (preflight)
                    freopen(errorlog, "w", stdout);
        else
                freopen(errorlog, "a", stdout);
        ::setvbuf(stdout, (char *)NULL, _IONBF, 0);
    }
        
    // preflight needs a description file
    if ( preflight && !bcastSetupFilePath )
    {   qtss_printf("- PlaylistBroadcaster: Error.  \"Preflight\" requires a broadcast description file.\n" );
        ::usage();
        ::exit(-1);
    }

    // don't complain about lack of description File if we were asked to display some options.  
    if ( displayedOptions > 0 && !bcastSetupFilePath )
        ::exit(0);      // <-- we displayed option info, but have no description file to broadcast
        
    PreFlightOrBroadcast( bcastSetupFilePath, preflight, daemonize, showMovieList, writeCurrentMovieFile,destinationIP,writeNewSDP, errorlog ); // <- exits() on failure -- NOT ANYMORE
    
    return 0;

}

BroadcasterSession *StartBroadcastRTSPSession(PLBroadcastDef *broadcastParms)
{   
    TaskThreadPool::AddThreads(1);
    TimeoutTask::Initialize();
    Socket::StartThread();

    UInt32 inAddr = SocketUtils::ConvertStringToAddr(broadcastParms->mDestAddress);
    
    UInt16 inPort = atoi(broadcastParms->mRTSPPort);
    char * theURL = new char[512];
    StringTranslator::EncodeURL(broadcastParms->mDestSDPFile, strlen(broadcastParms->mDestSDPFile) + 1, theURL, 512);
    BroadcasterSession::BroadcasterType inBroadcasterType;
        
    if (sPushOverTCP)
         inBroadcasterType = BroadcasterSession::kRTSPTCPBroadcasterType;
    else
         inBroadcasterType = BroadcasterSession::kRTSPUDPBroadcasterType;
    
    UInt32 inDurationInSec = 0;
    UInt32 inStartPlayTimeInSec = 0;
    UInt32 inRTCPIntervalInSec = 5; 
    UInt32 inOptionsIntervalInSec = 0;
    UInt32 inHTTPCookie = 1; 
    Bool16 inAppendJunkData = false; 
    UInt32 inReadInterval = 50;
    UInt32 inSockRcvBufSize = 32768;
    StrPtrLen sdpSPL(sSDPBuffer,maxSDPbuffSize);
    sRTSPClientError = QTFileBroadcaster::eNetworkConnectionError;
    sBroadcasterSession = NEW BroadcasterSession(inAddr, 
                            inPort,                 
                            theURL,
                            inBroadcasterType,      // Client type
                            inDurationInSec,        // Movie length
                            inStartPlayTimeInSec,                   // Movie start time
                            inRTCPIntervalInSec,                    // RTCP interval
                            inOptionsIntervalInSec,                 // Options interval
                            inHTTPCookie,   // HTTP cookie
                            inAppendJunkData,
                            inReadInterval, // Interval between data reads  
                            inSockRcvBufSize,
                            &sdpSPL,
                            broadcastParms->mName,
                            broadcastParms->mPassword,
                            sDeepDebug,
                            sBurst);    

    return  sBroadcasterSession;
}

Bool16 AnnounceBroadcast(PLBroadcastDef *broadcastParms,QTFileBroadcaster   *fileBroadcasterPtr)
{   // return true if no Announce required or broadcast is ok.

    if (!sAnnounceBroadcast) return true;

	// if the address is a multicast address then we can't announce the broadcast.
    
    if(SocketUtils::IsMulticastIPAddr(ntohl(inet_addr(broadcastParms->mDestAddress)))) {
        sAnnounceBroadcast = false;
        return true;
    }
    
#if !MACOSXEVENTQUEUE
    ::select_startevents();//initialize the select() implementation of the event queue
#endif

    if (SocketUtils::GetNumIPAddrs() == 0)
    {
        qtss_printf("IP must be enabled to run PlaylistBroadcaster\n");
        //::exit(0); // why exit here? If we return false here, the calling function will take care of the error
                return false;
    }

    broadcastParms->mTheSession = sBroadcasterSession = StartBroadcastRTSPSession(broadcastParms);
    while (!sBroadcasterSession->IsDone() && BroadcasterSession::kBroadcasting != sBroadcasterSession->GetState()) 
    {   OSThread::Sleep(100);
    }
    sRTSPClientError = 0;
    int broadcastErr = sBroadcasterSession->GetRequestStatus();
    
    Bool16 isOK = false;    
    if (broadcastErr != 200) do 
    {
        if (412 == broadcastErr)
        {   
            ::EvalBroadcasterErr(broadcastErr);
            break;  
        }
        
        if (200 != broadcastErr)
        {   //qtss_printf("broadcastErr = %ld sBroadcasterSession->GetDeathState()=%ld sBroadcasterSession->GetReasonForDying()=%ld\n",broadcastErr,sBroadcasterSession->GetDeathState(),sBroadcasterSession->GetReasonForDying());
            if (sBroadcasterSession->GetDeathState() == BroadcasterSession::kSendingAnnounce && sBroadcasterSession->GetReasonForDying() == BroadcasterSession::kConnectionFailed)
                ::EvalBroadcasterErr(QTFileBroadcaster::eNetworkConnectionError);
            else if (sBroadcasterSession->GetDeathState() == BroadcasterSession::kSendingAnnounce && sBroadcasterSession->GetReasonForDying() == BroadcasterSession::kBadSDP)
                 ::EvalBroadcasterErr(QTFileBroadcaster::eSDPFileInvalid);
            else if (401 == broadcastErr)
                ::EvalBroadcasterErr(QTFileBroadcaster::eNetworkAuthorization);
            else if ((500 == broadcastErr) || (400 == broadcastErr) )
                ::EvalBroadcasterErr(QTFileBroadcaster::eNetworkNotSupported);
            else 
                ::EvalBroadcasterErr(QTFileBroadcaster::eNetworkRequestError);

            break;
        }   
        
        if (sBroadcasterSession != NULL && BroadcasterSession::kDiedNormally != sBroadcasterSession->GetReasonForDying())
        {   ::EvalBroadcasterErr(QTFileBroadcaster::eNetworkRequestError);
            break;
        }   
    
        isOK = true;
        
    } while (false);
    else 
        isOK = true;
        
    if (isOK)
    {
        broadcastErr = fileBroadcasterPtr->SetUp(broadcastParms, &sQuitImmediate);
        if (  broadcastErr )
        {   ::EvalBroadcasterErr(broadcastErr);
            qtss_printf( "- Broadcaster setup failed.\n" );
            isOK = false;
            if (sBroadcasterSession != NULL && !sBroadcasterSession->IsDone())
                sBroadcasterSession->TearDownNow();
        }
    }
    
    return isOK;
}

char* GetBroadcastDirPath(const char * setupFilePath)
{
    int len = 2;
    
    if (setupFilePath != NULL)
        len = ::strlen(setupFilePath);

    char *setupDirPath = new char [ len + 1 ];
    
    if ( setupDirPath )
    {   
        strcpy( setupDirPath, setupFilePath );
        char* endOfDirPath = strrchr( setupDirPath, kPathDelimiterChar );
        
        if ( endOfDirPath )
        {
            endOfDirPath++;
            *endOfDirPath= 0;           
            
            int  chDirErr = ::chdir( setupDirPath );            
            if ( chDirErr )
                chDirErr = errno;
                
            //qtss_printf("- PLB DEBUG MSG: setupDirPath==%s\n  chdir err: %i\n", setupDirPath, chDirErr);         
        }
        else
        {   
            setupDirPath[0] = '.';
            setupDirPath[1] = kPathDelimiterChar;
            setupDirPath[2] = 0;
        }
            
    }
    
    //qtss_printf("GetBroadcastDirPath setupDirPath = %s\n",setupDirPath);
    return setupDirPath;
}

void CreateCurrentAndUpcomingFiles(PLBroadcastDef* broadcastParms)
{
    if (!::strcmp(broadcastParms->mShowCurrent, "enabled")) 
    {   if(FileCreateAndCheckAccess(broadcastParms->mCurrentFile))
        {   /* error */
            sgLogger->LogInfo( "PlaylistBroadcaster Error: Failed to create current broadcast file" );
        }
    }


    if (!::strcmp(broadcastParms->mShowUpcoming, "enabled")) 
    {   if(FileCreateAndCheckAccess(broadcastParms->mUpcomingFile))
        {   /* error */
            sgLogger->LogInfo( "PlaylistBroadcaster Error:  Failed to create upcoming broadcast file" );
        }
    }

}

void UpdatePlaylistFiles(PLBroadcastDef* broadcastParms, PlaylistPicker *picker,PlaylistPicker *insertPicker)
{
    if (    (NULL == broadcastParms)
         ||  (NULL == picker)
        ||  (NULL == insertPicker)
        ) return;
        
     if(!access(broadcastParms->mStopFile, R_OK))
    {
        picker->CleanList();
        PopulatePickerFromFile(picker, broadcastParms->mStopFile, "", NULL);

        sTempPicker->CleanList();

        remove(broadcastParms->mStopFile);
        picker->mStopFlag = true;
    }

    /* if .replacelist file exists - replace current playlist */
    if(!access(broadcastParms->mReplaceFile, R_OK))
    {
        picker->CleanList();     
        PopulatePickerFromFile(picker, broadcastParms->mReplaceFile, "", NULL);
        
        sTempPicker->CleanList();
        
        remove(broadcastParms->mReplaceFile);
        picker->mStopFlag = false;
    }

    /* if .insertlist file exists - insert into current playlist */
    if(!access(broadcastParms->mInsertFile, R_OK))
    {
        insertPicker->CleanList();
        sTempPicker->CleanList();

        PopulatePickerFromFile(insertPicker, broadcastParms->mInsertFile, "", NULL);
        remove(broadcastParms->mInsertFile);
        picker->mStopFlag = false;
    }


                // write upcoming playlist to .upcoming file 
    if (!::strcmp(broadcastParms->mShowUpcoming, "enabled")) 
    {
        FILE *upcomingFile = fopen(broadcastParms->mUpcomingFile, "w");
        if(upcomingFile)
        {
            sElementCount = 0;
        
            if (!::strcmp(broadcastParms->mPlayMode, "weighted_random")) 
                qtss_fprintf(upcomingFile,"#random play - upcoming list not supported\n");
            else
            {   qtss_fprintf(upcomingFile,"*PLAY-LIST*\n");
                ShowPlaylistElements(insertPicker,upcomingFile);
                ShowPlaylistElements(picker,upcomingFile);
                if (    picker->GetNumMovies() == 0 
                        && !picker->mStopFlag 
                        && 0 != ::strcmp(broadcastParms->mPlayMode, "sequential") 
                    )
                {   picker->CleanList();
                    PopulatePickerFromFile(picker,broadcastParms->mPlayListFile,"",NULL);
                    ShowPlaylistElements(picker,upcomingFile);
                    sTempPicker->CleanList();
                    PopulatePickerFromFile(sTempPicker,broadcastParms->mPlayListFile,"",NULL);
                }
                
                if  (   sElementCount <= sMaxUpcomingListSize 
                        && 0 == ::strcmp(broadcastParms->mPlayMode, "sequential_looped")
                    )
                {   if (sTempPicker->GetNumMovies() == 0)
                    {   sTempPicker->CleanList();
                        PopulatePickerFromFile(sTempPicker,broadcastParms->mPlayListFile,"",NULL);
                    }
                    while (sElementCount <= sMaxUpcomingListSize )
                        ShowPlaylistElements(sTempPicker,upcomingFile);
                }
            }
            fclose(upcomingFile);
        }   
    }
    else
    {   
        if (    picker->GetNumMovies() == 0 
                && !picker->mStopFlag 
                && ::strcmp(broadcastParms->mPlayMode, "sequential") 
            )
        {   picker->CleanList();
            PopulatePickerFromFile(picker,broadcastParms->mPlayListFile,"",NULL);
        }       
    }       
        
}


void UpdateCurrentFile(PLBroadcastDef* broadcastParms, char *thePick)
{
    if ( (NULL == broadcastParms) || (NULL == thePick) ) 
        return;
        
    // save currently playing song to .current file 
    if (!::strcmp(broadcastParms->mShowCurrent, "enabled")) 
    {   FILE *currentFile = fopen(broadcastParms->mCurrentFile, "w");
        if(currentFile)
        {
            if (sSetupDirPath.EqualIgnoreCase(thePick, sSetupDirPath.Len) || '\\' == thePick[0] || '/' == thePick[0])
                qtss_fprintf(currentFile,"u=%s\n",thePick);
            else
                qtss_fprintf(currentFile,"u=%s%s\n", sSetupDirPath.Ptr,thePick);

            fclose(currentFile);
        }   
    }
                        

    
}


static void PreFlightOrBroadcast( const char *bcastSetupFilePath, bool preflight, bool daemonize, bool showMovieList, bool writeCurrentMovieFile, char *destinationIP,bool writeNewSDP, const char* errorFilePath)
{
    PLBroadcastDef*     broadcastParms = NULL;
    PlaylistPicker*     picker = NULL;
    PlaylistPicker*     insertPicker = NULL;
    
    QTFileBroadcaster   fileBroadcaster;
    int     broadcastErr = 0;
    long    moviePlayCount;
    char*   thePick = NULL;
    int     numMovieErrors;
    bool    sdpFileCreated = false;
    char    *allocatedIPStr = NULL;
    bool    generateNewSDP = false;
    int     numErrorsBeforeSDPGen = 0;
    char    theUserAgentStr[128];
    char*   thePLBStr = "PlaylistBroadcaster";
       
    RegisterEventHandlers();

    if ( !PreFlightSetupFile( bcastSetupFilePath ) )    // returns true on success and false on failure
        {
            sNumErrors++;
            goto bail;
        }
        
    if (destinationIP != NULL)
    { 
        allocatedIPStr = new char[strlen(destinationIP)+1];
        strcpy(allocatedIPStr,destinationIP);
        generateNewSDP = true;
    }
    broadcastParms = new PLBroadcastDef( bcastSetupFilePath, allocatedIPStr,  sDeepDebug);  
    sBroadcastParms = broadcastParms;
    
    if( !broadcastParms )
    {   
        qtss_printf("- PlaylistBroadcaster: Error. Out of memory.\n" );
        sNumErrors++;
        goto bail;
    }

    
    if ( !broadcastParms->ParamsAreValid() )
    {   
        qtss_printf("- PlaylistBroadcaster: Error reading the broadcast description file \"%s\". (bad format or missing file)\n", bcastSetupFilePath );
        broadcastParms->ShowErrorParams();
        sNumErrors++;
        goto bail;
    }
    
    
    if ( preflight || sDeepDebug)
        ShowSetupParams(broadcastParms, bcastSetupFilePath);

    if (NULL == GetBroadcastDirPath(bcastSetupFilePath))
    {   
        qtss_printf("- PlaylistBroadcaster: Error. Out of memory.\n" );
        sNumErrors++;
        goto bail;
    }
    sSetupDirPath.Set(GetBroadcastDirPath(bcastSetupFilePath), strlen(GetBroadcastDirPath(bcastSetupFilePath)));

    if (preflight)
    {
        picker = new PlaylistPicker(false);             // make sequential picker, no looping
    }
    else
    {   
        picker = MakePickerFromConfig( broadcastParms ); // make  picker according to parms
        sTempPicker =  new PlaylistPicker(false);
        insertPicker = new PlaylistPicker(false);
        insertPicker->mRemoveFlag = true;
    }
    
    if ( !picker )
    {   
        qtss_printf("- PlaylistBroadcaster: Error. Out of memory.\n" );
        sNumErrors++;
        goto bail;
    }
    
    if ( preflight || sDeepDebug)
        qtss_printf("\n");
    
    Assert( broadcastParms->mPlayListFile );
    if ( broadcastParms->mPlayListFile )
    {   
        char*   fileName;
        
        fileName = broadcastParms->mPlayListFile;
        // initial call uses empty string for path, NULL for loop detection list
        (void)PopulatePickerFromFile( picker, fileName, "", NULL );
        
        // ignore errors, if we have movies in the list, play them
    }
    
    if ( preflight )
    {
        if ( picker->mNumToPickFrom == 1 )
            qtss_printf( "\nThere is (%li) movie in the Playlist.\n\n", (long) picker->mNumToPickFrom );
        else
            qtss_printf( "\nThere are (%li) movies in the Playlist.\n\n", (long) picker->mNumToPickFrom );
    }   
    
    if ( !picker->mNumToPickFrom )
    {   
        qtss_printf( "- PlaylistBroadcaster setup failed: There are no movies to play.\n" );
        sNumErrors++;
        goto bail;
    }
    

    // check that we have enough movies to cover the recent movies list.
    if ( preflight && broadcastParms->ParamsAreValid() )
    {
        if (  !strcmp( broadcastParms->mPlayMode, "weighted_random" ) ) // this implies "random" play
        {   
            if ( broadcastParms->mLimitPlayQueueLength >=  picker->mNumToPickFrom )
            {
                broadcastParms->mLimitPlayQueueLength = picker->mNumToPickFrom-1;
                qtss_printf("- PlaylistBroadcaster Warning:\n  The recent_movies_list_size setting is greater than \n");
                qtss_printf("  or equal to the number of movies in the playlist.\n" );
            }
        }
    }
    
    // create the log file
    sgLogger = new BroadcastLog( broadcastParms, &sSetupDirPath );
    
    
    Assert( sgLogger != NULL );
    
    if( sgLogger == NULL )
    {   
        qtss_printf("- PlaylistBroadcaster: Error. Out of memory.\n" );
        sNumErrors++;
        goto bail;
    }
    
    numErrorsBeforeSDPGen = sNumErrors;
    sdpFileCreated = DoSDPGen( broadcastParms, preflight, writeNewSDP,generateNewSDP, &sNumErrors, picker->GetFirstFile());
    if( sNumErrors > numErrorsBeforeSDPGen )
        goto bail;
            
    if (!sAnnounceBroadcast)
        broadcastErr = fileBroadcaster.SetUp(broadcastParms, &sQuitImmediate);
    
    if (  broadcastErr )
    {   
        ::EvalBroadcasterErr(broadcastErr);
        qtss_printf( "- Broadcaster setup failed.\n" );
        sNumErrors++;
        goto bail;
    }
    
    if (preflight)
    {    
        fileBroadcaster.fPlay = false;
    }
    if ( !PreflightTrackerFileAccess( R_OK | W_OK ) )
    {
        sNumErrors++;
        goto bail;
    }
       
    //Unless the Debug command line option is set, daemonize the process at this point
    if (daemonize)
    {   
#ifndef __Win32__
        qtss_printf("- PlaylistBroadcaster: Started in background.\n");
        
        // keep the same working directory..
        if (::daemon( 1, 0 ) != 0)
        {
            qtss_printf("- PlaylistBroadcaster:  System error (%i).\n", errno);
            goto bail;
        }

#endif  //__Win32__
    }
        
        // If daemonize, then reopen stdout to the error file 
        if (daemonize && (errorFilePath != NULL))
        {
                freopen(errorFilePath, "a", stdout);
                ::setvbuf(stdout, (char *)NULL, _IONBF, 0);
        }
        

#ifndef __Win32__
    qtss_snprintf(theUserAgentStr, ::strlen(thePLBStr) + 1 + ::strlen(kVersionString) + 1, "%s/%s", thePLBStr, kVersionString);
#else
    _snprintf(theUserAgentStr, ::strlen(thePLBStr) + 1 + ::strlen(kVersionString) + 1, "%s/%s", thePLBStr, kVersionString);
#endif
    RTSPClient::SetUserAgentStr(theUserAgentStr);
        
        if (!preflight && !AnnounceBroadcast(broadcastParms,&fileBroadcaster)) 
    {
        sNumErrors++;
        goto bail;
    }
        
        // ^ daemon must be called before we Open the log and tracker since we'll
    // get a new pid, our files close,  ( does SIGTERM get sent? )
    
    if (( sgLogger != NULL ) && ( sgLogger->WantsLogging() ))
        sgLogger->EnableLog( false ); // don't append ".log" to name for PLB
    
    if ( sgLogger->WantsLogging() && !sgLogger->IsLogEnabled() )
    {
        if (  sgLogger->LogDirName() && *sgLogger->LogDirName() )
            qtss_printf("- PlaylistBroadcaster: The log file failed to open.\n  ( path: %s/%s )\n  Exiting.\n", sgLogger->LogDirName(), sgLogger->LogFileName() );
        else
            qtss_printf("- PlaylistBroadcaster: The log file failed to open.\n  ( path: %s )\n  Exiting.\n", sgLogger->LogFileName() );
        
        sNumErrors++;
        goto bail;
    }
    
    
    if (broadcastParms->mPIDFile != NULL)
    {
        if(!FileCreateAndCheckAccess(broadcastParms->mPIDFile))
        {
            FILE *pidFile = fopen(broadcastParms->mPIDFile, "w");
            if(pidFile)
            {
                qtss_fprintf(pidFile,"%d\n",getpid());
                fclose(pidFile);
            }   
        }
    }
    else if ( !AddOurPIDToTracker( bcastSetupFilePath ) ) // <-- doesn't exit on failure anymore - returns false if failed
        {
            // writes to the broadcast list file only if the pid_file config param doesn't exist
            sNumErrors++;
            goto bail;
        }
        
    if ( !preflight )
        sgLogger->LogInfo( "PlaylistBroadcaster started." );
    else
        sgLogger->LogInfo( "PlaylistBroadcaster preflight started." );

    //+ make the RTP movie broadcaster
    
    if ( !preflight )
    {
        qtss_printf( "\n" );
        qtss_printf( "[pick#] movie path\n" );
        qtss_printf( "----------------------------\n" );
    }
    else
    {       
        qtss_printf( "\n" );
        qtss_printf( "Problems found\n" );
        qtss_printf( "--------------\n" );
    }

    if(!preflight)
        CreateCurrentAndUpcomingFiles(broadcastParms);
    
    moviePlayCount = 0;
    numMovieErrors = 0;
    sMaxUpcomingListSize = ::atoi( broadcastParms->mMaxUpcomingMovieListSize );
            
    while (true)
    {
    
        if (!showMovieList && !preflight)
        {
            UpdatePlaylistFiles(broadcastParms,  picker, insertPicker);
        }
       
        if (NULL != insertPicker)
            thePick = insertPicker->PickOne(); 
            
        if (NULL == thePick && (NULL != picker))
            thePick = picker->PickOne();
        
        
        if ( (thePick != NULL) && (!preflight || showMovieList) )
        {
            // display the picks in debug mode, but not preflight
            qtss_printf( "[%li] ", moviePlayCount );
            {   
                if (sSetupDirPath.EqualIgnoreCase(thePick, sSetupDirPath.Len) || '\\' == thePick[0] || '/' == thePick[0])
                    qtss_printf("%s picked\n", thePick);
                else
                    qtss_printf("%s%s picked\n", sSetupDirPath.Ptr,thePick);
            }

        }
        
        
        if ( !showMovieList )
        {
            int playError;
                
            if(!preflight)
            {   UpdateCurrentFile(broadcastParms, thePick);
                
                /* if playlist is about to run out repopulate it */
                if  (   !::strcmp(broadcastParms->mPlayMode, "sequential_looped") )
                {   
                    if(NULL == thePick &&!picker->mStopFlag)
                    {   
                        if (picker->GetNumMovies() == 0) 
                            break;
                        else
                            continue;
                    }
                        
                }
            }

            if (thePick == NULL)
                break;
            
            ++moviePlayCount;
            
            if(!preflight)
            {
                SInt64 startTime = OS::Milliseconds();
                SInt64 endTime = 0;
                playError = fileBroadcaster.PlayMovie( thePick, broadcastParms->mCurrentFile );
                endTime = OS::Milliseconds();
                // log the result of broacasting the picked movie
                sgLogger->LogMediaData( thePick, 
                                        fileBroadcaster.fCurrentMovieName, 
                                        fileBroadcaster.fCurrentMovieCopyright, 
                                        fileBroadcaster.fCurrentMovieComment, 
                                        fileBroadcaster.fCurrentMovieAuthor, 
                                        fileBroadcaster.fCurrentMovieArtist, 
                                        fileBroadcaster.fCurrentMovieAlbum, 
                                        (UInt32) ((endTime - startTime)/1000L),
                                        playError);
            }
            else
            {
                playError = fileBroadcaster.PlayMovie( thePick, NULL );
            }
            
            if (sQuitImmediate) 
            {   
                break;
            }
            
            if (sBroadcasterSession != NULL && sBroadcasterSession->GetReasonForDying() != BroadcasterSession::kDiedNormally)
            {   
                playError = ::EvalBroadcasterErr(QTFileBroadcaster::eNetworkConnectionFailed);
                sNumErrors++;
                goto bail;
            }
                
            if ( playError == 0 && sAnnounceBroadcast)
            {   
                int theErr = sBroadcasterSession->GetRequestStatus();
                if (200 != theErr)
                {   
                    if (401 == theErr)
                        playError = QTFileBroadcaster::eNetworkAuthorization;
                    else if (500 == theErr)
                        playError = QTFileBroadcaster::eNetworkNotSupported;
                    else 
                        playError = QTFileBroadcaster::eNetworkConnectionError;
                        
                    sNumErrors++;
                    goto bail;
                }
            }
            
            if (playError)
            {   
                playError = ::EvalBroadcasterErr(playError);
                
                if (playError == QTFileBroadcaster::eNetworkConnectionError)
                    goto bail;

                qtss_printf("  (file: %s err: %d %s)\n", thePick, playError,GetMovieFileErrString( playError ) );
                sNumWarnings++;
                numMovieErrors++;
            }
            else
            {
                int tracks = fileBroadcaster.GetMovieTrackCount() ;
                int mtracks = fileBroadcaster.GetMappedMovieTrackCount();

                if (tracks != mtracks)
                {   
                    sNumWarnings++;
                    numMovieErrors++;
                    qtss_printf("- PlaylistBroadcaster: Warning, movie tracks do not match the SDP file.\n" );
                    qtss_printf("  Movie: %s .\n", thePick );
                    qtss_printf("  %i of %i hinted tracks will not broadcast.\n", tracks- mtracks, tracks );
                }
            }
            
            if ( !preflight && (playError != 0) )
                sgLogger->LogMediaError( thePick, GetMovieFileErrString( playError ),NULL );
        }
        

        delete [] thePick;
        thePick = NULL;
    } 
    
    remove(broadcastParms->mCurrentFile);
    remove(broadcastParms->mUpcomingFile);  

    if ( preflight )
    {
        

        char    str[256];   
        qtss_printf( " - "  );
        if (numMovieErrors == 1)
            strcpy(str, "PlaylistBroadcaster found one problem movie file.");
        else
            qtss_sprintf( str, "PlaylistBroadcaster found %d problem movie files." , numMovieErrors );
        qtss_printf( "%s\n", str );
        if (sgLogger != NULL) 
            sgLogger->LogInfo( str );
        
        if (numMovieErrors == moviePlayCount)
        {
            qtss_printf("There are no valid movies to play\n");
            sNumErrors++;
        }
    }
    

    if (NULL != sBroadcasterSession && !sBroadcasterSession->IsDone() )
    {   sErrorEvaluted = true;
    }
        
bail:

    Cleanup();

    delete picker;
    
    sBroadcastParms = NULL;
    delete broadcastParms;

    if ( !preflight )
    {
        if (sgLogger != NULL) 
        {   if (!sQuitImmediate)
                sgLogger->LogInfo( "PlaylistBroadcaster finished." );
            else
                sgLogger->LogInfo( "PlaylistBroadcaster stopped." );            
        }
        
        if (!sQuitImmediate) // test sQuitImmediate again 
            qtss_printf( "\nPlaylistBroadcaster broadcast finished.\n" ); 
        else
            qtss_printf( "\nPlaylistBroadcaster broadcast stopped.\n" ); 
    }
    else
    {
        if (sgLogger != NULL) 
        {   if (!sQuitImmediate)
                sgLogger->LogInfo( "PlaylistBroadcaster preflight finished." );
            else
                sgLogger->LogInfo( "PlaylistBroadcaster preflight stopped." );
        }
        
        if (!sQuitImmediate)  // test sQuitImmediate again
            qtss_printf( "\nPlaylistBroadcaster preflight finished.\n" ); 
        else
            qtss_printf( "\nPlaylistBroadcaster preflight stopped.\n" ); 

    }
    sgLogger = NULL; // protect the interrupt handler and just let it die don't delete because it is a task thread

#ifndef __Win32__
    if ( sgTrackingSucceeded )
    {
        // remove ourselves from the tracker
        // this is the "normal" remove, also signal handlers
        // may remove us.
        
        BCasterTracker  tracker( sgTrackerFilePath );
        
        tracker.RemoveByProcessID( getpid() );
        tracker.Save();
    }
#endif //__Win32__
    if (sBroadcasterSession != NULL && !sBroadcasterSession->IsDone())
    {   
        //qtss_printf("QUIT now sBroadcasterSession->TearDownNow();\n");
        sBroadcasterSession->TearDownNow();
        int count=0;
        while (count++ < 30 && !sBroadcasterSession->IsDone() )
        {   sBroadcasterSession->Run();
            OSThread::Sleep(100);
        }
        sBroadcasterSession = NULL;
    }
   
}

static void Cleanup()
{
    if (sCleanupDone == true)
        return;
    
    sCleanupDone = true;
    
    if (sPreflight)
    {
            qtss_printf("Warnings: %d\n", sNumWarnings);
            qtss_printf("Errors: %d\n", sNumErrors);
    }
    else
    {
            qtss_printf("Broadcast Warnings: %d\n", sNumWarnings);
            qtss_printf("Broadcast Errors: %d\n", sNumErrors);
    }
    
    RemoveFiles(sBroadcastParms);
        
}

static void version()
{
    /*
        print PlaylistBroadcaster version and build info
        
        see revision.h
    */
    
    //RoadRunner/2.0.0-v24 Built on: Sep 17 1999 , 16:09:53
    //revision.h (project level file) -- include with -i option
    qtss_printf("PlaylistBroadcaster/%s Built on: %s, %s\n",  kVersionString, __DATE__, __TIME__ );

}

static void usage()
{
    /*
        print PlaylistBroadcaster usage string

    */
#ifndef __Win32__
    qtss_printf("usage: PlaylistBroadcaster [-v] [-h] [-p] [-c] [-a] [-t] [-i destAddress] [-e filename] [-f] [-d] [-l] [-s broadcastNum] filename\n" );
#else
    qtss_printf("usage: PlaylistBroadcaster [-v] [-h] [-p] [-c] [-a] [-t] [-i destAddress] [-e filename] [-f] filename\n" );
#endif

    qtss_printf("       -v: Display version\n" );
    qtss_printf("       -h: Display help\n" );
    qtss_printf("       -p: Verify a broadcast description file and movie list.\n" );
    qtss_printf("       -c: Show the current movie in the log file.\n" );
    qtss_printf("       -a: Announce the broadcast to the server.\n" );
    qtss_printf("       -t: Send the broadcast over TCP to the server.\n" );
    qtss_printf("       -i: Specify the destination ip address. Over-rides config file value.\n" );
    qtss_printf("       -e: Log errors to filename.\n" );
    qtss_printf("       -f: Force a new SDP file to be created even if one already exists.\n" );
#ifndef __Win32__
    qtss_printf("       -d: Run attached to the terminal.\n" );
    qtss_printf("       -l: List running currently broadcasts.\n" );
    qtss_printf("       -s: Stop a running broadcast.\n" );
#endif
    qtss_printf("        filename: Broadcast description filename.\n" );


}



static void help()
{
    /*
        print PlaylistBroadcaster help info

    */
    ::version();
    
    ::usage();
    
    
    qtss_printf("\n\nSample broadcast description file: ");
    PLBroadcastDef(NULL,NULL,false);

}



static PlaylistPicker* MakePickerFromConfig( PLBroadcastDef* broadcastParms )
{
    // construct a PlaylistPicker object using options set from a PLBroadcastDef
    
    PlaylistPicker *picker = NULL;
    
    if ( broadcastParms && broadcastParms->ParamsAreValid() )
    {
        if ( broadcastParms->mPlayMode )
        {
            if ( !::strcmp( broadcastParms->mPlayMode, "weighted_random" ) )
            {
                int     noPlayQueueLen = 0;

                noPlayQueueLen = broadcastParms->mLimitPlayQueueLength;
                
                picker = new PlaylistPicker( 10, noPlayQueueLen );
                
            }
            else if ( !::strcmp( broadcastParms->mPlayMode, "sequential_looped" ) )
            {           
                picker = new PlaylistPicker(true);
                picker->mRemoveFlag = true;
            }
            else if ( !::strcmp( broadcastParms->mPlayMode, "sequential" ) )
            {           
                picker = new PlaylistPicker(false);
                picker->mRemoveFlag = true;
            }
        
        }
    }
    
    return picker;
}

static const char* GetMovieFileErrString( int err )
{
    static  char buff[80];
    
    switch ( err )
    {
        case 0:
            break;
            
        case QTFileBroadcaster::eMovieFileNotFound:
            return "Movie file not found."; 
        
        case QTFileBroadcaster::eMovieFileNoHintedTracks:
            return "Movie file has no hinted tracks."; 

        case QTFileBroadcaster::eMovieFileNoSDPMatches : 
            return "Movie file does not match SDP.";
            
        case QTFileBroadcaster::eMovieFileInvalid:
            return "Movie file is invalid.";
            
        case QTFileBroadcaster::eMovieFileInvalidName:
            return "Movie file name is missing or too long."; 

        default:
            qtss_sprintf( buff, "Movie set up error %d occured.",err );
            return buff;
    }
    
    return NULL;

}

static int sErr = 0;
static int EvalBroadcasterErr(int err)
{
    int returnErr = err;
    if (sErr != 0) 
        return sErr;
    
    switch (err)
    {   case 412:
        {   qtss_printf("- Server Session Failed: The request was denied.");
            qtss_printf("\n");
            break;
        }
        case QTFileBroadcaster::eNoAvailableSockets :   
        case QTFileBroadcaster::eSDPFileNotFound    :
        case QTFileBroadcaster::eSDPDestAddrInvalid :
        case QTFileBroadcaster::eSDPFileInvalid     : 
        case QTFileBroadcaster::eSDPFileNoMedia     : 
        case QTFileBroadcaster::eSDPFileNoPorts     :
        case QTFileBroadcaster::eSDPFileInvalidPort :   
        {   qtss_printf("- SDP set up failed:  ");
            switch( err )
            {
                case QTFileBroadcaster::eNoAvailableSockets : qtss_printf("System error. No sockets are available to broadcast from."); 
                break;
                case QTFileBroadcaster::eSDPFileNotFound : qtss_printf("The SDP file is missing."); 
                break;
                case QTFileBroadcaster::eSDPDestAddrInvalid : qtss_printf("The SDP file server address is invalid."); 
                break;
                case QTFileBroadcaster::eSDPFileInvalid      : qtss_printf("The SDP file is invalid."); 
                break; 
                case QTFileBroadcaster::eSDPFileNoMedia      : qtss_printf("The SDP file is missing media (m=) references."); 
                break; 
                case QTFileBroadcaster::eSDPFileNoPorts      : qtss_printf("The SDP file is missing server port information."); 
                break;
                case QTFileBroadcaster::eSDPFileInvalidPort  : qtss_printf("The SDP file contains an invalid port. Valid range is 5004 - 65530."); 
                break;

                default: qtss_printf("SDP set up error %d occured.",err);
                break;  
            
            };
            qtss_printf("\n");
        }
        break;
        
        case QTFileBroadcaster::eSDPFileInvalidTTL:
        case QTFileBroadcaster::eDescriptionInvalidDestPort:
        case QTFileBroadcaster::eSDPFileInvalidName: 
        case QTFileBroadcaster::eNetworkSDPFileNameInvalidBadPath:
        case QTFileBroadcaster::eNetworkSDPFileNameInvalidMissing:
        {   qtss_printf("- Description set up failed: ");
            switch( err )
            {
            
                case QTFileBroadcaster::eSDPFileInvalidTTL   : qtss_printf("The multicast_ttl value is incorrect. Valid range is 1 to 255."); 
                break;
                case QTFileBroadcaster::eDescriptionInvalidDestPort  : qtss_printf("The destination_base_port value is incorrect. Valid range is 5004 - 65530."); 
                break;
                case QTFileBroadcaster::eSDPFileInvalidName  : qtss_printf("The sdp_file name is missing or too long."); 
                break;
                case QTFileBroadcaster::eNetworkSDPFileNameInvalidBadPath: qtss_printf("The specified destination_sdp_file must a be relative file path in the movies directory."); 
                break;
                case QTFileBroadcaster::eNetworkSDPFileNameInvalidMissing: qtss_printf("The specified destination_sdp_file name is missing."); 
                break;
                            
                default: qtss_printf("Description set up error %d occured.",err);
                break;  
            }
            qtss_printf("\n");
        }
        break;
        
        
        
        case QTFileBroadcaster::eMovieFileNotFound          :
        case QTFileBroadcaster::eMovieFileNoHintedTracks    :
        case QTFileBroadcaster::eMovieFileNoSDPMatches      :
        case QTFileBroadcaster::eMovieFileInvalid           :
        case QTFileBroadcaster::eMovieFileInvalidName       :
        {   qtss_printf("- Movie set up failed: ");
            switch( err )
            {   
                case QTFileBroadcaster::eMovieFileNotFound          : qtss_printf("Movie file not found."); 
                break;
                case QTFileBroadcaster::eMovieFileNoHintedTracks    : qtss_printf("Movie file has no hinted tracks."); 
                break;
                case QTFileBroadcaster::eMovieFileNoSDPMatches      : qtss_printf("Movie file does not match SDP."); 
                break;
                case QTFileBroadcaster::eMovieFileInvalid           : qtss_printf("Movie file is invalid."); 
                break;
                case QTFileBroadcaster::eMovieFileInvalidName       : qtss_printf("Movie file name is missing or too long."); 
                break;
        
                default: qtss_printf("Movie set up error %d occured.",err);
                break;  
        
            };
            qtss_printf("\n");
        }
        break;
        
        case QTFileBroadcaster::eMem:       
        case QTFileBroadcaster::eInternalError:
        {   qtss_printf("- Internal Error: ");
            switch( err )
            {   
                case QTFileBroadcaster::eMem            : qtss_printf("Memory error."); 
                break;
                
                case QTFileBroadcaster::eInternalError   : qtss_printf("Internal error."); 
                break;
            
                default: qtss_printf("internal error %d occured.",err);
                break;  
            }
            qtss_printf("\n");
        }
        break;
        
        case QTFileBroadcaster::eFailedBind:
        case QTFileBroadcaster::eNetworkConnectionError:
        case QTFileBroadcaster::eNetworkRequestError:
        case QTFileBroadcaster::eNetworkConnectionStopped:
        case QTFileBroadcaster::eNetworkAuthorization:
        case QTFileBroadcaster::eNetworkNotSupported:
        case QTFileBroadcaster::eNetworkConnectionFailed:
        {   sErrorEvaluted = true;
            qtss_printf("- Network Connection: ");
            switch( err )
            {   
                case QTFileBroadcaster::eFailedBind      : qtss_printf("A Socket failed trying to open and bind to a local port.\n."); 
                break;

                case QTFileBroadcaster::eNetworkConnectionError  : qtss_printf("Failed to connect."); 
                break;
                
                case QTFileBroadcaster::eNetworkRequestError     : qtss_printf("Server returned error."); 
                break;
            
                case QTFileBroadcaster::eNetworkConnectionStopped: qtss_printf("Connection stopped."); 
                break;

                case QTFileBroadcaster::eNetworkAuthorization: qtss_printf("Authorization failed."); 
                break;

                case QTFileBroadcaster::eNetworkNotSupported: qtss_printf("Connection not supported by server."); 
                break;

                case QTFileBroadcaster::eNetworkConnectionFailed     : qtss_printf("Disconnected."); 
                break;

                default: qtss_printf("internal error %d occured.",err);
                break;  
            }
            qtss_printf("\n");
            returnErr = QTFileBroadcaster::eNetworkConnectionError;
        }
        break;
            
        default:
            
        break;
    }
    
    sErr = returnErr;

    return returnErr;
}


static int MyAccess( const char* path, int mode )
{
    int     error = 0;

#ifndef __Win32__
    if ( access( path, mode ) )
        error = errno;
    else
        error = 0;
#endif 
    
    return error;
}

static int PreflightClientBufferDelay( const char * delay, Float32 *ioDelay)
{
    
    int     numPreflightErrors = 0;
    
    if ( NULL == delay|| 0 == delay[0] )
        numPreflightErrors++;
    else 
    {   Float32 delayValue = 0.0;
        ::sscanf(delay, "%f", &delayValue);
        if (delayValue < 0.0)
            numPreflightErrors++;
        if (ioDelay != NULL)
            *ioDelay = delayValue;
    }       
    
    if (numPreflightErrors > 0)
        qtss_printf("- PlaylistBroadcaster: The client_buffer_delay parameter is invalid.\n" );

    return numPreflightErrors;
}

static int PreflightDestSDPFileAccess( const char * sdpPath )
{
    int     numPreflightErrors = 0;
    
    if ( NULL == sdpPath || 0==sdpPath[0] || 0 == ::strcmp(sdpPath, "no_name") )
    {   qtss_printf("- PlaylistBroadcaster: The destination_sdp_file parameter is missing from the Broadcaster description file.\n" );
        numPreflightErrors++;
    }

    return numPreflightErrors;

}

static int PreflightSDPFileAccess( const char * sdpPath )
{
    int     numPreflightErrors = 0;

    Assert( sdpPath );
    
    if ( !sdpPath )
    {   qtss_printf("- PlaylistBroadcaster: The sdp_file parameter is missing from the Broadcaster description file.\n" );
        numPreflightErrors++;
    }
    else
    {   int     accessError;
    
        accessError = MyAccess( sdpPath, R_OK | W_OK );
        
        switch( accessError )
        {
        
            case 0:
            case ENOENT:    // if its not there, we'll create it
                break;
            
            case EACCES:
                qtss_printf("- PlaylistBroadcaster: Permission to access the SDP File was denied.\n  Read/Write access is required.\n  (path: %s, errno: %i).\n", sdpPath, accessError);
                numPreflightErrors++;
                break;
                
            default:
                qtss_printf("- PlaylistBroadcaster: Unable to access the SDP File.\n  (path: %s, errno: %i).\n", sdpPath, accessError);
                numPreflightErrors++;
                break;
                
        }
    
    }

    return numPreflightErrors;

}

static int PreflightDestinationAddress( const char *destAddress )
{
    int     numPreflightErrors = 0;


    if ( !destAddress || (SocketUtils::ConvertStringToAddr(destAddress) == INADDR_NONE) )
    {   qtss_printf("- PlaylistBroadcaster: Error, desitination_ip_address parameter missing or incorrect in the Broadcaster description file.\n" );
        numPreflightErrors++;
    }
    
    return numPreflightErrors;

}

static int PreflightBasePort( const char *basePort )
{
    int     numPreflightErrors = 0;

        
    Assert( basePort );
    if ( basePort == NULL )
    {   qtss_printf("- PlaylistBroadcaster: Error, destination_base_port parameter missing from the Broadcaster description file.\n" );
        numPreflightErrors++;
    }
    else if ( (atoi(basePort) & 1)  != 0)
    {
        qtss_printf("- PlaylistBroadcaster: Warning, the destination_base_port parameter(%s) is an odd port number.  It should be even.\n", basePort );        
    }
    
    return numPreflightErrors;

}


static int PreflightReferenceMovieAccess( const char *refMoviePath )
{
    int     numPreflightErrors = 0;
    
    Assert( refMoviePath );
    if ( !refMoviePath )
    {   qtss_printf("- PlaylistBroadcaster: Error, sdp_reference_movie parameter missing from the Broadcaster description file.\n" );
        numPreflightErrors++;
    }
    else
    {   int     accessError;
    
        accessError = MyAccess( refMoviePath, R_OK );
        
        switch( accessError )
        {           
            case 0:
                break;
                
            case ENOENT:
                qtss_printf("- PlaylistBroadcaster: Error, SDP Reference Movie is missing.\n  (path: %s, errno: %i).\n", refMoviePath, accessError);
                numPreflightErrors++;
                break;
            
            case EACCES:
                qtss_printf("- PlaylistBroadcaster: Permission to access the SDP reference movie was denied.\n  Read access required.\n  (path: %s, errno: %i).\n", refMoviePath, accessError);
                numPreflightErrors++;
                break;
                
            default:
                qtss_printf("- PlaylistBroadcaster: Unable to access the SDP reference movie.\n  (path: %s, errno: %i).\n", refMoviePath, accessError);
                numPreflightErrors++;
                break;
                
        }
    
    }

    return numPreflightErrors;
}


static void ShowPlaylistStatus()
{   
#ifndef __Win32__
    BCasterTracker  tracker( sgTrackerFilePath );

    if ( tracker.IsOpen() )
    {
        tracker.Show();
    }
    else
    {   
        qtss_printf("- PlaylistBroadcaster: Could not open %s.  Reason: access denied.\n", sgTrackerFilePath );
        qtss_printf("- PlaylistBroadcaster: Change user or change the directory's privileges to access %s.\n",sgTrackerFilePath) ;
        ::exit(-1);

    }
#else
    qtss_printf("Showing Playlist status is currently not supported on this platform\n");
#endif
}

static void StopABroadcast( const char* arg )
{
#ifndef __Win32__
    if ( arg )
    {   
        
        BCasterTracker  tracker( sgTrackerFilePath );
        int             playlistIDToKill;
        
        playlistIDToKill = ::atoi( arg );                       
        playlistIDToKill--;     // convert from UI one based to to zero based ID for BCasterTracker

        
        if ( tracker.IsOpen() )
        {
            bool    broadcastIDIsValid;
            int     error;
            
            error = tracker.Remove( playlistIDToKill );
            
            if ( !error )   // remove the xth item from the list.
            {   tracker.Save();
                broadcastIDIsValid = true;  
            }
            else
                broadcastIDIsValid = false;
                
            if ( !broadcastIDIsValid )
            {
                if ( playlistIDToKill >= 0 )
                {
                    if ( error == ESRCH || error == -1 )
                        qtss_printf( "- PlaylistBroadcaster Broadcast ID (%s) not running.\n", arg );
                    else
                        qtss_printf( "- PlaylistBroadcaster Broadcast ID (%s), permission to stop denied (%i).\n", arg, error );
                    
                }
                else
                {   qtss_printf( "- Bad argument for stop: (%s).\n", arg );
                    ::exit( -1 );
                }
            }
            else
                qtss_printf("PlaylistBroadcaster stopped Broadcast ID: %s.\n", arg);
        }
        else
        {   qtss_printf("- PlaylistBroadcaster: Could not open %s.  Reason: access denied.\n", sgTrackerFilePath );
            qtss_printf("- PlaylistBroadcaster: Change user or change the directory's privileges to access %s.\n",sgTrackerFilePath) ;
            ::exit( -1 );

        }
    }
    else
    {   // getopt will catch this problem before we see it.
        qtss_printf("- Stop requires a Broadcast ID.\n");
        ::exit( -1 );
    }
#else
    qtss_printf("Stopping a broadcast is currently not supported on this platform\n");
#endif
}


static bool DoSDPGen( PLBroadcastDef *broadcastParms, bool preflight, bool overWriteSDP, bool generateNewSDP, int* numErrorsPtr, char* refMovie)
{
    int numSDPSetupErrors = 0;
    bool sdpFileCreated = false;
    
    if (sAnnounceBroadcast)
    {   numSDPSetupErrors += PreflightDestSDPFileAccess( broadcastParms->mDestSDPFile );
        Assert(broadcastParms->mBasePort != NULL);
        Assert(broadcastParms->mBasePort[0]!=0);
        broadcastParms->mBasePort[0] = '0';//set to dynamic. ignore any defined.
        broadcastParms->mBasePort[1] = 0;
    }
    
    if (broadcastParms->mSDPReferenceMovie != NULL)
        refMovie = broadcastParms->mSDPReferenceMovie;
        
    qtss_printf("Ref Movie = %s\n", refMovie);
    qtss_printf("SDP file = %s\n", broadcastParms->mSDPFile);
 
    numSDPSetupErrors += PreflightSDPFileAccess( broadcastParms->mSDPFile );
    
    
    numSDPSetupErrors += PreflightReferenceMovieAccess( refMovie );
    
    
    numSDPSetupErrors += PreflightDestinationAddress( broadcastParms->mDestAddress );


    numSDPSetupErrors += PreflightBasePort( broadcastParms->mBasePort );

    Float32 bufferDelay = 0.0;  
    numSDPSetupErrors += PreflightClientBufferDelay(  broadcastParms->mClientBufferDelay,&bufferDelay );
    
    if ( numSDPSetupErrors == 0 )
    {
        SDPGen* sdpGen = new SDPGen;
        
        int     sdpResult = -1;
        
        Assert( sdpGen );
        
        if ( sdpGen )
        {   
            sdpGen->SetClientBufferDelay(bufferDelay); // sdp "a=x-bufferdelay: value" 
            sdpGen->KeepSDPTracks(false); // set this to keep a=control track ids if we are going to ANNOUNCE the sdp to the server.
            sdpGen->AddIndexTracks(true); // set this if KeepTracks is false and to ANNOUNCE the sdp to the server.
            sdpResult = sdpGen->Run( refMovie, broadcastParms->mSDPFile, 
                          broadcastParms->mBasePort, broadcastParms->mDestAddress, 
                          sSDPBuffer, maxSDPbuffSize, 
                          overWriteSDP,  generateNewSDP,
                          broadcastParms->mStartTime,
                          broadcastParms->mEndTime,
                          broadcastParms->mIsDynamic,
                          broadcastParms->mTTL
                          );
        
            sdpFileCreated =  sdpGen->fSDPFileCreated;
            
            if ( sdpGen->fSDPFileCreated && !preflight)
                qtss_printf( "- PlaylistBroadcaster: Created SDP file.\n  (path: %s)\n", broadcastParms->mSDPFile);
        }
        
        
        if ( sdpResult )
        {
            if (sdpResult ==  -2)
                qtss_printf("- SDP generation failed: Unable to create the SDP File.\n  (path: %s, errno: %d).\n", broadcastParms->mSDPFile, sdpResult);
            else
                qtss_printf( "- SDP generation failed (error: %li).\n", (long)sdpResult );
                        
            (*numErrorsPtr)++;
            return sdpFileCreated;
        }
        
        if ( sdpGen )
            delete sdpGen;
        
        sdpGen = NULL;
    }
    else
    {   qtss_printf( "- PlaylistBroadcaster: Too many SDP set up errors, exiting.\n" );
        *numErrorsPtr += numSDPSetupErrors;
    }

    return sdpFileCreated;

}

static bool PreflightTrackerFileAccess( int mode )
{
    
#ifndef __Win32__   
    int     trackerError;

    int dirlen = strlen(sgTrackerDirPath) + 1;
    OSCharArrayDeleter trackDir(new char[dirlen]);
    char *trackDirPtr = trackDir.GetObject();
    ::memcpy(trackDirPtr, sgTrackerDirPath, dirlen);

    trackerError= OS::RecursiveMakeDir( trackDirPtr );
    
    if ( trackerError == 0 )
        trackerError = MyAccess( sgTrackerFilePath, mode );
    
    switch ( trackerError )
    {           
        case 0:
        case ENOENT:
            break;
            
        default:
            qtss_printf("- PlaylistBroadcaster: Error opening %s.  (errno: %i).\n", sgTrackerFilePath, trackerError);
            qtss_printf("- PlaylistBroadcaster: Change user or change the directory's privileges to access %s.\n",sgTrackerFilePath) ;
            return false;
            break;
            
    }
#endif
    return true;
}

static void ShowSetupParams(PLBroadcastDef* broadcastParms, const char *bcastSetupFilePath)
{       
    qtss_printf( "\n" );
    qtss_printf( "PlaylistBroadcaster broadcast description File\n" );
    qtss_printf( "----------------------------------------------\n" );
    qtss_printf( "%s\n", bcastSetupFilePath );

    broadcastParms->ShowSettings();

}

static bool PreFlightSetupFile( const char * bcastSetupFilePath )
{
        bool success = true;
        
    // now complain!
    if ( !bcastSetupFilePath )
    {   qtss_printf("- PlaylistBroadcaster: A broadcast description file is required.\n" );
        ::usage();
        success = false;
    }
        else
    {   int     accessError;
    
        accessError = MyAccess(bcastSetupFilePath, R_OK  );
        
        switch( accessError )
        {
        
            case 0:
                break;
                
            case ENOENT:    
                qtss_printf("- PlaylistBroadcaster: The broadcast description file is missing.\n  (path: %s, errno: %i).\n", bcastSetupFilePath, accessError);
                success = false;
                break;
            
            case EACCES:
                qtss_printf("- PlaylistBroadcaster: Permission denied to access the broadcast description file.\n  Read access required.\n  (path: %s, errno: %i).\n", bcastSetupFilePath, accessError);
                success = false;
                break;
                
            default:
                qtss_printf("- PlaylistBroadcaster: Unable to access the broadcast description file.\n  (path: %s, errno: %i).\n", bcastSetupFilePath, accessError);
                success = false;
                break;
                
        }
    
    }

        return success;
}

static bool AddOurPIDToTracker( const char* bcastSetupFilePath )
{
    // add our pid and Broadcast description file File to the tracker
#ifndef __Win32__   
    BCasterTracker  tracker( sgTrackerFilePath );
    
    if ( tracker.IsOpen() )
    {   sgTrackingSucceeded = 1;
        tracker.Add( getpid(), bcastSetupFilePath );
        tracker.Save();
    }
    else
    {               
        qtss_printf("- PlaylistBroadcaster: Could not open %s.  Reason: access denied.\n", sgTrackerFilePath );
        qtss_printf("- PlaylistBroadcaster: Change user or change the directory's privileges to access %s.\n",sgTrackerFilePath) ;
        return false;
    }
#endif
    return true; // return true if pid was successfully added to the tracker
}

#if 0

static void ShowPickDistribution( PlaylistPicker *picker )
{       
    qtss_printf( "\n" );
    qtss_printf( "Pick Distribution by Bucket\n" );
    qtss_printf( "---------------------------\n" );
    
    UInt32      bucketIndex;
    
    for ( bucketIndex = 0; bucketIndex < picker->GetNumBuckets(); bucketIndex++ )
    {   
        qtss_printf( "bucket total for w: %li, (%li)\n", (bucketIndex + 1), (long)picker->mPickCounts[bucketIndex] );
    
    }
}

// archaic debug code

static void PrintContents( PLDoubleLinkedListNode<SimplePlayListElement> *node, void *  );
static void ShowPicker(PlaylistPicker *picker);


static void PrintContents( PLDoubleLinkedListNode<SimplePlayListElement> *node, void *  )
{
    qtss_printf( "element name %s\n", node->mElement->mElementName );
}

static void ShowPicker(PlaylistPicker *picker)
{
    int x;
    
    for ( x= 0; x < picker->GetNumBuckets(); x++ )
    {
        picker->GetBucket(x)->ForEach( PrintContents, NULL );
    }

}

#endif
/* changed by emil@popwire.com (see relaod.txt for info) */
bool FileCreateAndCheckAccess(char *theFileName){
    FILE *theFile;


#ifndef __Win32__
    if(access(theFileName, F_OK)){
        /* file does not exist  - create and set rights */
        theFile = ::fopen(theFileName, "w+");
        if(theFile){
            /* make sure everybody has r/w access to file */
            (void)::chmod(theFileName, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
            (void)::fclose(theFile);
        }else return 1;
    }else{
        /* file exists - check rights */
        if(access(theFileName, R_OK|W_OK))return 2;
    }

#else

    theFile = ::fopen(theFileName,"a");

    if (theFile) ::fclose(theFile);

#endif


    return 0;
}

static void PrintPlaylistElement(PLDoubleLinkedListNode<SimplePlayListElement> *node,void *file)
{   
    sElementCount ++;
    if (sElementCount <= sMaxUpcomingListSize)  
    {   char* thePick = node->fElement->mElementName;
        if (sSetupDirPath.EqualIgnoreCase(thePick, sSetupDirPath.Len) || '\\' == thePick[0] || '/' == thePick[0])
            qtss_fprintf((FILE*)file,"%s\n", thePick);
        else
            qtss_fprintf((FILE*)file,"%s%s\n", sSetupDirPath.Ptr,thePick);
    }
}

static void ShowPlaylistElements(PlaylistPicker *picker,FILE *file)
{
    if (sElementCount > sMaxUpcomingListSize)   
        return;
        
    UInt32  x;
    for (x= 0;x<picker->GetNumBuckets();x++)
    {   
        picker->GetBucket(x)->ForEach(PrintPlaylistElement,file);
    }
}
/* ***************************************************** */

static void RegisterEventHandlers()
{
#ifdef  __Win32__
    SetConsoleCtrlHandler( (PHANDLER_ROUTINE) SignalEventHandler, true);
    return;
#endif

#ifndef __Win32__   

struct sigaction act;
    
#if defined(sun) || defined(i386) || defined(__MacOSX__) || defined(__powerpc__) || defined (__sgi_cc__) || defined(__osf__) || defined(__hpux__)
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
    act.sa_handler = (void(*)(int))&SignalEventHandler;
#elif defined(__sgi__) 
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	act.sa_handler = (void(*)(...))&SignalEventHandler;
#else
    act.sa_mask = 0;
    act.sa_flags = 0;
    act.sa_handler = (void(*)(...))&SignalEventHandler;
#endif

    if ( ::signal(SIGTERM, SIG_IGN) != SIG_IGN) 
    {   // from kill...
        if ( ::sigaction(SIGTERM, &act, NULL) != 0 )
        {   qtss_printf( "- PlaylistBroadcaster: System error (%i).\n", (int)SIG_ERR );
        }
    }

    if ( ::signal(SIGINT, SIG_IGN) != SIG_IGN) 
    {   // ^C signal
        if ( ::sigaction(SIGINT, &act, NULL)  != 0 )
        {   qtss_printf( "- PlaylistBroadcaster: System error (%i).\n", (int)SIG_ERR );
        }
        
    }
    
    if ( ::signal(SIGPIPE, SIG_IGN) != SIG_IGN) 
    {   // broken pipe probably from a failed RTSP session (the server went down?)
        if ( ::sigaction(SIGPIPE, &act, NULL)   != 0 )
        {   qtss_printf( "- PlaylistBroadcaster: System error (%i).\n", (int)SIG_ERR );
        }
        
    }
 
    if ( ::signal(SIGHUP, SIG_IGN) != SIG_IGN) 
    {   // broken pipe probably from a failed RTSP session (the server went down?)
        if ( ::sigaction(SIGHUP, &act, NULL)  != 0)
        {   qtss_printf( "- PlaylistBroadcaster: System error (%i).\n", (int)SIG_ERR );
        }
        
    }
    
    
#endif


}

/* ========================================================================
 * Signal and error handler.
 */
static void RemoveFiles(PLBroadcastDef* broadcastParms)
{
    if (broadcastParms != NULL)
    {
        if (broadcastParms->mPIDFile != NULL)
                    remove(broadcastParms->mPIDFile);
        if (broadcastParms->mStopFile != NULL)
            remove(broadcastParms->mStopFile);
        if (broadcastParms->mReplaceFile != NULL)
            remove(broadcastParms->mReplaceFile);
        if (broadcastParms->mInsertFile != NULL)
            remove(broadcastParms->mInsertFile);
        if (broadcastParms->mCurrentFile != NULL)
            remove(broadcastParms->mCurrentFile);
        if (broadcastParms->mUpcomingFile != NULL)
            remove(broadcastParms->mUpcomingFile);  
    }

}
/* ========================================================================
 * Signal and error handler.
 */
static void SignalEventHandler( int signalID )
{   

#ifdef __Win32__
        if ( (signalID != SIGINT) && (signalID != SIGTERM) )
#else
        if (signalID == SIGPIPE)
#endif
            sNumErrors++;
        
        // evaluate the error
    if (sRTSPClientError != 0 && !sErrorEvaluted)
    {   
        EvalBroadcasterErr(sRTSPClientError);
        sErrorEvaluted = true;
    }
    else if (sBroadcasterSession != NULL && !sErrorEvaluted)
    {
#ifndef __Win32__
        if ( (signalID == SIGINT) || (signalID == SIGTERM) )
        {   EvalBroadcasterErr(QTFileBroadcaster::eNetworkConnectionStopped);
        }
        else
        {   EvalBroadcasterErr(QTFileBroadcaster::eNetworkConnectionFailed);
        }
#else       
        EvalBroadcasterErr(QTFileBroadcaster::eNetworkConnectionStopped);
#endif
        sErrorEvaluted = true;
    }

        // do the cleanup - write warning and error messages and remove files
        Cleanup();
        
#ifndef __Win32__
    if (sBroadcasterSession != NULL)
    {   

        if ( (signalID == SIGINT) || (signalID == SIGTERM) )
            sQuitImmediate = true; // just in case we get called again
            
        return; //we need to let the broadcaster session teardown and clean up
    }

    if ( sgTrackingSucceeded )
    {   
        // give tracker scope so that it really does it's
        // thing before "exit" is called.
        BCasterTracker  tracker( sgTrackerFilePath );
    
        tracker.RemoveByProcessID( getpid() );
        tracker.Save();
    }
    
#endif  

    if (!sQuitImmediate) // do once
    {   
        sQuitImmediate = true; // just in case we get called again

#ifdef __Win32__

        if (sBroadcasterSession && !sBroadcasterSession->IsDone())
        {   sBroadcasterSession->TearDownNow();
            OSThread::Sleep(1000);
        }
        if (NULL == sBroadcasterSession)
            qtss_printf("\n"); // make sure the message was printed before quitting.
        
#endif      
        return;
        
    }
    ::exit(-1);
}
