// rdcatchd.cpp // // The Rivendell Netcatcher Daemon // // (C) Copyright 2002-2016 Fred Gleason // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 as // published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public // License along with this program; if not, write to the Free Software // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. // #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include RDConfig *catch_config; // Logging function that works within and outside the MainObject. //static RDConfig *rd_config = NULL; void LogLine(RDConfig::LogPriority prio,const QString &line) { FILE *logfile; catch_config->log("rdcatchd",prio,line); if(catch_config->catchdLogname().isEmpty()) { return; } QDateTime current=QDateTime::currentDateTime(); logfile=fopen(catch_config->catchdLogname(),"a"); if(logfile==NULL) { return; } chmod(catch_config->catchdLogname(),S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH); fprintf(logfile,"%02d/%02d/%4d - %02d:%02d:%02d.%03d : %s\n", current.date().month(), current.date().day(), current.date().year(), current.time().hour(), current.time().minute(), current.time().second(), current.time().msec(), (const char *)line); fclose(logfile); } void SigHandler(int signum) { pid_t local_pid; switch(signum) { case SIGINT: case SIGTERM: RDDeletePid(RD_PID_DIR,"rdcatchd.pid"); LogLine(RDConfig::LogNotice,"rdcatchd exiting"); delete catch_config; exit(0); break; case SIGCHLD: local_pid=waitpid(-1,NULL,WNOHANG); while(local_pid>0) { local_pid=waitpid(-1,NULL,WNOHANG); } signal(SIGCHLD,SigHandler); return; } } MainObject::MainObject(QObject *parent) :QObject(parent) { QString sql; RDSqlQuery *q; bool skip_db_check=false; unsigned schema=0; // // Load the config // catch_config=new RDConfig(); catch_config->load(); // // Read Command Options // RDCmdSwitch *cmd= new RDCmdSwitch(qApp->argc(),qApp->argv(),"rdcatchd",RDCATCHD_USAGE); for(unsigned i=0;ikeys();i++) { if(cmd->key(i)=="--event-id") { RunBatch(cmd); return; } if(cmd->key(i)=="--skip-db-check") { skip_db_check=true; } } // // Make sure we're the only instance running // if(RDCheckDaemon(RD_RDCATCHD_PID)) { printf("rdcatchd: aborting - multiple instances not allowed"); exit(1); } // // Initialize Data Structures // debug=false; for(unsigned i=0;istart(RDCATCHD_FREE_EVENTS_INTERVAL); server=new RDCatchdSocket(RDCATCHD_TCP_PORT,0,this); if(!server->ok()) { printf("rdcatchd: aborting - couldn't bind socket"); exit(1); } connect(server,SIGNAL(connection(int)),this,SLOT(newConnection(int))); // // Open Database // QString err (tr("ERROR rdcatchd aborting - ")); catch_db=RDInitDb(&schema,&err); if(!catch_db) { printf(err.ascii()); exit(1); } if((schema!=RD_VERSION_DATABASE)&&(!skip_db_check)) { fprintf(stderr, "rdcatchd: database version mismatch, should be %u, is %u\n", RD_VERSION_DATABASE,schema); exit(256); } connect (RDDbStatus(),SIGNAL(logText(RDConfig::LogPriority,const QString &)), this,SLOT(log(RDConfig::LogPriority,const QString &))); // // Create RDCatchConf // catch_conf=new RDCatchConf(catch_config->stationName()); // // GPI Mappers // catch_gpi_start_mapper=new QSignalMapper(this); connect(catch_gpi_start_mapper,SIGNAL(mapped(int)), this,SLOT(startTimerData(int))); catch_gpi_offset_mapper=new QSignalMapper(this); connect(catch_gpi_offset_mapper,SIGNAL(mapped(int)), this,SLOT(offsetTimerData(int))); // // Xload Timer // catch_xload_timer=new QTimer(this); connect(catch_xload_timer,SIGNAL(timeout()),this,SLOT(updateXloadsData())); // // RIPCD Connection // catch_ripc=new RDRipc(catch_config->stationName()); catch_ripc->connectHost("localhost",RIPCD_TCP_PORT,catch_config->password()); connect(catch_ripc,SIGNAL(rmlReceived(RDMacro *)), this,SLOT(rmlReceivedData(RDMacro *))); connect(catch_ripc,SIGNAL(gpiStateChanged(int,int,bool)), this,SLOT(gpiStateChangedData(int,int,bool))); // // System Configuration // catch_system=new RDSystem(); // // Station Configuration // catch_rdstation=new RDStation(catch_config->stationName()); // // CAE Connection // catch_cae=new RDCae(catch_rdstation,catch_config,this); connect(catch_cae,SIGNAL(isConnected(bool)), this,SLOT(isConnectedData(bool))); connect(catch_cae,SIGNAL(recordLoaded(int,int)), this,SLOT(recordLoadedData(int,int))); connect(catch_cae,SIGNAL(recording(int,int)), this,SLOT(recordingData(int,int))); connect(catch_cae,SIGNAL(recordStopped(int,int)), this,SLOT(recordStoppedData(int,int))); connect(catch_cae,SIGNAL(recordUnloaded(int,int,unsigned)), this,SLOT(recordUnloadedData(int,int,unsigned))); connect(catch_cae,SIGNAL(playLoaded(int)), this,SLOT(playLoadedData(int))); connect(catch_cae,SIGNAL(playing(int)), this,SLOT(playingData(int))); connect(catch_cae,SIGNAL(playStopped(int)), this,SLOT(playStoppedData(int))); connect(catch_cae,SIGNAL(playUnloaded(int)), this,SLOT(playUnloadedData(int))); catch_cae->connectHost(); // // Sound Initialization // RDSetMixerPorts(catch_config->stationName(),catch_cae); sql=QString().sprintf("select CHANNEL,CARD_NUMBER,PORT_NUMBER from DECKS \ where (STATION_NAME=\"%s\")&&\ (CARD_NUMBER!=-1)&&(CHANNEL>0)&&(CHANNEL<9)", (const char *)catch_config->stationName()); q=new RDSqlQuery(sql); while(q->next()) { if((q->value(1).toInt()>=0)&&(q->value(2).toInt()>=0)) { catch_record_deck_status[q->value(0).toUInt()-1]=RDDeck::Idle; } } delete q; LoadDeckList(); // // Initialize Monitor Passthroughs // sql=QString().sprintf("select CARD_NUMBER,PORT_NUMBER,\ MON_PORT_NUMBER,CHANNEL from DECKS\ where (STATION_NAME=\"%s\")&&(CHANNEL<=%d)&&\ (CARD_NUMBER>=0)&&(MON_PORT_NUMBER>=0)&&\ (DEFAULT_MONITOR_ON=\"Y\")", (const char *)catch_config->stationName(), MAX_DECKS); q=new RDSqlQuery(sql); while(q->next()) { catch_cae->setPassthroughVolume(q->value(0).toInt(),q->value(1).toInt(), q->value(2).toInt(),0); catch_monitor_state[q->value(3).toUInt()-1]=true; } delete q; StartDropboxes(); // // Playout Event Players // for(unsigned i=0;isetTimeOffset(catch_rdstation->timeOffset()); connect(catch_engine,SIGNAL(timeout(int)),this,SLOT(engineData(int))); LoadEngine(); if(qApp->argc()==1) { RDDetach(catch_config->logCoreDumpDirectory()); } else { debug=true; } ::signal(SIGINT,SigHandler); ::signal(SIGTERM,SigHandler); ::signal(SIGCHLD,SigHandler); if(!RDWritePid(RD_PID_DIR,"rdcatchd.pid")) { printf("rdcatchd: aborting - can't write pid file\n"); exit(1); } // // Start Heartbeat Timer // timer=new QTimer(this); connect(timer,SIGNAL(timeout()),this,SLOT(heartbeatData())); timer->start(RDCATCHD_HEARTBEAT_INTERVAL); // // Meter Timer // timer=new QTimer(this); connect(timer,SIGNAL(timeout()),this,SLOT(meterData())); timer->start(RD_METER_UPDATE_INTERVAL); // // Heartbeat Timer // catch_heartbeat_timer=new QTimer(this); connect(catch_heartbeat_timer,SIGNAL(timeout()), this,SLOT(sysHeartbeatData())); LoadHeartbeat(); // // Mark Interrupted Events // sql=QString().sprintf("update RECORDINGS set EXIT_CODE=%d\ where ((EXIT_CODE=%d)||(EXIT_CODE=%d))||\ (EXIT_CODE=%d)&&(STATION_NAME=\"%s\")", RDRecording::Interrupted, RDRecording::Uploading, RDRecording::Downloading, RDRecording::RecordActive, (const char *)catch_config->stationName()); q=new RDSqlQuery(sql); delete q; sql=QString().sprintf("update RECORDINGS set EXIT_CODE=%d\ where ((EXIT_CODE=%d)||(EXIT_CODE=%d))&&\ (STATION_NAME=\"%s\")", RDRecording::Ok, RDRecording::Waiting, RDRecording::PlayActive, (const char *)catch_config->stationName()); q=new RDSqlQuery(sql); delete q; // // Schedule Startup Cart // timer=new QTimer(this,"startup_cart_timer"); connect(timer,SIGNAL(timeout()),this,SLOT(startupCartData())); timer->start(10000,true); // // Set Realtime Permissions // if(catch_config->useRealtime()) { struct sched_param sp; memset(&sp,0,sizeof(sp)); if(catch_config->realtimePriority()>0) { sp.sched_priority=catch_config->realtimePriority()-1; } if(sched_setscheduler(getpid(),SCHED_FIFO,&sp)!=0) { LogLine(RDConfig::LogWarning, QString().sprintf("unable to set realtime permissions, %s", strerror(errno))); } mlockall(MCL_CURRENT|MCL_FUTURE); } LogLine(RDConfig::LogNotice,"rdcatchd started"); } void MainObject::log(RDConfig::LogPriority prio,const QString &msg) { LogLine(prio,msg); } void MainObject::newConnection(int fd) { unsigned i=0; while((isetSocket(fd); connect(socket[i],SIGNAL(readyReadID(int)),this,SLOT(socketData(int))); connect(socket[i],SIGNAL(connectionClosedID(int)), this,SLOT(socketKill(int))); LogLine(RDConfig::LogDebug,"rdcatchd new connection open"); } void MainObject::rmlReceivedData(RDMacro *rml) { if(rml->role()!=RDMacro::Cmd) { return; } RunLocalMacros(rml); } void MainObject::gpiStateChangedData(int matrix,int line,bool state) { // LogLine(QString().sprintf("gpiStateChangedData(%d,%d,%d)", // matrix,line,state)); if(!state) { return; } std::vector handled_events; QTime current_time=QTime::currentTime(); // // Start Events // for(unsigned i=0;iisActive())) { if(catch_events[i].startOffset()>0) { catch_events[i].gpiOffsetTimer()-> start(catch_events[i].startOffset(),true); catch_events[i].gpiStartTimer()->stop(); BroadcastCommand(QString().sprintf("RE %d %d %d!", catch_events[i].channel(), RDDeck::Ready, catch_events[i].id())); } else { if(StartRecording(i)) { catch_events[i].gpiStartTimer()->stop(); } } handled_events.push_back(i); } } } // // End Events // for(unsigned i=0;i current_time)) { bool handled=false; for(unsigned j=0;j stopRecord(catch_record_card[catch_events[i].channel()-1], catch_record_stream[catch_events[i].channel()-1]); } } } } } void MainObject::startTimerData(int id) { int event=GetEvent(id); unsigned deck=catch_events[event].channel()-1; // bool waiting=false; catch_events[event].setStatus(RDDeck::Idle); for(unsigned i=0;istop(); } } void MainObject::engineData(int id) { // LogLine(QString().sprintf("engineData(%d)",id)); QString sql; RDSqlQuery *q; RDStation *rdstation; int event=GetEvent(id); // // Generate Effective Date // QDate date=QDate::currentDate(); QTime current_time=QTime::currentTime(); if((current_time.msecsTo(QTime(23,59,59))+1000)timeOffset()) { date=date.addDays(1); } // // Ignore inactive or non-existent events // if(event<0) { LogLine(RDConfig::LogDebug,QString(). sprintf("cannot find event %d, ignoring!",id)); return; } if(!catch_events[event].isActive()) { LogLine(RDConfig::LogDebug,QString(). sprintf("event %d is marked inactive, ignoring",id)); return; } if(!catch_events[event].dayOfWeek(date.dayOfWeek())) { LogLine(RDConfig::LogDebug,QString(). sprintf("event %d is not valid for this DOW, ignoring", id)); return; } // // Check for Playout Deck Availability // if(catch_events[event].type()==RDRecording::Playout) { if(catch_playout_deck_status[catch_events[event].channel()-129]!= RDDeck::Idle) { LogLine(RDConfig::LogWarning,QString(). sprintf("playout deck P%d is busy for event %d, skipping", catch_events[event].channel()-128, catch_events[event].id())); WriteExitCode(event,RDRecording::DeviceBusy); BroadcastCommand(QString().sprintf("RE 0 %d %d %s!",RDDeck::Recording, catch_events[event].id(), (const char *)catch_events[event].cutName())); return; } } // // Load Deck Parameters // switch(catch_events[event].type()) { case RDRecording::Recording: if(!RDCut::exists(catch_events[event].cutName())) { WriteExitCode(event,RDRecording::NoCut); BroadcastCommand(QString(). sprintf("RE %d %d %d!", catch_events[event].channel(), catch_record_deck_status[catch_events[event]. channel()-1], catch_events[event].id())); LogLine(RDConfig::LogWarning,QString(). sprintf("record aborted: no such cut: %s, id: %d", (const char *)catch_events[event].cutName(), catch_events[event].id())); return; } catch_record_card[catch_events[event].channel()-1]=-1; catch_record_stream[catch_events[event].channel()-1]=-1; sql=QString().sprintf("select CARD_NUMBER,PORT_NUMBER,\ SWITCH_STATION,SWITCH_MATRIX,SWITCH_OUTPUT,\ SWITCH_DELAY from DECKS \ where (STATION_NAME=\"%s\")&&(CHANNEL=%d)", (const char *)catch_config->stationName(), catch_events[event].channel()); q=new RDSqlQuery(sql); if(q->first()) { catch_record_card[catch_events[event].channel()-1]= q->value(0).toInt(); catch_record_stream[catch_events[event].channel()-1]= q->value(1).toInt(); if(q->value(2).toString()==QString("[none]")) { catch_swaddress[catch_events[event].channel()-1]=QHostAddress(); } else { rdstation=new RDStation(q->value(2).toString()); catch_swaddress[catch_events[event].channel()-1]= rdstation->address(); delete rdstation; } catch_swmatrix[catch_events[event].channel()-1]=q->value(3).toInt(); catch_swoutput[catch_events[event].channel()-1]=q->value(4).toInt(); catch_swdelay[catch_events[event].channel()-1]=q->value(5).toInt(); } else { LogLine(RDConfig::LogNotice,QString(). sprintf("id %d specified non-existent record deck, ignored", catch_events[event].id())); delete q; return; } delete q; sql=QString("delete from CUT_EVENTS where ")+ "CUT_NAME=\""+catch_events[event].cutName()+"\""; q=new RDSqlQuery(sql); delete q; switch(catch_events[event].startType()) { case RDRecording::HardStart: StartRecording(event); break; case RDRecording::GpiStart: catch_events[event].gpiStartTimer()-> start(catch_events[event].startLength()- (QTime().msecsTo(current_time)- QTime().msecsTo(catch_events[event].startTime())),true); catch_record_deck_status[catch_events[event].channel()-1]= RDDeck::Waiting; catch_record_id[catch_events[event].channel()-1]= catch_events[event].id(); catch_events[event].setStatus(RDDeck::Waiting); WriteExitCode(event,RDRecording::Waiting); BroadcastCommand(QString().sprintf("RE %d %d %d!", catch_events[event].channel(), catch_record_deck_status[catch_events[event]. channel()-1], catch_events[event].id())); LogLine(RDConfig::LogNotice,QString(). sprintf("gpi start window opens: event: %d, gpi: %d:%d", id,catch_events[event].startMatrix(), catch_events[event].startLine())); break; } break; case RDRecording::Playout: if(!RDCut::exists(catch_events[event].cutName())) { WriteExitCode(event,RDRecording::NoCut); BroadcastCommand(QString(). sprintf("RE %d %d %d!", catch_events[event].channel(), catch_playout_deck_status[catch_events[event]. channel()-129], catch_events[event].id())); LogLine(RDConfig::LogNotice,QString(). sprintf("playout aborted: no such cut: %s, id: %d", (const char *)catch_events[event].cutName(), catch_events[event].id())); return; } catch_playout_card[catch_events[event].channel()-129]=-1; catch_playout_stream[catch_events[event].channel()-129]=-1; sql=QString().sprintf("select CARD_NUMBER,PORT_NUMBER,PORT_NUMBER \ from DECKS where (STATION_NAME=\"%s\")&&(CHANNEL=%d)", (const char *)catch_config->stationName(), catch_events[event].channel()); q=new RDSqlQuery(sql); if(q->first()) { catch_playout_id[catch_events[event].channel()-129]=id; catch_playout_card[catch_events[event].channel()-129]= q->value(0).toInt(); catch_playout_stream[catch_events[event].channel()-129]= q->value(1).toInt(); catch_playout_port[catch_events[event].channel()-129]= q->value(2).toInt(); } else { LogLine(RDConfig::LogDebug,QString(). sprintf("id %d specified non-existent play deck, ignored", catch_events[event].id())); delete q; return; } delete q; StartPlayout(event); break; case RDRecording::MacroEvent: if(!RDCart::exists(catch_events[event].macroCart())) { WriteExitCode(event,RDRecording::NoCut); BroadcastCommand(QString(). sprintf("RE 0 0 %d!",catch_events[event].id())); LogLine(RDConfig::LogDebug, QString().sprintf("macro aborted: no such cart: %u, id: %d", catch_events[event].macroCart(), catch_events[event].id())); return; } StartMacroEvent(event); break; case RDRecording::SwitchEvent: StartSwitchEvent(event); break; case RDRecording::Download: if(!RDCut::exists(catch_events[event].cutName())) { WriteExitCode(event,RDRecording::NoCut); BroadcastCommand(QString(). sprintf("RE 0 0 %d!",catch_events[event].id())); LogLine(RDConfig::LogDebug,QString(). sprintf("download aborted: no such cut: %s, id: %d", (const char *)catch_events[event].cutName(), catch_events[event].id())); return; } // // Load Import Parameters // sql=QString().sprintf("select DEFAULT_FORMAT,DEFAULT_CHANNELS,\ DEFAULT_SAMPRATE,DEFAULT_LAYER,DEFAULT_BITRATE,\ RIPPER_LEVEL\ from RDLIBRARY where STATION=\"%s\"", (const char *)catch_config->stationName()); q=new RDSqlQuery(sql); if(q->first()) { catch_default_format=q->value(0).toInt(); catch_default_channels=q->value(1).toInt(); catch_default_samplerate=q->value(2).toInt(); catch_default_layer=q->value(3).toInt(); catch_default_bitrate=q->value(4).toInt(); catch_ripper_level=q->value(5).toInt(); } else { LogLine(RDConfig::LogWarning, "unable to load import audio configuration"); delete q; return; } delete q; catch_events[event]. setResolvedUrl(RDDateTimeDecode(catch_events[event].url(), QDateTime(date.addDays(catch_events[event].eventdateOffset()), current_time))); StartDownloadEvent(event); break; case RDRecording::Upload: if(!RDCut::exists(catch_events[event].cutName())) { WriteExitCode(event,RDRecording::NoCut); BroadcastCommand(QString(). sprintf("RE 0 0 %d!",catch_events[event].id())); LogLine(RDConfig::LogNotice,QString(). sprintf("upload aborted: no such cut: %s, id: %d", (const char *)catch_events[event].cutName(), catch_events[event].id())); return; } StartUploadEvent(event); break; } } void MainObject::socketData(int ch) { ParseCommand(ch); } void MainObject::socketKill(int ch) { KillSocket(ch); } void MainObject::isConnectedData(bool state) { if(!state) { LogLine(RDConfig::LogErr, "aborting - unable to connect to Core AudioEngine"); exit(1); } } void MainObject::recordLoadedData(int card,int stream) { int deck=GetRecordDeck(card,stream); catch_record_deck_status[deck-1]=RDDeck::Ready; BroadcastCommand(QString().sprintf("RE %d %d %d!", deck,catch_record_deck_status[deck-1], catch_record_id[deck-1])); LogLine(RDConfig::LogDebug,QString(). sprintf("Loaded - Card: %d Stream: %d\n",card,stream)); } void MainObject::recordingData(int card,int stream) { int deck=GetRecordDeck(card,stream); catch_record_deck_status[deck-1]=RDDeck::Recording; WriteExitCodeById(catch_record_id[deck-1],RDRecording::RecordActive); QString cutname; int event=GetEvent(catch_record_id[deck-1]); if(event>=0) { cutname=catch_events[event].cutName(); } BroadcastCommand(QString().sprintf("RE %d %d %d %s!", deck,catch_record_deck_status[deck-1], catch_record_id[deck-1], (const char *)cutname)); catch_record_status[deck-1]=true; if(debug) { printf("Recording - Card: %d Stream: %d, Id: %d\n",card,stream, catch_record_id[deck-1]); } } void MainObject::recordStoppedData(int card,int stream) { int deck=GetRecordDeck(card,stream); short levels[2]={-10000,-10000}; catch_record_status[deck-1]=false; if(debug) { printf("Stopped - Card: %d Stream: %d\n",card,stream); } SendMeterLevel(deck-1,levels); catch_cae->unloadRecord(card,stream); } void MainObject::recordUnloadedData(int card,int stream,unsigned msecs) { int deck=GetRecordDeck(card,stream); if(deck<1) { LogLine(RDConfig::LogDebug,QString(). sprintf("invalid record deck: Card: %d Stream: %d", card,stream)); return; } int event=GetEvent(catch_record_id[deck-1]); if(event<0) { catch_record_deck_status[deck-1]=RDDeck::Idle; catch_record_aborting[deck-1]=false; LogLine(RDConfig::LogDebug,QString(). sprintf("invalid record event: Id: %d", catch_record_id[deck-1])); RunRmlRecordingCache(deck); return; } if(catch_events[event].normalizeLevel()==0) { CheckInRecording(catch_record_name[deck-1],&catch_events[event],msecs, catch_record_threshold[deck-1]); } else { StartBatch(catch_events[event].id()); } if(catch_record_aborting[deck-1]) { LogLine(RDConfig::LogNotice,QString(). sprintf("record aborted: cut %s", (const char *)catch_record_name[deck-1])); WriteExitCodeById(catch_record_id[deck-1],RDRecording::Interrupted); } else { LogLine(RDConfig::LogInfo,QString(). sprintf("record complete: cut %s", (const char *)catch_record_name[deck-1])); WriteExitCodeById(catch_record_id[deck-1],RDRecording::Ok); } BroadcastCommand(QString().sprintf("RE %d %d %d!", deck,RDDeck::Idle, catch_record_id[deck-1])); catch_record_id[deck-1]=0; if(debug) { printf("Unloaded - Card: %d Stream: %d\n",card,stream); } if(catch_events[event].oneShot()) { PurgeEvent(event); } catch_record_deck_status[deck-1]=RDDeck::Idle; catch_events[event].setStatus(RDDeck::Idle); catch_record_aborting[deck-1]=false; // // Restart the event (if needed) // if((catch_events[event].type()==RDRecording::Recording)&& (catch_events[event].startType()==RDRecording::GpiStart)&& ((catch_events[event].endType()==RDRecording::GpiEnd)|| (catch_events[event].endType()==RDRecording::LengthEnd))&& catch_events[event].allowMultipleRecordings()) { engineData(catch_events[event].id()); } else { RunRmlRecordingCache(deck); } } void MainObject::playLoadedData(int handle) { int deck=GetPlayoutDeck(handle); catch_playout_deck_status[deck-129]=RDDeck::Ready; BroadcastCommand(QString().sprintf("RE %d %d %d!", deck,catch_playout_deck_status[deck-129], catch_playout_id[deck-129])); if(debug) { printf("Play Loaded - Card: %d Stream: %d\n", catch_playout_card[deck-129], catch_playout_stream[deck-129]); } } void MainObject::playingData(int handle) { int deck=GetPlayoutDeck(handle); catch_playout_deck_status[deck-129]=RDDeck::Recording; WriteExitCodeById(catch_playout_id[deck-129], RDRecording::PlayActive); BroadcastCommand(QString().sprintf("RE %d %d %d!", deck,catch_playout_deck_status[deck-129], catch_playout_id[deck-129])); catch_playout_status[GetPlayoutDeck(handle)]=true; if(debug) { printf("Playing - Card: %d Stream: %d\n", catch_playout_card[deck-129], catch_playout_stream[deck-129]); } } void MainObject::playStoppedData(int handle) { int deck=GetPlayoutDeck(handle); short levels[2]={-10000,-10000}; catch_playout_status[deck-129]=false; catch_playout_event_player[deck-129]->stop(); LogLine(RDConfig::LogNotice,QString(). sprintf("playout stopped: cut %s", (const char *)catch_playout_name[deck-129])); if(debug) { printf("Playout stopped - Card: %d Stream: %d\n", catch_playout_card[deck-129], catch_playout_stream[deck-129]); } SendMeterLevel(deck,levels); catch_cae->unloadPlay(handle); } void MainObject::playUnloadedData(int handle) { int deck=GetPlayoutDeck(handle); LogLine(RDConfig::LogInfo,QString(). sprintf("play complete: cut %s", (const char *)catch_playout_name[deck-129])); catch_playout_deck_status[deck-129]=RDDeck::Idle; WriteExitCodeById(catch_playout_id[deck-129],RDRecording::Ok); BroadcastCommand(QString().sprintf("RE %d %d %d!",deck, catch_playout_deck_status[deck-129], catch_playout_id[deck-129])); if(debug) { printf("Play unloaded - Card: %d Stream: %d\n", catch_playout_card[deck-129],catch_playout_stream[deck-129]); } if(catch_events[catch_playout_event_id[deck-129]].oneShot()) { PurgeEvent(catch_playout_event_id[deck-129]); } catch_events[catch_playout_event_id[deck-129]].setStatus(RDDeck::Idle); catch_playout_event_id[deck-129]=-1; catch_playout_id[deck-129]=0; } void MainObject::runCartData(int chan,int number,unsigned cartnum) { RDCart *cart=new RDCart(cartnum); if(cart->exists()&&(cart->type()==RDCart::Macro)) { ExecuteMacroCart(cart); } delete cart; SendDeckEvent(chan,number); } void MainObject::meterData() { short levels[2]; for(int i=0;iinputMeterUpdate(catch_record_card[i],catch_record_stream[i], levels); SendMeterLevel(i+1,levels); } if(catch_playout_deck_status[i]==RDDeck::Recording) { catch_cae-> outputMeterUpdate(catch_playout_card[i],catch_playout_port[i], levels); SendMeterLevel(i+129,levels); } } } void MainObject::eventFinishedData(int id) { LogLine(RDConfig::LogDebug,QString().sprintf("eventFinishedData(%d)",id)); if(catch_macro_event_id[id]>=0) { LogLine(RDConfig::LogDebug,QString(). sprintf("Clearing event_id: %d",catch_macro_event_id[id])); if(catch_macro_event_id[id]exists()) { ExecuteMacroCart(cart); } delete cart; } void MainObject::updateXloadsData() { std::vector::iterator it; for(unsigned i=0;istop(); } } void MainObject::startupCartData() { unsigned cartnum=catch_rdstation->startupCart(); if(cartnum>0) { RDCart *cart=new RDCart(cartnum); if(cart->exists()) { ExecuteMacroCart(cart); LogLine(RDConfig::LogInfo,QString(). sprintf("ran startup cart %06u",cartnum)); } else { LogLine(RDConfig::LogNotice,QString(). sprintf("startup cart %06u was invalid",cartnum)); } delete cart; } } bool MainObject::StartRecording(int event) { QString str; int length=0; QTime current_time=QTime::currentTime(); if((event<0)||(event>=(int)catch_events.size())) { LogLine(RDConfig::LogDebug,"invalid event offset received, ignored"); return false; } // // Ensure the Deck is Valid // unsigned deck=catch_events[event].channel(); if((catch_record_card[deck-1]<0)|| (catch_record_stream[deck-1]<0)) { WriteExitCodeById(catch_events[event].id(),RDRecording::InternalError); BroadcastCommand(QString().sprintf("RE %d %d %d!", deck,catch_record_deck_status[deck-1], catch_events[event].id())); LogLine(RDConfig::LogWarning,QString(). sprintf("invalid audio device for deck: %d, event: %d", deck,catch_events[event].id())); return false; } // // Ensure the Deck is Available // if((catch_record_deck_status[deck-1]!=RDDeck::Idle)&& (catch_record_deck_status[deck-1]!=RDDeck::Waiting)) { WriteExitCodeById(catch_events[event].id(),RDRecording::DeviceBusy); BroadcastCommand(QString().sprintf("RE %d %d %d!", deck,catch_record_deck_status[deck-1], catch_events[event].id())); LogLine(RDConfig::LogNotice,QString(). sprintf("device busy for deck: %d, event: %d | in use by event: %d", deck,catch_events[event].id(),catch_record_id[deck-1])); return false; } // // Get Record Length // switch(catch_events[event].endType()) { case RDRecording::LengthEnd: length=catch_events[event].length(); break; case RDRecording::HardEnd: length=current_time.msecsTo(catch_events[event].endTime()); break; case RDRecording::GpiEnd: length=current_time.msecsTo(catch_events[event].endTime(). addMSecs(catch_events[event].endLength())); if(catch_events[event].maxGpiRecordLength()setRole(RDMacro::Cmd); rml->setAddress(catch_swaddress[deck-1]); rml->setEchoRequested(false); rml->setArgQuantity(3); rml->setCommand(RDMacro::ST); rml->setArg(0,catch_swmatrix[deck-1]); rml->setArg(1,catch_events[event].switchInput()); rml->setArg(2,catch_swoutput[deck-1]); char str[RD_RML_MAX_LENGTH]; if(rml->generateString(str,RD_RML_MAX_LENGTH)) { catch_ripc->sendRml(rml); LogLine(RDConfig::LogDebug,QString(). sprintf("sending switcher command: %s",str)); } else { LogLine(RDConfig::LogNotice,"switcher command is malformed!"); } delete rml; } // // Set Temp Name // RDCae::AudioCoding format=catch_events[event].format(); QString cut_name; if(catch_events[event].normalizeLevel()==0) { cut_name=catch_events[event].cutName(); } else { cut_name=QString().sprintf("rdcatchd-record-%d",catch_events[event].id()); catch_events[event]. setTempName(GetTempRecordingName(catch_events[event].id())); catch_events[event].setDeleteTempFile(true); format=RDCae::Pcm24; } // // Start the recording // catch_cae->loadRecord(catch_record_card[deck-1], catch_record_stream[deck-1], cut_name, format, catch_events[event].channels(), catch_events[event].sampleRate(), catch_events[event].bitrate()); catch_cae->record(catch_record_card[deck-1],catch_record_stream[deck-1], length,0); catch_events[event].setStatus(RDDeck::Recording); str=QString().sprintf("record started: deck: %d, event: %d", deck,catch_events[event].id()); str+=QString().sprintf(" card: %d, stream: %d, cut: %s length: %d", catch_record_card[deck-1], catch_record_stream[deck-1], (const char *)cut_name,length); LogLine(RDConfig::LogInfo,str); // // Cache Selected Fields // catch_record_name[deck-1]=catch_events[event].cutName(); catch_record_threshold[deck-1]=catch_events[event].trimThreshold(); catch_record_id[deck-1]=catch_events[event].id(); // // Update Cut Record // RDCut *cut=new RDCut(catch_events[event].cutName()); cut->setOriginDatetime(QDateTime::currentDateTime()); cut->setOriginName(catch_config->stationName()); switch(catch_events[event].format()) { case RDCae::Pcm16: cut->setCodingFormat(0); break; case RDCae::MpegL2: cut->setCodingFormat(1); break; default: cut->setCodingFormat(0); break; } cut->setChannels(catch_events[event].channels()); cut->setSampleRate(catch_events[event].sampleRate()); cut->setBitRate(catch_events[event].bitrate()); cut->setPlayCounter(0); cut->setSegueStartPoint(-1); cut->setSegueEndPoint(-1); cut->setTalkStartPoint(-1); cut->setTalkEndPoint(-1); cut->setHookStartPoint(-1); cut->setHookEndPoint(-1); cut->setFadeupPoint(-1); cut->setFadedownPoint(-1); bool valid; QDateTime datetime=cut->startDatetime(&valid); if(valid) { datetime.setDate(QDate::currentDate(). addDays(catch_events[event].startdateOffset())); cut->setStartDatetime(datetime,true); datetime=cut->endDatetime(&valid); datetime.setDate(QDate::currentDate(). addDays(catch_events[event].enddateOffset())); cut->setEndDatetime(datetime,true); } delete cut; return true; } void MainObject::StartPlayout(int event) { unsigned deck=catch_events[event].channel(); if((catch_playout_card[deck-129]<0)) { LogLine(RDConfig::LogWarning,QString(). sprintf("invalid audio device for deck: %d, event: %d", deck,catch_events[event].id())); return; } // // Get cut parameters // QString sql=QString().sprintf("select START_POINT,END_POINT from CUTS\ where CUT_NAME=\"%s\"", (const char *)catch_events[event].cutName()); RDSqlQuery *q=new RDSqlQuery(sql); if(!q->first()) { return; } int start=q->value(0).toInt(); int end=q->value(1).toInt(); delete q; // // Start the playout // catch_playout_event_player[deck-129]->load(catch_events[event].cutName()); catch_cae->loadPlay(catch_playout_card[deck-129], catch_events[event].cutName(), &catch_playout_stream[deck-129], &catch_playout_handle[deck-129]); RDSetMixerOutputPort(catch_cae,catch_playout_card[deck-129], catch_playout_stream[deck-129], catch_playout_port[deck-129]); catch_cae->positionPlay(catch_playout_handle[deck-129],start); catch_playout_event_player[deck-129]->start(start); catch_cae-> play(catch_playout_handle[deck-129],end-start,RD_TIMESCALE_DIVISOR,0); catch_cae->setPlayPortActive(catch_playout_card[deck-129], catch_playout_port[deck-129], catch_playout_stream[deck-129]); catch_events[event].setStatus(RDDeck::Recording); LogLine(RDConfig::LogDebug,QString(). sprintf("playout started: deck: %d, event %d", deck,catch_events[event].id())); LogLine(RDConfig::LogDebug,QString(). sprintf(" card %d, stream %d , cut=%s", catch_playout_card[deck-129], catch_playout_stream[deck-129], (const char *)catch_events[event].cutName())); // // Cache Selected Fields // catch_playout_name[deck-129]=catch_events[event].cutName(); catch_playout_event_id[deck-129]=event; } void MainObject::StartMacroEvent(int event) { RDCart *cart=new RDCart(catch_events[event].macroCart()); if(!cart->exists()) { LogLine(RDConfig::LogWarning,QString(). sprintf("cart %u does not exist!", catch_events[event].macroCart())); delete cart; return; } if(cart->type()!=RDCart::Macro) { LogLine(RDConfig::LogWarning,QString(). sprintf("%u is not a macro cart!", catch_events[event].macroCart())); delete cart; return; } if(ExecuteMacroCart(cart,catch_events[event].id(),event)) { LogLine(RDConfig::LogInfo,QString(). sprintf("executing macro cart: %u",catch_events[event].macroCart())); } delete cart; } void MainObject::StartSwitchEvent(int event) { char cmd[RD_RML_MAX_LENGTH]; RDMacro *rml=new RDMacro(); rml->setAddress(catch_rdstation->address()); rml->setRole(RDMacro::Cmd); rml->setEchoRequested(false); rml->setCommand(RDMacro::ST); rml->setArgQuantity(3); rml->setArg(0,catch_events[event].channel()); rml->setArg(1,catch_events[event].switchInput()); rml->setArg(2,catch_events[event].switchOutput()); rml->generateString(cmd,RD_RML_MAX_LENGTH); LogLine(RDConfig::LogInfo,QString(). sprintf("sent switch event, rml: %s",cmd)); catch_ripc->sendRml(rml); delete rml; if(catch_events[event].oneShot()) { PurgeEvent(event); } } void MainObject::StartDownloadEvent(int event) { WriteExitCode(event,RDRecording::Downloading); catch_active_xloads.push_back(event); if(!catch_xload_timer->isActive()) { catch_xload_timer->start(XLOAD_UPDATE_INTERVAL); } BroadcastCommand(QString().sprintf("RE 0 %d %d!", RDDeck::Recording, catch_events[event].id())); StartBatch(catch_events[event].id()); } void MainObject::StartUploadEvent(int event) { WriteExitCode(event,RDRecording::Uploading); catch_active_xloads.push_back(event); if(!catch_xload_timer->isActive()) { catch_xload_timer->start(XLOAD_UPDATE_INTERVAL); } BroadcastCommand(QString().sprintf("RE 0 %d %d!",RDDeck::Recording, catch_events[event].id())); StartBatch(catch_events[event].id()); } bool MainObject::ExecuteMacroCart(RDCart *cart,int id,int event) { int event_id=GetFreeEvent(); if(event_id<0) { LogLine(RDConfig::LogWarning,"unable to allocate event context!"); return false; } if(event!=-1) { catch_events[event].setStatus(RDDeck::Recording); } if(id!=-1) { BroadcastCommand(QString().sprintf("RE 0 %d %d!",RDDeck::Recording,id)); } catch_macro_event_id[event_id]=id; catch_event_pool[event_id]= new RDMacroEvent(catch_rdstation->address(),catch_ripc,this,"event"); catch_event_mapper->setMapping(catch_event_pool[event_id],event_id); connect(catch_event_pool[event_id],SIGNAL(finished()), catch_event_mapper,SLOT(map())); QString cmd=cart->macros(); catch_event_pool[event_id]->load(cmd); catch_event_pool[event_id]->exec(); return true; } void MainObject::SendFullStatus(int ch) { for(unsigned i=0;ireadBlock(buf,256))>0) { buf[c]=0; for(int i=0;iclose(); KillSocket(ch); return; } if(!strcmp(args[ch][0],"PW")) { // Password Authenticate if(!strcmp(args[ch][1],catch_config->password())) { auth[ch]=true; EchoCommand(ch,"PW +!"); return; } else { auth[ch]=false; EchoCommand(ch,"PW -!"); return; } } // // Priviledged Commands // Authentication required to execute these! // if(!auth[ch]) { EchoArgs(ch,'-'); return; } if(!strcmp(args[ch][0],"RS")) { // Reset EchoArgs(ch,'+'); LoadEngine(); } if(!strcmp(args[ch][0],"RA")) { // Add Event if(AddEvent(QString(args[ch][1]).toInt())) { EchoArgs(ch,'+'); BroadcastCommand(QString().sprintf("RU %s!",args[ch][1]),ch); } else { EchoArgs(ch,'-'); } } if(!strcmp(args[ch][0],"RR")) { // Remove Event RemoveEvent(QString(args[ch][1]).toInt()); EchoArgs(ch,'+'); BroadcastCommand(QString().sprintf("RU %s!",args[ch][1]),ch); } if(!strcmp(args[ch][0],"RU")) { // Update Event if(UpdateEvent(QString(args[ch][1]).toInt())) { EchoArgs(ch,'+'); } else { EchoArgs(ch,'-'); } } if(!strcmp(args[ch][0],"RD")) { // Load Deck List EchoArgs(ch,'+'); LoadDeckList(); } if(!strcmp(args[ch][0],"RO")) { // Reload Time Offset EchoArgs(ch,'+'); catch_engine->setTimeOffset(catch_rdstation->timeOffset()); } if(!strcmp(args[ch][0],"RE")) { // Request Status if(sscanf(args[ch][1],"%u",&chan)!=1) { EchoArgs(ch,'-'); return; } if(chan==0) { SendFullStatus(ch); return; } chan--; if(chan=128)&&(chan<(MAX_DECKS+128))) { if(catch_playout_deck_status[chan-128]==RDDeck::Offline) { EchoArgs(ch,'-'); return; } EchoCommand(ch, QString().sprintf("RE %u %d %d!", chan+1,catch_playout_deck_status[chan-128], catch_playout_id[chan-128])); return; } EchoArgs(ch,'-'); return; } if(!strcmp(args[ch][0],"RM")) { // Enable/Disable Metering if(!strcmp(args[ch][1],"0")) { // Disable Metering catch_meter_enabled[ch]=false; } if(!strcmp(args[ch][1],"1")) { // Enable Metering catch_meter_enabled[ch]=true; } } if(!strcmp(args[ch][0],"SR")) { // Stop Recording if(sscanf(args[ch][1],"%d",&chan)!=1) { return; } if((chan>0)&&(chan<(MAX_DECKS+1))) { switch(catch_record_deck_status[chan-1]) { case RDDeck::Recording: catch_record_aborting[chan-1]=true; catch_cae->stopRecord(catch_record_card[chan-1], catch_record_stream[chan-1]); break; case RDDeck::Waiting: startTimerData(catch_record_id[chan-1]); break; default: break; } } if((chan>128)&&(chan<(MAX_DECKS+129))) { switch(catch_playout_deck_status[chan-129]) { case RDDeck::Recording: catch_cae->stopPlay(catch_playout_handle[chan-129]); break; default: break; } } } if(!strcmp(args[ch][0],"RH")) { // Reload Heartbeat Configuration LoadHeartbeat(); } if(!strcmp(args[ch][0],"RX")) { // Restart Dropbox Instances StartDropboxes(); } if(!strcmp(args[ch][0],"MN")) { // Monitor State if(sscanf(args[ch][1],"%d",&chan)!=1) { return; } if((chan>0)&&(chan<(MAX_DECKS+1))) { if(catch_monitor_port[chan-1]>=0) { if(args[ch][2][0]=='1') { catch_cae->setPassthroughVolume(catch_record_card[chan-1], catch_record_stream[chan-1], catch_monitor_port[chan-1],0); catch_monitor_state[chan-1]=true; BroadcastCommand(QString().sprintf("MN %d 1!",chan)); } else { catch_cae->setPassthroughVolume(catch_record_card[chan-1], catch_record_stream[chan-1], catch_monitor_port[chan-1], RD_MUTE_DEPTH); catch_monitor_state[chan-1]=false; BroadcastCommand(QString().sprintf("MN %d 0!",chan)); } } } } if(!strcmp(args[ch][0],"SC")) { // Set Exit Code if(sscanf(args[ch][1],"%d",&id)!=1) { return; } if(sscanf(args[ch][2],"%d",&code)!=1) { return; } str=""; for(int i=3;istate()==QSocket::Connection) { socket[ch]->writeBlock(command,strlen(command)); } } void MainObject::BroadcastCommand(const char *command,int except_ch) { // LogLine(RDConfig::LogDebug,QString().sprintf("rdcatchd: BroadcastCommand(%s)",command)); for(unsigned i=0;iname()); q=new RDSqlQuery(sql); while(q->next()) { catch_events.push_back(CatchEvent()); LoadEvent(q,&catch_events.back(),true); } LogLine(RDConfig::LogInfo,QString().sprintf("loaded %d events",(int)catch_events.size())); delete q; LogLine(RDConfig::LogInfo,"rdcatchd engine load ends"); } QString MainObject::LoadEventSql() { return QString("select ")+ "ID,"+ // 00 "IS_ACTIVE,"+ // 01 "TYPE,"+ // 02 "CHANNEL,"+ // 03 "CUT_NAME,"+ // 04 "SUN,"+ // 05 "MON,"+ // 06 "TUE,"+ // 07 "WED,"+ // 08 "THU,"+ // 09 "FRI,"+ // 10 "SAT,"+ // 11 "START_TIME,"+ // 12 "LENGTH,"+ // 13 "START_GPI,"+ // 14 "END_GPI,"+ // 15 "TRIM_THRESHOLD,"+ // 16 "STARTDATE_OFFSET,"+ // 17 "ENDDATE_OFFSET,"+ // 18 "FORMAT," // 19 "CHANNELS,"+ // 20 "SAMPRATE,"+ // 21 "BITRATE,"+ // 22 "MACRO_CART,"+ // 23 "SWITCH_INPUT,"+ // 24 "SWITCH_OUTPUT,"+ // 25 "ONE_SHOT,"+ // 26 "START_TYPE,"+ // 27 "START_LENGTH,"+ // 28 "START_MATRIX,"+ // 29 "START_LINE,"+ // 30 "START_OFFSET,"+ // 31 "END_TYPE,"+ // 32 "END_TIME,"+ // 33 "END_LENGTH,"+ // 34 "END_MATRIX,"+ // 35 "END_LINE,"+ // 36 "URL,"+ // 37 "URL_USERNAME,"+ // 38 "URL_PASSWORD,"+ // 39 "QUALITY,"+ // 40 "NORMALIZE_LEVEL,"+ // 41 "ALLOW_MULT_RECS,"+ // 42 "MAX_GPI_REC_LENGTH,"+ // 43 "DESCRIPTION,"+ // 44 "FEED_ID,"+ // 45 "EVENTDATE_OFFSET,"+ // 46 "ENABLE_METADATA "+ // 47 "from RECORDINGS"; } void MainObject::LoadEvent(RDSqlQuery *q,CatchEvent *e,bool add) { e->setId(q->value(0).toUInt()); e->setIsActive(RDBool(q->value(1).toString())); e->setType((RDRecording::Type)q->value(2).toInt()); e->setChannel(q->value(3).toUInt()); e->setCutName(q->value(4).toString()); e->setDayOfWeek(7,RDBool(q->value(5).toString())); e->setDayOfWeek(1,RDBool(q->value(6).toString())); e->setDayOfWeek(2,RDBool(q->value(7).toString())); e->setDayOfWeek(3,RDBool(q->value(8).toString())); e->setDayOfWeek(4,RDBool(q->value(9).toString())); e->setDayOfWeek(5,RDBool(q->value(10).toString())); e->setDayOfWeek(6,RDBool(q->value(11).toString())); e->setStartTime(q->value(12).toTime()); e->setLength(q->value(13).toUInt()); e->setStartGpi(q->value(14).toInt()); e->setEndGpi(q->value(15).toInt()); e->setTrimThreshold(q->value(16).toUInt()); e->setStartdateOffset(q->value(17).toUInt()); e->setEnddateOffset(q->value(18).toUInt()); switch((RDSettings::Format)q->value(19).toInt()) { case RDSettings::Pcm16: e->setFormat(RDCae::Pcm16); break; case RDSettings::Pcm24: e->setFormat(RDCae::Pcm24); break; case RDSettings::MpegL2: case RDSettings::MpegL2Wav: e->setFormat(RDCae::MpegL2); break; case RDSettings::MpegL3: e->setFormat(RDCae::MpegL3); break; case RDSettings::MpegL1: case RDSettings::Flac: case RDSettings::OggVorbis: break; } e->setChannels(q->value(20).toInt()); e->setSampleRate(catch_system->sampleRate()); //e->setSampleRate(q->value(21).toInt()); e->setBitrate(q->value(22).toInt()); e->setMacroCart(q->value(23).toInt()); e->setSwitchInput(q->value(24).toInt()); e->setSwitchOutput(q->value(25).toInt()); e->setStatus(RDDeck::Idle); e->setOneShot(RDBool(q->value(26).toString())); e->setStartType((RDRecording::StartType)q->value(27).toInt()); e->setStartLength(q->value(28).toInt()); e->setStartMatrix(q->value(29).toInt()); e->setStartLine(q->value(30).toInt()); e->setStartOffset(q->value(31).toInt()); e->setEndType((RDRecording::EndType)q->value(32).toInt()); e->setEndTime(q->value(33).toTime()); e->setEndLength(q->value(34).toInt()); e->setEndMatrix(q->value(35).toInt()); e->setEndLine(q->value(36).toInt()); e->setUrl(q->value(37).toString()); e->setUrlUsername(q->value(38).toString()); e->setUrlPassword(q->value(39).toString()); e->setQuality(q->value(40).toInt()); e->setNormalizeLevel(q->value(41).toInt()); e->setAllowMultipleRecordings(RDBool(q->value(42).toString())); e->setMaxGpiRecordLength(q->value(43).toUInt()); e->setDescription(q->value(44).toString()); e->setFeedId(q->value(45).toUInt()); e->setEventdateOffset(q->value(46).toInt()); e->setEnableMetadata(RDBool(q->value(47).toString())); if(add) { if(e->startType()==RDRecording::GpiStart) { e->setGpiStartTimer(new QTimer(this)); catch_gpi_start_mapper->setMapping(e->gpiStartTimer(),e->id()); connect(e->gpiStartTimer(),SIGNAL(timeout()), catch_gpi_start_mapper,SLOT(map())); e->setGpiOffsetTimer(new QTimer(this)); catch_gpi_offset_mapper->setMapping(e->gpiOffsetTimer(),e->id()); connect(e->gpiOffsetTimer(),SIGNAL(timeout()), catch_gpi_offset_mapper,SLOT(map())); } catch_engine->addEvent(e->id(),e->startTime()); } } void MainObject::LoadDeckList() { RDDeck::Status status[MAX_DECKS]; // // Record Decks // for(int i=0;i0)&&(CHANNEL<9)", (const char *)catch_config->stationName()); RDSqlQuery *q=new RDSqlQuery(sql); while(q->next()) { status[q->value(0).toUInt()-1]=RDDeck::Idle; catch_record_card[q->value(0).toUInt()-1]=q->value(1).toInt(); catch_record_stream[q->value(0).toUInt()-1]=q->value(2).toInt(); catch_monitor_port[q->value(0).toUInt()-1]=q->value(3).toInt(); } delete q; for(int i=0;istopRecord(catch_record_card[i],catch_record_stream[i]); catch_record_deck_status[i]=RDDeck::Offline; } } else { catch_record_deck_status[i]=status[i]; } } // // Playout Decks // for(int i=0;i128)&&(CHANNEL<137)", (const char *)catch_config->stationName()); q=new RDSqlQuery(sql); while(q->next()) { status[q->value(0).toUInt()-129]=RDDeck::Idle; } delete q; for(int i=0;istopPlay(catch_playout_handle[i]); catch_playout_deck_status[i]=RDDeck::Offline; } } else { catch_playout_deck_status[i]=status[i]; } } } int MainObject::GetRecordDeck(int card,int stream) { for(int i=0;iname(),id); q=new RDSqlQuery(sql); if(q->first()) { catch_events.push_back(CatchEvent()); LoadEvent(q,&catch_events.back(),true); switch((RDRecording::Type)q->value(2).toInt()) { case RDRecording::Recording: LogLine(RDConfig::LogNotice,QString(). sprintf("loading event %d, Type: recording, Cut: %s", id,(const char *)q->value(4).toString())); break; case RDRecording::Playout: LogLine(RDConfig::LogNotice,QString(). sprintf("loading event %d, Type: playout, Cut: %s", id,(const char *)q->value(4).toString())); break; case RDRecording::MacroEvent: LogLine(RDConfig::LogNotice,QString(). sprintf("loading event %d, Type: macro, Cart: %d", id,q->value(23).toUInt())); break; case RDRecording::SwitchEvent: LogLine(RDConfig::LogNotice,QString().sprintf( "loading event %d, Type: switch, Matrix: %d, Source: %d Dest: %d", id,q->value(3).toInt(),q->value(24).toInt(), q->value(25).toInt())); break; case RDRecording::Download: LogLine(RDConfig::LogNotice,QString(). sprintf("loading event %d, Type: download, Cut: %s", id,(const char *)q->value(4).toString())); break; case RDRecording::Upload: LogLine(RDConfig::LogNotice,QString(). sprintf("loading event %d, Type: upload, Cut: %s", id,(const char *)q->value(4).toString())); break; } delete q; return true; } LogLine(RDConfig::LogWarning,QString().sprintf("event %d not found, not loaded",id)); delete q; return false; } void MainObject::RemoveEvent(int id) { int event=GetEvent(id); if(event<0) { LogLine(RDConfig::LogNotice,QString(). sprintf("event %d not found, not removed",id)); return; } switch(catch_events[event].type()) { case RDRecording::Recording: LogLine(RDConfig::LogDebug,QString(). sprintf("removed event %d, Type: recording, Cut: %s", id,(const char *)catch_events[event].cutName())); break; case RDRecording::Playout: LogLine(RDConfig::LogDebug,QString(). sprintf("removed event %d, Type: playout, Cut: %s", id, (const char *)catch_events[event].cutName())); break; case RDRecording::MacroEvent: LogLine(RDConfig::LogDebug,QString(). sprintf("removed event %d, Type: macro, Cart: %u", id, catch_events[event].macroCart())); break; case RDRecording::SwitchEvent: LogLine(RDConfig::LogDebug,QString().sprintf( "removed event %d, Type: switch, Matrix: %d, Source: %d Dest: %d", id, catch_events[event].channel(), catch_events[event].switchInput(), catch_events[event].switchOutput())); break; case RDRecording::Download: LogLine(RDConfig::LogDebug,QString(). sprintf("removed event %d, Type: download, Cut: %s", id,(const char *)catch_events[event].cutName())); break; case RDRecording::Upload: LogLine(RDConfig::LogDebug,QString(). sprintf("removed event %d, Type: upload, Cut: %s", id,(const char *)catch_events[event].cutName())); break; } std::vector::iterator it=catch_events.begin()+event; catch_events.erase(it,it+1); catch_engine->removeEvent(id); } bool MainObject::UpdateEvent(int id) { RemoveEvent(id); return AddEvent(id); } int MainObject::GetEvent(int id) { for(unsigned i=0;iremoveEvent(catch_events[event].id()); std::vector::iterator it=catch_events.begin()+event; catch_events.erase(it,it+1); } void MainObject::LoadHeartbeat() { if(catch_heartbeat_timer->isActive()) { catch_heartbeat_timer->stop(); } QString sql=QString().sprintf("select HEARTBEAT_CART,HEARTBEAT_INTERVAL\ from STATIONS where NAME=\"%s\"", (const char *)catch_rdstation->name()); RDSqlQuery *q=new RDSqlQuery(sql); if(q->first()) { if((q->value(0).toUInt()!=0)&&(q->value(1).toUInt()!=0)) { catch_heartbeat_cart=q->value(0).toUInt(); sysHeartbeatData(); catch_heartbeat_timer->start(q->value(1).toUInt()); } } delete q; } void MainObject::CheckInRecording(QString cutname,CatchEvent *evt, unsigned msecs,unsigned threshold) { RDCut *cut=new RDCut(cutname); RDSettings *s=new RDSettings(); s->setFormat((RDSettings::Format)evt->format()); s->setSampleRate(evt->sampleRate()); s->setBitRate(evt->bitrate()); s->setChannels(evt->channels()); cut->checkInRecording(catch_config->stationName(),s,msecs); delete s; cut->autoTrim(RDCut::AudioBoth,-threshold); RDCart *cart=new RDCart(cut->cartNumber()); cart->updateLength(); delete cart; delete cut; chown(RDCut::pathName(cutname),catch_config->uid(),catch_config->gid()); } void MainObject::CheckInPodcast(CatchEvent *e) const { QString sql; RDSqlQuery *q; // // Purge Stale Casts // sql=QString().sprintf("delete from PODCASTS where \ (FEED_ID=%d)&&(AUDIO_FILENAME=\"%s\")", e->feedId(), (const char *)RDGetBasePart(e->resolvedUrl())); q=new RDSqlQuery(sql); delete q; // // Get Channel Parameters // sql=QString().sprintf("select ENABLE_AUTOPOST,CHANNEL_TITLE,\ CHANNEL_DESCRIPTION,CHANNEL_CATEGORY,\ CHANNEL_LINK,MAX_SHELF_LIFE from FEEDS \ where ID=%u",e->feedId()); q=new RDSqlQuery(sql); if(!q->first()) { delete q; return; } // // Add the Cast Entry // RDPodcast::Status status=RDPodcast::StatusPending; if(q->value(0).toString().lower()=="y") { status=RDPodcast::StatusActive; } sql=QString().sprintf("insert into PODCASTS set \ FEED_ID=%u,\ STATUS=%u,\ ITEM_TITLE=\"%s\",\ ITEM_DESCRIPTION=\"%s\",\ ITEM_CATEGORY=\"%s\",\ ITEM_LINK=\"%s\",\ AUDIO_FILENAME=\"%s\",\ AUDIO_LENGTH=%u,\ AUDIO_TIME=%u,\ SHELF_LIFE=%u,\ EFFECTIVE_DATETIME=now(),\ ORIGIN_DATETIME=now()", e->feedId(), status, (const char *)RDEscapeString(q->value(1).toString()), (const char *)RDEscapeString(q->value(2).toString()), (const char *)RDEscapeString(q->value(3).toString()), (const char *)RDEscapeString(q->value(4).toString()), (const char *)RDGetBasePart(e->resolvedUrl()), e->podcastLength(), e->podcastTime(), q->value(5).toUInt()); delete q; q=new RDSqlQuery(sql); delete q; // // Update the Build Date // sql=QString().sprintf("update FEEDS set LAST_BUILD_DATETIME=now() \ where ID=%u",e->feedId()); q=new RDSqlQuery(sql); delete q; } RDRecording::ExitCode MainObject::ReadExitCode(int event) { RDRecording::ExitCode code=RDRecording::InternalError; QString sql=QString().sprintf("select EXIT_CODE from RECORDINGS\ where ID=%d",catch_events[event].id()); RDSqlQuery *q=new RDSqlQuery(sql); if(q->first()) { code=(RDRecording::ExitCode)q->value(0).toInt(); } delete q; return code; } void MainObject::WriteExitCode(int event,RDRecording::ExitCode code, const QString &err_text) { QString sql=QString().sprintf("update RECORDINGS set EXIT_CODE=%d,\ EXIT_TEXT=\"%s\"\ where ID=%d",code, (const char *)RDEscapeString(err_text), catch_events[event].id()); RDSqlQuery *q=new RDSqlQuery(sql); delete q; switch(code) { case RDRecording::Ok: case RDRecording::Downloading: case RDRecording::Uploading: case RDRecording::RecordActive: case RDRecording::PlayActive: case RDRecording::Waiting: break; case RDRecording::ServerError: case RDRecording::InternalError: SendErrorMessage(&catch_events[event], RDRecording::exitString(code)+": "+err_text, catch_conf->errorRml()); break; default: SendErrorMessage(&catch_events[event],RDRecording::exitString(code), catch_conf->errorRml()); break; } } void MainObject::WriteExitCodeById(int id,RDRecording::ExitCode code, const QString &err_text) { QString sql=QString().sprintf("update RECORDINGS set EXIT_CODE=%d,\ EXIT_TEXT=\"%s\" \ where ID=%d",code, (const char *)RDEscapeString(err_text), id); RDSqlQuery *q=new RDSqlQuery(sql); delete q; } QString MainObject::BuildTempName(int event,QString str) { return BuildTempName(&catch_events[event],str); } QString MainObject::BuildTempName(CatchEvent *evt,QString str) { return QString().sprintf("%s/rdcatchd-%s-%d.%s",(const char *)catch_temp_dir, (const char *)str,evt->id(), (const char *)GetFileExtension(evt->resolvedUrl())); } QString MainObject::GetFileExtension(QString filename) { for(int i=filename.length()-1;i>=0;i--) { if(((const char *)filename)[i]=='/') { return QString(); } if(((const char *)filename)[i]=='.') { return filename.right(filename.length()-i-1); } } return QString(); } /* This is an overloaded virtual function to tell a session manager not to restart this daemon. */ void QApplication::saveState(QSessionManager &sm) { sm.setRestartHint(QSessionManager::RestartNever); LogLine(RDConfig::LogDebug,"rdcatchd saveState(), set restart hint to Never"); return; }; bool MainObject::SendErrorMessage(CatchEvent *event,const QString &err_desc, QString rml) { if(rml.isEmpty()) { return false; } ResolveErrorWildcards(event,err_desc,&rml); // // Execute the RML // int event_id=GetFreeEvent(); if(event_id<0) { LogLine(RDConfig::LogWarning,"unable to allocate event context!"); } catch_macro_event_id[event_id]=event->id()+RDCATCHD_ERROR_ID_OFFSET; catch_event_pool[event_id]= new RDMacroEvent(catch_rdstation->address(),catch_ripc,this,"event"); catch_event_mapper->setMapping(catch_event_pool[event_id],event_id); connect(catch_event_pool[event_id],SIGNAL(finished()), catch_event_mapper,SLOT(map())); bool res=catch_event_pool[event_id]->load(rml); catch_event_pool[event_id]->exec(); LogLine(RDConfig::LogNotice,QString(). sprintf("executed error rml: id=%d, rml=\"%s\", res=%d", event->id(),(const char *)rml,res)); return true; } void MainObject::ResolveErrorWildcards(CatchEvent *event, const QString &err_desc,QString *rml) { rml->replace("%d",event->description()); rml->replace("%e",err_desc); // Error Description rml->replace("%i",QString().sprintf("%u",event->id())); rml->replace("%t",event->startTime().toString("hh:mm:ss")); rml->replace("%y",RDRecording::typeString(event->type())); switch(event->type()) { case RDRecording::Recording: rml->replace("%k",QString().sprintf("%d",event->channel())); rml->replace("%n",event->cutName().left(6)); rml->replace("%u","n/a"); break; case RDRecording::Playout: rml->replace("%k",QString().sprintf("%d",event->channel()-128)); rml->replace("%n",event->cutName().left(6)); rml->replace("%u","n/a"); break; case RDRecording::Upload: rml->replace("%k","n/a"); rml->replace("%n",event->cutName().left(6)); rml->replace("%u",event->resolvedUrl()); break; case RDRecording::Download: rml->replace("%k","n/a"); rml->replace("%n",event->cutName().left(6)); rml->replace("%u",event->resolvedUrl()); break; case RDRecording::MacroEvent: rml->replace("%k","n/a"); rml->replace("%n",QString().sprintf("%06u",event->macroCart())); rml->replace("%u","n/a"); break; case RDRecording::SwitchEvent: rml->replace("%k","n/a"); rml->replace("%n",tr("n/a")); rml->replace("%u","n/a"); break; } } void MainObject::StartDropboxes() { QString sql; RDSqlQuery *q; RDSqlQuery *q1; // // Kill Old Instances // system("killall rdimport"); // // Launch Dropbox Configurations // sql=QString("select ID,GROUP_NAME,PATH,NORMALIZATION_LEVEL,")+ "AUTOTRIM_LEVEL,TO_CART,USE_CARTCHUNK_ID,TITLE_FROM_CARTCHUNK_ID,"+ "DELETE_CUTS,METADATA_PATTERN,FIX_BROKEN_FORMATS,LOG_PATH,"+ "DELETE_SOURCE,STARTDATE_OFFSET,ENDDATE_OFFSET,ID,"+ "IMPORT_CREATE_DATES,CREATE_STARTDATE_OFFSET,"+ "CREATE_ENDDATE_OFFSET,SET_USER_DEFINED "+ "from DROPBOXES where STATION_NAME=\""+ RDEscapeString(catch_config->stationName())+"\""; q=new RDSqlQuery(sql); while(q->next()) { QString cmd=QString(). sprintf("nice rdimport --persistent-dropbox-id=%d --drop-box --log-mode", q->value(15).toInt()); sql=QString("select SCHED_CODE from DROPBOX_SCHED_CODES where ")+ QString().sprintf("DROPBOX_ID=%d",q->value(0).toInt()); q1=new RDSqlQuery(sql); while(q1->next()) { cmd+=QString(" --add-scheduler-code=\"")+q1->value(0).toString()+"\""; } delete q1; cmd+= QString().sprintf(" --normalization-level=%d",q->value(3).toInt()/100); cmd+= QString().sprintf(" --autotrim-level=%d",q->value(4).toInt()/100); if(q->value(5).toUInt()>0) { cmd+=QString().sprintf(" --to-cart=%u",q->value(5).toUInt()); } if(q->value(6).toString()=="Y") { cmd+=" --use-cartchunk-cutid"; } if(q->value(7).toString()=="Y") { cmd+=" --title-from-cartchunk-cutid"; } if(q->value(8).toString()=="Y") { cmd+=" --delete-cuts"; } if(!q->value(9).toString().isEmpty()) { cmd+=QString().sprintf(" \"--metadata-pattern=%s\"", (const char *)q->value(9).toString()); } if(q->value(10).toString()=="Y") { cmd+=" --fix-broken-formats"; } if(q->value(12).toString()=="Y") { cmd+=" --delete-source"; } if(q->value(16).toString()=="Y") { cmd+=QString().sprintf(" --create-startdate-offset=%d", q->value(17).toInt()); cmd+=QString().sprintf(" --create-enddate-offset=%d", q->value(18).toInt()); } if(!q->value(19).toString().isEmpty()) { cmd+=" --set-user-defined="+RDEscapeString(q->value(19).toString()); } cmd+=QString().sprintf(" --startdate-offset=%d",q->value(13).toInt()); cmd+=QString().sprintf(" --enddate-offset=%d",q->value(14).toInt()); cmd+=QString().sprintf(" %s \"%s\"",(const char *)q->value(1).toString(), (const char *)q->value(2).toString()); if(!q->value(11).toString().isEmpty()) { cmd+=QString().sprintf(" >> %s 2>> %s", (const char *)q->value(11).toString(), (const char *)q->value(11).toString()); } else { cmd+=" > /dev/null 2> /dev/null"; } cmd+=" &"; LogLine(RDConfig::LogInfo,QString(). sprintf("launching dropbox configuration: \"%s\"", (const char *)cmd)); if(fork()==0) { system(cmd); exit(0); } } delete q; } unsigned MainObject::GetNextDynamicId() { unsigned id=RDCATCHD_DYNAMIC_BASE_ID; for(unsigned i=0;i=id) { id=catch_events[id].id()+1; } } return id; } void MainObject::RunRmlRecordingCache(int chan) { if(catch_record_pending_cartnum[chan-1]==0) { return; } StartRmlRecording(chan,catch_record_pending_cartnum[chan-1], catch_record_pending_cutnum[chan-1], catch_record_pending_maxlen[chan-1]); catch_record_pending_cartnum[chan-1]=0; } void MainObject::StartRmlRecording(int chan,int cartnum,int cutnum,int maxlen) { RDDeck *deck=new RDDeck(catch_config->stationName(),chan); RDCut *cut=new RDCut(cartnum,cutnum); QDateTime dt=QDateTime(QDate::currentDate(),QTime::currentTime()); catch_events.push_back(CatchEvent()); catch_events.back().setId(GetNextDynamicId()); catch_events.back().setIsActive(true); catch_events.back().setOneShot(true); catch_events.back().setType(RDRecording::Recording); catch_events.back().setChannel(chan); catch_events.back().setCutName(cut->cutName()); catch_events.back().setDayOfWeek(dt.date().dayOfWeek(),true); catch_events.back().setStartTime(dt.time()); catch_events.back().setEndType(RDRecording::LengthEnd); catch_events.back().setLength(maxlen); catch_events.back(). setFormat((RDCae::AudioCoding)deck->defaultFormat()); catch_events.back().setChannels(deck->defaultChannels()); catch_events.back().setSampleRate(catch_system->sampleRate()); catch_events.back().setBitrate(deck->defaultBitrate()); catch_events.back().setNormalizeLevel(0); StartRecording(catch_events.size()-1); delete cut; delete deck; } void MainObject::StartBatch(int id) { if((fork())==0) { QString bin=QString(RD_PREFIX)+"/"+"bin/rdcatchd"; execl(bin,(const char *)bin, (const char *)QString().sprintf("--event-id=%d",id), (char *)NULL); LogLine(RDConfig::LogErr,QString(). sprintf("failed to exec %s --event-id=%d: %s",(const char *)bin, id,strerror(errno))); exit(0); } } QString MainObject::GetTempRecordingName(int id) const { return QString().sprintf("%s/rdcatchd-record-%d.%s", RDConfiguration()->audioRoot().ascii(),id, RDConfiguration()->audioExtension().ascii()); } int main(int argc,char *argv[]) { QApplication a(argc,argv,false); new MainObject(); return a.exec(); }