11# include <sys/param.h>
13# include <sys/mount.h>
54#define LOC QString("Scheduler: ")
55#define LOC_WARN QString("Scheduler, Warning: ")
56#define LOC_ERR QString("Scheduler, Error: ")
63 const QString& tmptable,
Scheduler *master_sched) :
65 m_recordTable(tmptable),
66 m_priorityTable(
"powerpriority"),
67 m_specSched(master_sched),
79 if (tmptable ==
"powerpriority_tmp")
94 start(QThread::LowPriority);
160 if (!query.
exec(
"SELECT count(*) FROM capturecard") || !query.
next())
169 LOG(VB_GENERAL, LOG_ERR,
LOC +
170 "No capture cards are defined in the database.\n\t\t\t"
171 "Perhaps you should re-read the installation instructions?");
175 query.
prepare(
"SELECT sourceid,name FROM videosource ORDER BY sourceid;");
190 "WHERE sourceid = :SOURCEID "
194 if (!subquery.
exec())
198 else if (!subquery.
next())
200 LOG(VB_GENERAL, LOG_WARNING,
LOC +
201 QString(
"Video source '%1' is defined, "
202 "but is not attached to a card input.")
203 .arg(query.
value(1).toString()));
213 LOG(VB_GENERAL, LOG_ERR,
LOC +
214 "No channel sources defined in the database");
263 return aprec < bprec;
286 Qt::CaseInsensitive);
300 Qt::CaseInsensitive);
314 int arec =
static_cast<int>
319 int brec =
static_cast<int>
334 int atype =
static_cast<int>
337 int btype =
static_cast<int>
341 return atype > btype;
344 int apast =
static_cast<int>
346 int bpast =
static_cast<int>
349 return apast < bpast;
380 int arec =
static_cast<int>
383 int brec =
static_cast<int>
396 int atype =
static_cast<int>
399 int btype =
static_cast<int>
403 return atype > btype;
406 int apast =
static_cast<int>
408 int bpast =
static_cast<int>
411 return apast < bpast;
446 LOG(VB_SCHEDULE, LOG_INFO,
"BuildWorkList...");
451 LOG(VB_SCHEDULE, LOG_INFO,
"AddNewRecords...");
453 LOG(VB_SCHEDULE, LOG_INFO,
"AddNotListed...");
456 LOG(VB_SCHEDULE, LOG_INFO,
"Sort by time...");
458 LOG(VB_SCHEDULE, LOG_INFO,
"PruneOverlaps...");
461 LOG(VB_SCHEDULE, LOG_INFO,
"Sort by priority...");
463 LOG(VB_SCHEDULE, LOG_INFO,
"BuildListMaps...");
465 LOG(VB_SCHEDULE, LOG_INFO,
"SchedNewRecords...");
467 LOG(VB_SCHEDULE, LOG_INFO,
"SchedLiveTV...");
469 LOG(VB_SCHEDULE, LOG_INFO,
"ClearListMaps...");
474 LOG(VB_SCHEDULE, LOG_INFO,
"Sort by time...");
476 LOG(VB_SCHEDULE, LOG_INFO,
"PruneRedundants...");
479 LOG(VB_SCHEDULE, LOG_INFO,
"Sort by time...");
481 LOG(VB_SCHEDULE, LOG_INFO,
"ClearWorkList...");
499 where =
"WHERE recordid IS NULL ";
501 thequery = QString(
"CREATE TEMPORARY TABLE recordmatch ") +
502 "SELECT * FROM recordmatch " + where +
"; ";
506 bool ok = query.
exec();
514 thequery =
"ALTER TABLE recordmatch "
515 " ADD UNIQUE INDEX (recordid, chanid, starttime); ";
523 thequery =
"ALTER TABLE recordmatch "
524 " ADD INDEX (chanid, starttime, manualid); ";
534 auto fillstart = nowAsDuration<std::chrono::microseconds>();
536 auto fillend = nowAsDuration<std::chrono::microseconds>();
537 auto matchTime = fillend - fillstart;
539 LOG(VB_SCHEDULE, LOG_INFO,
"CreateTempTables...");
542 fillstart = nowAsDuration<std::chrono::microseconds>();
543 LOG(VB_SCHEDULE, LOG_INFO,
"UpdateDuplicates...");
545 fillend = nowAsDuration<std::chrono::microseconds>();
546 auto checkTime = fillend - fillstart;
548 fillstart = nowAsDuration<std::chrono::microseconds>();
550 fillend = nowAsDuration<std::chrono::microseconds>();
551 auto placeTime = fillend - fillstart;
553 LOG(VB_SCHEDULE, LOG_INFO,
"DeleteTempTables...");
557 queryDrop.
prepare(
"DROP TABLE recordmatch;");
558 if (!queryDrop.
exec())
564 QString msg = QString(
"Speculative scheduled %1 items in %2 "
565 "= %3 match + %4 check + %5 place")
567 .arg(duration_cast<floatsecs>(matchTime + checkTime + placeTime).count(), 0,
'f', 1)
568 .arg(duration_cast<floatsecs>(matchTime).count(), 0,
'f', 2)
569 .arg(duration_cast<floatsecs>(checkTime).count(), 0,
'f', 2)
570 .arg(duration_cast<floatsecs>(placeTime).count(), 0,
'f', 2);
571 LOG(VB_GENERAL, LOG_INFO, msg);
582 for (
auto & it : schedList)
593 LOG(VB_SCHEDULE, LOG_INFO,
"--- print list start ---");
594 LOG(VB_SCHEDULE, LOG_INFO,
"Title - Subtitle Ch Station "
595 "Day Start End G I T N Pri");
597 for (
auto *first : list)
599 if (onlyFutureRecordings &&
600 ((first->GetRecordingEndTime() < now &&
601 first->GetScheduledEndTime() < now) ||
602 (first->GetRecordingStartTime() < now && !
Recording(first))))
608 LOG(VB_SCHEDULE, LOG_INFO,
"--- print list end ---");
621 static QString initialOutstr =
" ";
623 static QString initialOutstr =
"";
626 QString outstr = initialOutstr +
prefix;
629 .leftJustified(34 -
prefix.length(),
' ',
true);
631 outstr += QString(
"%1 %2 %3 %4-%5 %6 %7 ")
633 p->GetChanNum().rightJustified(5,
' '),
634 p->GetChannelSchedulingID().leftJustified(7,
' ',
true),
635 p->GetRecordingStartTime().toLocalTime().toString(
"dd hh:mm"),
636 p->GetRecordingEndTime().toLocalTime().toString(
"hh:mm"),
637 p->GetShortInputName().rightJustified(2,
' '),
638 QString::number(
p->GetInputID()).rightJustified(2,
' '));
639 outstr += QString(
"%1 %2 %3")
640 .arg(
toQChar(
p->GetRecordingRuleType()))
642 .arg(
p->GetRecordingPriority());
643 if (
p->GetRecordingPriority2())
644 outstr += QString(
"/%1").arg(
p->GetRecordingPriority2());
646 LOG(VB_SCHEDULE, LOG_INFO, outstr);
655 if (
p->IsSameTitleTimeslotAndChannel(*pginfo))
674 LOG(VB_GENERAL, LOG_INFO,
675 QString(
"Updating status for %1 on cardid [%2] (%3 => %4)")
677 QString::number(
p->GetInputID()),
679 p->GetRecordingRuleType()),
681 p->GetRecordingRuleType())));
689 p->AddHistory(
false);
707 const QDateTime &startts,
709 const QDateTime &recendts)
715 if (
p->GetInputID() == cardid &&
p->GetChanID() == chanid &&
716 p->GetScheduledStartTime() == startts)
718 p->SetRecordingEndTime(recendts);
720 if (
p->GetRecordingStatus() != recstatus)
722 LOG(VB_GENERAL, LOG_INFO,
723 QString(
"Updating status for %1 on cardid [%2] (%3 => %4)")
725 QString::number(
p->GetInputID()),
727 p->GetRecordingRuleType()),
729 p->GetRecordingRuleType())));
735 p->SetRecordingStatus(recstatus);
737 p->AddHistory(
false);
788 LOG(VB_GENERAL, LOG_ERR,
789 QString(
"Failed to change end time on card %1 to %2")
836 for (
auto *sp : slavelist)
842 if (!sp->GetTitle().isEmpty() &&
843 sp->GetScheduledStartTime() == rp->GetScheduledStartTime() &&
844 sp->GetChannelSchedulingID().compare(
845 rp->GetChannelSchedulingID(), Qt::CaseInsensitive) == 0 &&
846 sp->GetTitle().compare(rp->GetTitle(),
847 Qt::CaseInsensitive) == 0)
849 if (sp->GetInputID() == rp->GetInputID() ||
854 rp->SetRecordingStatus(sp->GetRecordingStatus());
856 rp->AddHistory(
false);
857 LOG(VB_GENERAL, LOG_INFO,
858 QString(
"setting %1/%2/\"%3\" as %4")
859 .arg(QString::number(sp->GetInputID()),
860 sp->GetChannelSchedulingID(),
866 LOG(VB_GENERAL, LOG_NOTICE,
867 QString(
"%1/%2/\"%3\" is already recording on card %4")
868 .arg(sp->GetInputID())
869 .arg(sp->GetChannelSchedulingID(),
871 .arg(rp->GetInputID()));
874 else if (sp->GetInputID() == rp->GetInputID() &&
881 rp->AddHistory(
false);
882 LOG(VB_GENERAL, LOG_INFO,
883 QString(
"setting %1/%2/\"%3\" as aborted")
884 .arg(QString::number(rp->GetInputID()),
885 rp->GetChannelSchedulingID(),
890 if (sp->GetInputID() && !found)
892 sp->m_mplexId = sp->QueryMplexID();
896 sp->AddHistory(
false);
897 LOG(VB_GENERAL, LOG_INFO,
898 QString(
"adding %1/%2/\"%3\" as recording")
899 .arg(QString::number(sp->GetInputID()),
900 sp->GetChannelSchedulingID(),
912 if (rp->GetInputID() == cardid &&
921 rp->AddHistory(
false,
false,
true);
926 rp->AddHistory(
false);
929 LOG(VB_GENERAL, LOG_INFO, QString(
"setting %1/%2/\"%3\" as aborted")
930 .arg(QString::number(rp->GetInputID()), rp->GetChannelSchedulingID(),
982 for (
auto it = reclist.begin(); it != reclist.end(); ++it)
1010 *(dreciter++) =
nullptr;
1019 QMap<uint, uint> badinputs;
1034 ++badinputs[
p->GetInputID()];
1037 conflictlist->push_back(
p);
1043 QMap<uint, uint>::iterator it;
1044 for (it = badinputs.begin(); it != badinputs.end(); ++it)
1047 QString(
"Ignored %1 entries for invalid input %2")
1048 .arg(badinputs[it.value()]).arg(it.key()));
1083 bool ignoreinput)
const
1086 for ( ; iter != cardlist.end(); ++iter)
1099 msg = QString(
"comparing '%1' on %2 with '%3' on %4")
1100 .arg(
p->GetTitle(),
p->GetChanNum(),
1104 if (
p->GetInputID() != q->
GetInputID() && !ignoreinput)
1106 const std::vector<unsigned int> &conflicting_inputs =
1108 if (
find(conflicting_inputs.begin(), conflicting_inputs.end(),
1109 q->
GetInputID()) == conflicting_inputs.end())
1112 msg +=
" cardid== ";
1121 msg +=
" no-overlap ";
1128 (((
p->m_mplexId != 0U) &&
p->m_mplexId == q->
m_mplexId) ||
1129 ((
p->m_mplexId == 0U) &&
p->GetChanID() == q->
GetChanID()));
1141 msg +=
" no-overlap ";
1150 LOG(VB_SCHEDULE, LOG_INFO, msg);
1151 LOG(VB_SCHEDULE, LOG_INFO,
1152 QString(
" cardid's: [%1], [%2] Share an input group, "
1153 "mplexid's: %3, %4")
1167 LOG(VB_SCHEDULE, LOG_INFO,
"Found conflict");
1170 *paffinity += affinity;
1175 LOG(VB_SCHEDULE, LOG_INFO,
"No conflict");
1178 *paffinity += affinity;
1186 bool checkAll)
const
1189 auto k = conflictlist.cbegin();
1196 return firstConflict;
1223 for (
auto *q : showinglist)
1232 if (q->IsSameTitleStartTimeAndChannel(*
p))
1238 if (q->GetRecordingStartTime() <
p->GetRecordingStartTime())
1250 p->m_savedrecstatus =
p->GetRecordingStatus();
1258 p->SetRecordingStatus(
p->m_savedrecstatus);
1279 uint bestaffinity = 0;
1281 for (
auto *q : *showinglist)
1287 (q->GetRecordingPriority() <
p->GetRecordingPriority() ||
1288 (q->GetRecordingPriority() ==
p->GetRecordingPriority() &&
1289 q->GetRecordingPriority2() <
p->GetRecordingPriority2())))
1301 if (!
p->IsSameTitleStartTimeAndChannel(*q))
1336 PrintRec(q, QString(
" %1:").arg(affinity));
1337 if (!best || affinity > bestaffinity)
1340 bestaffinity = affinity;
1348 QString msg = QString(
1349 "Moved \"%1\" on chanid: %2 from card: %3 to %4 at %5 "
1350 "to avoid LiveTV conflict")
1351 .arg(
p->GetTitle()).arg(
p->GetChanID())
1354 LOG(VB_GENERAL, LOG_INFO, msg);
1366 p->SetRecordingStatus(oldstatus);
1374 LOG(VB_SCHEDULE, LOG_DEBUG,
1375 "+ = schedule this showing to be recorded");
1376 LOG(VB_SCHEDULE, LOG_DEBUG,
1377 "n: = could schedule this showing with affinity");
1378 LOG(VB_SCHEDULE, LOG_DEBUG,
1379 "n# = could not schedule this showing, with affinity");
1380 LOG(VB_SCHEDULE, LOG_DEBUG,
1381 "! = conflict caused by this showing");
1382 LOG(VB_SCHEDULE, LOG_DEBUG,
1383 "/ = retry this showing, same priority pass");
1384 LOG(VB_SCHEDULE, LOG_DEBUG,
1385 "? = retry this showing, lower priority pass");
1386 LOG(VB_SCHEDULE, LOG_DEBUG,
1387 "> = try another showing for this program");
1388 LOG(VB_SCHEDULE, LOG_DEBUG,
1389 "- = unschedule a showing in favor of another one");
1408 auto levelStart = i;
1409 int recpriority = (*i)->GetRecordingPriority();
1414 (*i)->GetRecordingPriority() != recpriority)
1417 auto sublevelStart = i;
1418 int recpriority2 = (*i)->GetRecordingPriority2();
1419 LOG(VB_SCHEDULE, LOG_DEBUG, QString(
"Trying priority %1/%2...")
1420 .arg(recpriority).arg(recpriority2));
1424 LOG(VB_SCHEDULE, LOG_DEBUG, QString(
"Retrying priority %1/%2...")
1425 .arg(recpriority).arg(recpriority2));
1430 LOG(VB_SCHEDULE, LOG_DEBUG, QString(
"Retrying priority %1/*...")
1440 int recpriority,
int recpriority2)
1446 for ( ; i != end; ++i)
1448 if ((*i)->GetRecordingPriority() != recpriority ||
1449 (*i)->GetRecordingPriority2() != recpriority2 ||
1456 (*i)->GetRecordingPriority() != recpriority ||
1457 (*i)->GetRecordingPriority2() != recpriority2)
1462 uint bestaffinity = 0;
1465 for ( ; i != end; ++i)
1467 if ((*i)->GetRecordingPriority() != recpriority ||
1468 (*i)->GetRecordingPriority2() != recpriority2 ||
1469 (*i)->GetRecordingStartTime() !=
1471 (*i)->GetRecordingRuleID() !=
1473 (*i)->GetTitle() != first->
GetTitle() ||
1488 PrintRec(*i, QString(
" %1#").arg(affinity));
1493 PrintRec(*i, QString(
" %1:").arg(affinity));
1494 if (!best || affinity > bestaffinity)
1497 bestaffinity = affinity;
1518 bool samePriority,
bool livetv)
1522 for ( ; i != end; ++i)
1525 retry_list.push_back(*i);
1527 std::stable_sort(retry_list.begin(), retry_list.end(),
comp_retry);
1529 for (
auto *
p : retry_list)
1548 auto k = conflictlist.cbegin();
1570 int lastrecpri2 = 0;
1605 p->SetRecordingStatus(
p->m_oldrecstatus);
1616 p->ClearInputName();
1634 p->GetRecordingPriority2() >
1638 lastrecpri2 -
p->GetRecordingPriority2());
1653 QMap<int, QDateTime> nextRecMap;
1661 nextRecMap[
p->GetRecordingRuleID()].isNull())
1663 nextRecMap[
p->GetRecordingRuleID()] =
p->GetRecordingStartTime();
1667 p->GetParentRecordingRuleID() > 0 &&
1670 nextRecMap[
p->GetParentRecordingRuleID()].isNull())
1672 nextRecMap[
p->GetParentRecordingRuleID()] =
1673 p->GetRecordingStartTime();
1679 query.
prepare(
"SELECT recordid, next_record FROM record;");
1685 while (query.
next())
1687 int recid = query.
value(0).toInt();
1690 if (next_record == nextRecMap[recid])
1693 if (nextRecMap[recid].isValid())
1695 subquery.
prepare(
"UPDATE record SET next_record = :NEXTREC "
1696 "WHERE recordid = :RECORDID;");
1698 subquery.
bindValue(
":NEXTREC", nextRecMap[recid]);
1699 if (!subquery.
exec())
1702 else if (next_record.isValid())
1704 subquery.
prepare(
"UPDATE record "
1705 "SET next_record = NULL "
1706 "WHERE recordid = :RECORDID;");
1708 if (!subquery.
exec())
1720 strlist << QString::number(retlist.size());
1722 while (!retlist.empty())
1725 p->ToStringList(strlist);
1727 retlist.pop_front();
1738 nullptr,
true); ++i)
1749 bool hasconflicts =
false;
1753 if (recRuleId > 0 &&
1754 p->GetRecordingRuleID() !=
static_cast<uint>(recRuleId))
1757 hasconflicts =
true;
1761 return hasconflicts;
1768 bool hasconflicts =
false;
1772 if (recRuleId > 0 &&
1773 p->GetRecordingRuleID() !=
static_cast<uint>(recRuleId))
1777 hasconflicts =
true;
1781 return hasconflicts;
1788 QMap<QString,ProgramInfo*> recMap;
1805 if (recordedid ==
p->GetRecordingID())
1834 strList << QString::number(static_cast<int>(hasconflicts));
1835 strList << QString::number(retlist.size());
1837 while (!retlist.empty())
1840 p->ToStringList(strList);
1842 retlist.pop_front();
1854 strList << QString::number(schedlist.size());
1856 while (!schedlist.empty())
1861 schedlist.pop_front();
1876 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"AddRecording() recid: %1")
1882 p->IsSameTitleTimeslotAndChannel(pi))
1884 LOG(VB_GENERAL, LOG_INFO,
LOC +
"Not adding recording, " +
1885 QString(
"'%1' is already in reclist.")
1891 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
1892 QString(
"Adding '%1' to reclist.").arg(pi.
GetTitle()));
1895 new_pi->m_mplexId = new_pi->QueryMplexID();
1896 new_pi->m_sgroupId =
m_sinputInfoMap[new_pi->GetInputID()].m_sgroupId;
1902 new_pi->AddHistory(
false);
1905 new_pi->GetRecordingRule();
1909 QString(
"AddRecording %1").arg(pi.
GetTitle()));
1917 LOG(VB_GENERAL, LOG_ERR,
LOC +
1918 "IsBusyRecording() -> true, no tvList or no rcinfo");
1929 bool is_busy = rctv1->
IsBusy(&busy_input, -1s);
1941 const std::vector<unsigned int> &inputids =
m_sinputInfoMap[inputid].m_conflictingInputs;
1942 std::vector<unsigned int> &group_inputs =
m_sinputInfoMap[inputid].m_groupInputs;
1943 for (
uint id : inputids)
1948 LOG(VB_SCHEDULE, LOG_ERR,
LOC +
1949 QString(
"IsBusyRecording() -> true, rctv(NULL) for input %2")
1956 if (rctv2->
IsBusy(&busy_input, -1s))
1977 std::find(group_inputs.begin(), group_inputs.end(),
1978 id) != group_inputs.end())
1995 query.
prepare(
"UPDATE oldrecorded SET recstatus = :RSABORTED "
1996 " WHERE recstatus = :RSRECORDING OR "
1997 " recstatus = :RSTUNING OR "
1998 " recstatus = :RSFAILING");
2007 query.
prepare(
"UPDATE oldrecorded SET recstatus = :RSMISSED "
2008 "WHERE recstatus = :RSWILLRECORD OR "
2009 " recstatus = :RSPENDING");
2018 query.
prepare(
"UPDATE oldrecorded SET recstatus = :RSPREVIOUS "
2019 "WHERE recstatus = :RSCURRENT");
2028 query.
prepare(
"UPDATE oldrecorded SET future = 0 "
2029 "WHERE future > 0 AND "
2030 " endtime < (NOW() - INTERVAL 475 MINUTE)");
2057 std::chrono::seconds prerollseconds = 0s;
2058 std::chrono::seconds wakeThreshold = 5min;
2061 bool blockShutdown =
2063 bool firstRun =
true;
2066 QDateTime idleSince = QDateTime();
2067 std::chrono::seconds schedRunTime = 0s;
2068 bool statuschanged =
false;
2070 QDateTime nextWakeTime = nextStartTime;
2084 nextWakeTime = std::min(nextWakeTime, nextStartTime);
2086 auto secs_to_next = std::chrono::seconds(curtime.secsTo(nextStartTime));
2087 auto sched_sleep = std::max(std::chrono::milliseconds(curtime.msecsTo(nextWakeTime)), 0ms);
2089 sched_sleep = std::min(sched_sleep, 15000ms);
2091 int const kSleepCheck = 300;
2092 bool checkSlaves = curtime >= nextSleepCheck;
2096 if ((secs_to_next > -60s && secs_to_next < schedRunTime) ||
2097 (!haveRequests && !checkSlaves))
2099 if (sched_sleep > 0ms)
2101 LOG(VB_SCHEDULE, LOG_INFO,
2102 QString(
"sleeping for %1 ms "
2103 "(s2n: %2 sr: %3 qr: %4 cs: %5)")
2104 .arg(sched_sleep.count()).arg(secs_to_next.count()).arg(schedRunTime.count())
2105 .arg(haveRequests).arg(checkSlaves));
2127 wakeThreshold = std::max(wakeThreshold, prerollseconds + 120s);
2129 QElapsedTimer
t;
t.start();
2132 statuschanged =
true;
2135 auto elapsed = std::chrono::ceil<std::chrono::seconds>(std::chrono::milliseconds(
t.elapsed()));
2136 schedRunTime = std::max(elapsed + elapsed/2 + 2s, schedRunTime);
2157 checkSlaves =
false;
2167 nextWakeTime = nextSleepCheck;
2171 for ( ; startIter !=
m_recList.end(); ++startIter)
2173 if ((*startIter)->GetRecordingStatus() !=
2174 (*startIter)->m_oldrecstatus)
2182 for (
auto it = startIter; it !=
m_recList.end(); ++it)
2184 auto secsleft = std::chrono::seconds(curtime.secsTo((*it)->GetRecordingStartTime()));
2185 auto timeBeforePreroll = secsleft - prerollseconds;
2186 if (timeBeforePreroll <= wakeThreshold)
2191 if (timeBeforePreroll > 0s)
2193 std::chrono::seconds waitpending;
2194 if (timeBeforePreroll > 120s)
2195 waitpending = timeBeforePreroll -120s;
2197 waitpending = std::min(timeBeforePreroll, 30s);
2211 for (
auto it = startIter; it !=
m_recList.end() && !done; ++it)
2214 **it, statuschanged, nextStartTime, nextWakeTime,
2238 if (idleSince.isValid())
2243 else if (idleSince.addSecs((
idleTimeoutSecs - 30s).count()) <= curtime)
2249 statuschanged =
false;
2256 const QString &title,
const QString &subtitle,
2257 const QString &descrip,
2258 const QString &programid)
2261 QString filterClause;
2264 if (!title.isEmpty())
2266 filterClause +=
"AND p.title = :TITLE ";
2267 bindings[
":TITLE"] = title;
2271 if (programid !=
"**any**")
2273 filterClause +=
"AND (0 ";
2274 if (!subtitle.isEmpty())
2277 filterClause +=
"OR p.subtitle = :SUBTITLE1 "
2278 "OR p.description = :SUBTITLE2 ";
2279 bindings[
":SUBTITLE1"] = subtitle;
2280 bindings[
":SUBTITLE2"] = subtitle;
2282 if (!descrip.isEmpty())
2285 filterClause +=
"OR p.description = :DESCRIP1 "
2286 "OR p.subtitle = :DESCRIP2 ";
2287 bindings[
":DESCRIP1"] = descrip;
2288 bindings[
":DESCRIP2"] = descrip;
2290 if (!programid.isEmpty())
2292 filterClause +=
"OR p.programid = :PROGRAMID ";
2293 bindings[
":PROGRAMID"] = programid;
2295 filterClause +=
") ";
2298 query.
prepare(QString(
"UPDATE recordmatch rm "
2300 " ON rm.recordid = r.recordid "
2301 "INNER JOIN program p "
2302 " ON rm.chanid = p.chanid "
2303 " AND rm.starttime = p.starttime "
2304 " AND rm.manualid = p.manualid "
2305 "SET oldrecduplicate = -1 "
2306 "WHERE p.generic = 0 "
2307 " AND r.type NOT IN (%2, %3, %4) ")
2313 MSqlBindings::const_iterator it;
2314 for (it = bindings.cbegin(); it != bindings.cend(); ++it)
2319 if (findid && programid !=
"**any**")
2321 query.
prepare(
"UPDATE recordmatch rm "
2322 "SET oldrecduplicate = -1 "
2323 "WHERE rm.recordid = :RECORDID "
2324 " AND rm.findid = :FINDID");
2338 auto fillstart = nowAsDuration<std::chrono::microseconds>();
2340 bool deleteFuture =
false;
2341 bool runCheck =
false;
2347 if (!request.empty())
2349 tokens = request[0].split(
' ', Qt::SkipEmptyParts);
2352 if (request.empty() || tokens.empty())
2354 LOG(VB_GENERAL, LOG_ERR,
"Empty Reschedule request received");
2358 LOG(VB_GENERAL, LOG_INFO, QString(
"Reschedule requested for %1")
2359 .arg(request.join(
" | ")));
2361 if (tokens[0] ==
"MATCH")
2363 if (tokens.size() < 5)
2365 LOG(VB_GENERAL, LOG_ERR,
2366 QString(
"Invalid RescheduleMatch request received (%1)")
2371 uint recordid = tokens[1].toUInt();
2372 uint sourceid = tokens[2].toUInt();
2373 uint mplexid = tokens[3].toUInt();
2375 deleteFuture =
true;
2383 else if (tokens[0] ==
"CHECK")
2385 if (tokens.size() < 4 || request.size() < 5)
2387 LOG(VB_GENERAL, LOG_ERR,
2388 QString(
"Invalid RescheduleCheck request received (%1)")
2393 uint recordid = tokens[2].toUInt();
2394 uint findid = tokens[3].toUInt();
2395 const QString& title = request[1];
2396 const QString& subtitle = request[2];
2397 const QString& descrip = request[3];
2398 const QString& programid = request[4];
2407 else if (tokens[0] !=
"PLACE")
2409 LOG(VB_GENERAL, LOG_ERR,
2410 QString(
"Unknown Reschedule request received (%1)")
2420 query.
prepare(
"DELETE oldrecorded FROM oldrecorded "
2421 "LEFT JOIN recordmatch ON "
2422 " recordmatch.chanid = oldrecorded.chanid AND "
2423 " recordmatch.starttime = oldrecorded.starttime "
2424 "WHERE oldrecorded.future > 0 AND "
2425 " recordmatch.recordid IS NULL");
2430 auto fillend = nowAsDuration<std::chrono::microseconds>();
2431 auto matchTime = fillend - fillstart;
2433 LOG(VB_SCHEDULE, LOG_INFO,
"CreateTempTables...");
2436 fillstart = nowAsDuration<std::chrono::microseconds>();
2439 LOG(VB_SCHEDULE, LOG_INFO,
"UpdateDuplicates...");
2442 fillend = nowAsDuration<std::chrono::microseconds>();
2443 auto checkTime = fillend - fillstart;
2445 fillstart = nowAsDuration<std::chrono::microseconds>();
2447 fillend = nowAsDuration<std::chrono::microseconds>();
2448 auto placeTime = fillend - fillstart;
2450 LOG(VB_SCHEDULE, LOG_INFO,
"DeleteTempTables...");
2460 LOG(VB_GENERAL, LOG_INFO,
"Reschedule interrupted, will retry");
2465 msg = QString(
"Scheduled %1 items in %2 "
2466 "= %3 match + %4 check + %5 place")
2468 .arg(duration_cast<floatsecs>(matchTime + checkTime + placeTime).count(), 0,
'f', 1)
2469 .arg(duration_cast<floatsecs>(matchTime).count(), 0,
'f', 2)
2470 .arg(duration_cast<floatsecs>(checkTime).count(), 0,
'f', 2)
2471 .arg(duration_cast<floatsecs>(placeTime).count(), 0,
'f', 2);
2472 LOG(VB_GENERAL, LOG_INFO, msg);
2477 if (
p->GetRecordingStatus() !=
p->m_oldrecstatus)
2480 p->AddHistory(
false,
false,
false);
2484 p->AddHistory(
false,
false,
false);
2486 p->AddHistory(
false,
false,
true);
2488 else if (
p->m_future)
2494 p->m_future =
false;
2503 std::chrono::seconds prerollseconds,
2506 bool blockShutdown =
true;
2511 QString startupParam =
"user";
2515 for ( ; firstRunIter !=
m_recList.end(); ++firstRunIter)
2526 ((std::chrono::seconds(curtime.secsTo((*firstRunIter)->GetRecordingStartTime())) -
2529 LOG(VB_GENERAL, LOG_INFO,
LOC +
"AUTO-Startup assumed");
2530 startupParam =
"auto";
2534 blockShutdown =
false;
2538 LOG(VB_GENERAL, LOG_INFO,
LOC +
"Seem to be woken up by USER");
2550 return blockShutdown;
2556 static constexpr std::array<const std::chrono::seconds,4> kSysEventSecs = { 120s, 90s, 60s, 30s };
2560 auto secsleft = std::chrono::seconds(curtime.secsTo(nextrectime));
2570 bool pendingEventSent =
false;
2571 for (
size_t i = 0; i < kSysEventSecs.size(); i++)
2573 auto pending_secs = std::max((secsleft - prerollseconds), 0s);
2574 if ((pending_secs <= kSysEventSecs[i]) &&
2577 if (!pendingEventSent)
2580 QString(
"REC_PENDING SECS %1").arg(pending_secs.count()), &ri);
2584 pendingEventSent =
true;
2590 for (
size_t i = 0; i < kSysEventSecs.size(); i++)
2598 keys.insert(rec->MakeUniqueKey());
2599 keys.insert(
"something");
2602 QSet<QString>::iterator sit =
m_sysEvents[i].begin();
2605 if (!keys.contains(*sit))
2616 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
2617 QString(
"Slave Backend %1 is being awakened to record: %2")
2624 ((secsleft - prerollseconds) < 210s) &&
2628 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
2629 QString(
"Slave Backend %1 not available yet, "
2630 "trying to wake it up again.")
2637 ((secsleft - prerollseconds) < 150s) &&
2640 LOG(VB_GENERAL, LOG_WARNING,
LOC +
2641 QString(
"Slave Backend %1 has NOT come "
2642 "back from sleep yet in 150 seconds. Setting "
2643 "slave status to unknown and attempting "
2644 "to reschedule around its tuners.")
2647 for (
auto * enc : std::as_const(*
m_tvList))
2659 QDateTime &nextStartTime, QDateTime &nextWakeTime,
2660 std::chrono::seconds prerollseconds)
2667 std::chrono::seconds origprerollseconds = prerollseconds;
2674 auto nextwake = std::chrono::seconds(nextWakeTime.secsTo(nextrectime));
2675 if (nextwake - prerollseconds > 5min)
2677 nextStartTime = std::min(nextStartTime, nextrectime);
2681 if (curtime < nextrectime)
2682 nextWakeTime = std::min(nextWakeTime, nextrectime);
2688 auto secsleft = std::chrono::seconds(curtime.secsTo(nextrectime));
2693 if (secsleft - prerollseconds > 1min)
2695 nextStartTime = std::min(nextStartTime, nextrectime.addSecs(-30));
2696 nextWakeTime = std::min(nextWakeTime,
2697 nextrectime.addSecs(-prerollseconds.count() - 60));
2710 if (secsleft - prerollseconds > 35s)
2712 nextStartTime = std::min(nextStartTime, nextrectime.addSecs(-30));
2713 nextWakeTime = std::min(nextWakeTime,
2714 nextrectime.addSecs(-prerollseconds.count() - 35));
2723 QString msg = QString(
"Invalid cardid [%1] for %2")
2725 LOG(VB_GENERAL, LOG_ERR,
LOC + msg);
2729 statuschanged =
true;
2737 QString msg = QString(
"SUPPRESSED recording \"%1\" on channel: "
2738 "%2 on cardid: [%3], sourceid %4. Tuner "
2739 "is locked by an external application.")
2744 LOG(VB_GENERAL, LOG_NOTICE, msg);
2748 statuschanged =
true;
2759 if (prerollseconds > 0s)
2767 if (isBusyRecording)
2770 nextWakeTime = std::min(nextWakeTime, curtime.addSecs(5));
2771 prerollseconds = 0s;
2775 if (secsleft - prerollseconds > 30s)
2777 nextStartTime = std::min(nextStartTime, nextrectime.addSecs(-30));
2778 nextWakeTime = std::min(nextWakeTime,
2779 nextrectime.addSecs(-prerollseconds.count() - 30));
2787 LOG(VB_SCHEDULE, LOG_WARNING,
2788 QString(
"WARNING: Slave Backend %1 has NOT come "
2789 "back from sleep yet. Recording can "
2790 "not begin yet for: %2")
2796 LOG(VB_SCHEDULE, LOG_WARNING,
2797 QString(
"WARNING: Slave Backend %1 has NOT come "
2798 "back from sleep yet. Setting slave "
2799 "status to unknown and attempting "
2800 "to reschedule around its tuners.")
2803 for (
auto * enc : std::as_const(*
m_tvList))
2812 nextStartTime = std::min(nextStartTime, nextrectime);
2813 nextWakeTime = std::min(nextWakeTime, curtime.addSecs(1));
2820 QString recording_dir;
2839 MythEvent me(QString(
"ADD_CHILD_INPUT %1")
2842 nextWakeTime = std::min(nextWakeTime, curtime.addSecs(1));
2852 nexttv->
RecordPending(&tempri, std::max(secsleft, 0s),
false);
2858 if (secsleft - prerollseconds > 0s)
2860 nextStartTime = std::min(nextStartTime, nextrectime);
2861 nextWakeTime = std::min(nextWakeTime,
2862 nextrectime.addSecs(-prerollseconds.count()));
2867#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
2868 recstartts = QDateTime(
2870 QTime(recstartts.time().hour(), recstartts.time().minute()), Qt::UTC);
2872 recstartts = QDateTime(
2874 QTime(recstartts.time().hour(), recstartts.time().minute()),
2875 QTimeZone(QTimeZone::UTC));
2880 QString details = QString(
"%1: channel %2 on cardid [%3], sourceid %4")
2907 statuschanged =
true;
2920 bool doSchedAfterStart =
2930 msg = QString(
"Started recording");
2932 msg = QString(
"Tuning recording");
2934 msg = QString(
"Canceled recording (%1)")
2936 LOG(VB_GENERAL, LOG_INFO, QString(
"%1: %2").arg(msg, details));
2944 MythEvent me(QString(
"FORCE_DELETE_RECORDING %1 %2")
2952 std::chrono::seconds prerollseconds)
2957 LOG(VB_SCHEDULE, LOG_DEBUG,
2958 QString(
"Assigning input for %1/%2/\"%3\"")
2969 for (
uint i = 0; !bestid && i < inputs.size(); ++i)
2971 uint inputid = inputs[i];
2979 auto recstarttime = std::chrono::seconds(now.secsTo(
p->GetRecordingStartTime()));
2980 if (recstarttime > prerollseconds + 60s)
2982 if (
p->GetInputID() != inputid)
2999 LOG(VB_SCHEDULE, LOG_DEBUG,
3000 QString(
"Input %1 has a pending recording").arg(inputid));
3009 LOG(VB_SCHEDULE, LOG_DEBUG,
3010 QString(
"Input %1 is recording").arg(inputid));
3015 LOG(VB_SCHEDULE, LOG_DEBUG,
3016 QString(
"Input %1 is recording but will be free")
3025 LOG(VB_SCHEDULE, LOG_DEBUG,
3026 QString(
"Input %1 is recording but has to stop")
3032 LOG(VB_SCHEDULE, LOG_DEBUG,
3033 QString(
"Input %1 is recording but could be free")
3045 bool isbusy = rctv->
IsBusy(&busy_info, -1s);
3051 LOG(VB_SCHEDULE, LOG_DEBUG,
3052 QString(
"Input %1 is free").arg(inputid));
3058 LOG(VB_SCHEDULE, LOG_DEBUG,
3059 QString(
"Input %1 is on livetv but has to stop")
3070 LOG(VB_SCHEDULE, LOG_INFO,
3071 QString(
"Assigned input %1 for %2/%3/\"%4\"")
3079 LOG(VB_SCHEDULE, LOG_WARNING,
3080 QString(
"Failed to assign input for %1/%2/\"%3\"")
3086 return bestid != 0U;
3096 bool &blockShutdown, QDateTime &idleSince,
3097 std::chrono::seconds prerollseconds,
3103 uint logmask = VB_IDLE;
3104 int now = QTime::currentTime().msecsSinceStartOfDay();
3105 int tm = std::chrono::milliseconds(now) / 15min;
3108 logmask = VB_GENERAL;
3127 LOG(VB_GENERAL, LOG_NOTICE,
"Client is connected, removing startup block on shutdown");
3128 blockShutdown =
false;
3139 bool recording =
false;
3142 QMap<int, EncoderLink *>::const_iterator it;
3146 if ((*it)->IsBusy())
3160 if (!blocking && !recording && !activeJobs && !delay)
3167 if (idleSince.isValid())
3169 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN -1"));
3172 idleSince = QDateTime();
3177 if (statuschanged || !idleSince.isValid())
3179 bool wasValid = idleSince.isValid();
3181 idleSince = curtime;
3184 for ( ; idleIter !=
m_recList.end(); ++idleIter)
3186 if ((*idleIter)->GetRecordingStatus() ==
3188 (*idleIter)->GetRecordingStatus() ==
3195 auto recstarttime = std::chrono::seconds(curtime.secsTo((*idleIter)->GetRecordingStartTime()));
3198 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3199 "a recording is due to "
3201 idleSince = QDateTime();
3212 if (guideRunTime.isValid() &&
3216 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3217 "mythfilldatabase is due to "
3219 idleSince = QDateTime();
3224 if (idleSince.isValid())
3227 if (wasValid && !idleSince.isValid())
3229 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN -1"));
3234 if (idleSince.isValid())
3246 LOG(VB_GENERAL, LOG_WARNING,
3247 "Waited more than 60"
3248 " seconds for shutdown to complete"
3249 " - resetting idle time");
3250 idleSince = QDateTime();
3256 blockShutdown, logmask))
3262 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN -1"));
3268 auto itime = std::chrono::seconds(idleSince.secsTo(curtime));
3272 msg = QString(
"I\'m idle now... shutdown will "
3273 "occur in %1 seconds.")
3275 LOG(VB_GENERAL, LOG_NOTICE, msg);
3276 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN %1")
3283 msg = QString(
"%1 secs left to system shutdown!").arg(remain);
3284 LOG(logmask, LOG_NOTICE, msg);
3285 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN %1").arg(remain));
3294 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3295 "of an active encoder");
3297 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3298 "of a connected client");
3301 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3305 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3306 "of delay request from external application");
3309 if (idleSince.isValid())
3311 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN -1"));
3314 idleSince = QDateTime();
3321 QDateTime &idleSince,
3322 bool &blockShutdown,
uint logmask)
3324 bool retval =
false;
3334 LOG(logmask, LOG_INFO,
3335 "CheckShutdownServer returned - OK to shutdown");
3339 LOG(logmask, LOG_NOTICE,
3340 "CheckShutdownServer returned - Not OK to shutdown");
3342 idleSince = QDateTime();
3345 LOG(logmask, LOG_NOTICE,
3346 "CheckShutdownServer returned - Not OK to shutdown, "
3354 idleSince = QDateTime();
3359 m_noAutoShutdown =
true;
3363 LOG(VB_GENERAL, LOG_NOTICE,
3364 "CheckShutdownServer returned - Not OK");
3367 LOG(VB_GENERAL, LOG_NOTICE, QString(
3368 "CheckShutdownServer returned - Error %1").arg(state));
3381 QDateTime &idleSince)
3386 for ( ; recIter !=
m_recList.end(); ++recIter)
3394 QDateTime restarttime;
3399 .addSecs(-prerollseconds.count());
3407 && guideRefreshTime.isValid()
3409 && (restarttime.isNull() || guideRefreshTime < restarttime))
3410 restarttime = guideRefreshTime;
3412 if (restarttime.isValid())
3416 restarttime = restarttime.addSecs((-1LL) * add);
3419 "hh:mm yyyy-MM-dd");
3421 "echo \'Wakeuptime would "
3422 "be $time if command "
3425 if (setwakeup_cmd.isEmpty())
3427 LOG(VB_GENERAL, LOG_NOTICE,
3428 "SetWakeuptimeCommand is empty, shutdown aborted");
3429 idleSince = QDateTime();
3433 if (wakeup_timeformat ==
"time_t")
3436 setwakeup_cmd.replace(
"$time",
3437 time_ts.setNum(restarttime.toSecsSinceEpoch())
3442 setwakeup_cmd.replace(
3443 "$time", restarttime.toLocalTime().toString(wakeup_timeformat));
3446 LOG(VB_GENERAL, LOG_NOTICE,
3447 QString(
"Running the command to set the next "
3448 "scheduled wakeup time :-\n\t\t\t\t") + setwakeup_cmd);
3453 LOG(VB_GENERAL, LOG_ERR,
3454 "SetWakeuptimeCommand failed, shutdown aborted");
3455 idleSince = QDateTime();
3470 "sudo /sbin/halt -p");
3472 if (!halt_cmd.isEmpty())
3477 LOG(VB_GENERAL, LOG_NOTICE,
3478 QString(
"Running the command to shutdown "
3479 "this computer :-\n\t\t\t\t") + halt_cmd);
3486 LOG(VB_GENERAL, LOG_ERR,
"ServerHaltCommand failed, shutdown aborted");
3491 idleSince = QDateTime();
3497 std::chrono::seconds prerollseconds = 0s;
3498 std::chrono::seconds secsleft = 0s;
3502 bool someSlavesCanSleep =
false;
3503 for (
auto * enc : std::as_const(*
m_tvList))
3505 if (enc->CanSleep())
3506 someSlavesCanSleep =
true;
3509 if (!someSlavesCanSleep)
3512 LOG(VB_SCHEDULE, LOG_INFO,
3513 "Scheduler, Checking for slaves that can be shut down");
3515 auto sleepThreshold =
3518 LOG(VB_SCHEDULE, LOG_DEBUG,
3519 QString(
" Getting list of slaves that will be active in the "
3520 "next %1 minutes.") .arg(duration_cast<std::chrono::minutes>(sleepThreshold).count()));
3522 LOG(VB_SCHEDULE, LOG_DEBUG,
"Checking scheduler's reclist");
3524 QStringList SlavesInUse;
3534 auto recstarttime = std::chrono::seconds(curtime.secsTo(pginfo->GetRecordingStartTime()));
3535 secsleft = recstarttime - prerollseconds;
3536 if (secsleft > sleepThreshold)
3541 EncoderLink *enc = (*m_tvList)[pginfo->GetInputID()];
3548 LOG(VB_SCHEDULE, LOG_DEBUG,
3549 QString(
" Slave %1 will be in use in %2 minutes")
3551 .arg(duration_cast<std::chrono::minutes>(secsleft).count()));
3555 LOG(VB_SCHEDULE, LOG_DEBUG,
3556 QString(
" Slave %1 is in use currently "
3565 LOG(VB_SCHEDULE, LOG_DEBUG,
" Checking inuseprograms table:");
3568 query.
prepare(
"SELECT DISTINCT hostname, recusage FROM inuseprograms "
3569 "WHERE lastupdatetime > :ONEHOURAGO ;");
3570 query.
bindValue(
":ONEHOURAGO", oneHourAgo);
3573 while(query.
next()) {
3574 SlavesInUse << query.
value(0).toString();
3575 LOG(VB_SCHEDULE, LOG_DEBUG,
3576 QString(
" Slave %1 is marked as in use by a %2")
3577 .arg(query.
value(0).toString(),
3578 query.
value(1).toString()));
3582 LOG(VB_SCHEDULE, LOG_DEBUG, QString(
" Shutting down slaves which will "
3583 "be inactive for the next %1 minutes and can be put to sleep.")
3584 .arg(sleepThreshold.count() / 60));
3586 for (
auto * enc : std::as_const(*
m_tvList))
3588 if ((!enc->IsLocal()) &&
3590 (!SlavesInUse.contains(enc->GetHostName())) &&
3591 (!enc->IsFallingAsleep()))
3593 QString sleepCommand =
3595 enc->GetHostName());
3596 QString wakeUpCommand =
3598 enc->GetHostName());
3600 if (!sleepCommand.isEmpty() && !wakeUpCommand.isEmpty())
3602 QString thisHost = enc->GetHostName();
3604 LOG(VB_SCHEDULE, LOG_DEBUG,
3605 QString(
" Commanding %1 to go to sleep.")
3608 if (enc->GoToSleep())
3610 for (
auto * slv : std::as_const(*
m_tvList))
3612 if (slv->GetHostName() == thisHost)
3614 LOG(VB_SCHEDULE, LOG_DEBUG,
3615 QString(
" Marking card %1 on slave %2 "
3616 "as falling asleep.")
3617 .arg(slv->GetInputID())
3618 .arg(slv->GetHostName()));
3625 LOG(VB_GENERAL, LOG_ERR,
LOC +
3626 QString(
"Unable to shutdown %1 slave backend, setting "
3627 "sleep status to undefined.").arg(thisHost));
3628 for (
auto * slv : std::as_const(*
m_tvList))
3630 if (slv->GetHostName() == thisHost)
3643 LOG(VB_GENERAL, LOG_NOTICE,
3644 QString(
"Tried to Wake Up %1, but this is the "
3645 "master backend and it is not asleep.")
3646 .arg(slaveHostname));
3653 if (wakeUpCommand.isEmpty()) {
3654 LOG(VB_GENERAL, LOG_NOTICE,
3655 QString(
"Trying to Wake Up %1, but this slave "
3656 "does not have a WakeUpCommand set.").arg(slaveHostname));
3658 for (
auto * enc : std::as_const(*
m_tvList))
3660 if (enc->GetHostName() == slaveHostname)
3668 for (
auto * enc : std::as_const(*
m_tvList))
3670 if (setWakingStatus && (enc->GetHostName() == slaveHostname))
3672 enc->SetLastWakeTime(curtime);
3677 LOG(VB_SCHEDULE, LOG_NOTICE, QString(
"Executing '%1' to wake up slave.")
3678 .arg(wakeUpCommand));
3690 QStringList SlavesThatCanWake;
3692 for (
auto * enc : std::as_const(*
m_tvList))
3697 thisSlave = enc->GetHostName();
3701 (!SlavesThatCanWake.contains(thisSlave)))
3702 SlavesThatCanWake << thisSlave;
3706 for (; slave < SlavesThatCanWake.count(); slave++)
3708 thisSlave = SlavesThatCanWake[slave];
3709 LOG(VB_SCHEDULE, LOG_NOTICE,
3710 QString(
"Scheduler, Sending wakeup command to slave: %1")
3720 query.
prepare(QString(
"SELECT type,title,subtitle,description,"
3721 "station,startdate,starttime,"
3722 "enddate,endtime,season,episode,inetref,last_record "
3725 if (!query.
exec() || query.
size() != 1)
3735 QString title = query.
value(1).toString();
3736 QString subtitle = query.
value(2).toString();
3737 QString description = query.
value(3).toString();
3738 QString station = query.
value(4).toString();
3739#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
3740 QDateTime startdt = QDateTime(query.
value(5).toDate(),
3741 query.
value(6).toTime(), Qt::UTC);
3742 int duration = startdt.secsTo(
3743 QDateTime(query.
value(7).toDate(),
3744 query.
value(8).toTime(), Qt::UTC));
3746 QDateTime startdt = QDateTime(query.
value(5).toDate(),
3747 query.
value(6).toTime(),
3748 QTimeZone(QTimeZone::UTC));
3749 int duration = startdt.secsTo(
3750 QDateTime(query.
value(7).toDate(),
3751 query.
value(8).toTime(),
3752 QTimeZone(QTimeZone::UTC)));
3755 int season = query.
value(9).toInt();
3756 int episode = query.
value(10).toInt();
3757 QString inetref = query.
value(11).toString();
3761 QDate originalairdate = QDate(query.
value(12).toDate());
3763 if (description.isEmpty())
3764 description = startdt.toLocalTime().toString();
3766 query.
prepare(
"SELECT chanid from channel "
3767 "WHERE deleted IS NULL AND callsign = :STATION");
3775 std::vector<unsigned int> chanidlist;
3776 while (query.
next())
3777 chanidlist.push_back(query.
value(0).toUInt());
3781 bool weekday =
false;
3783 QDateTime lstartdt = startdt.toLocalTime();
3798 weekday = (lstartdt.date().dayOfWeek() < 6);
3799 daysoff = lstartdt.date().daysTo(
3801#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
3802 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3803 lstartdt.time(), Qt::LocalTime).toUTC();
3805 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3807 QTimeZone(QTimeZone::LocalTime)
3815 daysoff = lstartdt.date().daysTo(
3817 daysoff = (daysoff + 6) / 7 * 7;
3818#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
3819 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3820 lstartdt.time(), Qt::LocalTime).toUTC();
3822 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3824 QTimeZone(QTimeZone::LocalTime)
3829 LOG(VB_GENERAL, LOG_ERR,
3830 QString(
"Invalid rectype for manual recordid %1").arg(recordid));
3836 for (
uint id : chanidlist)
3838 if (weekday && startdt.toLocalTime().date().dayOfWeek() >= 6)
3841 query.
prepare(
"REPLACE INTO program (chanid, starttime, endtime,"
3842 " title, subtitle, description, manualid,"
3843 " season, episode, inetref, originalairdate, generic) "
3844 "VALUES (:CHANID, :STARTTIME, :ENDTIME, :TITLE,"
3845 " :SUBTITLE, :DESCRIPTION, :RECORDID, "
3846 " :SEASON, :EPISODE, :INETREF, :ORIGINALAIRDATE, 1)");
3849 query.
bindValue(
":ENDTIME", startdt.addSecs(duration));
3852 query.
bindValue(
":DESCRIPTION", description);
3856 query.
bindValue(
":ORIGINALAIRDATE", originalairdate);
3865 daysoff += skipdays;
3866#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
3867 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3868 lstartdt.time(), Qt::LocalTime).toUTC();
3870 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3872 QTimeZone(QTimeZone::LocalTime)
3886 query = QString(
"SELECT recordid,search,subtitle,description "
3887 "FROM %1 WHERE search <> %2 AND "
3888 "(recordid = %3 OR %4 = 0) ")
3900 while (result.
next())
3902 QString
prefix = QString(
":NR%1").arg(count);
3903 qphrase = result.
value(3).toString();
3909 LOG(VB_GENERAL, LOG_ERR,
3910 QString(
"Invalid search key in recordid %1")
3911 .arg(result.
value(0).toString()));
3915 QString bindrecid =
prefix +
"RECID";
3916 QString bindphrase =
prefix +
"PHRASE";
3917 QString bindlikephrase1 =
prefix +
"LIKEPHRASE1";
3918 QString bindlikephrase2 =
prefix +
"LIKEPHRASE2";
3919 QString bindlikephrase3 =
prefix +
"LIKEPHRASE3";
3921 bindings[bindrecid] = result.
value(0).toString();
3927 qphrase.remove(
';');
3928 from << result.
value(2).toString();
3929 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
3930 QString(
" AND program.manualid = 0 AND ( %2 )")
3934 bindings[bindlikephrase1] = QString(
"%") + qphrase +
"%";
3936 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
" AND "
3937 "program.manualid = 0 AND "
3938 "program.title LIKE " + bindlikephrase1);
3941 bindings[bindlikephrase1] = QString(
"%") + qphrase +
"%";
3942 bindings[bindlikephrase2] = QString(
"%") + qphrase +
"%";
3943 bindings[bindlikephrase3] = QString(
"%") + qphrase +
"%";
3945 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
3946 " AND program.manualid = 0"
3947 " AND (program.title LIKE " + bindlikephrase1 +
3948 " OR program.subtitle LIKE " + bindlikephrase2 +
3949 " OR program.description LIKE " + bindlikephrase3 +
")");
3952 bindings[bindphrase] = qphrase;
3953 from <<
", people, credits";
3954 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
" AND "
3955 "program.manualid = 0 AND "
3956 "people.name LIKE " + bindphrase +
" AND "
3957 "credits.person = people.person AND "
3958 "program.chanid = credits.chanid AND "
3959 "program.starttime = credits.starttime");
3964 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
3966 QString(
"program.manualid = %1.recordid ")
3970 LOG(VB_GENERAL, LOG_ERR,
3971 QString(
"Unknown RecSearchType (%1) for recordid %2")
3972 .arg(result.
value(1).toInt())
3973 .arg(result.
value(0).toString()));
3974 bindings.remove(bindrecid);
3981 if (recordid == 0 || from.count() == 0)
3983 QString recidmatch =
"";
3985 recidmatch =
"RECTABLE.recordid = :NRRECORDID AND ";
3986 QString s1 = recidmatch +
3987 "RECTABLE.type <> :NRTEMPLATE AND "
3988 "RECTABLE.search = :NRST AND "
3989 "program.manualid = 0 AND "
3990 "program.title = RECTABLE.title ";
3992 QString s2 = recidmatch +
3993 "RECTABLE.type <> :NRTEMPLATE AND "
3994 "RECTABLE.search = :NRST AND "
3995 "program.manualid = 0 AND "
3996 "program.seriesid <> '' AND "
3997 "program.seriesid = RECTABLE.seriesid ";
4007 bindings[
":NRRECORDID"] = recordid;
4013" WHEN RECTABLE.type IN (%1, %2, %3) THEN 0 "
4014" WHEN RECTABLE.type IN (%4, %5, %6) THEN -1 "
4015" ELSE (program.generic - 1) "
4021"(CASE RECTABLE.type "
4023" THEN RECTABLE.findid "
4025" THEN to_days(date_sub(convert_tz(program.starttime, 'UTC', 'SYSTEM'), "
4026" interval time_format(RECTABLE.findtime, '%H:%i') hour_minute)) "
4028" THEN floor((to_days(date_sub(convert_tz(program.starttime, 'UTC', "
4029" 'SYSTEM'), interval time_format(RECTABLE.findtime, '%H:%i') "
4030" hour_minute)) - RECTABLE.findday)/7) * 7 + RECTABLE.findday "
4032" THEN RECTABLE.findid "
4041 const QDateTime &maxstarttime)
4045 QString deleteClause;
4046 QString filterClause = QString(
" AND program.endtime > "
4047 "(NOW() - INTERVAL 480 MINUTE)");
4051 deleteClause +=
" AND recordmatch.recordid = :RECORDID";
4052 bindings[
":RECORDID"] = recordid;
4056 deleteClause +=
" AND channel.sourceid = :SOURCEID";
4057 filterClause +=
" AND channel.sourceid = :SOURCEID";
4058 bindings[
":SOURCEID"] = sourceid;
4062 deleteClause +=
" AND channel.mplexid = :MPLEXID";
4063 filterClause +=
" AND channel.mplexid = :MPLEXID";
4064 bindings[
":MPLEXID"] = mplexid;
4066 if (maxstarttime.isValid())
4068 deleteClause +=
" AND recordmatch.starttime <= :MAXSTARTTIME";
4069 filterClause +=
" AND program.starttime <= :MAXSTARTTIME";
4070 bindings[
":MAXSTARTTIME"] = maxstarttime;
4073 query.
prepare(QString(
"DELETE recordmatch FROM recordmatch, channel "
4074 "WHERE recordmatch.chanid = channel.chanid")
4076 MSqlBindings::const_iterator it;
4077 for (it = bindings.cbegin(); it != bindings.cend(); ++it)
4085 bindings.remove(
":RECORDID");
4087 query.
prepare(
"SELECT filterid, clause FROM recordfilter "
4088 "WHERE filterid >= 0 AND filterid < :NUMFILTERS AND "
4089 " TRIM(clause) <> ''");
4096 while (query.
next())
4098 filterClause += QString(
" AND (((RECTABLE.filter & %1) = 0) OR (%2))")
4099 .arg(1 << query.
value(0).toInt()).arg(query.
value(1).toString());
4103 query.
prepare(
"SELECT NULL from record "
4104 "WHERE type = :FINDONE AND findid <= 0;");
4113 QDate epoch(1970, 1, 1);
4116 query.
prepare(
"UPDATE record set findid = :FINDID "
4117 "WHERE type = :FINDONE AND findid <= 0;");
4124 QStringList fromclauses;
4125 QStringList whereclauses;
4131 for (
int clause = 0; clause < fromclauses.count(); ++clause)
4133 LOG(VB_SCHEDULE, LOG_INFO, QString(
"Query %1: %2/%3")
4134 .arg(QString::number(clause), fromclauses[clause],
4135 whereclauses[clause]));
4139 for (
int clause = 0; clause < fromclauses.count(); ++clause)
4141 QString query2 = QString(
4142"REPLACE INTO recordmatch (recordid, chanid, starttime, manualid, "
4143" oldrecduplicate, findid) "
4144"SELECT RECTABLE.recordid, program.chanid, program.starttime, "
4145" IF(search = %1, RECTABLE.recordid, 0), ").arg(
kManualSearch) +
4147"FROM (RECTABLE, program INNER JOIN channel "
4148" ON channel.chanid = program.chanid) ") + fromclauses[clause] + QString(
4149" WHERE ") + whereclauses[clause] +
4150 QString(
" AND channel.deleted IS NULL "
4151 " AND channel.visible > 0 ") +
4152 filterClause + QString(
" AND "
4155" (RECTABLE.type = %1 "
4156" OR RECTABLE.type = %2 "
4157" OR RECTABLE.type = %3 "
4158" OR RECTABLE.type = %4) "
4160" ((RECTABLE.type = %6 "
4161" OR RECTABLE.type = %7 "
4162" OR RECTABLE.type = %8)"
4164" ADDTIME(RECTABLE.startdate, RECTABLE.starttime) = program.starttime "
4166" RECTABLE.station = channel.callsign) "
4178 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- Start DB Query %1...")
4181 auto dbstart = nowAsDuration<std::chrono::microseconds>();
4185 for (it = bindings.cbegin(); it != bindings.cend(); ++it)
4187 if (query2.contains(it.key()))
4191 bool ok = result.
exec();
4192 auto dbend = nowAsDuration<std::chrono::microseconds>();
4193 auto dbTime = dbend - dbstart;
4201 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- %1 results in %2 sec.")
4203 .arg(duration_cast<std::chrono::seconds>(dbTime).count()));
4207 LOG(VB_SCHEDULE, LOG_INFO,
" +-- Done.");
4216 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_record;");
4222 result.
prepare(
"CREATE TEMPORARY TABLE sched_temp_record "
4229 result.
prepare(
"INSERT sched_temp_record SELECT * from record;");
4237 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_recorded;");
4243 result.
prepare(
"CREATE TEMPORARY TABLE sched_temp_recorded "
4250 result.
prepare(
"INSERT sched_temp_recorded SELECT * from recorded;");
4264 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_record;");
4269 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_recorded;");
4277 if (schedTmpRecord ==
"record")
4278 schedTmpRecord =
"sched_temp_record";
4280 QString rmquery = QString(
4281"UPDATE recordmatch "
4282" INNER JOIN RECTABLE ON (recordmatch.recordid = RECTABLE.recordid) "
4283" INNER JOIN program p ON (recordmatch.chanid = p.chanid AND "
4284" recordmatch.starttime = p.starttime AND "
4285" recordmatch.manualid = p.manualid) "
4286" LEFT JOIN oldrecorded ON "
4288" RECTABLE.dupmethod > 1 AND "
4289" oldrecorded.duplicate <> 0 AND "
4290" p.title = oldrecorded.title AND "
4294" (p.programid <> '' "
4295" AND p.programid = oldrecorded.programid) "
4299" (p.programid = '' OR oldrecorded.programid = '' OR "
4300" LEFT(p.programid, LOCATE('/', p.programid)) <> "
4301" LEFT(oldrecorded.programid, LOCATE('/', oldrecorded.programid))) " :
4302" (p.programid = '' OR oldrecorded.programid = '') " )
4305" (((RECTABLE.dupmethod & 0x02) = 0) OR (p.subtitle <> '' "
4306" AND p.subtitle = oldrecorded.subtitle)) "
4308" (((RECTABLE.dupmethod & 0x04) = 0) OR (p.description <> '' "
4309" AND p.description = oldrecorded.description)) "
4311" (((RECTABLE.dupmethod & 0x08) = 0) OR "
4312" (p.subtitle <> '' AND "
4313" (p.subtitle = oldrecorded.subtitle OR "
4314" (oldrecorded.subtitle = '' AND "
4315" p.subtitle = oldrecorded.description))) OR "
4316" (p.subtitle = '' AND p.description <> '' AND "
4317" (p.description = oldrecorded.subtitle OR "
4318" (oldrecorded.subtitle = '' AND "
4319" p.description = oldrecorded.description)))) "
4323" LEFT JOIN sched_temp_recorded recorded ON "
4325" RECTABLE.dupmethod > 1 AND "
4326" recorded.duplicate <> 0 AND "
4327" p.title = recorded.title AND "
4328" p.generic = 0 AND "
4329" recorded.recgroup NOT IN ('LiveTV','Deleted') "
4332" (p.programid <> '' "
4333" AND p.programid = recorded.programid) "
4337" (p.programid = '' OR recorded.programid = '' OR "
4338" LEFT(p.programid, LOCATE('/', p.programid)) <> "
4339" LEFT(recorded.programid, LOCATE('/', recorded.programid))) " :
4340" (p.programid = '' OR recorded.programid = '') ")
4343" (((RECTABLE.dupmethod & 0x02) = 0) OR (p.subtitle <> '' "
4344" AND p.subtitle = recorded.subtitle)) "
4346" (((RECTABLE.dupmethod & 0x04) = 0) OR (p.description <> '' "
4347" AND p.description = recorded.description)) "
4349" (((RECTABLE.dupmethod & 0x08) = 0) OR "
4350" (p.subtitle <> '' AND "
4351" (p.subtitle = recorded.subtitle OR "
4352" (recorded.subtitle = '' AND "
4353" p.subtitle = recorded.description))) OR "
4354" (p.subtitle = '' AND p.description <> '' AND "
4355" (p.description = recorded.subtitle OR "
4356" (recorded.subtitle = '' AND "
4357" p.description = recorded.description)))) "
4361" LEFT JOIN oldfind ON "
4362" (oldfind.recordid = recordmatch.recordid AND "
4363" oldfind.findid = recordmatch.findid) "
4364" SET oldrecduplicate = (oldrecorded.endtime IS NOT NULL), "
4365" recduplicate = (recorded.endtime IS NOT NULL), "
4366" findduplicate = (oldfind.findid IS NOT NULL), "
4367" oldrecstatus = oldrecorded.recstatus "
4368" WHERE p.endtime >= (NOW() - INTERVAL 480 MINUTE) "
4369" AND oldrecduplicate = -1 "
4371 rmquery.replace(
"RECTABLE", schedTmpRecord);
4385 if (schedTmpRecord ==
"record")
4386 schedTmpRecord =
"sched_temp_record";
4390 QMap<int, bool> cardMap;
4391 for (
auto * enc : std::as_const(*
m_tvList))
4393 if (enc->IsConnected() || enc->IsAsleep())
4394 cardMap[enc->GetInputID()] =
true;
4397 QMap<int, bool> tooManyMap;
4398 bool checkTooMany =
false;
4402 rlist.
prepare(QString(
"SELECT recordid, title, maxepisodes, maxnewest "
4403 "FROM %1").arg(schedTmpRecord));
4411 while (rlist.
next())
4413 int recid = rlist.
value(0).toInt();
4415 int maxEpisodes = rlist.
value(2).toInt();
4416 int maxNewest = rlist.
value(3).toInt();
4418 tooManyMap[recid] =
false;
4421 if (maxEpisodes && !maxNewest)
4425 epicnt.
prepare(
"SELECT DISTINCT chanid, progstart, progend "
4427 "WHERE recordid = :RECID AND preserve = 0 "
4428 "AND recgroup NOT IN ('LiveTV','Deleted');");
4433 if (epicnt.
size() >= maxEpisodes - 1)
4436 if (epicnt.
size() >= maxEpisodes)
4438 tooManyMap[recid] =
true;
4439 checkTooMany =
true;
4455 QString pwrpri =
"channel.recpriority + capturecard.recpriority";
4459 pwrpri += QString(
" + "
4460 "IF(capturecard.cardid = RECTABLE.prefinput, 1, 0) * %1")
4466 pwrpri += QString(
" + IF(program.hdtv > 0 OR "
4467 "FIND_IN_SET('HDTV', program.videoprop) > 0, 1, 0) * %1")
4473 pwrpri += QString(
" + "
4474 "IF(FIND_IN_SET('WIDESCREEN', program.videoprop) > 0, 1, 0) * %1")
4480 pwrpri += QString(
" + "
4481 "IF(FIND_IN_SET('SIGNED', program.subtitletypes) > 0, 1, 0) * %1")
4487 pwrpri += QString(
" + "
4488 "IF(FIND_IN_SET('ONSCREEN', program.subtitletypes) > 0, 1, 0) * %1")
4489 .arg(onscrpriority);
4494 pwrpri += QString(
" + "
4495 "IF(FIND_IN_SET('NORMAL', program.subtitletypes) > 0 OR "
4496 "program.closecaptioned > 0 OR program.subtitled > 0, 1, 0) * %1")
4502 pwrpri += QString(
" + "
4503 "IF(FIND_IN_SET('HARDHEAR', program.subtitletypes) > 0 OR "
4504 "FIND_IN_SET('HARDHEAR', program.audioprop) > 0, 1, 0) * %1")
4510 pwrpri += QString(
" + "
4511 "IF(FIND_IN_SET('VISUALIMPAIR', program.audioprop) > 0, 1, 0) * %1")
4517 result.
prepare(QString(
"SELECT recpriority, selectclause FROM %1;")
4526 while (result.
next())
4528 if (result.
value(0).toBool())
4530 QString sclause = result.
value(1).toString();
4532 sclause.remove(
';');
4533 pwrpri += QString(
" + IF(%1, 1, 0) * %2")
4534 .arg(sclause).arg(result.
value(0).toInt());
4537 pwrpri += QString(
" AS powerpriority ");
4539 pwrpri.replace(
"program.",
"p.");
4540 pwrpri.replace(
"channel.",
"c.");
4541 QString query = QString(
4543 " c.chanid, c.sourceid, p.starttime, "
4544 " p.endtime, p.title, p.subtitle, "
4545 " p.description, c.channum, c.callsign, "
4546 " c.name, oldrecduplicate, p.category, "
4547 " RECTABLE.recpriority, RECTABLE.dupin, recduplicate, "
4548 " findduplicate, RECTABLE.type, RECTABLE.recordid, "
4549 " p.starttime - INTERVAL RECTABLE.startoffset "
4550 " minute AS recstartts, "
4551 " p.endtime + INTERVAL RECTABLE.endoffset "
4552 " minute AS recendts, "
4553 " p.previouslyshown, "
4554 " RECTABLE.recgroup, RECTABLE.dupmethod, c.commmethod, "
4555 " capturecard.cardid, 0, p.seriesid, "
4556 " p.programid, RECTABLE.inetref, p.category_type, "
4557 " p.airdate, p.stars, p.originalairdate, "
4558 " RECTABLE.inactive, RECTABLE.parentid, recordmatch.findid, "
4559 " RECTABLE.playgroup, oldrecstatus.recstatus, "
4560 " oldrecstatus.reactivate, p.videoprop+0, "
4561 " p.subtitletypes+0, p.audioprop+0, RECTABLE.storagegroup, "
4562 " capturecard.hostname, recordmatch.oldrecstatus, NULL, "
4563 " oldrecstatus.future, capturecard.schedorder, "
4564 " p.syndicatedepisodenumber, p.partnumber, p.parttotal, "
4565 " c.mplexid, capturecard.displayname, "
4566 " p.season, p.episode, p.totalepisodes, ") +
4569 "INNER JOIN RECTABLE ON (recordmatch.recordid = RECTABLE.recordid) "
4570 "INNER JOIN program AS p "
4571 "ON ( recordmatch.chanid = p.chanid AND "
4572 " recordmatch.starttime = p.starttime AND "
4573 " recordmatch.manualid = p.manualid ) "
4574 "INNER JOIN channel AS c "
4575 "ON ( c.chanid = p.chanid ) "
4576 "INNER JOIN capturecard "
4577 "ON ( c.sourceid = capturecard.sourceid AND "
4578 " ( capturecard.schedorder <> 0 OR "
4579 " capturecard.parentid = 0 ) ) "
4580 "LEFT JOIN oldrecorded as oldrecstatus "
4581 "ON ( oldrecstatus.station = c.callsign AND "
4582 " oldrecstatus.starttime = p.starttime AND "
4583 " oldrecstatus.title = p.title ) "
4584 "WHERE p.endtime > (NOW() - INTERVAL 480 MINUTE) "
4585 "ORDER BY RECTABLE.recordid DESC, p.starttime, p.title, c.callsign, "
4587 query.replace(
"RECTABLE", schedTmpRecord);
4589 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- Start DB Query..."));
4591 auto dbstart = nowAsDuration<std::chrono::microseconds>();
4598 auto dbend = nowAsDuration<std::chrono::microseconds>();
4599 auto dbTime = dbend - dbstart;
4601 LOG(VB_SCHEDULE, LOG_INFO,
4602 QString(
" |-- %1 results in %2 sec. Processing...")
4604 .arg(duration_cast<std::chrono::seconds>(dbTime).count()));
4608 while (result.
next())
4614 uint recordid = result.
value(17).toUInt();
4616 QString title = result.
value(4).toString();
4617 QString callsign = result.
value(8).toString();
4627 uint mplexid = result.
value(51).toUInt();
4628 if (mplexid == 32767)
4631 QString inputname = result.
value(52).toString();
4632 if (inputname.isEmpty())
4633 inputname = QString(
"Input %1").arg(result.
value(24).toUInt());
4638 result.
value(5).toString(),
4640 result.
value(6).toString(),
4641 result.
value(53).toInt(),
4642 result.
value(54).toInt(),
4643 result.
value(55).toInt(),
4644 result.
value(48).toString(),
4645 result.
value(11).toString(),
4647 result.
value(0).toUInt(),
4648 result.
value(7).toString(),
4650 result.
value(9).toString(),
4652 result.
value(21).toString(),
4653 result.
value(36).toString(),
4655 result.
value(43).toString(),
4656 result.
value(42).toString(),
4658 result.
value(30).toUInt(),
4659 result.
value(49).toUInt(),
4660 result.
value(50).toUInt(),
4662 result.
value(26).toString(),
4663 result.
value(27).toString(),
4664 result.
value(28).toString(),
4667 result.
value(12).toInt(),
4674 result.
value(31).toFloat(),
4675 (result.
value(32).isNull()) ? QDate() :
4679 result.
value(20).toBool(),
4682 result.
value(38).toBool(),
4685 result.
value(34).toUInt(),
4690 result.
value(1).toUInt(),
4691 result.
value(24).toUInt(),
4693 result.
value(35).toUInt(),
4696 result.
value(40).toUInt(),
4697 result.
value(39).toUInt(),
4698 result.
value(41).toUInt(),
4699 result.
value(46).toBool(),
4700 result.
value(47).toInt(),
4702 result.
value(24).toUInt(),
4705 if (!
p->m_future && !
p->IsReactivated() &&
4709 p->SetRecordingStatus(
p->m_oldrecstatus);
4712 p->SetRecordingPriority2(result.
value(56).toInt());
4721 if (
p->IsSameTitleStartTimeAndChannel(*r))
4723 if (r->m_sgroupId ==
p->m_sgroupId &&
4724 r->GetRecordingEndTime() !=
p->GetRecordingEndTime() &&
4725 (r->GetRecordingRuleID() ==
p->GetRecordingRuleID() ||
4740 tmpList.push_back(
p);
4747 (!cardMap.contains(
p->GetInputID()) || (
p->m_schedOrder == 0)))
4750 if (
p->m_schedOrder == 0 &&
4753 LOG(VB_GENERAL, LOG_WARNING,
LOC +
4754 QString(
"Channel %1, Title %2 %3 cardinput.schedorder = %4, "
4755 "it must be >0 to record from this input.")
4756 .arg(
p->GetChannelName(),
p->GetTitle(),
4757 p->GetScheduledStartTime().toString(),
4758 QString::number(
p->m_schedOrder)));
4764 if (checkTooMany && tooManyMap[
p->GetRecordingRuleID()] &&
4765 !
p->IsReactivated())
4773 else if (result.
value(15).toBool() && !
p->IsReactivated())
4777 !
p->IsReactivated() &&
4797 bool inactive = result.
value(33).toBool();
4812 p->SetRecordingStatus(newrecstatus);
4814 tmpList.push_back(
p);
4817 LOG(VB_SCHEDULE, LOG_INFO,
" +-- Cleanup...");
4818 for (
auto &
tmp : tmpList)
4826 QString query = QString(
4827 "SELECT RECTABLE.title, RECTABLE.subtitle, "
4828 " RECTABLE.description, RECTABLE.season, "
4829 " RECTABLE.episode, RECTABLE.category, "
4830 " RECTABLE.chanid, channel.channum, "
4831 " RECTABLE.station, channel.name, "
4832 " RECTABLE.recgroup, RECTABLE.playgroup, "
4833 " RECTABLE.seriesid, RECTABLE.programid, "
4834 " RECTABLE.inetref, RECTABLE.recpriority, "
4835 " RECTABLE.startdate, RECTABLE.starttime, "
4836 " RECTABLE.enddate, RECTABLE.endtime, "
4837 " RECTABLE.recordid, RECTABLE.type, "
4838 " RECTABLE.dupin, RECTABLE.dupmethod, "
4839 " RECTABLE.findid, "
4840 " RECTABLE.startoffset, RECTABLE.endoffset, "
4841 " channel.commmethod "
4843 "INNER JOIN channel ON (channel.chanid = RECTABLE.chanid) "
4844 "LEFT JOIN recordmatch on RECTABLE.recordid = recordmatch.recordid "
4845 "WHERE (type = %1 OR type = %2) AND "
4846 " recordmatch.chanid IS NULL")
4852 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- Start DB Query..."));
4854 auto dbstart = nowAsDuration<std::chrono::microseconds>();
4857 bool ok = result.
exec();
4858 auto dbend = nowAsDuration<std::chrono::microseconds>();
4859 auto dbTime = dbend - dbstart;
4867 LOG(VB_SCHEDULE, LOG_INFO,
4868 QString(
" |-- %1 results in %2 sec. Processing...")
4870 .arg(duration_cast<std::chrono::seconds>(dbTime).count()));
4872 while (result.
next())
4875#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
4877 result.
value(16).toDate(), result.
value(17).toTime(), Qt::UTC);
4879 result.
value(18).toDate(), result.
value(19).toTime(), Qt::UTC);
4881 static const QTimeZone utc(QTimeZone::UTC);
4883 result.
value(16).toDate(), result.
value(17).toTime(), utc);
4885 result.
value(18).toDate(), result.
value(19).toTime(), utc);
4888 QDateTime recstartts = startts.addSecs(result.
value(25).toInt() * -60LL);
4889 QDateTime recendts = endts.addSecs( result.
value(26).toInt() * +60LL);
4891 if (recstartts >= recendts)
4894 recstartts = startts;
4905 result.
value(0).toString(),
4907 (sor) ? result.
value(1).toString() : QString(),
4909 (sor) ? result.
value(2).toString() : QString(),
4910 result.
value(3).toUInt(),
4911 result.
value(4).toUInt(),
4914 result.
value(6).toUInt(),
4915 result.
value(7).toString(),
4916 result.
value(8).toString(),
4917 result.
value(9).toString(),
4919 result.
value(10).toString(),
4920 result.
value(11).toString(),
4922 result.
value(12).toString(),
4923 result.
value(13).toString(),
4924 result.
value(14).toString(),
4926 result.
value(15).toInt(),
4929 recstartts, recendts,
4933 result.
value(20).toUInt(),
4939 result.
value(24).toUInt(),
4943 tmpList.push_back(
p);
4946 for (
auto &
tmp : tmpList)
4957 QString sortColumn =
"title";
4967 QString prefixes = sh->getPrefixes();
4968 sortColumn =
"REGEXP_REPLACE(record.title,'" + prefixes +
"','')";
4972 sortColumn =
"record.recpriority";
4975 sortColumn =
"record.last_record";
4983 sortColumn =
"record.next_record IS NULL, record.next_record";
4986 sortColumn =
"record.type";
4990 QString order =
"ASC";
4994 QString query = QString(
4995 "SELECT record.title, record.subtitle, "
4996 " record.description, record.season, "
4997 " record.episode, record.category, "
4998 " record.chanid, channel.channum, "
4999 " record.station, channel.name, "
5000 " record.recgroup, record.playgroup, "
5001 " record.seriesid, record.programid, "
5002 " record.inetref, record.recpriority, "
5003 " record.startdate, record.starttime, "
5004 " record.enddate, record.endtime, "
5005 " record.recordid, record.type, "
5006 " record.dupin, record.dupmethod, "
5008 " channel.commmethod "
5010 "LEFT JOIN channel ON channel.callsign = record.station "
5011 " AND deleted IS NULL "
5012 "GROUP BY recordid "
5015 query = query.arg(sortColumn, order);
5026 while (result.
next())
5029#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
5030 QDateTime startts = QDateTime(result.
value(16).toDate(),
5031 result.
value(17).toTime(), Qt::UTC);
5032 QDateTime endts = QDateTime(result.
value(18).toDate(),
5033 result.
value(19).toTime(), Qt::UTC);
5035 static const QTimeZone utc(QTimeZone::UTC);
5036 QDateTime startts = QDateTime(result.
value(16).toDate(),
5037 result.
value(17).toTime(), utc);
5038 QDateTime endts = QDateTime(result.
value(18).toDate(),
5039 result.
value(19).toTime(), utc);
5042 if (!startts.isValid())
5044#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
5049 QTimeZone(QTimeZone::UTC));
5052 if (!endts.isValid())
5056 result.
value(0).toString(), QString(),
5057 result.
value(1).toString(), QString(),
5058 result.
value(2).toString(), result.
value(3).toUInt(),
5059 result.
value(4).toUInt(), result.
value(5).toString(),
5061 result.
value(6).toUInt(), result.
value(7).toString(),
5062 result.
value(8).toString(), result.
value(9).toString(),
5064 result.
value(10).toString(), result.
value(11).toString(),
5066 result.
value(12).toString(), result.
value(13).toString(),
5067 result.
value(14).toString(),
5069 result.
value(15).toInt(),
5076 result.
value(20).toUInt(), rectype,
5080 result.
value(24).toUInt(),
5179 QString recording_dir;
5183 "LiveTV", cur, cur.addSecs(3600), cardid,
5188 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"FindNextLiveTVDir: next dir is '%1'")
5189 .arg(recording_dir));
5196 const QString &title,
5198 const QString &storagegroup,
5199 const QDateTime &recstartts,
5200 const QDateTime &recendts,
5202 QString &recording_dir,
5205 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
"FillRecordingDir: Starting");
5210 if (cnt++ % 20 == 0)
5211 LOG(VB_SCHEDULE, LOG_WARNING,
"Waiting for main server.");
5212 std::this_thread::sleep_for(50ms);
5219 QStringList recsCounted;
5220 std::list<FileSystemInfo *> fsInfoList;
5221 std::list<FileSystemInfo *>::iterator fslistit;
5223 recording_dir.clear();
5225 if (dirlist.size() == 1)
5227 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5228 QString(
"FillRecordingDir: The only directory in the %1 Storage "
5229 "Group is %2, so it will be used by default.")
5230 .arg(storagegroup, dirlist[0]));
5231 recording_dir = dirlist[0];
5232 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
"FillRecordingDir: Finished");
5237 int weightPerRecording =
5239 int weightPerPlayback =
5241 int weightPerCommFlag =
5243 int weightPerTranscode =
5246 QString storageScheduler =
5248 int localStartingWeight =
5250 (storageScheduler !=
"Combination") ? 0
5251 : (
int)(-1.99 * weightPerRecording));
5252 int remoteStartingWeight =
5254 std::chrono::seconds maxOverlap =
5259 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5260 "FillRecordingDir: Calculating initial FS Weights.");
5271 tmpWeight = localStartingWeight;
5272 msg +=
" is local (" + QString::number(tmpWeight) +
")";
5276 tmpWeight = remoteStartingWeight;
5277 msg +=
" is remote (+" + QString::number(tmpWeight) +
")";
5287 msg +=
", has SGweightPerDir offset of "
5288 + QString::number(tmpWeight) +
")";
5290 msg +=
". initial dir weight = " + QString::number(fs->
getWeight());
5291 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, msg);
5293 fsInfoList.push_back(fs);
5296 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5297 "FillRecordingDir: Adjusting FS Weights from inuseprograms.");
5300 saveRecDir.
prepare(
"UPDATE inuseprograms "
5301 "SET recdir = :RECDIR "
5302 "WHERE chanid = :CHANID AND "
5303 " starttime = :STARTTIME");
5306 "SELECT i.chanid, i.starttime, r.endtime, recusage, rechost, recdir "
5307 "FROM inuseprograms i, recorded r "
5308 "WHERE DATE_ADD(lastupdatetime, INTERVAL 16 MINUTE) > NOW() AND "
5309 " i.chanid = r.chanid AND "
5310 " i.starttime = r.starttime");
5318 while (query.
next())
5320 uint recChanid = query.
value(0).toUInt();
5323 QString recUsage( query.
value(3).toString());
5324 QString recHost( query.
value(4).toString());
5325 QString recDir( query.
value(5).toString());
5327 if (recDir.isEmpty())
5331 recDir = recDir.isEmpty() ?
"_UNKNOWN_" : recDir;
5333 saveRecDir.
bindValue(
":RECDIR", recDir);
5334 saveRecDir.
bindValue(
":CHANID", recChanid);
5335 saveRecDir.
bindValue(
":STARTTIME", recStart);
5336 if (!saveRecDir.
exec())
5339 if (recDir ==
"_UNKNOWN_")
5342 for (fslistit = fsInfoList.begin();
5343 fslistit != fsInfoList.end(); ++fslistit)
5349 int weightOffset = 0;
5353 if (recEnd > recstartts.addSecs(maxOverlap.count()))
5355 weightOffset += weightPerRecording;
5356 recsCounted << QString::number(recChanid) +
":" +
5362 weightOffset += weightPerPlayback;
5366 weightOffset += weightPerCommFlag;
5370 weightOffset += weightPerTranscode;
5375 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5376 QString(
" %1 @ %2 in use by '%3' on %4:%5, FSID "
5377 "#%6, FSID weightOffset +%7.")
5378 .arg(QString::number(recChanid),
5380 recUsage, recHost, recDir,
5382 QString::number(weightOffset)));
5390 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5391 QString(
" %1:%2 => old weight %3 plus "
5393 .arg(
fs2->getHostname(),
5395 .arg(
fs2->getWeight())
5397 .arg(
fs2->getWeight() + weightOffset));
5399 fs2->setWeight(
fs2->getWeight() + weightOffset);
5409 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5410 "FillRecordingDir: Adjusting FS Weights from scheduler.");
5412 for (
auto *thispg : reclist)
5414 if ((recendts < thispg->GetRecordingStartTime()) ||
5415 (recstartts > thispg->GetRecordingEndTime()) ||
5418 (thispg->GetInputID() == 0) ||
5419 (recsCounted.contains(QString(
"%1:%2").arg(thispg->GetChanID())
5421 (thispg->GetPathname().isEmpty()))
5424 for (fslistit = fsInfoList.begin();
5425 fslistit != fsInfoList.end(); ++fslistit)
5428 if ((fs->
getHostname() == thispg->GetHostname()) &&
5429 (fs->
getPath() == thispg->GetPathname()))
5431 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5432 QString(
"%1 @ %2 will record on %3:%4, FSID #%5, "
5433 "weightPerRecording +%6.")
5434 .arg(thispg->GetChanID())
5437 .arg(fs->
getFSysID()).arg(weightPerRecording));
5446 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5447 QString(
" %1:%2 => old weight %3 plus %4 = %5")
5448 .arg(
fs2->getHostname(),
fs2->getPath())
5449 .arg(
fs2->getWeight()).arg(weightPerRecording)
5450 .arg(
fs2->getWeight() + weightPerRecording));
5452 fs2->setWeight(
fs2->getWeight() + weightPerRecording);
5460 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5461 QString(
"Using '%1' Storage Scheduler directory sorting algorithm.")
5462 .arg(storageScheduler));
5464 if (storageScheduler ==
"BalancedFreeSpace")
5466 else if (storageScheduler ==
"BalancedPercFreeSpace")
5468 else if (storageScheduler ==
"BalancedDiskIO")
5475 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5476 "--- FillRecordingDir Sorted fsInfoList start ---");
5477 for (fslistit = fsInfoList.begin();fslistit != fsInfoList.end();
5481 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
"%1:%2")
5483 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
" Location : %1")
5484 .arg((fs->
isLocal()) ?
"local" :
"remote"));
5485 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
" weight : %1")
5487 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
" free space : %5")
5490 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5491 "--- FillRecordingDir Sorted fsInfoList end ---");
5500 long long maxSizeKB = (maxByterate + maxByterate/3) *
5501 recstartts.secsTo(recendts) / 1024;
5503 bool simulateAutoExpire =
5506 (fsInfoList.size() > 1));
5520 for (
unsigned int pass = 1; pass <= 3; pass++)
5522 bool foundDir =
false;
5524 if ((pass == 2) && simulateAutoExpire)
5527 QMap <int , long long> remainingSpaceKB;
5528 for (fslistit = fsInfoList.begin();
5529 fslistit != fsInfoList.end(); ++fslistit)
5531 remainingSpaceKB[(*fslistit)->getFSysID()] =
5532 (*fslistit)->getFreeSpace();
5539 for (
auto & expire : expiring)
5543 for (fslistit = fsInfoList.begin();
5544 fslistit != fsInfoList.end(); ++fslistit)
5547 if (expire->GetHostname() != (*fslistit)->getHostname())
5551 if (!dirlist.contains((*fslistit)->getPath()))
5555 (*fslistit)->getPath() +
"/" + expire->GetPathname();
5562 if (checkFile.exists())
5570 QString backuppath = expire->GetPathname();
5572 bool foundSlave =
false;
5574 for (
auto * enc : std::as_const(*
m_tvList))
5576 if (enc->GetHostName() ==
5579 enc->CheckFile(programinfo);
5597 LOG(VB_GENERAL, LOG_ERR,
5598 QString(
"Unable to match '%1' "
5599 "to any file system. Ignoring it.")
5600 .arg(expire->GetBasename()));
5606 expire->GetFilesize() / 1024;
5609 long long desiredSpaceKB =
5613 (desiredSpaceKB + maxSizeKB))
5615 recording_dir = fs->
getPath();
5618 LOG(VB_FILE, LOG_INFO,
5619 QString(
"pass 2: '%1' will record in '%2' "
5620 "although there is only %3 MB free and the "
5621 "AutoExpirer wants at least %4 MB. This "
5622 "directory has the highest priority files "
5623 "to be expired from the AutoExpire list and "
5624 "there are enough that the Expirer should "
5625 "be able to free up space for this recording.")
5626 .arg(title, recording_dir)
5628 .arg(desiredSpaceKB / 1024));
5639 for (fslistit = fsInfoList.begin();
5640 fslistit != fsInfoList.end(); ++fslistit)
5642 long long desiredSpaceKB = 0;
5649 (dirlist.contains(fs->
getPath())) &&
5653 recording_dir = fs->
getPath();
5658 LOG(VB_FILE, LOG_INFO,
5659 QString(
"pass 1: '%1' will record in "
5660 "'%2' which has %3 MB free. This recording "
5661 "could use a max of %4 MB and the "
5662 "AutoExpirer wants to keep %5 MB free.")
5663 .arg(title, recording_dir)
5665 .arg(maxSizeKB / 1024)
5666 .arg(desiredSpaceKB / 1024));
5670 LOG(VB_FILE, LOG_INFO,
5671 QString(
"pass %1: '%2' will record in "
5672 "'%3' although there is only %4 MB free and "
5673 "the AutoExpirer wants at least %5 MB. "
5674 "Something will have to be deleted or expired "
5675 "in order for this recording to complete "
5677 .arg(pass).arg(title, recording_dir)
5679 .arg(desiredSpaceKB / 1024));
5692 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
"FillRecordingDir: Finished");
5705 QMap <int, bool> fsMap;
5706 for (
const auto&
fs1 : std::as_const(fsInfos))
5708 fsMap[
fs1.getFSysID()] =
true;
5712 LOG(VB_FILE, LOG_INFO,
LOC +
5713 QString(
"FillDirectoryInfoCache: found %1 unique filesystems")
5714 .arg(fsMap.size()));
5721 auto secsleft = std::chrono::seconds(curtime.secsTo(
m_livetvTime));
5725 if (secsleft - prerollseconds > 120s)
5729 for (
auto * enc : std::as_const(*
m_tvList))
5745 if (
m_schedTime.secsTo(dummy->GetRecordingEndTime()) < 1800)
5746 dummy->SetRecordingEndTime(
m_schedTime.addSecs(1800));
5747 dummy->SetInputID(enc->GetInputID());
5748 dummy->m_mplexId = dummy->QueryMplexID();
5771 bool autoStart =
false;
5773 QDateTime startupTime = QDateTime();
5779 if (startupTime.isValid())
5782 startupSecs = std::max(startupSecs, 15 * 60s);
5789 LOG(VB_GENERAL, LOG_INFO,
5790 "Close to auto-start time, AUTO-Startup assumed");
5796 LOG(VB_GENERAL, LOG_INFO,
5797 "Close to MythFillDB suggested run time, AUTO-Startup to fetch guide data?");
5803 LOG(VB_GENERAL, LOG_DEBUG,
5804 "NOT close to auto-start time, USER-initiated startup assumed");
5807 else if (!s.isEmpty())
5809 LOG(VB_GENERAL, LOG_ERR,
LOC +
5810 QString(
"Invalid MythShutdownWakeupTime specified in database (%1)")
5822 QMap<uint, QSet<uint> > inputSets;
5823 query.
prepare(
"SELECT DISTINCT ci1.cardid, ci2.cardid "
5824 "FROM capturecard ci1, capturecard ci2, "
5825 " inputgroup ig1, inputgroup ig2 "
5826 "WHERE ci1.cardid = ig1.cardinputid AND "
5827 " ci2.cardid = ig2.cardinputid AND"
5828 " ig1.inputgroupid = ig2.inputgroupid AND "
5829 " ci1.cardid <= ci2.cardid "
5830 "ORDER BY ci1.cardid, ci2.cardid");
5836 while (query.
next())
5840 inputSets[id0].insert(id1);
5841 inputSets[id1].insert(id0);
5844 QMap<uint, QSet<uint> >::iterator mit;
5845 for (mit = inputSets.begin(); mit != inputSets.end(); ++mit)
5847 uint inputid = mit.key();
5855 QSet<uint> fullset = mit.value();
5856 QSet<uint> checkset;
5857 QSet<uint>::const_iterator sit;
5858 while (checkset != fullset)
5861 for (
int item : std::as_const(checkset))
5862 fullset += inputSets[item];
5867 auto *conflictlist =
new RecList();
5869 for (
int item : std::as_const(checkset))
5871 LOG(VB_SCHEDULE, LOG_INFO,
5872 QString(
"Assigning input %1 to conflict set %2")
5880 query.
prepare(
"SELECT ci.cardid "
5881 "FROM capturecard ci "
5882 "LEFT JOIN inputgroup ig "
5883 " ON ci.cardid = ig.cardinputid "
5884 "WHERE ig.cardinputid IS NULL");
5890 while (query.
next())
5894 LOG(VB_GENERAL, LOG_ERR,
LOC +
5895 QString(
"Input %1 is not assigned to any input group").arg(
id));
5896 auto *conflictlist =
new RecList();
5898 LOG(VB_SCHEDULE, LOG_INFO,
5899 QString(
"Assigning input %1 to conflict set %2")
5913 query.
prepare(
"SELECT cardid, parentid, schedgroup "
5915 "WHERE sourceid > 0 "
5923 while (query.
next())
5926 uint parentid = query.
value(1).toUInt();
5943 LOG(VB_SCHEDULE, LOG_INFO,
5944 QString(
"Added SchedInputInfo i=%1, g=%2, sg=%3")
5953 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
5954 QString(
"AddChildInput: Handling parent = %1, input = %2")
5955 .arg(parentid).arg(childid));
std::vector< ProgramInfo * > pginfolist_t
static GlobalSpinBoxSetting * idleTimeoutSecs()
static GlobalSpinBoxSetting * idleWaitForRecordingTime()
static GlobalTextEditSetting * startupCommand()
static GlobalTextEditSetting * preSDWUCheckCommand()
static void Update(int encoder, int fsID, bool immediately)
This is used to update the global AutoExpire instance "expirer".
void GetAllExpiring(QStringList &strList)
Gets the full list of programs that can expire in expiration order.
uint64_t GetDesiredSpace(int fsID) const
Used by the scheduler to select the next recording dir.
static void ClearExpireList(pginfolist_t &expireList, bool deleteProg=true)
Clears expireList, freeing any ProgramInfo's if necessary.
static std::vector< uint > GetChildInputIDs(uint inputid)
static std::vector< uint > GetConflictingInputs(uint inputid)
Provides an interface to both local and remote TVRec's for the mythbackend.
void SetNextLiveTVDir(const QString &dir)
Tells TVRec where to put the next LiveTV recording.
RecStatus::Type StartRecording(ProgramInfo *rec)
Tells TVRec to Start recording the program "rec" as soon as possible.
bool IsConnected(void) const
Returns true if the EncoderLink instance is usable.
long long GetMaxBitrate(void)
Returns maximum bits per second this recorder might output.
bool IsLocal(void) const
Returns true for a local EncoderLink.
bool IsWaking(void) const
Returns true if the encoder is waking up.
bool IsBusy(InputInfo *busy_input=nullptr, std::chrono::seconds time_buffer=5s)
Returns true if the recorder is busy, or will be within the next time_buffer seconds.
bool IsTunerLocked(void) const
Returns true iff the tuner is locked.
QDateTime GetLastWakeTime(void) const
Get the last time the encoder was awakened.
void RecordPending(const ProgramInfo *rec, std::chrono::seconds secsleft, bool hasLater)
Tells TVRec there is a pending recording "rec" in "secsleft" seconds.
QDateTime GetSleepStatusTime(void) const
Get the last time the sleep status was changed.
bool IsAsleep(void) const
Returns true if the encoder is asleep.
QString GetHostName(void) const
Returns the remote host for a non-local EncoderLink.
void setWeight(int weight)
QString getHostname() const
int64_t getTotalSpace() const
int64_t getFreeSpace() const
static bool HasRunningOrPendingJobs(std::chrono::minutes startingWithinMins=0min)
QSqlQuery wrapper that fetches a DB connection from the connection pool.
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
QVariant value(int i) const
static MSqlQueryInfo SchedCon()
Returns dedicated connection. (Required for using temporary SQL tables.)
bool isActive(void) const
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
static MSqlQueryInfo ChannelCon()
Returns dedicated connection. (Required for using temporary SQL tables.)
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
This is a wrapper around QThread that does several additional things.
bool isRunning(void) const
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
static void usleep(std::chrono::microseconds time)
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
void ShutSlaveBackendsDown(const QString &haltcmd)
Sends the Slavebackends the request to shut down using haltcmd.
bool isClientConnected(bool onlyBlockingClients=false)
void GetFilesystemInfos(FileSystemInfoList &fsInfos, bool useCache=true)
QString GetHostName(void)
QString GetSetting(const QString &key, const QString &defaultval="")
void SendSystemEvent(const QString &msg)
bool SaveSettingOnHost(const QString &key, const QString &newValue, const QString &host)
QString GetSettingOnHost(const QString &key, const QString &host, const QString &defaultval="")
void dispatch(const MythEvent &event)
int GetNumSetting(const QString &key, int defaultval=0)
std::enable_if_t< std::chrono::__is_duration< T >::value, T > GetDurSetting(const QString &key, T defaultval=T::zero())
bool GetBoolSetting(const QString &key, bool defaultval=false)
static void DBError(const QString &where, const MSqlQuery &query)
T dequeue()
Removes item from front of list and returns a copy. O(1).
void enqueue(const T &d)
Adds item to the back of the list. O(1).
This class is used as a container for messages.
Holds information on recordings and videos.
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
uint GetRecordingRuleID(void) const
QString toString(Verbosity v=kLongDescription, const QString &sep=":", const QString &grp="\"") const
void SetRecordingPriority2(int priority)
bool IsSameTitleStartTimeAndChannel(const ProgramInfo &other) const
Checks title, chanid or callsign and start times for equality.
QString GetProgramID(void) const
bool IsDuplicateProgram(const ProgramInfo &other) const
Checks for duplicates according to dupmethod.
void SetRecordingRuleType(RecordingType type)
uint GetRecordingID(void) const
QDateTime GetScheduledEndTime(void) const
The scheduled end time of the program.
void SetRecordingStatus(RecStatus::Type status)
QString GetHostname(void) const
static bool UsingProgramIDAuthority(void)
uint GetSourceID(void) const
QString DiscoverRecordingDirectory(void)
bool IsReactivated(void) const
QString GetDescription(void) const
QString GetStorageGroup(void) const
void SetRecordingStartTime(const QDateTime &dt)
QString GetTitle(void) const
static void CheckProgramIDAuthorities(void)
QDateTime GetRecordingStartTime(void) const
Approximate time the recording started.
QDateTime GetScheduledStartTime(void) const
The scheduled start time of program.
QString GetChanNum(void) const
This is the channel "number", in the form 1, 1_2, 1-2, 1#1, etc.
void SetRecordingRuleID(uint id)
QString MakeUniqueKey(void) const
Creates a unique string that can be used to identify an existing recording.
bool IsSameRecording(const ProgramInfo &other) const
int GetRecordingPriority(void) const
QString GetPathname(void) const
uint GetInputID(void) const
int GetRecordingPriority2(void) const
uint GetParentRecordingRuleID(void) const
void ToStringList(QStringList &list) const
Serializes ProgramInfo into a QStringList which can be passed over a socket.
void SetRecordingEndTime(const QDateTime &dt)
RecStatus::Type GetRecordingStatus(void) const
QDateTime GetRecordingEndTime(void) const
Approximate time the recording should have ended, did end, or is intended to end.
QString GetSubtitle(void) const
void SetPathname(const QString &pn)
RecordingType GetRecordingRuleType(void) const
QString GetChannelSchedulingID(void) const
This is the unique programming identifier of a channel.
static QString toString(RecStatus::Type recstatus, uint id)
Converts "recstatus" into a short (unreadable) string.
static QString toUIState(RecStatus::Type recstatus)
static void create(Scheduler *scheduler, RecordingInfo &ri)
Create an instance of the RecordingExtender if necessary, and add this recording to the list of new r...
Holds information on a TV Program one might wish to record.
RecStatus::Type m_oldrecstatus
static const QRegularExpression kReLeadingAnd
void AddHistory(bool resched=true, bool forcedup=false, bool future=false)
Adds recording history, creating "record" it if necessary.
void SetRecordingID(uint _recordedid) override
static const int kNumFilters
QWaitCondition m_reschedWait
QMap< int, bool > m_schedAfterStartMap
const RecordingInfo * FindConflict(const RecordingInfo *p, OpenEndType openEnd=openEndNever, uint *affinity=nullptr, bool checkAll=false) const
QMap< int, EncoderLink * > * m_tvList
bool WakeUpSlave(const QString &slaveHostname, bool setWakingStatus=true)
void SlaveConnected(const RecordingList &slavelist)
void FillDirectoryInfoCache(void)
static void PrintRec(const RecordingInfo *p, const QString &prefix="")
bool IsSameProgram(const RecordingInfo *a, const RecordingInfo *b) const
QMap< QString, FileSystemInfo > m_fsInfoCache
bool AssignGroupInput(RecordingInfo &ri, std::chrono::seconds prerollseconds)
void BackupRecStatus(void)
bool IsBusyRecording(const RecordingInfo *rcinfo)
MythDeque< QStringList > m_reschedQueue
void MarkOtherShowings(RecordingInfo *p)
bool HaveQueuedRequests(void)
static bool VerifyCards(void)
QDateTime m_lastPrepareTime
bool GetAllPending(RecList &retList, int recRuleId=0) const
std::chrono::milliseconds m_delayShutdownTime
void RestoreRecStatus(void)
bool CreateConflictLists(void)
void CreateTempTables(void)
void EnqueueCheck(const RecordingInfo &recinfo, const QString &why)
std::pair< const RecordingInfo *, const RecordingInfo * > IsSameKey
void AddChildInput(uint parentid, uint childid)
void FillRecordListFromDB(uint recordid=0)
void UpdateMatches(uint recordid, uint sourceid, uint mplexid, const QDateTime &maxstarttime)
void ShutdownServer(std::chrono::seconds prerollseconds, QDateTime &idleSince)
void SchedNewFirstPass(RecIter &start, const RecIter &end, int recpriority, int recpriority2)
QMutex m_resetIdleTimeLock
bool HandleRecording(RecordingInfo &ri, bool &statuschanged, QDateTime &nextStartTime, QDateTime &nextWakeTime, std::chrono::seconds prerollseconds)
bool HandleRunSchedulerStartup(std::chrono::seconds prerollseconds, std::chrono::minutes idleWaitForRecordingTime)
void BuildNewRecordsQueries(uint recordid, QStringList &from, QStringList &where, MSqlBindings &bindings)
void UpdateNextRecord(void)
QSet< uint > m_schedOrderWarned
void EnqueuePlace(const QString &why)
Scheduler(bool runthread, QMap< int, EncoderLink * > *_tvList, const QString &tmptable="record", Scheduler *master_sched=nullptr)
static bool WasStartedAutomatically()
bool FindNextConflict(const RecList &cardlist, const RecordingInfo *p, RecConstIter &iter, OpenEndType openEnd=openEndNever, uint *paffinity=nullptr, bool ignoreinput=false) const
int FillRecordingDir(const QString &title, const QString &hostname, const QString &storagegroup, const QDateTime &recstartts, const QDateTime &recendts, uint cardid, QString &recording_dir, const RecList &reclist)
void ResetDuplicates(uint recordid, uint findid, const QString &title, const QString &subtitle, const QString &descrip, const QString &programid)
void PrintList(bool onlyFutureRecordings=false)
bool FillRecordList(void)
static void GetAllScheduled(QStringList &strList, SchedSortColumn sortBy=kSortTitle, bool ascending=true)
Returns all scheduled programs serialized into a QStringList.
void SchedNewRetryPass(const RecIter &start, const RecIter &end, bool samePriority, bool livetv=false)
void HandleWakeSlave(RecordingInfo &ri, std::chrono::seconds prerollseconds)
void getConflicting(RecordingInfo *pginfo, QStringList &strlist)
std::vector< RecList * > m_conflictLists
bool ChangeRecordingEnd(RecordingInfo *oldp, RecordingInfo *newp)
RecStatus::Type GetRecStatus(const ProgramInfo &pginfo)
void run(void) override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
void DeleteTempTables(void)
void EnqueueMatch(uint recordid, uint sourceid, uint mplexid, const QDateTime &maxstarttime, const QString &why)
void Reschedule(const QStringList &request)
bool InitInputInfoMap(void)
QMap< uint, RecList > m_recordIdListMap
void PruneRedundants(void)
void SlaveDisconnected(uint cardid)
MainServer * m_mainServer
void PutInactiveSlavesToSleep(void)
QMap< QString, ProgramInfo * > GetRecording(void) const override
void OldRecordedFixups(void)
bool TryAnotherShowing(RecordingInfo *p, bool samePriority, bool livetv=false)
void GetNextLiveTVDir(uint cardid)
void FillRecordListFromMaster(void)
void ClearRequestQueue(void)
void UpdateManuals(uint recordid)
IsSameCacheType m_cacheIsSameProgram
std::array< QSet< QString >, 4 > m_sysEvents
void AddRecording(const RecordingInfo &pi)
bool HandleReschedule(void)
void UpdateDuplicates(void)
void HandleIdleShutdown(bool &blockShutdown, QDateTime &idleSince, std::chrono::seconds prerollseconds, std::chrono::seconds idleTimeoutSecs, std::chrono::minutes idleWaitForRecordingTime, bool statuschanged)
void MarkShowingsList(const RecList &showinglist, RecordingInfo *p)
QMap< uint, SchedInputInfo > m_sinputInfoMap
void HandleRecordingStatusChange(RecordingInfo &ri, RecStatus::Type recStatus, const QString &details)
void SetMainServer(MainServer *ms)
void UpdateRecStatus(RecordingInfo *pginfo)
QMap< QString, RecList > m_titleListMap
void SchedNewRecords(void)
static bool CheckShutdownServer(std::chrono::seconds prerollseconds, QDateTime &idleSince, bool &blockShutdown, uint logmask)
QStringList GetDirList(void) const
static QReadWriteLock s_inputsLock
static pid_list_t::iterator find(const PIDInfoMap &map, pid_list_t &list, pid_list_t::iterator begin, pid_list_t::iterator end, bool find_open)
@ GENERIC_EXIT_OK
Exited with no error.
@ GENERIC_EXIT_NOT_OK
Exited with error.
QVector< FileSystemInfo > FileSystemInfoList
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
QMap< QString, QVariant > MSqlBindings
typedef for a map of string -> string bindings for generic queries.
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
bool IsMACAddress(const QString &MAC)
bool WakeOnLAN(const QString &MAC)
RecList::const_iterator RecConstIter
RecList::iterator RecIter
std::deque< RecordingInfo * > RecList
std::shared_ptr< MythSortHelper > getMythSortHelper(void)
Get a pointer to the MythSortHelper singleton.
void SendMythSystemRecEvent(const QString &msg, const RecordingInfo *pginfo)
uint myth_system(const QString &command, uint flags, std::chrono::seconds timeout)
QDateTime as_utc(const QDateTime &old_dt)
Returns copy of QDateTime with TimeSpec set to UTC.
std::chrono::seconds secsInPast(const QDateTime &past)
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
@ kDatabase
Default UTC, database format.
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
ProgramInfo::CategoryType string_to_myth_category_type(const QString &category_type)
bool LoadFromScheduler(AutoDeleteDeque< TYPE * > &destination, bool &hasConflicts, const QString &altTable="", int recordid=-1)
const QString kTranscoderInUseID
const QString kPlayerInUseID
const QString kFlaggerInUseID
const QString kRecorderInUseID
QChar toQChar(RecordingType rectype)
Converts "rectype" into a human readable character.
int RecTypePrecedence(RecordingType rectype)
Converts a RecordingType to a simple integer so it's specificity can be compared to another.
static QString fs1(QT_TRANSLATE_NOOP("SchedFilterEditor", "Identifiable episode"))
static QString fs2(QT_TRANSLATE_NOOP("SchedFilterEditor", "First showing"))
static bool comp_retry(RecordingInfo *a, RecordingInfo *b)
static bool comp_storage_combination(FileSystemInfo *a, FileSystemInfo *b)
static bool comp_redundant(RecordingInfo *a, RecordingInfo *b)
static QString progfindid
static bool comp_overlap(RecordingInfo *a, RecordingInfo *b)
static void erase_nulls(RecList &reclist)
static bool comp_recstart(RecordingInfo *a, RecordingInfo *b)
static bool comp_storage_disk_io(FileSystemInfo *a, FileSystemInfo *b)
static bool comp_storage_perc_free_space(FileSystemInfo *a, FileSystemInfo *b)
static bool comp_storage_free_space(FileSystemInfo *a, FileSystemInfo *b)
static bool comp_priority(RecordingInfo *a, RecordingInfo *b)
static QString progdupinit
static bool Recording(const RecordingInfo *p)
static constexpr int64_t kProgramInUseInterval
@ sStatus_Waking
A slave is marked as waking when the master runs the slave's wakeup command.
@ sStatus_Undefined
A slave's sleep status is undefined when it has never connected to the master backend or is not able ...
@ sStatus_FallingAsleep
A slave is marked as falling asleep when told to shutdown by the master.
@ kState_WatchingLiveTV
Watching LiveTV is the state for when we are watching a recording and the user has control over the c...