Index: programs/mythbackend/scheduler.h =================================================================== --- programs/mythbackend/scheduler.h (revision 26756) +++ programs/mythbackend/scheduler.h (working copy) @@ -148,6 +148,25 @@ const RecList &reclist); void FillDirectoryInfoCache(bool force = false); + int CalcTimeToNextHandleRecordingEvent( + const QDateTime &curtime, + RecConstIter startIter, const RecList &reclist, + int prerollseconds, int max_sleep /*ms*/); + bool HandleReschedule(void); + bool HandleRunSchedulerStartup( + int prerollseconds, int idleWaitForRecordingTime); + void HandleWakeSlave(RecordingInfo &ri, int prerollseconds); + bool HandleRecording( + RecordingInfo &ri, bool &statuschanged, int prerollseconds); + void HandleTuning( + RecordingInfo &ri, bool &statuschanged); + void HandleRecordingStatusChange( + RecordingInfo &ri, RecStatusTypes recStatus, const QString &details); + void HandleIdleShutdown( + bool &blockShutdown, QDateTime &idleSince, int prerollseconds, + int idleTimeoutSecs, int idleWaitForRecordingTime); + + MythDeque reschedQueue; QMutex reschedLock; QMutex recordmatchLock; Index: programs/mythbackend/scheduler.cpp =================================================================== --- programs/mythbackend/scheduler.cpp (revision 26756) +++ programs/mythbackend/scheduler.cpp (working copy) @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -214,6 +215,7 @@ static inline bool Recording(const RecordingInfo *p) { return (p->GetRecordingStatus() == rsRecording || + p->GetRecordingStatus() == rsTuning || p->GetRecordingStatus() == rsWillRecord); } @@ -289,8 +291,10 @@ static bool comp_priority(RecordingInfo *a, RecordingInfo *b) { - int arec = (a->GetRecordingStatus() != rsRecording); - int brec = (b->GetRecordingStatus() != rsRecording); + int arec = (a->GetRecordingStatus() != rsRecording && + a->GetRecordingStatus() != rsTuning); + int brec = (b->GetRecordingStatus() != rsRecording && + b->GetRecordingStatus() != rsTuning); if (arec != brec) return arec < brec; @@ -644,14 +648,14 @@ if (sp->GetCardID() == rp->GetCardID()) { found = true; - rp->SetRecordingStatus(rsRecording); + rp->SetRecordingStatus(sp->GetRecordingStatus()); reclist_changed = true; rp->AddHistory(false); - VERBOSE(VB_IMPORTANT, QString("setting %1/%2/\"%3\" as " - "recording") + VERBOSE(VB_IMPORTANT, QString("setting %1/%2/\"%3\" as %4") .arg(sp->GetCardID()) .arg(sp->GetChannelSchedulingID()) - .arg(sp->GetTitle())); + .arg(sp->GetTitle()) + .arg(toUIState(sp->GetRecordingStatus()))); } else { @@ -664,7 +668,8 @@ } } else if (sp->GetCardID() == rp->GetCardID() && - rp->GetRecordingStatus() == rsRecording) + (rp->GetRecordingStatus() == rsRecording || + rp->GetRecordingStatus() == rsTuning)) { rp->SetRecordingStatus(rsAborted); reclist_changed = true; @@ -699,7 +704,8 @@ RecordingInfo *rp = *ri; if (rp->GetCardID() == cardid && - rp->GetRecordingStatus() == rsRecording) + (rp->GetRecordingStatus() == rsRecording || + rp->GetRecordingStatus() == rsTuning)) { rp->SetRecordingStatus(rsAborted); reclist_changed = true; @@ -720,7 +726,8 @@ for (; i != reclist.end(); ++i) { RecordingInfo *p = *i; - if (p->GetRecordingStatus() == rsRecording) + if (p->GetRecordingStatus() == rsRecording || + p->GetRecordingStatus() == rsTuning) worklist.push_back(new RecordingInfo(*p)); } } @@ -806,6 +813,7 @@ { RecordingInfo *p = *i; if (p->GetRecordingStatus() == rsRecording || + p->GetRecordingStatus() == rsTuning || p->GetRecordingStatus() == rsWillRecord || p->GetRecordingStatus() == rsUnknown) { @@ -1024,7 +1032,8 @@ { PrintRec(p, " >"); - if (p->GetRecordingStatus() == rsRecording) + if (p->GetRecordingStatus() == rsRecording || + p->GetRecordingStatus() == rsTuning) return false; RecList *showinglist = &titlelistmap[p->GetTitle()]; @@ -1171,7 +1180,8 @@ while (i != worklist.end()) { RecordingInfo *p = *i; - if (p->GetRecordingStatus() == rsRecording) + if (p->GetRecordingStatus() == rsRecording || + p->GetRecordingStatus() == rsTuning) MarkOtherShowings(p); else if (p->GetRecordingStatus() == rsUnknown) { @@ -1304,6 +1314,7 @@ // Delete anything that has already passed since we can't // change history, can we? if (p->GetRecordingStatus() != rsRecording && + p->GetRecordingStatus() != rsTuning && p->GetScheduledEndTime() < schedTime && p->GetRecordingEndTime() < schedTime) { @@ -1478,7 +1489,8 @@ RecConstIter it = reclist.begin(); for (; it != reclist.end(); ++it) { - if (rsRecording == (*it)->GetRecordingStatus()) + if (rsRecording == (*it)->GetRecordingStatus() || + rsTuning == (*it)->GetRecordingStatus()) recMap[(*it)->MakeUniqueKey()] = new ProgramInfo(**it); } @@ -1493,8 +1505,9 @@ { if (pginfo.IsSameRecording(**it)) { - return (rsRecording == (**it).GetRecordingStatus()) ? - rsRecording : pginfo.GetRecordingStatus(); + return (rsRecording == (**it).GetRecordingStatus() || + rsTuning == (**it).GetRecordingStatus()) ? + (**it).GetRecordingStatus() : pginfo.GetRecordingStatus(); } } @@ -1630,41 +1643,14 @@ void Scheduler::RunScheduler(void) { - int prerollseconds = 0; - int wakeThreshold = gCoreContext->GetNumSetting("WakeUpThreshold", 300); - int secsleft = 0; - EncoderLink *nexttv = NULL; - - RecordingInfo *nextRecording = NULL; - QDateTime nextrectime; - QString schedid; - - QDateTime curtime; - QDateTime lastupdate = QDateTime::currentDateTime().addDays(-1); - QDateTime lastSleepCheck = QDateTime::currentDateTime().addDays(-1); - - RecIter startIter = reclist.begin(); - - bool blockShutdown = gCoreContext->GetNumSetting("blockSDWUwithoutClient", 1); - QDateTime idleSince = QDateTime(); - int idleTimeoutSecs = 0; - int idleWaitForRecordingTime = 0; - bool firstRun = true; - - QString sysEventKey; - int sysEventSecs[5] = { 120, 90, 60, 30, 0 }; - QListsysEvents[4]; - - struct timeval fillstart, fillend; - float matchTime, placeTime; - // Mark anything that was recording as aborted. We'll fix it up. // if possible, after the slaves connect and we start scheduling. MSqlQuery query(dbConn); query.prepare("UPDATE oldrecorded SET recstatus = :RSABORTED " - " WHERE recstatus = :RSRECORDING"); + " WHERE recstatus = :RSRECORDING OR recstatus = :RSTUNING"); query.bindValue(":RSABORTED", rsAborted); query.bindValue(":RSRECORDING", rsRecording); + query.bindValue(":RSTUNING", rsTuning); if (!query.exec()) MythDB::DBError("UpdateAborted", query); @@ -1673,603 +1659,807 @@ Reschedule(-1); - while (1) + int prerollseconds = 0; + int wakeThreshold = 300; + int idleTimeoutSecs = 0; + int idleWaitForRecordingTime = 15; // in minutes + bool blockShutdown = + gCoreContext->GetNumSetting("blockSDWUwithoutClient", 1); + bool firstRun = true; + QDateTime lastSleepCheck = QDateTime::currentDateTime().addDays(-1); + RecIter startIter = reclist.begin(); + QDateTime idleSince = QDateTime(); + int maxSleep = 60000; // maximum sleep time in milliseconds + int schedRunTime = 30; // max scheduler run time in seconds + + while (true) { - curtime = QDateTime::currentDateTime(); + QDateTime curtime = QDateTime::currentDateTime(); bool statuschanged = false; + int secs_to_next = (startIter != reclist.end()) ? + curtime.secsTo((*startIter)->GetRecordingStartTime()) : 60*60; - if ((startIter != reclist.end() && - curtime.secsTo((*startIter)->GetRecordingStartTime()) < 30)) - sleep(1); + // If we're about to start a recording don't do any reschedules... + // instead sleep for a bit + if (secs_to_next < (schedRunTime + 2)) + { + int msecs = CalcTimeToNextHandleRecordingEvent( + curtime, startIter, reclist, prerollseconds, maxSleep); + if (msecs > 200) + (void) ::usleep(msecs * 1000); + } else { + int sched_sleep = (secs_to_next - schedRunTime - 1) * 1000; + sched_sleep = min(sched_sleep, maxSleep); reschedLock.lock(); if (reschedQueue.empty()) - reschedWait.wait(&reschedLock, 1000); + reschedWait.wait(&reschedLock, sched_sleep); bool queue_empty = reschedQueue.empty(); reschedLock.unlock(); - if (!queue_empty) + QTime t; t.start(); + if (!queue_empty && HandleReschedule()) { - // We might have been inactive for a long time, so make - // sure our DB connection is fresh before continuing. - dbConn = MSqlQuery::SchedCon(); + statuschanged = true; + startIter = reclist.begin(); - gettimeofday(&fillstart, NULL); - QString msg; + // The master backend is a long lived program, so + // we reload some key settings on each reschedule. + prerollseconds = + gCoreContext->GetNumSetting("RecordPreRoll", 0); + wakeThreshold = + gCoreContext->GetNumSetting("WakeUpThreshold", 300); + idleTimeoutSecs = + gCoreContext->GetNumSetting("idleTimeoutSecs", 0); + idleWaitForRecordingTime = + gCoreContext->GetNumSetting("idleWaitForRecordingTime", 15); + } + int e = t.elapsed(); + if (e > 0) + { + schedRunTime = (firstRun) ? 0 : schedRunTime; + schedRunTime = + max((int) ceil((e + 999 / 1000) * 1.5f), schedRunTime); + VERBOSE(VB_IMPORTANT, QString("schedRunTime: %1 seconds") + .arg(schedRunTime)); + } - reschedLock.lock(); - while (!reschedQueue.empty()) - { - int recordid = reschedQueue.dequeue(); - reschedLock.unlock(); + if (firstRun) + { + blockShutdown &= HandleRunSchedulerStartup( + prerollseconds, idleWaitForRecordingTime); + firstRun = false; + } - VERBOSE(VB_GENERAL, - QString("Reschedule requested for id %1.") - .arg(recordid)); + // Unless a recording is about to start, check for slaves + // that can be put to sleep if it has been at least five + // minutes since we last put slaves to sleep. + curtime = QDateTime::currentDateTime(); + secs_to_next = (startIter != reclist.end()) ? + curtime.secsTo((*startIter)->GetRecordingStartTime()) : 60*60; + if ((secs_to_next > schedRunTime * 1.5f) && + (lastSleepCheck.secsTo(curtime) > 300)) + { + PutInactiveSlavesToSleep(); + lastSleepCheck = QDateTime::currentDateTime(); + } + } - if (recordid != 0) - { - if (recordid == -1) - { - reschedLock.lock(); - reschedQueue.clear(); - reschedLock.unlock(); - } + // Skip past recordings that are already history + // (i.e. AddHistory() has been called setting oldrecstatus) + for ( ; startIter != reclist.end(); ++startIter) + { + if ((*startIter)->GetRecordingStatus() != + (*startIter)->oldrecstatus) + { + break; + } + } - QMutexLocker locker(&recordmatchLock); - UpdateMatches(recordid); - } + // Start any recordings that are due to be started + // & call RecordPending for recordings due to start in 30 seconds + // & handle rsTuning updates + bool done = false; + for (RecIter it = startIter; it != reclist.end() && !done; ++it) + done = HandleRecording(**it, statuschanged, prerollseconds); - reschedLock.lock(); - } - reschedLock.unlock(); + /// Wake any slave backends that need waking + curtime = QDateTime::currentDateTime(); + for (RecIter it = startIter; it != reclist.end() && !done; ++it) + { + int secsleft = curtime.secsTo((*it)->GetRecordingStartTime()); + if ((secsleft - prerollseconds) <= wakeThreshold) + HandleWakeSlave(**it, prerollseconds); + else + break; + } - gettimeofday(&fillend, NULL); + if (statuschanged) + { + MythEvent me("SCHEDULE_CHANGE"); + gCoreContext->dispatch(me); + idleSince = QDateTime(); + } - matchTime = ((fillend.tv_sec - fillstart.tv_sec ) * 1000000 + - (fillend.tv_usec - fillstart.tv_usec)) / 1000000.0; + // if idletimeout is 0, the user disabled the auto-shutdown feature + if ((idleTimeoutSecs > 0) && (m_mainServer != NULL)) + { + HandleIdleShutdown(blockShutdown, idleSince, prerollseconds, + idleTimeoutSecs, idleWaitForRecordingTime); + } + } +} - gettimeofday(&fillstart, NULL); - bool worklistused = FillRecordList(); - gettimeofday(&fillend, NULL); - if (worklistused) - { - UpdateNextRecord(); - PrintList(); - } - else - { - VERBOSE(VB_GENERAL, "Reschedule interrupted, will retry"); - Reschedule(0); - continue; - } +int Scheduler::CalcTimeToNextHandleRecordingEvent( + const QDateTime &curtime, + RecConstIter startIter, const RecList &reclist, + int prerollseconds, int max_sleep /*ms*/) +{ + if (startIter == reclist.end()) + return max_sleep; - placeTime = ((fillend.tv_sec - fillstart.tv_sec ) * 1000000 + - (fillend.tv_usec - fillstart.tv_usec)) / 1000000.0; + int msecs = max_sleep; + for (RecConstIter i = startIter; i != reclist.end() && (msecs > 0); ++i) + { + // Check on recordings that we've told to start, but have + // not yet started every second or so. + if ((*i)->GetRecordingStatus() == rsTuning) + { + msecs = min(msecs, 1000); + continue; + } - msg.sprintf("Scheduled %d items in " - "%.1f = %.2f match + %.2f place", - (int)reclist.size(), - matchTime + placeTime, matchTime, placeTime); + // These recordings have already been handled.. + if ((*i)->GetRecordingStatus() == (*i)->oldrecstatus) + continue; - VERBOSE(VB_GENERAL, msg); - gCoreContext->LogEntry("scheduler", LP_INFO, - "Scheduled items", msg); + int secs_to_next = curtime.secsTo((*i)->GetRecordingStartTime()); - fsInfoCacheFillTime = - QDateTime::currentDateTime().addSecs(-1000); + if (!recPendingList[(*i)->MakeUniqueSchedulerKey()]) + secs_to_next -= 30; - lastupdate = curtime; - startIter = reclist.begin(); - statuschanged = true; + if (secs_to_next < 0) + { + msecs = 0; + break; + } - // Determine if the user wants us to start recording early - // and by how many seconds - prerollseconds = gCoreContext->GetNumSetting("RecordPreRoll"); + // This is what normally breaks us out of the loop... + if (secs_to_next > max_sleep) + { + msecs = min(msecs, max_sleep); + break; + } - idleTimeoutSecs = - gCoreContext->GetNumSetting("idleTimeoutSecs", 0); - idleWaitForRecordingTime = - gCoreContext->GetNumSetting("idleWaitForRecordingTime", 15); + if (secs_to_next > 31) + { + msecs = min(msecs, 30 * 1000); + continue; + } - if (firstRun) - { - //the parameter given to the startup_cmd. "user" means a user - // started the BE, 'auto' means it was started automatically - QString startupParam = "user"; + if ((secs_to_next-1) * 1000 > msecs) + continue; - // find the first recording that WILL be recorded - RecIter firstRunIter = reclist.begin(); - for ( ; firstRunIter != reclist.end(); ++firstRunIter) - if ((*firstRunIter)->GetRecordingStatus() == rsWillRecord) - break; + if (secs_to_next < 15) + { + QTime st = (*i)->GetRecordingStartTime().time(); + int tmp = curtime.time().msecsTo(st); + tmp = (tmp < 0) ? tmp + 86400000 : tmp; + msecs = (tmp > 15*1000) ? 0 : min(msecs, tmp); + } + else + { + msecs = min(msecs, (secs_to_next-1) * 1000); + } + } - // have we been started automatically? - if (WasStartedAutomatically() || - ((firstRunIter != reclist.end()) && - ((curtime.secsTo((*firstRunIter)->GetRecordingStartTime()) - prerollseconds) - < (idleWaitForRecordingTime * 60)))) - { - VERBOSE(VB_IMPORTANT, "AUTO-Startup assumed"); - startupParam = "auto"; + return min(msecs, max_sleep); +} - // Since we've started automatically, don't wait for - // client to connect before allowing shutdown. - blockShutdown = false; - } - else - { - VERBOSE(VB_IMPORTANT, "Seem to be woken up by USER"); - } +bool Scheduler::HandleReschedule(void) +{ + // We might have been inactive for a long time, so make + // sure our DB connection is fresh before continuing. + dbConn = MSqlQuery::SchedCon(); - QString startupCommand = - gCoreContext->GetSetting("startupCommand", ""); - if (!startupCommand.isEmpty()) - { - startupCommand.replace("$status", startupParam); - myth_system(startupCommand); - } - firstRun = false; - } + struct timeval fillstart; + gettimeofday(&fillstart, NULL); + QString msg; - PutInactiveSlavesToSleep(); - lastSleepCheck = QDateTime::currentDateTime(); + reschedLock.lock(); + while (!reschedQueue.empty()) + { + int recordid = reschedQueue.dequeue(); + reschedLock.unlock(); - SendMythSystemEvent("SCHEDULER_RAN"); + VERBOSE(VB_GENERAL, + QString("Reschedule requested for id %1.") + .arg(recordid)); + + if (recordid != 0) + { + if (recordid == -1) + { + reschedLock.lock(); + reschedQueue.clear(); + reschedLock.unlock(); } + + QMutexLocker locker(&recordmatchLock); + UpdateMatches(recordid); } - for ( ; startIter != reclist.end(); ++startIter) - if ((*startIter)->GetRecordingStatus() != - (*startIter)->oldrecstatus) - break; + reschedLock.lock(); + } + reschedLock.unlock(); - curtime = QDateTime::currentDateTime(); + struct timeval fillend; + gettimeofday(&fillend, NULL); - // About every 5 minutes check for slaves that can be put to sleep - if (lastSleepCheck.secsTo(curtime) > 300) - { - PutInactiveSlavesToSleep(); - lastSleepCheck = QDateTime::currentDateTime(); - } + float matchTime = ((fillend.tv_sec - fillstart.tv_sec ) * 1000000 + + (fillend.tv_usec - fillstart.tv_usec)) / 1000000.0; - // Go through the list of recordings starting in the next few minutes - // and wakeup any slaves that are asleep - RecIter recIter = startIter; - for ( ; schedulingEnabled && recIter != reclist.end(); ++recIter) - { - nextRecording = *recIter; - nextrectime = nextRecording->GetRecordingStartTime(); - secsleft = curtime.secsTo(nextrectime); + gettimeofday(&fillstart, NULL); + bool worklistused = FillRecordList(); + gettimeofday(&fillend, NULL); + if (worklistused) + { + UpdateNextRecord(); + PrintList(); + } + else + { + VERBOSE(VB_GENERAL, "Reschedule interrupted, will retry"); + Reschedule(0); + return false; + } - if ((secsleft - prerollseconds) > wakeThreshold) - break; + float placeTime = ((fillend.tv_sec - fillstart.tv_sec ) * 1000000 + + (fillend.tv_usec - fillstart.tv_usec)) / 1000000.0; - if (m_tvList->find(nextRecording->GetCardID()) == m_tvList->end()) - continue; + msg.sprintf("Scheduled %d items in " + "%.1f = %.2f match + %.2f place", + (int)reclist.size(), + matchTime + placeTime, matchTime, placeTime); - sysEventKey = QString("%1:%2").arg(nextRecording->GetChanID()) - .arg(nextrectime.toString(Qt::ISODate)); - int i = 0; - bool pendingEventSent = false; - while (sysEventSecs[i] != 0) - { - if ((secsleft <= sysEventSecs[i]) && - (!sysEvents[i].contains(sysEventKey))) - { - if (!pendingEventSent) - SendMythSystemRecEvent - (QString("REC_PENDING SECS %1").arg(secsleft), - nextRecording); + VERBOSE(VB_GENERAL, msg); + gCoreContext->LogEntry("scheduler", LP_INFO, + "Scheduled items", msg); - sysEvents[i].append(sysEventKey); - pendingEventSent = true; - } - i++; - } + fsInfoCacheFillTime = + QDateTime::currentDateTime().addSecs(-1000); - nexttv = (*m_tvList)[nextRecording->GetCardID()]; + SendMythSystemEvent("SCHEDULER_RAN"); - if (nexttv->IsAsleep() && !nexttv->IsWaking()) - { - VERBOSE(VB_SCHEDULE, QString("Slave Backend %1 is being " - "awakened to record: %2") - .arg(nexttv->GetHostName()) - .arg(nextRecording->GetTitle())); + return true; +} - if (!WakeUpSlave(nexttv->GetHostName())) - { - Reschedule(0); - continue; - } - } - else if ((nexttv->IsWaking()) && - ((secsleft - prerollseconds) < 210) && - (nexttv->GetSleepStatusTime().secsTo(curtime) < 300) && - (nexttv->GetLastWakeTime().secsTo(curtime) > 10)) +bool Scheduler::HandleRunSchedulerStartup( + int prerollseconds, int idleWaitForRecordingTime) +{ + bool blockShutdown = true; + + // The parameter given to the startup_cmd. "user" means a user + // probably started the backend process, "auto" means it was + // started probably automatically. + QString startupParam = "user"; + + // find the first recording that WILL be recorded + RecIter firstRunIter = reclist.begin(); + for ( ; firstRunIter != reclist.end(); ++firstRunIter) + { + if ((*firstRunIter)->GetRecordingStatus() == rsWillRecord) + break; + } + + // have we been started automatically? + QDateTime curtime = QDateTime::currentDateTime(); + if (WasStartedAutomatically() || + ((firstRunIter != reclist.end()) && + ((curtime.secsTo((*firstRunIter)->GetRecordingStartTime()) - + prerollseconds) < (idleWaitForRecordingTime * 60)))) + { + VERBOSE(VB_GENERAL, LOC + "AUTO-Startup assumed"); + startupParam = "auto"; + + // Since we've started automatically, don't wait for + // client to connect before allowing shutdown. + blockShutdown = false; + } + else + { + VERBOSE(VB_GENERAL, LOC + "Seem to be woken up by USER"); + } + + QString startupCommand = gCoreContext->GetSetting("startupCommand", ""); + if (!startupCommand.isEmpty()) + { + startupCommand.replace("$status", startupParam); + myth_system(startupCommand); + } + + return blockShutdown; +} + +// If a recording is about to start on a backend in a few minutes, wake it... +void Scheduler::HandleWakeSlave(RecordingInfo &ri, int prerollseconds) +{ + static const int sysEventSecs[5] = { 120, 90, 60, 30, 0 }; + QString sysEventKey; + QList sysEvents[4]; + + QDateTime curtime = QDateTime::currentDateTime(); + QDateTime nextrectime = ri.GetRecordingStartTime(); + int secsleft = curtime.secsTo(nextrectime); + + QMap::iterator tvit = m_tvList->find(ri.GetCardID()); + if (tvit == m_tvList->end()) + return; + + sysEventKey = QString("%1:%2").arg(ri.GetChanID()) + .arg(nextrectime.toString(Qt::ISODate)); + + int i = 0; + bool pendingEventSent = false; + while (sysEventSecs[i] != 0) + { + if ((secsleft <= sysEventSecs[i]) && + (!sysEvents[i].contains(sysEventKey))) + { + if (!pendingEventSent) { - VERBOSE(VB_SCHEDULE, QString("Slave Backend %1 not " - "available yet, trying to wake it up again.") - .arg(nexttv->GetHostName())); - if (!WakeUpSlave(nexttv->GetHostName(), false)) - { - Reschedule(0); - continue; - } + SendMythSystemRecEvent( + QString("REC_PENDING SECS %1").arg(secsleft), &ri); } - else if ((nexttv->IsWaking()) && - ((secsleft - prerollseconds) < 150) && - (nexttv->GetSleepStatusTime().secsTo(curtime) < 300)) - { - VERBOSE(VB_SCHEDULE, - QString("WARNING: Slave Backend %1 has NOT come " - "back from sleep yet in 150 seconds. Setting " - "slave status to unknown and attempting " - "to reschedule around its tuners.") - .arg(nexttv->GetHostName())); - QMap::Iterator enciter = - m_tvList->begin(); - for (; enciter != m_tvList->end(); ++enciter) - { - EncoderLink *enc = *enciter; - if (enc->GetHostName() == nexttv->GetHostName()) - enc->SetSleepStatus(sStatus_Undefined); - } + sysEvents[i].append(sysEventKey); + pendingEventSent = true; + } + i++; + } - Reschedule(0); - } + EncoderLink *nexttv = *tvit; + + if (nexttv->IsAsleep() && !nexttv->IsWaking()) + { + VERBOSE(VB_SCHEDULE, LOC + + QString("Slave Backend %1 is being awakened to record: %2") + .arg(nexttv->GetHostName()).arg(ri.GetTitle())); + + if (!WakeUpSlave(nexttv->GetHostName())) + Reschedule(0); + } + else if ((nexttv->IsWaking()) && + ((secsleft - prerollseconds) < 210) && + (nexttv->GetSleepStatusTime().secsTo(curtime) < 300) && + (nexttv->GetLastWakeTime().secsTo(curtime) > 10)) + { + VERBOSE(VB_SCHEDULE, LOC + + QString("Slave Backend %1 not available yet, " + "trying to wake it up again.") + .arg(nexttv->GetHostName())); + + if (!WakeUpSlave(nexttv->GetHostName(), false)) + Reschedule(0); + } + else if ((nexttv->IsWaking()) && + ((secsleft - prerollseconds) < 150) && + (nexttv->GetSleepStatusTime().secsTo(curtime) < 300)) + { + VERBOSE(VB_GENERAL, LOC_WARN + + QString("Slave Backend %1 has NOT come " + "back from sleep yet in 150 seconds. Setting " + "slave status to unknown and attempting " + "to reschedule around its tuners.") + .arg(nexttv->GetHostName())); + + QMap::iterator it = m_tvList->begin(); + for (; it != m_tvList->end(); ++it) + { + if ((*it)->GetHostName() == nexttv->GetHostName()) + (*it)->SetSleepStatus(sStatus_Undefined); } - for ( recIter = startIter ; recIter != reclist.end(); ++recIter) + Reschedule(0); + } +} + +bool Scheduler::HandleRecording( + RecordingInfo &ri, bool &statuschanged, int prerollseconds) +{ + if (ri.GetRecordingStatus() == rsTuning) + { + HandleTuning(ri, statuschanged); + return false; + } + + if (ri.GetRecordingStatus() != rsWillRecord) + { + if (ri.GetRecordingStatus() != ri.oldrecstatus && + ri.GetRecordingStartTime() <= QDateTime::currentDateTime()) { - QString msg, details; - int fsID = -1; + ri.AddHistory(false); + } + return false; + } - nextRecording = *recIter; + QDateTime nextrectime = ri.GetRecordingStartTime(); + QDateTime curtime = QDateTime::currentDateTime(); + int secsleft = curtime.secsTo(nextrectime); + QString schedid = ri.MakeUniqueSchedulerKey(); - if (nextRecording->GetRecordingStatus() != rsTuning) - { - if (nextRecording->GetRecordingStatus() != rsWillRecord) - { - if (nextRecording->GetRecordingStatus() != - nextRecording->oldrecstatus && - nextRecording->GetRecordingStartTime() <= curtime) - nextRecording->AddHistory(false); - continue; - } + if (secsleft - prerollseconds < 60) + { + if (!recPendingList.contains(schedid)) + { + recPendingList[schedid] = false; + + livetvTime = (livetvTime < nextrectime) ? + nextrectime : livetvTime; - nextrectime = nextRecording->GetRecordingStartTime(); - secsleft = curtime.secsTo(nextrectime); - schedid = nextRecording->MakeUniqueSchedulerKey(); + Reschedule(0); + } + } - if (secsleft - prerollseconds < 60) - { - if (!recPendingList.contains(schedid)) - { - recPendingList[schedid] = false; + if (secsleft - prerollseconds > 35) + return true; - livetvTime = (livetvTime < nextrectime) ? - nextrectime : livetvTime; + QMap::iterator tvit = m_tvList->find(ri.GetCardID()); + if (tvit == m_tvList->end()) + { + QString msg = QString("Invalid cardid (%1) for %2") + .arg(ri.GetCardID()).arg(ri.GetTitle()); + VERBOSE(VB_GENERAL, LOC + msg); - Reschedule(0); - } - } + QMutexLocker lockit(reclist_lock); + ri.SetRecordingStatus(rsTunerBusy); + ri.AddHistory(true); + statuschanged = true; + return false; + } - if (secsleft - prerollseconds > 35) - break; - } + EncoderLink *nexttv = *tvit; - if (m_tvList->find(nextRecording->GetCardID()) == m_tvList->end()) - { - msg = QString("invalid cardid (%1) for %2") - .arg(nextRecording->GetCardID()) - .arg(nextRecording->GetTitle()); - VERBOSE(VB_GENERAL, msg); + if (nexttv->IsTunerLocked()) + { + QString msg = QString("SUPPRESSED recording \"%1\" on channel: " + "%2 on cardid: %3, sourceid %4. Tuner " + "is locked by an external application.") + .arg(ri.GetTitle()) + .arg(ri.GetChanID()) + .arg(ri.GetCardID()) + .arg(ri.GetSourceID()); + QByteArray amsg = msg.toLocal8Bit(); + VERBOSE(VB_GENERAL, msg.constData()); - QMutexLocker lockit(reclist_lock); - nextRecording->SetRecordingStatus(rsTunerBusy); - nextRecording->AddHistory(true); - statuschanged = true; - continue; - } + QMutexLocker lockit(reclist_lock); + ri.SetRecordingStatus(rsTunerBusy); + ri.AddHistory(true); + statuschanged = true; + return false; + } - nexttv = (*m_tvList)[nextRecording->GetCardID()]; - // cerr << "nexttv = " << nextRecording->GetCardID(); - // cerr << " title: " << nextRecording->GetTitle() << endl; + if ((prerollseconds > 0) && !IsBusyRecording(&ri)) + { + // Will use pre-roll settings only if no other + // program is currently being recorded + secsleft -= prerollseconds; + } - if (nextRecording->GetRecordingStatus() != rsTuning) - { - if (nexttv->IsTunerLocked()) - { - msg = QString("SUPPRESSED recording \"%1\" on channel: " - "%2 on cardid: %3, sourceid %4. Tuner " - "is locked by an external application.") - .arg(nextRecording->GetTitle()) - .arg(nextRecording->GetChanID()) - .arg(nextRecording->GetCardID()) - .arg(nextRecording->GetSourceID()); - QByteArray amsg = msg.toLocal8Bit(); - VERBOSE(VB_GENERAL, msg.constData()); + //VERBOSE(VB_GENERAL, secsleft << " seconds until " + //<< ri.GetTitle()); - QMutexLocker lockit(reclist_lock); - nextRecording->SetRecordingStatus(rsTunerBusy); - nextRecording->AddHistory(true); - statuschanged = true; - continue; - } + if (secsleft > 30) + return false; - if (!IsBusyRecording(nextRecording)) - { - // Will use pre-roll settings only if no other - // program is currently being recorded - secsleft -= prerollseconds; - } + if (nexttv->IsWaking()) + { + if (secsleft > 0) + { + VERBOSE(VB_SCHEDULE, + QString("WARNING: Slave Backend %1 has NOT come " + "back from sleep yet. Recording can " + "not begin yet for: %2") + .arg(nexttv->GetHostName()) + .arg(ri.GetTitle())); + } + else if (nexttv->GetLastWakeTime().secsTo(curtime) > 300) + { + VERBOSE(VB_SCHEDULE, + QString("WARNING: Slave Backend %1 has NOT come " + "back from sleep yet. Setting slave " + "status to unknown and attempting " + "to reschedule around its tuners.") + .arg(nexttv->GetHostName())); - //VERBOSE(VB_GENERAL, secsleft << " seconds until " << nextRecording->GetTitle()); + QMap::Iterator enciter = + m_tvList->begin(); + for (; enciter != m_tvList->end(); ++enciter) + { + EncoderLink *enc = *enciter; + if (enc->GetHostName() == nexttv->GetHostName()) + enc->SetSleepStatus(sStatus_Undefined); + } - if (secsleft > 30) - continue; + Reschedule(0); + } - if (nexttv->IsWaking()) - { - if (secsleft > 0) - { - VERBOSE(VB_SCHEDULE, - QString("WARNING: Slave Backend %1 has NOT come " - "back from sleep yet. Recording can " - "not begin yet for: %2") - .arg(nexttv->GetHostName()) - .arg(nextRecording->GetTitle())); - } - else if (nexttv->GetLastWakeTime().secsTo(curtime) > 300) - { - VERBOSE(VB_SCHEDULE, - QString("WARNING: Slave Backend %1 has NOT come " - "back from sleep yet. Setting slave " - "status to unknown and attempting " - "to reschedule around its tuners.") - .arg(nexttv->GetHostName())); + return false; + } - QMap::Iterator enciter = - m_tvList->begin(); - for (; enciter != m_tvList->end(); ++enciter) - { - EncoderLink *enc = *enciter; - if (enc->GetHostName() == nexttv->GetHostName()) - enc->SetSleepStatus(sStatus_Undefined); - } + int fsID = -1; + if (ri.GetPathname().isEmpty()) + { + QMutexLocker lockit(reclist_lock); + QString recording_dir; + fsID = FillRecordingDir( + ri.GetTitle(), + ri.GetHostname(), + ri.GetStorageGroup(), + ri.GetRecordingStartTime(), + ri.GetRecordingEndTime(), + ri.GetCardID(), + recording_dir, + reclist); + ri.SetPathname(recording_dir); + } - Reschedule(0); - } + if (!recPendingList[schedid]) + { + nexttv->RecordPending(&ri, max(secsleft, 0), + hasLaterList.contains(schedid)); + recPendingList[schedid] = true; + } - continue; - } + if (secsleft > 0) + return false; - if (nextRecording->GetPathname().isEmpty()) - { - QMutexLocker lockit(reclist_lock); - QString recording_dir; - fsID = FillRecordingDir( - nextRecording->GetTitle(), - nextRecording->GetHostname(), - nextRecording->GetStorageGroup(), - nextRecording->GetRecordingStartTime(), - nextRecording->GetRecordingEndTime(), - nextRecording->GetCardID(), - recording_dir, - reclist); - nextRecording->SetPathname(recording_dir); - } + QDateTime recstartts = mythCurrentDateTime().addSecs(30); + recstartts.setTime( + QTime(recstartts.time().hour(), recstartts.time().minute())); + ri.SetRecordingStartTime(recstartts); - if (!recPendingList[schedid]) - { - nexttv->RecordPending(nextRecording, max(secsleft, 0), - hasLaterList.contains(schedid)); - recPendingList[schedid] = true; - } + QMutexLocker lockit(reclist_lock); - if (secsleft > -2) - continue; + QString details = QString("%1: channel %2 on cardid %3, sourceid %4") + .arg(ri.toString(ProgramInfo::kTitleSubtitle)) + .arg(ri.GetChanID()) + .arg(ri.GetCardID()) + .arg(ri.GetSourceID()); - QDateTime recstartts = mythCurrentDateTime().addSecs(30); - recstartts.setTime - (QTime(recstartts.time().hour(), recstartts.time().minute())); - nextRecording->SetRecordingStartTime(recstartts); + RecStatusTypes recStatus = rsOffLine; + if (schedulingEnabled && nexttv->IsConnected()) + { + if (ri.GetRecordingStatus() == rsWillRecord) + { + recStatus = nexttv->StartRecording(&ri); + ri.AddHistory(false); - QMutexLocker lockit(reclist_lock); + // activate auto expirer + if (expirer) + expirer->Update(ri.GetCardID(), fsID, true); + } + } - details = QString("%1: channel %2 on cardid %3, sourceid %4") - .arg(nextRecording->toString(ProgramInfo::kTitleSubtitle)) - .arg(nextRecording->GetChanID()) - .arg(nextRecording->GetCardID()) - .arg(nextRecording->GetSourceID()); - } // if (nextRecording->GetRecordingStatus() != rsTuning) + HandleRecordingStatusChange(ri, recStatus, details); + statuschanged = true; - if (schedulingEnabled && nexttv->IsConnected()) - { - RecStatusTypes recStatus; + return false; +} - if (nextRecording->GetRecordingStatus() == rsWillRecord) - { - recStatus = nexttv->StartRecording(nextRecording); - nextRecording->AddHistory(false); - if (expirer) - { - // activate auto expirer - expirer->Update(nextRecording->GetCardID(), fsID, true); - } - } - else - recStatus = nexttv->GetRecordingStatus(); +void Scheduler::HandleRecordingStatusChange( + RecordingInfo &ri, RecStatusTypes recStatus, const QString &details) +{ + if (ri.GetRecordingStatus() == recStatus) + return; - nextRecording->SetRecordingStatus(recStatus); - if (recStatus == rsTuning) - continue; - } - else - nextRecording->SetRecordingStatus(rsOffLine); - bool doSchedAfterStart = - nextRecording->GetRecordingStatus() != rsRecording || - schedAfterStartMap[nextRecording->GetRecordingRuleID()] || - (nextRecording->GetParentRecordingRuleID() && - schedAfterStartMap[nextRecording->GetParentRecordingRuleID()]); - nextRecording->AddHistory(doSchedAfterStart); + ri.SetRecordingStatus(recStatus); - statuschanged = true; + if (rsTuning != recStatus) + { + bool doSchedAfterStart = + ((rsRecording != recStatus) && (rsTuning != recStatus)) || + schedAfterStartMap[ri.GetRecordingRuleID()] || + (ri.GetParentRecordingRuleID() && + schedAfterStartMap[ri.GetParentRecordingRuleID()]); + ri.AddHistory(doSchedAfterStart); + } - bool is_rec = (nextRecording->GetRecordingStatus() == rsRecording); - msg = is_rec ? - QString("Started recording") : - QString("Canceled recording (%1)") - .arg(toString(nextRecording->GetRecordingStatus(), - nextRecording->GetRecordingRuleType())); + QString msg = (rsRecording == recStatus) ? + QString("Started recording") : + ((rsTuning == recStatus) ? + QString("Tuning recording") : + QString("Canceled recording (%1)") + .arg(toString(ri.GetRecordingStatus(), ri.GetRecordingRuleType()))); - VERBOSE(VB_GENERAL, QString("%1: %2").arg(msg).arg(details)); - gCoreContext->LogEntry("scheduler", LP_NOTICE, msg, details); + VERBOSE(VB_GENERAL, QString("%1: %2").arg(msg).arg(details)); + gCoreContext->LogEntry("scheduler", LP_NOTICE, msg, details); - if (is_rec) - UpdateNextRecord(); + if ((rsRecording == recStatus) || (rsTuning == recStatus)) + { + UpdateNextRecord(); + } + else if (rsFailed == recStatus) + { + MythEvent me(QString("FORCE_DELETE_RECORDING %1 %2") + .arg(ri.GetChanID()) + .arg(ri.GetRecordingStartTime(ISODate))); + gCoreContext->dispatch(me); + } +} - if (nextRecording->GetRecordingStatus() == rsFailed) +void Scheduler::HandleTuning(RecordingInfo &ri, bool &statuschanged) +{ + if (rsTuning != ri.GetRecordingStatus()) + return; + + // Determine current recording status + QMap::iterator tvit = m_tvList->find(ri.GetCardID()); + RecStatusTypes recStatus = rsTunerBusy; + if (tvit == m_tvList->end()) + { + QString msg = QString("Invalid cardid (%1) for %2") + .arg(ri.GetCardID()).arg(ri.GetTitle()); + VERBOSE(VB_GENERAL, LOC + msg); + } + else + { + recStatus = (*tvit)->GetRecordingStatus(); + if (rsTuning == recStatus) + { + // If tuning is still taking place this long after we + // started give up on it so the scheduler can try to + // find another broadcast of the same material. + QDateTime curtime = QDateTime::currentDateTime(); + if ((ri.GetRecordingStartTime().secsTo(curtime) > 180) && + (ri.GetScheduledStartTime().secsTo(curtime) > 180)) { - MythEvent me(QString("FORCE_DELETE_RECORDING %1 %2") - .arg(nextRecording->GetChanID()) - .arg(nextRecording->GetRecordingStartTime(ISODate))); - gCoreContext->dispatch(me); + recStatus = rsFailed; } } + } - if (statuschanged) + // If the status has changed, handle it + if (rsTuning != recStatus) + { + QMutexLocker lockit(reclist_lock); + + QString details = QString("%1: channel %2 on cardid %3, sourceid %4") + .arg(ri.toString(ProgramInfo::kTitleSubtitle)) + .arg(ri.GetChanID()).arg(ri.GetCardID()).arg(ri.GetSourceID()); + HandleRecordingStatusChange(ri, recStatus, details); + statuschanged = true; + } +} + +void Scheduler::HandleIdleShutdown( + bool &blockShutdown, QDateTime &idleSince, + int prerollseconds, int idleTimeoutSecs, int idleWaitForRecordingTime) +{ + if ((idleTimeoutSecs <= 0) || (m_mainServer == NULL)) + return; + + // we release the block when a client connects + if (blockShutdown) + blockShutdown &= !m_mainServer->isClientConnected(); + else + { + QDateTime curtime = QDateTime::currentDateTime(); + + // find out, if we are currently recording (or LiveTV) + bool recording = false; + QMap::Iterator it; + for (it = m_tvList->begin(); (it != m_tvList->end()) && + !recording; ++it) { - MythEvent me("SCHEDULE_CHANGE"); - gCoreContext->dispatch(me); - idleSince = QDateTime(); + if ((*it)->IsBusy()) + recording = true; } - // if idletimeout is 0, the user disabled the auto-shutdown feature - if ((idleTimeoutSecs > 0) && (m_mainServer != NULL)) + if (!(m_mainServer->isClientConnected()) && !recording) { - // we release the block when a client connects - if (blockShutdown) - blockShutdown &= !m_mainServer->isClientConnected(); - else + // have we received a RESET_IDLETIME message? + resetIdleTime_lock.lock(); + if (resetIdleTime) { - // find out, if we are currently recording (or LiveTV) - bool recording = false; - QMap::Iterator it; - for (it = m_tvList->begin(); (it != m_tvList->end()) && - !recording; ++it) - { - if ((*it)->IsBusy()) - recording = true; - } + // yes - so reset the idleSince time + idleSince = QDateTime(); + resetIdleTime = false; + } + resetIdleTime_lock.unlock(); - if (!(m_mainServer->isClientConnected()) && !recording) + if (!idleSince.isValid()) + { + RecIter idleIter = reclist.begin(); + for ( ; idleIter != reclist.end(); ++idleIter) + if ((*idleIter)->GetRecordingStatus() == + rsWillRecord) + break; + + if (idleIter != reclist.end()) { - // have we received a RESET_IDLETIME message? - resetIdleTime_lock.lock(); - if (resetIdleTime) + if (curtime.secsTo + ((*idleIter)->GetRecordingStartTime()) - + prerollseconds > + (idleWaitForRecordingTime * 60) + + idleTimeoutSecs) { - // yes - so reset the idleSince time - idleSince = QDateTime(); - resetIdleTime = false; + idleSince = curtime; } - resetIdleTime_lock.unlock(); - - if (!idleSince.isValid()) + } + else + idleSince = curtime; + } + else + { + // is the machine already idling the timeout time? + if (idleSince.addSecs(idleTimeoutSecs) < curtime) + { + // are we waiting for shutdown? + if (m_isShuttingDown) { - RecIter idleIter = reclist.begin(); - for ( ; idleIter != reclist.end(); ++idleIter) - if ((*idleIter)->GetRecordingStatus() == - rsWillRecord) - break; - - if (idleIter != reclist.end()) + // if we have been waiting more that 60secs then assume + // something went wrong so reset and try again + if (idleSince.addSecs(idleTimeoutSecs + 60) < + curtime) { - if (curtime.secsTo - ((*idleIter)->GetRecordingStartTime()) - - prerollseconds > - (idleWaitForRecordingTime * 60) + - idleTimeoutSecs) - { - idleSince = curtime; - } + VERBOSE(VB_IMPORTANT, "Waited more than 60" + " seconds for shutdown to complete" + " - resetting idle time"); + idleSince = QDateTime(); + m_isShuttingDown = false; } - else - idleSince = curtime; } - else + else if (!m_isShuttingDown && + CheckShutdownServer(prerollseconds, + idleSince, + blockShutdown)) { - // is the machine already idling the timeout time? - if (idleSince.addSecs(idleTimeoutSecs) < curtime) - { - // are we waiting for shutdown? - if (m_isShuttingDown) - { - // if we have been waiting more that 60secs then assume - // something went wrong so reset and try again - if (idleSince.addSecs(idleTimeoutSecs + 60) < - curtime) - { - VERBOSE(VB_IMPORTANT, "Waited more than 60" - " seconds for shutdown to complete" - " - resetting idle time"); - idleSince = QDateTime(); - m_isShuttingDown = false; - } - } - else if (!m_isShuttingDown && - CheckShutdownServer(prerollseconds, - idleSince, - blockShutdown)) - { - ShutdownServer(prerollseconds, idleSince); - } - } - else - { - int itime = idleSince.secsTo(curtime); - QString msg; - if (itime == 1) - { - msg = QString("I\'m idle now... shutdown will " - "occur in %1 seconds.") - .arg(idleTimeoutSecs); - VERBOSE(VB_IMPORTANT, msg); - MythEvent me(QString("SHUTDOWN_COUNTDOWN %1") - .arg(idleTimeoutSecs)); - gCoreContext->dispatch(me); - } - else if (itime % 10 == 0) - { - msg = QString("%1 secs left to system " - "shutdown!") - .arg(idleTimeoutSecs - itime); - VERBOSE(VB_IDLE, msg); - MythEvent me(QString("SHUTDOWN_COUNTDOWN %1") - .arg(idleTimeoutSecs - itime)); - gCoreContext->dispatch(me); - } - } + ShutdownServer(prerollseconds, idleSince); } } else { - // not idle, make the time invalid - if (idleSince.isValid()) + int itime = idleSince.secsTo(curtime); + QString msg; + if (itime == 1) { - MythEvent me(QString("SHUTDOWN_COUNTDOWN -1")); + msg = QString("I\'m idle now... shutdown will " + "occur in %1 seconds.") + .arg(idleTimeoutSecs); + VERBOSE(VB_IMPORTANT, msg); + MythEvent me(QString("SHUTDOWN_COUNTDOWN %1") + .arg(idleTimeoutSecs)); gCoreContext->dispatch(me); } - idleSince = QDateTime(); + else if (itime % 10 == 0) + { + msg = QString("%1 secs left to system " + "shutdown!") + .arg(idleTimeoutSecs - itime); + VERBOSE(VB_IDLE, msg); + MythEvent me(QString("SHUTDOWN_COUNTDOWN %1") + .arg(idleTimeoutSecs - itime)); + gCoreContext->dispatch(me); + } } } } + else + { + // not idle, make the time invalid + if (idleSince.isValid()) + { + MythEvent me(QString("SHUTDOWN_COUNTDOWN -1")); + gCoreContext->dispatch(me); + } + idleSince = QDateTime(); + } } } + //returns true, if the shutdown is not blocked bool Scheduler::CheckShutdownServer(int prerollseconds, QDateTime &idleSince, bool &blockShutdown) @@ -2446,6 +2636,7 @@ RecordingInfo *pginfo = *recIter; if ((pginfo->GetRecordingStatus() != rsRecording) && + (pginfo->GetRecordingStatus() != rsTuning) && (pginfo->GetRecordingStatus() != rsWillRecord)) continue;