/* * * @APPLE_LICENSE_HEADER_START@ * * Copyright (c) 1999-2001 Apple Computer, Inc. All Rights Reserved. The * contents of this file constitute Original Code as defined in and are * subject to the Apple Public Source License Version 1.2 (the 'License'). * You may not use this file except in compliance with the License. Please * obtain a copy of the License at http://www.apple.com/publicsource and * read it before using this file. * * This 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@ * */ #include "MP3Broadcaster.h" #include "MP3MetaInfoUpdater.h" #include "StringTranslator.h" #include #include #include #include #ifndef __Win32__ #include #endif #ifndef __Win32__ #ifndef __MACOS__ #include #if defined (__solaris__) || defined (__osf__) #include "daemon.h" #else #ifndef __FreeBSD__ #include #endif #endif #else #include #endif #endif #include #include "OSHeaders.h" #include "OS.h" #include "OSMemory.h" #include "Task.h" #include "TimeoutTask.h" #include "OSArrayObjectDeleter.h" #include "ConfParser.h" #include "MP3FileBroadcaster.h" // must now inlcude this from the project level using the -i switch in the compiler #ifndef __MacOSXServer__ #ifndef __MacOSX__ #include "revision.h" #endif #endif MP3Broadcaster* MP3Broadcaster::sBroadcaster = NULL; //#include "MyAssert.h" MP3Broadcaster::MP3Broadcaster(char* ipaddr, int port, char* config, char* playList, char* workingDir) : mValid(true), mPort(8000), mBitRate(0), mFrequency(0), mUpcomingSongsListSize(7), mRecentSongsListSize(0), mLogging(0), mShowCurrent(true), mShowUpcoming(true), mNumErrors(0), mNumWarnings(0), mPreflight(false), mCleanupDone(false), mTempPicker(NULL), mSocket(NULL, 0), mLog(NULL) { sBroadcaster = this; strcpy(mIPAddr, "128.0.0.1"); strcpy(mPlayListPath, "/etc/streaming/mp3playlist.ply"); strcpy(mWorkingDirPath, "/etc/streaming/"); strcpy(mPlayMode, "sequential"); strcpy(mMountPoint, "/"); strcpy(mGenre, "Pop"); strcpy(mURL, ""); strcpy(mPIDFile, ""); int err = ::ParseConfigFile( false, config, ConfigSetter, this ); if (err) { mValid = false; return; } if (ipaddr != NULL) { // override config file // size limit for any IP addr is 255 strncpy(mIPAddr, ipaddr, 255); } if (port != 0) { // override config file mPort = port; } if (playList) { // override config file // size limit for any playlist string is PATH_MAX - 1 strncpy(mPlayListPath, playList, PATH_MAX-1); } if (workingDir) { // override config file // size limit for any working path is PATH_MAX - extension - 1 strncpy(mWorkingDirPath, playList, PATH_MAX-12); } CreateWorkingFilePath(".current", mCurrentFile); CreateWorkingFilePath(".upcoming", mUpcomingFile); CreateWorkingFilePath(".replacelist", mReplaceFile); CreateWorkingFilePath(".stoplist", mStopFile); CreateWorkingFilePath(".insertlist", mInsertFile); CreateWorkingFilePath(".log", mLogFile); } Bool16 MP3Broadcaster::ConfigSetter( const char* paramName, const char* paramValue[], void* userData ) { // return true if set fails MP3Broadcaster* thisPtr = (MP3Broadcaster*)userData; if (!::strcmp( "destination_ip_address", paramName) ) { if (strlen(paramValue[0]) >= sizeof(thisPtr->mIPAddr)) return true; strcpy(thisPtr->mIPAddr, paramValue[0]); return false; } else if (!::strcmp( "destination_base_port", paramName) ) { thisPtr->mPort = atoi(paramValue[0]); return false; } else if (!::strcmp( "max_upcoming_list_size", paramName) ) { if ( ::atoi( paramValue[0] ) < 0 ) return true; thisPtr->mUpcomingSongsListSize = ::atoi( paramValue[0] ); return false; } else if (!::strcmp( "play_mode", paramName) ) { if (strlen(paramValue[0]) >= sizeof(thisPtr->mPlayMode)) return true; strcpy(thisPtr->mPlayMode, paramValue[0]); return false; } else if (!::strcmp( "recent_songs_list_size", paramName) ) { if ( ::atoi( paramValue[0] ) < 0 ) return true; thisPtr->mRecentSongsListSize = ::atoi( paramValue[0] ); return false; } else if (!::strcmp( "playlist_file", paramName) ) { if (strlen(paramValue[0]) >= sizeof(thisPtr->mPlayListPath)) return true; strcpy(thisPtr->mPlayListPath, paramValue[0]); return false; } else if (!::strcmp( "working_dir", paramName) ) { if (strlen(paramValue[0]) >= sizeof(thisPtr->mWorkingDirPath)) return true; strcpy(thisPtr->mWorkingDirPath, paramValue[0]); return false; } else if (!::strcmp( "logging", paramName) ) { return SetEnabled(paramValue[0], &thisPtr->mLogging); } else if (!::strcmp( "show_current", paramName) ) { return SetEnabled(paramValue[0], &thisPtr->mShowCurrent); } else if (!::strcmp( "show_upcoming", paramName) ) { return SetEnabled(paramValue[0], &thisPtr->mShowUpcoming); } else if (!::strcmp( "broadcast_name", paramName) ) { if (strlen(paramValue[0]) >= sizeof(thisPtr->mName)) return true; strcpy(thisPtr->mName, paramValue[0]); return false; } else if (!::strcmp( "broadcast_password", paramName) ) { if (strlen(paramValue[0]) >= sizeof(thisPtr->mPassword)) return true; strcpy(thisPtr->mPassword, paramValue[0]); return false; } else if (!::strcmp( "broadcast_genre", paramName) ) { if (strlen(paramValue[0]) >= sizeof(thisPtr->mGenre)) return true; strcpy(thisPtr->mGenre, paramValue[0]); return false; } else if (!::strcmp( "broadcast_url", paramName) ) { if (strlen(paramValue[0]) >= sizeof(thisPtr->mURL)) return true; strcpy(thisPtr->mURL, paramValue[0]); return false; } else if (!::strcmp( "broadcast_bitrate", paramName) ) { if ( ::atoi( paramValue[0] ) < 0 ) return true; thisPtr->mBitRate = ::atoi( paramValue[0] ); return false; } else if (!::strcmp( "broadcast_sample_rate", paramName) ) { if ( ::atoi( paramValue[0] ) < -1 ) return true; thisPtr->mFrequency = ::atoi( paramValue[0] ); return false; } else if (!::strcmp( "broadcast_mount_point", paramName) ) { if (strlen(paramValue[0]) >= sizeof(thisPtr->mMountPoint)) return true; strcpy(thisPtr->mMountPoint, paramValue[0]); return false; } else if (!::strcmp( "pid_file", paramName) ) { if (strlen(paramValue[0]) >= sizeof(thisPtr->mPIDFile)) return true; strcpy(thisPtr->mPIDFile, paramValue[0]); return false; } return true; } Bool16 MP3Broadcaster::SetEnabled( const char* value, Bool16* field) { if ( ::strcmp( "enabled", value) && ::strcmp( "disabled", value) ) return true; *field = !strcmp( "enabled", value ); return false; } void MP3Broadcaster::CreateWorkingFilePath(char* extension, char* result) { if (strlen(mWorkingDirPath) + strlen(extension) >= PATH_MAX) result[0] = 0; else { strcpy(result, mWorkingDirPath); strcat(result, extension); } } void MP3Broadcaster::CreateCurrentAndUpcomingFiles() { if (mShowCurrent) { if(FileCreateAndCheckAccess(mCurrentFile)) { /* error */ mLog->LogInfo( "MP3Broadcaster Error: Failed to create current broadcast file" ); } } if (mShowUpcoming) { if(FileCreateAndCheckAccess(mUpcomingFile)) { /* error */ mLog->LogInfo( "MP3Broadcaster Error: Failed to create upcoming broadcast file" ); } } } void MP3Broadcaster::UpdateCurrentAndUpcomingFiles(char *thePick, PlaylistPicker *picker,PlaylistPicker *insertPicker) { if ( (NULL == thePick) || (NULL == picker) || (NULL == insertPicker) ) return; // save currently playing song to .current file if (mShowCurrent) { FILE *currentFile = fopen(mCurrentFile, "w"); if(currentFile) { ::fprintf(currentFile,"u=%s\n",thePick); fclose(currentFile); } } /* if .stoplist file exists - prepare to stop broadcast */ if(!access(mStopFile, R_OK)) { picker->CleanList(); PopulatePickerFromFile(picker, mStopFile, "", NULL); mTempPicker->CleanList(); remove(mStopFile); picker->mStopFlag = true; } /* if .replacelist file exists - replace current playlist */ if(!access(mReplaceFile, R_OK)) { picker->CleanList(); PopulatePickerFromFile(picker, mReplaceFile, "", NULL); mTempPicker->CleanList(); remove(mReplaceFile); picker->mStopFlag = false; } /* if .insertlist file exists - insert into current playlist */ if(!access(mInsertFile, R_OK)) { insertPicker->CleanList(); mTempPicker->CleanList(); PopulatePickerFromFile(insertPicker, mInsertFile, "", NULL); remove(mInsertFile); picker->mStopFlag = false; } // write upcoming playlist to .upcoming file if (mShowUpcoming) { FILE *upcomingFile = fopen(mUpcomingFile, "w"); if(upcomingFile) { mElementCount = 0; if (!::strcmp(mPlayMode, "weighted_random")) fprintf(upcomingFile,"#random play - upcoming list not supported\n"); else { fprintf(upcomingFile,"*PLAY-LIST*\n"); ShowPlaylistElements(insertPicker,upcomingFile); ShowPlaylistElements(picker,upcomingFile); if ( picker->GetNumMovies() == 0 && !picker->mStopFlag && 0 != ::strcmp(mPlayMode, "sequential") ) { picker->CleanList(); PopulatePickerFromFile(picker,mPlayListPath,"",NULL); ShowPlaylistElements(picker,upcomingFile); mTempPicker->CleanList(); PopulatePickerFromFile(mTempPicker,mPlayListPath,"",NULL); } if ( mElementCount <= mUpcomingSongsListSize && 0 == ::strcmp(mPlayMode, "sequential_looped") ) { if (mTempPicker->GetNumMovies() == 0) { mTempPicker->CleanList(); PopulatePickerFromFile(mTempPicker,mPlayListPath,"",NULL); } while (mElementCount <= mUpcomingSongsListSize ) ShowPlaylistElements(mTempPicker,upcomingFile); } } fclose(upcomingFile); } } else { if ( picker->GetNumMovies() == 0 && !picker->mStopFlag && ::strcmp(mPlayMode, "sequential") ) { picker->CleanList(); PopulatePickerFromFile(picker,mPlayListPath,"",NULL); } } } void MP3Broadcaster::PrintPlaylistElement(PLDoubleLinkedListNode *node,void *file) { sBroadcaster->mElementCount ++; if (sBroadcaster->mElementCount <= sBroadcaster->mUpcomingSongsListSize) { char* thePick = node->fElement->mElementName; ::fprintf((FILE*)file,"%s\n", thePick); } } void MP3Broadcaster::ShowPlaylistElements(PlaylistPicker *picker,FILE *file) { if (mElementCount > mUpcomingSongsListSize) return; UInt32 x; for (x= 0;xGetNumBuckets();x++) { picker->GetBucket(x)->ForEach(PrintPlaylistElement,file); } } void MP3Broadcaster::PreFlightOrBroadcast( bool preflight, bool daemonize, bool showMovieList, bool currentMovie, bool checkMP3s, const char* errorlog) { PlaylistPicker* picker = NULL; PlaylistPicker* insertPicker = NULL; MP3FileBroadcaster fileBroadcaster(&mSocket, mBitRate, mFrequency); MP3MetaInfoUpdater* metaInfoUpdater = NULL; long moviePlayCount; char* thePick = NULL; long numMovieErrors; int err; mPreflight = preflight; if ( preflight ) ShowSetupParams(); if (preflight) picker = new PlaylistPicker(false); // make sequential picker, no looping else { picker = MakePickerFromConfig(); // make picker according to parms mTempPicker = new PlaylistPicker(false); insertPicker = new PlaylistPicker(false); insertPicker->mRemoveFlag = true; } // initial call uses empty string for path, NULL for loop detection list (void)PopulatePickerFromFile( picker, mPlayListPath, "", NULL ); if ( preflight ) { if ( picker->mNumToPickFrom == 1 ) ::printf( "\nThere is one movie in the Playlist.\n\n" ); else ::printf( "\nThere are (%li) movies in the Playlist.\n\n", (long) picker->mNumToPickFrom ); } if ( picker->mNumToPickFrom == 0 ) { printf( "- MP3Broadcaster setup failed: There are no movies to play.\n" ); mNumErrors++; goto bail; } // check that we have enough movies to cover the recent movies list. if ( preflight ) { if ( !strcmp( mPlayMode, "weighted_random" ) ) // this implies "random" play { if ( mRecentSongsListSize >= picker->mNumToPickFrom ) { mRecentSongsListSize = picker->mNumToPickFrom - 1; printf("- PlaylistBroadcaster Warning:\n The recent_movies_list_size setting is greater than \n"); printf(" or equal to the number of movies in the playlist.\n" ); mNumWarnings++; } } } // create the log file mLog = new MP3BroadcasterLog( mWorkingDirPath, mLogFile, mLogging ); // if ( !PreflightTrackerFileAccess( R_OK | W_OK ) ) // goto bail; if (!preflight) { err = ConnectToServer(); if (err) { if (err == EACCES) ::printf("- MP3Broadcaster: Disconnected from Server. Bad password or mount point\n Exiting.\n" ); else ::printf("- MP3Broadcaster: Couldn't connect to server\n Exiting.\n" ); mNumErrors++; goto bail; } } //Unless the Debug command line option is set, daemonize the process at this point if (daemonize) { #ifndef __Win32__ ::printf("- MP3Broadcaster: Started in background.\n"); // keep the same working directory.. if (::daemon( 1, 0 ) != 0) { ::printf("- MP3Broadcaster: System error (%i).\n", errno); mNumErrors++; goto bail; } #endif } if (daemonize && (errorlog != NULL)) { freopen(errorlog, "a", stdout); ::setvbuf(stdout, (char *)NULL, _IONBF, 0); } if (!preflight) { metaInfoUpdater = new MP3MetaInfoUpdater(mPassword, mMountPoint, mSocket.GetRemoteAddr(), mPort); metaInfoUpdater->Start(); fileBroadcaster.SetInfoUpdater(metaInfoUpdater); } // ^ 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 (( mLog ) && ( mLogging )) mLog->EnableLog( false ); // don't append ".log" to name for PLB if ( mLogging && !mLog->IsLogEnabled() ) { if ( mLog->LogDirName() && *mLog->LogDirName() ) ::printf("- MP3Broadcaster: The log file failed to open.\n ( path: %s/%s )\n Exiting.\n", mLog->LogDirName(), mLog->LogFileName() ); else ::printf("- MP3Broadcaster: The log file failed to open.\n ( path: %s )\n Exiting.\n", mLog->LogFileName() ); mNumErrors++; goto bail; } if (mPIDFile[0] != 0) { if(!FileCreateAndCheckAccess(mPIDFile)) { FILE *pidFile = fopen(mPIDFile, "w"); if(pidFile) { ::fprintf(pidFile,"%d\n",getpid()); fclose(pidFile); } } } // AddOurPIDToTracker( bcastSetupFilePath ); // <-- exits on failure if ( !preflight || showMovieList ) mLog->LogInfo( "MP3Broadcaster started." ); else mLog->LogInfo( "MP3Broadcaster preflight started." ); if(!preflight) { CreateCurrentAndUpcomingFiles(); SendXAudioCastHeaders(); } if (!preflight) { // check the frequency of the first song fileBroadcaster.PlaySong( picker->GetFirstFile(), NULL, true, true ); } moviePlayCount = 0; numMovieErrors = 0; while (true) { 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 printf( "[%li] %s picked\n", moviePlayCount, thePick ); } if ( !showMovieList ) { int playError; if(!preflight) { UpdateCurrentAndUpcomingFiles(thePick, picker, insertPicker); /* if playlist is about to run out repopulate it */ if ( !::strcmp(mPlayMode, "sequential_looped") ) { if(NULL == thePick &&!picker->mStopFlag) { if (picker->GetNumMovies() == 0) break; else continue; } } } if (thePick == NULL) break; ++moviePlayCount; if (currentMovie && !preflight) { mLog->LogMoviePlay( thePick, NULL, "PLAY" ); } if(!preflight) { playError = fileBroadcaster.PlaySong( thePick, mCurrentFile ); } else { playError = fileBroadcaster.PlaySong( thePick, NULL, preflight, !checkMP3s ); } if (playError == MP3FileBroadcaster::kConnectionError) { // do something mNumErrors++; goto bail; } if ( !preflight ) mLog->LogMoviePlay( thePick, MapErrorToString(playError), NULL ); else if (playError != 0) { ::printf("File %s : ", thePick); MapErrorToString(playError); numMovieErrors++; mNumWarnings++; } } delete [] thePick; thePick = NULL; } remove(mCurrentFile); remove(mUpcomingFile); if ( preflight ) { char str[256]; printf( " - " ); if (numMovieErrors == 1) strcpy(str, "MP3Broadcaster found one problem MP3 file."); else sprintf( str, "MP3Broadcaster found %li problem MP3 files." , numMovieErrors ); printf( "%s\n", str ); if (mLog) mLog->LogInfo( str ); if (numMovieErrors == moviePlayCount) { printf("There are no valid MP3s to play\n"); mNumErrors++; } } bail: delete picker; if (metaInfoUpdater) delete metaInfoUpdater; Cleanup(); #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 } PlaylistPicker* MP3Broadcaster::MakePickerFromConfig() { // construct a PlaylistPicker object using options set PlaylistPicker *picker = NULL; if ( !::strcmp( mPlayMode, "weighted_random" ) ) { picker = new PlaylistPicker( 10, mRecentSongsListSize ); } else if ( !::strcmp( mPlayMode, "sequential_looped" ) ) { picker = new PlaylistPicker(true); picker->mRemoveFlag = true; } else if ( !::strcmp( mPlayMode, "sequential" ) ) { picker = new PlaylistPicker(false); picker->mRemoveFlag = true; } return picker; } bool MP3Broadcaster::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; } int MP3Broadcaster::ConnectToServer() { UInt32 addr; addr = SocketUtils::ConvertStringToAddr(mIPAddr); if (addr == INADDR_NONE) { struct hostent* theHostent = ::gethostbyname(mIPAddr); if (theHostent != NULL) addr = ntohl(*(UInt32*)(theHostent->h_addr_list[0])); else ::printf("Couldn't resolve address %s\n", mIPAddr); } OS_Error err = mSocket.Open(); err = mSocket.Connect(addr, mPort); if (err == 0) { char buffer1[512]; char buffer2[512]; UInt32 len; StringTranslator::EncodeURL(mMountPoint, strlen(mMountPoint) + 1, buffer1, sizeof(buffer1)); if (strlen(buffer1) + strlen(mPassword) + 12 <= 512) { if (buffer1[0] == '/') ::sprintf(buffer2, "SOURCE %s %s\n\n", mPassword, buffer1); else ::sprintf(buffer2, "SOURCE %s /%s\n\n", mPassword, buffer1); mSocket.Send(buffer2, strlen(buffer2), &len); } else return -1; char buffer3[3]; len = 0; mSocket.Read(buffer3, 2, &len); buffer3[2] = '\0'; if (::strcmp(buffer3, "OK") != 0) err = EACCES; } return err; } int MP3Broadcaster::SendXAudioCastHeaders() { char buffer[1024]; char temp[256]; UInt32 len; sprintf(buffer, "x-audiocast-name:%s\n", mName); sprintf(temp, "x-audiocast-genre:%s\n", mGenre); strcat(buffer, temp); sprintf(temp, "x-audiocast-public:%s\n", "0"); strcat(buffer, temp); // sprintf(temp, "x-audiocast-description:%s\n", ""); // strcat(buffer, temp); mSocket.Send(buffer, strlen(buffer), &len); return 0; } void MP3Broadcaster::ShowSetupParams() { ::printf( "\n" ); ::printf( "Configuration Settings\n" ); ::printf( "----------------------------\n" ); ::printf( "Destination address %s:%d\n", mIPAddr, mPort ); ::printf( "MP3 bitrate %d\n", mBitRate ); ::printf( "play_mode %s\n", mPlayMode ); ::printf( "recent_movies_list_size %d\n", mRecentSongsListSize ); ::printf( "playlist_file %s\n", mPlayListPath ); ::printf( "working_dir %s\n", mWorkingDirPath ); ::printf( "logging %d\n", mLogging ); ::printf( "log_file %s\n", mLogFile ); ::printf( "max_upcoming_list_size %d\n", mUpcomingSongsListSize ); ::printf( "show_current %d\n", mShowCurrent ); ::printf( "show_upcoming %d\n", mShowUpcoming ); ::printf( "broadcast_name \"%s\"\n", mName); ::printf( "broadcast_genre \"%s\"\n", mGenre); ::printf( "broadcast_mount_point \"%s\"\n", mMountPoint); ::printf( "broadcast_password \"XXXXX\"\n"); ::printf( "\n" ); } void MP3Broadcaster::RemoveFiles() { if (mPIDFile[0] != 0) { remove(mPIDFile); } remove(mStopFile); remove(mReplaceFile); remove(mInsertFile); remove(mCurrentFile); remove(mUpcomingFile); } char* MP3Broadcaster::MapErrorToString(int error) { char* result = NULL; if (error == MP3FileBroadcaster::kBadFileFormat) result = "Bad file format."; else if (error == MP3FileBroadcaster::kWrongFrequency) result = "Encoded at wrong frequency."; else if (error == MP3FileBroadcaster::kWrongBitRate) result = "Doesn't use desired bit rate."; else if (error == MP3FileBroadcaster::kCouldntOpenFile) result = "Couldn't open file."; if (result != NULL) ::printf("%s\n", result); return result; } void MP3Broadcaster::Cleanup(bool signalHandler) { if (mCleanupDone) return; mCleanupDone = true; if (signalHandler) { mNumErrors++; printf("- MP3Broadcaster: Disconnected from Server while playing. Exiting.\n"); } if (mPreflight) { printf("Warnings: %ld\n", mNumWarnings); printf("Errors: %ld\n", mNumErrors); } else { printf("Broadcast Warnings: %ld\n", mNumWarnings); printf("Broadcast Errors: %ld\n", mNumErrors); } RemoveFiles(); if (mLog) { if ( mPreflight ) mLog->LogInfo( "MP3Broadcaster preflight finished." ); else mLog->LogInfo( "MP3Broadcaster finished." ); } // mLog = NULL; // protect the interrupt handler and just let it die don't delete because it is a task thread }