Skip to content

Commit

Permalink
Improved the detection and handling of missed recordings.
Browse files Browse the repository at this point in the history
Previously, the scheduler had some rudimentary support for detecting
missed recordings.  The scheduler now saves the state of future
recordings in the (now inappropriately named) oldrecorded table.  It
then uses that information when it is restarted after a crash or hang
to determine what was missed.
  • Loading branch information
gigem committed Apr 10, 2011
1 parent 0bab8fb commit 543d783
Show file tree
Hide file tree
Showing 12 changed files with 109 additions and 22 deletions.
2 changes: 1 addition & 1 deletion mythtv/bindings/perl/MythTV.pm
Expand Up @@ -114,7 +114,7 @@ package MythTV;
# schema version supported in the main code. We need to check that the schema
# version in the database is as expected by the bindings, which are expected
# to be kept in sync with the main code.
our $SCHEMA_VERSION = "1270";
our $SCHEMA_VERSION = "1271";

# NUMPROGRAMLINES is defined in mythtv/libs/libmythtv/programinfo.h and is
# the number of items in a ProgramInfo QStringList group used by
Expand Down
2 changes: 1 addition & 1 deletion mythtv/bindings/python/MythTV/static.py
Expand Up @@ -5,7 +5,7 @@
"""

OWN_VERSION = (0,25,-1,1)
SCHEMA_VERSION = 1270
SCHEMA_VERSION = 1271
NVSCHEMA_VERSION = 1007
MUSICSCHEMA_VERSION = 1018
PROTO_VERSION = '65'
Expand Down
2 changes: 2 additions & 0 deletions mythtv/libs/libmyth/programinfo.cpp
Expand Up @@ -4119,6 +4119,7 @@ static bool FromProgramQuery(
"FROM program "
"LEFT JOIN channel ON program.chanid = channel.chanid "
"LEFT JOIN oldrecorded AS oldrecstatus ON "
" oldrecstatus.future = 0 AND "
" program.title = oldrecstatus.title AND "
" channel.callsign = oldrecstatus.station AND "
" program.starttime = oldrecstatus.starttime "
Expand Down Expand Up @@ -4224,6 +4225,7 @@ bool LoadFromOldRecorded(
" duplicate "
" FROM oldrecorded "
" LEFT JOIN channel ON oldrecorded.chanid = channel.chanid "
" WHERE oldrecorded.future = 0 "
+ sql;

query.prepare(querystr);
Expand Down
16 changes: 13 additions & 3 deletions mythtv/libs/libmyth/programtypes.cpp
Expand Up @@ -54,7 +54,7 @@ QString toUIState(RecStatusType recstatus)

if (recstatus == rsConflict || recstatus == rsOffLine ||
recstatus == rsTunerBusy || recstatus == rsFailed ||
recstatus == rsAborted)
recstatus == rsAborted || recstatus == rsMissed)
{
return "error";
}
Expand Down Expand Up @@ -114,6 +114,9 @@ QChar toQChar(RecStatusType recstatus, uint cardid)
case rsMissed:
ret = QObject::tr("M", "RecStatusChar rsMissed");
break;
case rsMissedFuture:
ret = QObject::tr("m", "RecStatusChar rsMissedFuture");
break;
case rsConflict:
ret = QObject::tr("C", "RecStatusChar rsConflict");
break;
Expand Down Expand Up @@ -184,6 +187,8 @@ QString toString(RecStatusType recstatus, RecordingType rectype)
return QObject::tr("Manual Cancel");
case rsMissed:
return QObject::tr("Missed");
case rsMissedFuture:
return QObject::tr("Missed Future");
case rsConflict:
return QObject::tr("Conflicting");
case rsLaterShowing:
Expand Down Expand Up @@ -240,8 +245,13 @@ QString toDescription(RecStatusType recstatus, const QDateTime &recstartts)
break;
case rsMissed:
message = QObject::tr(
"This showing was not recorded because it "
"was scheduled after it would have ended.");
"This showing was not recorded because the "
"master backend was hung or not running.");
break;
case rsMissedFuture:
message = QObject::tr(
"This showing was not recorded because the "
"master backend was hung or not running.");
break;
case rsCancelled:
message = QObject::tr(
Expand Down
1 change: 1 addition & 0 deletions mythtv/libs/libmyth/programtypes.h
Expand Up @@ -186,6 +186,7 @@ typedef enum SubtitleTypes {
} SubtitleType; // has 4 bits in ProgramInfo::properties

typedef enum RecStatusTypes {
rsMissedFuture = -11,
rsTuning = -10,
rsFailed = -9,
rsTunerBusy = -8,
Expand Down
13 changes: 12 additions & 1 deletion mythtv/libs/libmythtv/dbcheck.cpp
Expand Up @@ -21,7 +21,7 @@ using namespace std;
mythtv/bindings/perl/MythTV.pm
*/
/// This is the DB schema version expected by the running MythTV instance.
const QString currentDatabaseVersion = "1270";
const QString currentDatabaseVersion = "1271";

static bool UpdateDBVersionNumber(const QString &newnumber, QString &dbver);
static bool performActualUpdate(
Expand Down Expand Up @@ -5607,6 +5607,17 @@ NULL
return false;
}

if (dbver == "1270")
{
const char *updates[] = {
"ALTER TABLE oldrecorded ADD future TINYINT(1) NOT NULL DEFAULT 0;",
"UPDATE oldrecorded SET future=0;",
NULL
};
if (!performActualUpdate(updates, "1271", dbver))
return false;
}

return true;
}

Expand Down
13 changes: 8 additions & 5 deletions mythtv/libs/libmythtv/recordinginfo.cpp
Expand Up @@ -1129,12 +1129,13 @@ void RecordingInfo::ReactivateRecording(void)
/**
* \brief Adds recording history, creating "record" it if necessary.
*/
void RecordingInfo::AddHistory(bool resched, bool forcedup)
void RecordingInfo::AddHistory(bool resched, bool forcedup, bool future)
{
bool dup = (GetRecordingStatus() == rsRecorded || forcedup);
RecStatusType rs = (GetRecordingStatus() == rsCurrentRecording) ?
rsPreviousRecording : GetRecordingStatus();
oldrecstatus = GetRecordingStatus();
if (!future)
oldrecstatus = GetRecordingStatus();
if (dup)
SetReactivated(false);
uint erecid = parentid ? parentid : recordid;
Expand All @@ -1144,10 +1145,11 @@ void RecordingInfo::AddHistory(bool resched, bool forcedup)
result.prepare("REPLACE INTO oldrecorded (chanid,starttime,"
"endtime,title,subtitle,description,category,"
"seriesid,programid,findid,recordid,station,"
"rectype,recstatus,duplicate,reactivate) "
"rectype,recstatus,duplicate,reactivate,future) "
"VALUES(:CHANID,:START,:END,:TITLE,:SUBTITLE,:DESC,"
":CATEGORY,:SERIESID,:PROGRAMID,:FINDID,:RECORDID,"
":STATION,:RECTYPE,:RECSTATUS,:DUPLICATE,:REACTIVATE);");
":STATION,:RECTYPE,:RECSTATUS,:DUPLICATE,:REACTIVATE,"
":FUTURE);");
result.bindValue(":CHANID", chanid);
result.bindValue(":START", startts);
result.bindValue(":END", endts);
Expand All @@ -1164,6 +1166,7 @@ void RecordingInfo::AddHistory(bool resched, bool forcedup)
result.bindValue(":RECSTATUS", rs);
result.bindValue(":DUPLICATE", dup);
result.bindValue(":REACTIVATE", IsReactivated());
result.bindValue(":FUTURE", future);

if (!result.exec())
MythDB::DBError("addHistory", result);
Expand Down Expand Up @@ -1291,7 +1294,7 @@ void RecordingInfo::SetDupHistory(void)
MSqlQuery result(MSqlQuery::InitCon());

result.prepare("UPDATE oldrecorded SET duplicate = 1 "
"WHERE duplicate = 0 "
"WHERE future = 0 AND duplicate = 0 "
"AND title = :TITLE AND "
"((programid = '' AND subtitle = :SUBTITLE"
" AND description = :DESC) OR "
Expand Down
3 changes: 2 additions & 1 deletion mythtv/libs/libmythtv/recordinginfo.h
Expand Up @@ -192,7 +192,8 @@ class MTV_PUBLIC RecordingInfo : public ProgramInfo
void ToggleRecord(void);

// these five can be moved to programinfo
void AddHistory(bool resched = true, bool forcedup = false);//pi
void AddHistory(bool resched = true, bool forcedup = false,
bool future = false);//pi
void DeleteHistory(void);//pi
void ForgetHistory(void);//pi
void SetDupHistory(void);//pi
Expand Down
72 changes: 64 additions & 8 deletions mythtv/programs/mythbackend/scheduler.cpp
Expand Up @@ -1343,6 +1343,7 @@ void Scheduler::PruneRedundants(void)
// change history, can we?
if (p->GetRecordingStatus() != rsRecording &&
p->GetRecordingStatus() != rsTuning &&
p->GetRecordingStatus() != rsMissedFuture &&
p->GetScheduledEndTime() < schedTime &&
p->GetRecordingEndTime() < schedTime)
{
Expand All @@ -1361,7 +1362,12 @@ void Scheduler::PruneRedundants(void)
p->oldrecstatus != rsNotListed &&
!p->IsReactivated())
{
RecStatusType rs = p->GetRecordingStatus();
p->SetRecordingStatus(p->oldrecstatus);
// Re-mark rsMissedFuture entries so non-future history
// will be saved in the scheduler thread.
if (rs == rsMissedFuture)
p->oldrecstatus = rsMissedFuture;
}

if (!Recording(p))
Expand Down Expand Up @@ -1699,9 +1705,10 @@ void Scheduler::RunScheduler(void)
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);

// Mark anything that was recording as aborted. We'll fix it, if
// needed, after the slaves connect and we start scheduling.
query.prepare("UPDATE oldrecorded SET recstatus = :RSABORTED "
" WHERE recstatus = :RSRECORDING OR recstatus = :RSTUNING");
query.bindValue(":RSABORTED", rsAborted);
Expand All @@ -1710,6 +1717,24 @@ void Scheduler::RunScheduler(void)
if (!query.exec())
MythDB::DBError("UpdateAborted", query);

// Mark anything that was going to record as missed. We'll fix
// it, if needed, after we start scheduling.
query.prepare("UPDATE oldrecorded SET recstatus = :RSMISSED "
"WHERE recstatus = :RSWILLRECORD");
query.bindValue(":RSMISSED", rsMissed);
query.bindValue(":RSWILLRECORD", rsWillRecord);
if (!query.exec())
MythDB::DBError("UpdateMissed1", query);

// Clear the "future" status of anything older than the maximum
// endoffset. Anything more recent will bee handled elsewhere
// during normal processing.
query.prepare("UPDATE oldrecorded SET future = 0 "
"WHERE future > 0 AND "
" endtime < (NOW() - INTERVAL 8 HOUR)");
if (!query.exec())
MythDB::DBError("UpdateMissed2", query);

// wait for slaves to connect
sleep(3);

Expand Down Expand Up @@ -1863,6 +1888,22 @@ void Scheduler::RunScheduler(void)
PutInactiveSlavesToSleep();
lastSleepCheck = QDateTime::currentDateTime();

// Delete old, future entries from oldrecorded and
// write new ones as well as new missed entries.
query.prepare("DELETE FROM oldrecorded WHERE future > 0");
if (!query.exec())
MythDB::DBError("DeleteFuture", query);

RecIter it = reclist.begin();
for ( ; it != reclist.end(); ++it)
{
RecordingInfo *p = *it;
if (p->oldrecstatus == rsMissedFuture)
p->AddHistory(false, false, false);
else if (p->oldrecstatus == rsUnknown)
p->AddHistory(false, false, true);
}

SendMythSystemEvent("SCHEDULER_RAN");
}
}
Expand Down Expand Up @@ -3357,7 +3398,7 @@ void Scheduler::AddNewRecords(void)
" recduplicate = (recorded.endtime IS NOT NULL), "
" findduplicate = (oldfind.findid IS NOT NULL), "
" oldrecstatus = oldrecorded.recstatus "
" WHERE program.endtime >= NOW() - INTERVAL 1 DAY "
" WHERE program.endtime >= NOW() - INTERVAL 9 HOUR "
);
rmquery.replace("RECTABLE", schedTmpRecord);

Expand Down Expand Up @@ -3388,6 +3429,7 @@ void Scheduler::AddNewRecords(void)
" p.subtitletypes+0, p.audioprop+0, RECTABLE.storagegroup, "//39-41
" capturecard.hostname, recordmatch.oldrecstatus, "
" RECTABLE.avg_delay, "//42-44
" oldrecstatus.future, " //45
+ pwrpri + QString(
"FROM recordmatch "
"INNER JOIN RECTABLE ON (recordmatch.recordid = RECTABLE.recordid) "
Expand Down Expand Up @@ -3488,6 +3530,15 @@ void Scheduler::AddNewRecords(void)
result.value(38).toUInt(),//videoproperties
result.value(40).toUInt());//audioproperties

// If this was previously a futrue recording and it's still
// viable, start over.
bool future = result.value(45).toInt();
if (future && p->GetRecordingEndTime() >= schedTime)
{
p->SetRecordingStatus(rsUnknown);
p->oldrecstatus = rsUnknown;
}

if (!recTypeRecPriorityMap.contains(p->GetRecordingRuleType()))
{
recTypeRecPriorityMap[p->GetRecordingRuleType()] =
Expand All @@ -3496,7 +3547,7 @@ void Scheduler::AddNewRecords(void)

p->SetRecordingPriority(
p->GetRecordingPriority() + recTypeRecPriorityMap[p->GetRecordingRuleType()] +
result.value(45).toInt() +
result.value(46).toInt() +
((autopriority) ?
autopriority - (result.value(44).toInt() * autostrata / 200) : 0));

Expand Down Expand Up @@ -3568,11 +3619,16 @@ void Scheduler::AddNewRecords(void)
if (inactive)
newrecstatus = rsInactive;

// Mark anything that has already passed as missed. If it
// survives PruneOverlaps, it will get deleted or have its old
// status restored in PruneRedundants.
// Mark anything that has already passed as some type of
// missed. If it survives PruneOverlaps, it will get deleted
// or have its old status restored in PruneRedundants.
if (p->GetRecordingEndTime() < schedTime)
newrecstatus = rsMissed;
{
if (future)
newrecstatus = rsMissedFuture;
else
newrecstatus = rsMissed;
}

p->SetRecordingStatus(newrecstatus);

Expand Down
1 change: 1 addition & 0 deletions mythtv/programs/mythfrontend/progdetails.cpp
Expand Up @@ -520,6 +520,7 @@ void ProgDetails::loadPage(void)
{
query.prepare("SELECT recstatus, starttime "
"FROM oldrecorded WHERE duplicate > 0 AND "
"future = 0 AND "
"((programid <> '' AND programid = :PROGRAMID) OR "
" (title <> '' AND title = :TITLE AND "
" subtitle <> '' AND subtitle = :SUBTITLE AND "
Expand Down
3 changes: 2 additions & 1 deletion mythtv/programs/mythfrontend/proglist.cpp
Expand Up @@ -728,7 +728,8 @@ void ProgLister::DeleteOldSeries(bool ok)
return;

MSqlQuery query(MSqlQuery::InitCon());
query.prepare("DELETE FROM oldrecorded WHERE title = :TITLE");
query.prepare("DELETE FROM oldrecorded "
"WHERE title = :TITLE AND future = 0");
query.bindValue(":TITLE", pi->GetTitle());
if (!query.exec())
MythDB::DBError("ProgLister::DeleteOldSeries -- delete", query);
Expand Down
3 changes: 2 additions & 1 deletion mythtv/programs/mythfrontend/viewscheduled.cpp
Expand Up @@ -414,7 +414,8 @@ void ViewScheduled::FillList()
recstatus == rsOffLine ||
recstatus == rsTunerBusy ||
recstatus == rsFailed ||
recstatus == rsAborted)
recstatus == rsAborted ||
recstatus == rsMissed)
state = "error";
else if (recstatus == rsWillRecord)
{
Expand Down

0 comments on commit 543d783

Please sign in to comment.