2#if QT_VERSION >= QT_VERSION_CHECK(6,5,0)
3#include <QtSystemDetection>
16# include <sys/param.h>
18# include <sys/mount.h>
59#define LOC QString("Scheduler: ")
60#define LOC_WARN QString("Scheduler, Warning: ")
61#define LOC_ERR QString("Scheduler, Error: ")
68 const QString& tmptable,
Scheduler *master_sched) :
70 m_recordTable(tmptable),
71 m_priorityTable(
"powerpriority"),
72 m_specSched(master_sched),
84 if (tmptable ==
"powerpriority_tmp")
99 start(QThread::LowPriority);
165 if (!query.
exec(
"SELECT count(*) FROM capturecard") || !query.
next())
174 LOG(VB_GENERAL, LOG_ERR,
LOC +
175 "No capture cards are defined in the database.\n\t\t\t"
176 "Perhaps you should re-read the installation instructions?");
180 query.
prepare(
"SELECT sourceid,name FROM videosource ORDER BY sourceid;");
195 "WHERE sourceid = :SOURCEID "
199 if (!subquery.
exec())
203 else if (!subquery.
next())
205 LOG(VB_GENERAL, LOG_WARNING,
LOC +
206 QString(
"Video source '%1' is defined, "
207 "but is not attached to a card input.")
208 .arg(query.
value(1).toString()));
218 LOG(VB_GENERAL, LOG_ERR,
LOC +
219 "No channel sources defined in the database");
268 return aprec < bprec;
291 Qt::CaseInsensitive);
305 Qt::CaseInsensitive);
319 int arec =
static_cast<int>
324 int brec =
static_cast<int>
339 int atype =
static_cast<int>
342 int btype =
static_cast<int>
346 return atype > btype;
349 int apast =
static_cast<int>
351 int bpast =
static_cast<int>
354 return apast < bpast;
385 int arec =
static_cast<int>
388 int brec =
static_cast<int>
401 int atype =
static_cast<int>
404 int btype =
static_cast<int>
408 return atype > btype;
411 int apast =
static_cast<int>
413 int bpast =
static_cast<int>
416 return apast < bpast;
451 LOG(VB_SCHEDULE, LOG_INFO,
"BuildWorkList...");
456 LOG(VB_SCHEDULE, LOG_INFO,
"AddNewRecords...");
458 LOG(VB_SCHEDULE, LOG_INFO,
"AddNotListed...");
461 LOG(VB_SCHEDULE, LOG_INFO,
"Sort by time...");
463 LOG(VB_SCHEDULE, LOG_INFO,
"PruneOverlaps...");
466 LOG(VB_SCHEDULE, LOG_INFO,
"Sort by priority...");
468 LOG(VB_SCHEDULE, LOG_INFO,
"BuildListMaps...");
470 LOG(VB_SCHEDULE, LOG_INFO,
"SchedNewRecords...");
472 LOG(VB_SCHEDULE, LOG_INFO,
"SchedLiveTV...");
474 LOG(VB_SCHEDULE, LOG_INFO,
"ClearListMaps...");
479 LOG(VB_SCHEDULE, LOG_INFO,
"Sort by time...");
481 LOG(VB_SCHEDULE, LOG_INFO,
"PruneRedundants...");
484 LOG(VB_SCHEDULE, LOG_INFO,
"Sort by time...");
486 LOG(VB_SCHEDULE, LOG_INFO,
"ClearWorkList...");
504 where =
"WHERE recordid IS NULL ";
506 thequery = QString(
"CREATE TEMPORARY TABLE recordmatch ") +
507 "SELECT * FROM recordmatch " + where +
"; ";
511 bool ok = query.
exec();
519 thequery =
"ALTER TABLE recordmatch "
520 " ADD UNIQUE INDEX (recordid, chanid, starttime); ";
528 thequery =
"ALTER TABLE recordmatch "
529 " ADD INDEX (chanid, starttime, manualid); ";
539 auto fillstart = nowAsDuration<std::chrono::microseconds>();
541 auto fillend = nowAsDuration<std::chrono::microseconds>();
542 auto matchTime = fillend - fillstart;
544 LOG(VB_SCHEDULE, LOG_INFO,
"CreateTempTables...");
547 fillstart = nowAsDuration<std::chrono::microseconds>();
548 LOG(VB_SCHEDULE, LOG_INFO,
"UpdateDuplicates...");
550 fillend = nowAsDuration<std::chrono::microseconds>();
551 auto checkTime = fillend - fillstart;
553 fillstart = nowAsDuration<std::chrono::microseconds>();
555 fillend = nowAsDuration<std::chrono::microseconds>();
556 auto placeTime = fillend - fillstart;
558 LOG(VB_SCHEDULE, LOG_INFO,
"DeleteTempTables...");
562 queryDrop.
prepare(
"DROP TABLE recordmatch;");
563 if (!queryDrop.
exec())
569 QString msg = QString(
"Speculative scheduled %1 items in %2 "
570 "= %3 match + %4 check + %5 place")
572 .arg(duration_cast<floatsecs>(matchTime + checkTime + placeTime).count(), 0,
'f', 1)
573 .arg(duration_cast<floatsecs>(matchTime).count(), 0,
'f', 2)
574 .arg(duration_cast<floatsecs>(checkTime).count(), 0,
'f', 2)
575 .arg(duration_cast<floatsecs>(placeTime).count(), 0,
'f', 2);
576 LOG(VB_GENERAL, LOG_INFO, msg);
587 for (
auto & it : schedList)
598 LOG(VB_SCHEDULE, LOG_INFO,
"--- print list start ---");
599 LOG(VB_SCHEDULE, LOG_INFO,
"Title - Subtitle Ch Station "
600 "Day Start End G I T N Pri");
602 for (
auto *first : list)
604 if (onlyFutureRecordings &&
605 ((first->GetRecordingEndTime() < now &&
606 first->GetScheduledEndTime() < now) ||
607 (first->GetRecordingStartTime() < now && !
Recording(first))))
613 LOG(VB_SCHEDULE, LOG_INFO,
"--- print list end ---");
626 static QString initialOutstr =
" ";
628 static QString initialOutstr =
"";
631 QString outstr = initialOutstr +
prefix;
634 .leftJustified(34 -
prefix.length(),
' ',
true);
636 outstr += QString(
"%1 %2 %3 %4-%5 %6 %7 ")
638 p->GetChanNum().rightJustified(5,
' '),
639 p->GetChannelSchedulingID().leftJustified(7,
' ',
true),
640 p->GetRecordingStartTime().toLocalTime().toString(
"dd hh:mm"),
641 p->GetRecordingEndTime().toLocalTime().toString(
"hh:mm"),
642 p->GetShortInputName().rightJustified(2,
' '),
643 QString::number(
p->GetInputID()).rightJustified(2,
' '));
644 outstr += QString(
"%1 %2 %3")
645 .arg(
toQChar(
p->GetRecordingRuleType()))
647 .arg(
p->GetRecordingPriority());
648 if (
p->GetRecordingPriority2())
649 outstr += QString(
"/%1").arg(
p->GetRecordingPriority2());
651 LOG(VB_SCHEDULE, LOG_INFO, outstr);
660 if (
p->IsSameTitleTimeslotAndChannel(*pginfo))
679 LOG(VB_GENERAL, LOG_INFO,
680 QString(
"Updating status for %1 on cardid [%2] (%3 => %4)")
682 QString::number(
p->GetInputID()),
684 p->GetRecordingRuleType()),
686 p->GetRecordingRuleType())));
694 p->AddHistory(
false);
712 const QDateTime &startts,
714 const QDateTime &recendts)
720 if (
p->GetInputID() == cardid &&
p->GetChanID() == chanid &&
721 p->GetScheduledStartTime() == startts)
723 p->SetRecordingEndTime(recendts);
725 if (
p->GetRecordingStatus() != recstatus)
727 LOG(VB_GENERAL, LOG_INFO,
728 QString(
"Updating status for %1 on cardid [%2] (%3 => %4)")
730 QString::number(
p->GetInputID()),
732 p->GetRecordingRuleType()),
734 p->GetRecordingRuleType())));
740 p->SetRecordingStatus(recstatus);
742 p->AddHistory(
false);
793 LOG(VB_GENERAL, LOG_ERR,
794 QString(
"Failed to change end time on card %1 to %2")
841 for (
auto *sp : slavelist)
847 if (!sp->GetTitle().isEmpty() &&
848 sp->GetScheduledStartTime() == rp->GetScheduledStartTime() &&
849 sp->GetChannelSchedulingID().compare(
850 rp->GetChannelSchedulingID(), Qt::CaseInsensitive) == 0 &&
851 sp->GetTitle().compare(rp->GetTitle(),
852 Qt::CaseInsensitive) == 0)
854 if (sp->GetInputID() == rp->GetInputID() ||
859 rp->SetRecordingStatus(sp->GetRecordingStatus());
861 rp->AddHistory(
false);
862 LOG(VB_GENERAL, LOG_INFO,
863 QString(
"setting %1/%2/\"%3\" as %4")
864 .arg(QString::number(sp->GetInputID()),
865 sp->GetChannelSchedulingID(),
871 LOG(VB_GENERAL, LOG_NOTICE,
872 QString(
"%1/%2/\"%3\" is already recording on card %4")
873 .arg(sp->GetInputID())
874 .arg(sp->GetChannelSchedulingID(),
876 .arg(rp->GetInputID()));
879 else if (sp->GetInputID() == rp->GetInputID() &&
886 rp->AddHistory(
false);
887 LOG(VB_GENERAL, LOG_INFO,
888 QString(
"setting %1/%2/\"%3\" as aborted")
889 .arg(QString::number(rp->GetInputID()),
890 rp->GetChannelSchedulingID(),
895 if (sp->GetInputID() && !found)
897 sp->m_mplexId = sp->QueryMplexID();
901 sp->AddHistory(
false);
902 LOG(VB_GENERAL, LOG_INFO,
903 QString(
"adding %1/%2/\"%3\" as recording")
904 .arg(QString::number(sp->GetInputID()),
905 sp->GetChannelSchedulingID(),
917 if (rp->GetInputID() == cardid &&
926 rp->AddHistory(
false,
false,
true);
931 rp->AddHistory(
false);
934 LOG(VB_GENERAL, LOG_INFO, QString(
"setting %1/%2/\"%3\" as aborted")
935 .arg(QString::number(rp->GetInputID()), rp->GetChannelSchedulingID(),
987 for (
auto it = reclist.begin(); it != reclist.end(); ++it)
1015 *(dreciter++) =
nullptr;
1024 QMap<uint, uint> badinputs;
1039 ++badinputs[
p->GetInputID()];
1042 conflictlist->push_back(
p);
1048 QMap<uint, uint>::iterator it;
1049 for (it = badinputs.begin(); it != badinputs.end(); ++it)
1052 QString(
"Ignored %1 entries for invalid input %2")
1053 .arg(badinputs[it.value()]).arg(it.key()));
1088 bool ignoreinput)
const
1091 for ( ; iter != cardlist.end(); ++iter)
1104 msg = QString(
"comparing '%1' on %2 with '%3' on %4")
1105 .arg(
p->GetTitle(),
p->GetChanNum(),
1109 if (
p->GetInputID() != q->
GetInputID() && !ignoreinput)
1111 const std::vector<unsigned int> &conflicting_inputs =
1113#ifdef __cpp_lib_ranges_contains
1114 if (!std::ranges::contains(conflicting_inputs, q->
GetInputID()))
1117 q->
GetInputID()) == conflicting_inputs.end())
1121 msg +=
" cardid== ";
1130 msg +=
" no-overlap ";
1137 (((
p->m_mplexId != 0U) &&
p->m_mplexId == q->
m_mplexId) ||
1138 ((
p->m_mplexId == 0U) &&
p->GetChanID() == q->
GetChanID()));
1150 msg +=
" no-overlap ";
1159 LOG(VB_SCHEDULE, LOG_INFO, msg);
1160 LOG(VB_SCHEDULE, LOG_INFO,
1161 QString(
" cardid's: [%1], [%2] Share an input group, "
1162 "mplexid's: %3, %4")
1176 LOG(VB_SCHEDULE, LOG_INFO,
"Found conflict");
1179 *paffinity += affinity;
1184 LOG(VB_SCHEDULE, LOG_INFO,
"No conflict");
1187 *paffinity += affinity;
1195 bool checkAll)
const
1198 auto k = conflictlist.cbegin();
1205 return firstConflict;
1232 for (
auto *q : showinglist)
1241 if (q->IsSameTitleStartTimeAndChannel(*
p))
1247 if (q->GetRecordingStartTime() <
p->GetRecordingStartTime())
1259 p->m_savedrecstatus =
p->GetRecordingStatus();
1267 p->SetRecordingStatus(
p->m_savedrecstatus);
1288 uint bestaffinity = 0;
1290 for (
auto *q : *showinglist)
1296 (q->GetRecordingPriority() <
p->GetRecordingPriority() ||
1297 (q->GetRecordingPriority() ==
p->GetRecordingPriority() &&
1298 q->GetRecordingPriority2() <
p->GetRecordingPriority2())))
1310 if (!
p->IsSameTitleStartTimeAndChannel(*q))
1345 PrintRec(q, QString(
" %1:").arg(affinity));
1346 if (!best || affinity > bestaffinity)
1349 bestaffinity = affinity;
1357 QString msg = QString(
1358 "Moved \"%1\" on chanid: %2 from card: %3 to %4 at %5 "
1359 "to avoid LiveTV conflict")
1360 .arg(
p->GetTitle()).arg(
p->GetChanID())
1363 LOG(VB_GENERAL, LOG_INFO, msg);
1375 p->SetRecordingStatus(oldstatus);
1383 LOG(VB_SCHEDULE, LOG_DEBUG,
1384 "+ = schedule this showing to be recorded");
1385 LOG(VB_SCHEDULE, LOG_DEBUG,
1386 "n: = could schedule this showing with affinity");
1387 LOG(VB_SCHEDULE, LOG_DEBUG,
1388 "n# = could not schedule this showing, with affinity");
1389 LOG(VB_SCHEDULE, LOG_DEBUG,
1390 "! = conflict caused by this showing");
1391 LOG(VB_SCHEDULE, LOG_DEBUG,
1392 "/ = retry this showing, same priority pass");
1393 LOG(VB_SCHEDULE, LOG_DEBUG,
1394 "? = retry this showing, lower priority pass");
1395 LOG(VB_SCHEDULE, LOG_DEBUG,
1396 "> = try another showing for this program");
1397 LOG(VB_SCHEDULE, LOG_DEBUG,
1398 "- = unschedule a showing in favor of another one");
1417 auto levelStart = i;
1418 int recpriority = (*i)->GetRecordingPriority();
1423 (*i)->GetRecordingPriority() != recpriority)
1426 auto sublevelStart = i;
1427 int recpriority2 = (*i)->GetRecordingPriority2();
1428 LOG(VB_SCHEDULE, LOG_DEBUG, QString(
"Trying priority %1/%2...")
1429 .arg(recpriority).arg(recpriority2));
1433 LOG(VB_SCHEDULE, LOG_DEBUG, QString(
"Retrying priority %1/%2...")
1434 .arg(recpriority).arg(recpriority2));
1439 LOG(VB_SCHEDULE, LOG_DEBUG, QString(
"Retrying priority %1/*...")
1449 int recpriority,
int recpriority2)
1455 for ( ; i != end; ++i)
1457 if ((*i)->GetRecordingPriority() != recpriority ||
1458 (*i)->GetRecordingPriority2() != recpriority2 ||
1465 (*i)->GetRecordingPriority() != recpriority ||
1466 (*i)->GetRecordingPriority2() != recpriority2)
1471 uint bestaffinity = 0;
1474 for ( ; i != end; ++i)
1476 if ((*i)->GetRecordingPriority() != recpriority ||
1477 (*i)->GetRecordingPriority2() != recpriority2 ||
1478 (*i)->GetRecordingStartTime() !=
1480 (*i)->GetRecordingRuleID() !=
1482 (*i)->GetTitle() != first->
GetTitle() ||
1497 PrintRec(*i, QString(
" %1#").arg(affinity));
1502 PrintRec(*i, QString(
" %1:").arg(affinity));
1503 if (!best || affinity > bestaffinity)
1506 bestaffinity = affinity;
1527 bool samePriority,
bool livetv)
1531 for ( ; i != end; ++i)
1534 retry_list.push_back(*i);
1536 std::ranges::stable_sort(retry_list,
comp_retry);
1538 for (
auto *
p : retry_list)
1557 auto k = conflictlist.cbegin();
1579 int lastrecpri2 = 0;
1614 p->SetRecordingStatus(
p->m_oldrecstatus);
1625 p->ClearInputName();
1643 p->GetRecordingPriority2() >
1647 lastrecpri2 -
p->GetRecordingPriority2());
1662 QMap<int, QDateTime> nextRecMap;
1670 nextRecMap[
p->GetRecordingRuleID()].isNull())
1672 nextRecMap[
p->GetRecordingRuleID()] =
p->GetRecordingStartTime();
1676 p->GetParentRecordingRuleID() > 0 &&
1679 nextRecMap[
p->GetParentRecordingRuleID()].isNull())
1681 nextRecMap[
p->GetParentRecordingRuleID()] =
1682 p->GetRecordingStartTime();
1688 query.
prepare(
"SELECT recordid, next_record FROM record;");
1694 while (query.
next())
1696 int recid = query.
value(0).toInt();
1699 if (next_record == nextRecMap[recid])
1702 if (nextRecMap[recid].isValid())
1704 subquery.
prepare(
"UPDATE record SET next_record = :NEXTREC "
1705 "WHERE recordid = :RECORDID;");
1707 subquery.
bindValue(
":NEXTREC", nextRecMap[recid]);
1708 if (!subquery.
exec())
1711 else if (next_record.isValid())
1713 subquery.
prepare(
"UPDATE record "
1714 "SET next_record = NULL "
1715 "WHERE recordid = :RECORDID;");
1717 if (!subquery.
exec())
1729 strlist << QString::number(retlist.size());
1731 while (!retlist.empty())
1734 p->ToStringList(strlist);
1736 retlist.pop_front();
1747 nullptr,
true); ++i)
1758 bool hasconflicts =
false;
1762 if (recRuleId > 0 &&
1763 p->GetRecordingRuleID() !=
static_cast<uint>(recRuleId))
1766 hasconflicts =
true;
1770 return hasconflicts;
1777 bool hasconflicts =
false;
1781 if (recRuleId > 0 &&
1782 p->GetRecordingRuleID() !=
static_cast<uint>(recRuleId))
1786 hasconflicts =
true;
1790 return hasconflicts;
1797 QMap<QString,ProgramInfo*> recMap;
1814 if (recordedid ==
p->GetRecordingID())
1843 strList << QString::number(static_cast<int>(hasconflicts));
1844 strList << QString::number(retlist.size());
1846 while (!retlist.empty())
1849 p->ToStringList(strList);
1851 retlist.pop_front();
1863 strList << QString::number(schedlist.size());
1865 while (!schedlist.empty())
1870 schedlist.pop_front();
1885 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"AddRecording() recid: %1")
1891 p->IsSameTitleTimeslotAndChannel(pi))
1893 LOG(VB_GENERAL, LOG_INFO,
LOC +
"Not adding recording, " +
1894 QString(
"'%1' is already in reclist.")
1900 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
1901 QString(
"Adding '%1' to reclist.").arg(pi.
GetTitle()));
1904 new_pi->m_mplexId = new_pi->QueryMplexID();
1905 new_pi->m_sgroupId =
m_sinputInfoMap[new_pi->GetInputID()].m_sgroupId;
1911 new_pi->AddHistory(
false);
1914 new_pi->GetRecordingRule();
1918 QString(
"AddRecording %1").arg(pi.
GetTitle()));
1926 LOG(VB_GENERAL, LOG_ERR,
LOC +
1927 "IsBusyRecording() -> true, no tvList or no rcinfo");
1938 bool is_busy = rctv1->
IsBusy(&busy_input, -1s);
1950 const std::vector<unsigned int> &inputids =
m_sinputInfoMap[inputid].m_conflictingInputs;
1951 std::vector<unsigned int> &group_inputs =
m_sinputInfoMap[inputid].m_groupInputs;
1952 for (
uint id : inputids)
1957 LOG(VB_SCHEDULE, LOG_ERR,
LOC +
1958 QString(
"IsBusyRecording() -> true, rctv(NULL) for input %2")
1965 if (rctv2->
IsBusy(&busy_input, -1s))
1986#ifdef __cpp_lib_ranges_contains
1987 std::ranges::contains(group_inputs,
id))
2007 query.
prepare(
"UPDATE oldrecorded SET recstatus = :RSABORTED "
2008 " WHERE recstatus = :RSRECORDING OR "
2009 " recstatus = :RSTUNING OR "
2010 " recstatus = :RSFAILING");
2019 query.
prepare(
"UPDATE oldrecorded SET recstatus = :RSMISSED "
2020 "WHERE recstatus = :RSWILLRECORD OR "
2021 " recstatus = :RSPENDING");
2030 query.
prepare(
"UPDATE oldrecorded SET recstatus = :RSPREVIOUS "
2031 "WHERE recstatus = :RSCURRENT");
2040 query.
prepare(
"UPDATE oldrecorded SET future = 0 "
2041 "WHERE future > 0 AND "
2042 " endtime < (NOW() - INTERVAL 475 MINUTE)");
2062 std::this_thread::sleep_for(3s);
2069 std::chrono::seconds prerollseconds = 0s;
2070 std::chrono::seconds wakeThreshold = 5min;
2073 bool blockShutdown =
2075 bool firstRun =
true;
2078 QDateTime idleSince = QDateTime();
2079 std::chrono::seconds schedRunTime = 0s;
2080 bool statuschanged =
false;
2082 QDateTime nextWakeTime = nextStartTime;
2096 nextWakeTime = std::min(nextWakeTime, nextStartTime);
2098 auto secs_to_next = std::chrono::seconds(curtime.secsTo(nextStartTime));
2099 auto sched_sleep = std::max(std::chrono::milliseconds(curtime.msecsTo(nextWakeTime)), 0ms);
2101 sched_sleep = std::min(sched_sleep, 15000ms);
2103 int const kSleepCheck = 300;
2104 bool checkSlaves = curtime >= nextSleepCheck;
2108 if ((secs_to_next > -60s && secs_to_next < schedRunTime) ||
2109 (!haveRequests && !checkSlaves))
2111 if (sched_sleep > 0ms)
2113 LOG(VB_SCHEDULE, LOG_INFO,
2114 QString(
"sleeping for %1 ms "
2115 "(s2n: %2 sr: %3 qr: %4 cs: %5)")
2116 .arg(sched_sleep.count()).arg(secs_to_next.count()).arg(schedRunTime.count())
2117 .arg(haveRequests).arg(checkSlaves));
2139 wakeThreshold = std::max(wakeThreshold, prerollseconds + 120s);
2141 QElapsedTimer
t;
t.start();
2144 statuschanged =
true;
2147 auto elapsed = std::chrono::ceil<std::chrono::seconds>(std::chrono::milliseconds(
t.elapsed()));
2148 schedRunTime = std::max(elapsed + elapsed/2 + 2s, schedRunTime);
2169 checkSlaves =
false;
2179 nextWakeTime = nextSleepCheck;
2183 for ( ; startIter !=
m_recList.end(); ++startIter)
2185 if ((*startIter)->GetRecordingStatus() !=
2186 (*startIter)->m_oldrecstatus)
2194 for (
auto it = startIter; it !=
m_recList.end(); ++it)
2196 auto secsleft = std::chrono::seconds(curtime.secsTo((*it)->GetRecordingStartTime()));
2197 auto timeBeforePreroll = secsleft - prerollseconds;
2198 if (timeBeforePreroll <= wakeThreshold)
2203 if (timeBeforePreroll > 0s)
2205 std::chrono::seconds waitpending;
2206 if (timeBeforePreroll > 120s)
2207 waitpending = timeBeforePreroll -120s;
2209 waitpending = std::min(timeBeforePreroll, 30s);
2223 for (
auto it = startIter; it !=
m_recList.end() && !done; ++it)
2226 **it, statuschanged, nextStartTime, nextWakeTime,
2250 if (idleSince.isValid())
2255 else if (idleSince.addSecs((
idleTimeoutSecs - 30s).count()) <= curtime)
2261 statuschanged =
false;
2268 const QString &title,
const QString &subtitle,
2269 const QString &descrip,
2270 const QString &programid)
2273 QString filterClause;
2276 if (!title.isEmpty())
2278 filterClause +=
"AND p.title = :TITLE ";
2279 bindings[
":TITLE"] = title;
2283 if (programid !=
"**any**")
2285 filterClause +=
"AND (0 ";
2286 if (!subtitle.isEmpty())
2289 filterClause +=
"OR p.subtitle = :SUBTITLE1 "
2290 "OR p.description = :SUBTITLE2 ";
2291 bindings[
":SUBTITLE1"] = subtitle;
2292 bindings[
":SUBTITLE2"] = subtitle;
2294 if (!descrip.isEmpty())
2297 filterClause +=
"OR p.description = :DESCRIP1 "
2298 "OR p.subtitle = :DESCRIP2 ";
2299 bindings[
":DESCRIP1"] = descrip;
2300 bindings[
":DESCRIP2"] = descrip;
2302 if (!programid.isEmpty())
2304 filterClause +=
"OR p.programid = :PROGRAMID ";
2305 bindings[
":PROGRAMID"] = programid;
2307 filterClause +=
") ";
2310 query.
prepare(QString(
"UPDATE recordmatch rm "
2312 " ON rm.recordid = r.recordid "
2313 "INNER JOIN program p "
2314 " ON rm.chanid = p.chanid "
2315 " AND rm.starttime = p.starttime "
2316 " AND rm.manualid = p.manualid "
2317 "SET oldrecduplicate = -1 "
2318 "WHERE p.generic = 0 "
2319 " AND r.type NOT IN (%2, %3, %4) ")
2325 MSqlBindings::const_iterator it;
2326 for (it = bindings.cbegin(); it != bindings.cend(); ++it)
2331 if (findid && programid !=
"**any**")
2333 query.
prepare(
"UPDATE recordmatch rm "
2334 "SET oldrecduplicate = -1 "
2335 "WHERE rm.recordid = :RECORDID "
2336 " AND rm.findid = :FINDID");
2350 auto fillstart = nowAsDuration<std::chrono::microseconds>();
2352 bool deleteFuture =
false;
2353 bool runCheck =
false;
2359 if (!request.empty())
2361 tokens = request[0].split(
' ', Qt::SkipEmptyParts);
2364 if (request.empty() || tokens.empty())
2366 LOG(VB_GENERAL, LOG_ERR,
"Empty Reschedule request received");
2370 LOG(VB_GENERAL, LOG_INFO, QString(
"Reschedule requested for %1")
2371 .arg(request.join(
" | ")));
2373 if (tokens[0] ==
"MATCH")
2375 if (tokens.size() < 5)
2377 LOG(VB_GENERAL, LOG_ERR,
2378 QString(
"Invalid RescheduleMatch request received (%1)")
2383 uint recordid = tokens[1].toUInt();
2384 uint sourceid = tokens[2].toUInt();
2385 uint mplexid = tokens[3].toUInt();
2387 deleteFuture =
true;
2395 else if (tokens[0] ==
"CHECK")
2397 if (tokens.size() < 4 || request.size() < 5)
2399 LOG(VB_GENERAL, LOG_ERR,
2400 QString(
"Invalid RescheduleCheck request received (%1)")
2405 uint recordid = tokens[2].toUInt();
2406 uint findid = tokens[3].toUInt();
2407 const QString& title = request[1];
2408 const QString& subtitle = request[2];
2409 const QString& descrip = request[3];
2410 const QString& programid = request[4];
2419 else if (tokens[0] !=
"PLACE")
2421 LOG(VB_GENERAL, LOG_ERR,
2422 QString(
"Unknown Reschedule request received (%1)")
2432 query.
prepare(
"DELETE oldrecorded FROM oldrecorded "
2433 "LEFT JOIN recordmatch ON "
2434 " recordmatch.chanid = oldrecorded.chanid AND "
2435 " recordmatch.starttime = oldrecorded.starttime "
2436 "WHERE oldrecorded.future > 0 AND "
2437 " recordmatch.recordid IS NULL");
2442 auto fillend = nowAsDuration<std::chrono::microseconds>();
2443 auto matchTime = fillend - fillstart;
2445 LOG(VB_SCHEDULE, LOG_INFO,
"CreateTempTables...");
2448 fillstart = nowAsDuration<std::chrono::microseconds>();
2451 LOG(VB_SCHEDULE, LOG_INFO,
"UpdateDuplicates...");
2454 fillend = nowAsDuration<std::chrono::microseconds>();
2455 auto checkTime = fillend - fillstart;
2457 fillstart = nowAsDuration<std::chrono::microseconds>();
2459 fillend = nowAsDuration<std::chrono::microseconds>();
2460 auto placeTime = fillend - fillstart;
2462 LOG(VB_SCHEDULE, LOG_INFO,
"DeleteTempTables...");
2472 LOG(VB_GENERAL, LOG_INFO,
"Reschedule interrupted, will retry");
2477 msg = QString(
"Scheduled %1 items in %2 "
2478 "= %3 match + %4 check + %5 place")
2480 .arg(duration_cast<floatsecs>(matchTime + checkTime + placeTime).count(), 0,
'f', 1)
2481 .arg(duration_cast<floatsecs>(matchTime).count(), 0,
'f', 2)
2482 .arg(duration_cast<floatsecs>(checkTime).count(), 0,
'f', 2)
2483 .arg(duration_cast<floatsecs>(placeTime).count(), 0,
'f', 2);
2484 LOG(VB_GENERAL, LOG_INFO, msg);
2489 if (
p->GetRecordingStatus() !=
p->m_oldrecstatus)
2492 p->AddHistory(
false,
false,
false);
2496 p->AddHistory(
false,
false,
false);
2498 p->AddHistory(
false,
false,
true);
2500 else if (
p->m_future)
2506 p->m_future =
false;
2515 std::chrono::seconds prerollseconds,
2518 bool blockShutdown =
true;
2523 QString startupParam =
"user";
2527 for ( ; firstRunIter !=
m_recList.end(); ++firstRunIter)
2538 ((std::chrono::seconds(curtime.secsTo((*firstRunIter)->GetRecordingStartTime())) -
2541 LOG(VB_GENERAL, LOG_INFO,
LOC +
"AUTO-Startup assumed");
2542 startupParam =
"auto";
2546 blockShutdown =
false;
2550 LOG(VB_GENERAL, LOG_INFO,
LOC +
"Seem to be woken up by USER");
2562 return blockShutdown;
2568 static constexpr std::array<const std::chrono::seconds,4> kSysEventSecs = { 120s, 90s, 60s, 30s };
2572 auto secsleft = std::chrono::seconds(curtime.secsTo(nextrectime));
2582 bool pendingEventSent =
false;
2583 for (
size_t i = 0; i < kSysEventSecs.size(); i++)
2585 auto pending_secs = std::max((secsleft - prerollseconds), 0s);
2586 if ((pending_secs <= kSysEventSecs[i]) &&
2589 if (!pendingEventSent)
2592 QString(
"REC_PENDING SECS %1").arg(pending_secs.count()), &ri);
2596 pendingEventSent =
true;
2602 for (
size_t i = 0; i < kSysEventSecs.size(); i++)
2610 keys.insert(rec->MakeUniqueKey());
2611 keys.insert(
"something");
2614 QSet<QString>::iterator sit =
m_sysEvents[i].begin();
2617 if (!keys.contains(*sit))
2628 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
2629 QString(
"Slave Backend %1 is being awakened to record: %2")
2636 ((secsleft - prerollseconds) < 210s) &&
2640 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
2641 QString(
"Slave Backend %1 not available yet, "
2642 "trying to wake it up again.")
2649 ((secsleft - prerollseconds) < 150s) &&
2652 LOG(VB_GENERAL, LOG_WARNING,
LOC +
2653 QString(
"Slave Backend %1 has NOT come "
2654 "back from sleep yet in 150 seconds. Setting "
2655 "slave status to unknown and attempting "
2656 "to reschedule around its tuners.")
2659 for (
auto * enc : std::as_const(*
m_tvList))
2671 QDateTime &nextStartTime, QDateTime &nextWakeTime,
2672 std::chrono::seconds prerollseconds)
2679 std::chrono::seconds origprerollseconds = prerollseconds;
2686 auto nextwake = std::chrono::seconds(nextWakeTime.secsTo(nextrectime));
2687 if (nextwake - prerollseconds > 5min)
2689 nextStartTime = std::min(nextStartTime, nextrectime);
2693 if (curtime < nextrectime)
2694 nextWakeTime = std::min(nextWakeTime, nextrectime);
2700 auto secsleft = std::chrono::seconds(curtime.secsTo(nextrectime));
2705 if (secsleft - prerollseconds > 1min)
2707 nextStartTime = std::min(nextStartTime, nextrectime.addSecs(-30));
2708 nextWakeTime = std::min(nextWakeTime,
2709 nextrectime.addSecs(-prerollseconds.count() - 60));
2722 if (secsleft - prerollseconds > 35s)
2724 nextStartTime = std::min(nextStartTime, nextrectime.addSecs(-30));
2725 nextWakeTime = std::min(nextWakeTime,
2726 nextrectime.addSecs(-prerollseconds.count() - 35));
2735 QString msg = QString(
"Invalid cardid [%1] for %2")
2737 LOG(VB_GENERAL, LOG_ERR,
LOC + msg);
2741 statuschanged =
true;
2749 QString msg = QString(
"SUPPRESSED recording \"%1\" on channel: "
2750 "%2 on cardid: [%3], sourceid %4. Tuner "
2751 "is locked by an external application.")
2756 LOG(VB_GENERAL, LOG_NOTICE, msg);
2760 statuschanged =
true;
2771 if (prerollseconds > 0s)
2779 if (isBusyRecording)
2782 nextWakeTime = std::min(nextWakeTime, curtime.addSecs(5));
2783 prerollseconds = 0s;
2787 if (secsleft - prerollseconds > 30s)
2789 nextStartTime = std::min(nextStartTime, nextrectime.addSecs(-30));
2790 nextWakeTime = std::min(nextWakeTime,
2791 nextrectime.addSecs(-prerollseconds.count() - 30));
2799 LOG(VB_SCHEDULE, LOG_WARNING,
2800 QString(
"WARNING: Slave Backend %1 has NOT come "
2801 "back from sleep yet. Recording can "
2802 "not begin yet for: %2")
2808 LOG(VB_SCHEDULE, LOG_WARNING,
2809 QString(
"WARNING: Slave Backend %1 has NOT come "
2810 "back from sleep yet. Setting slave "
2811 "status to unknown and attempting "
2812 "to reschedule around its tuners.")
2815 for (
auto * enc : std::as_const(*
m_tvList))
2824 nextStartTime = std::min(nextStartTime, nextrectime);
2825 nextWakeTime = std::min(nextWakeTime, curtime.addSecs(1));
2832 QString recording_dir;
2851 MythEvent me(QString(
"ADD_CHILD_INPUT %1")
2854 nextWakeTime = std::min(nextWakeTime, curtime.addSecs(1));
2864 nexttv->
RecordPending(&tempri, std::max(secsleft, 0s),
false);
2870 if (secsleft - prerollseconds > 0s)
2872 nextStartTime = std::min(nextStartTime, nextrectime);
2873 nextWakeTime = std::min(nextWakeTime,
2874 nextrectime.addSecs(-prerollseconds.count()));
2879#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
2880 recstartts = QDateTime(
2882 QTime(recstartts.time().hour(), recstartts.time().minute()), Qt::UTC);
2884 recstartts = QDateTime(
2886 QTime(recstartts.time().hour(), recstartts.time().minute()),
2887 QTimeZone(QTimeZone::UTC));
2892 QString details = QString(
"%1: channel %2 on cardid [%3], sourceid %4")
2919 statuschanged =
true;
2932 bool doSchedAfterStart =
2942 msg = QString(
"Started recording");
2944 msg = QString(
"Tuning recording");
2946 msg = QString(
"Canceled recording (%1)")
2948 LOG(VB_GENERAL, LOG_INFO, QString(
"%1: %2").arg(msg, details));
2956 MythEvent me(QString(
"FORCE_DELETE_RECORDING %1 %2")
2964 std::chrono::seconds prerollseconds)
2969 LOG(VB_SCHEDULE, LOG_DEBUG,
2970 QString(
"Assigning input for %1/%2/\"%3\"")
2981 for (
uint i = 0; !bestid && i < inputs.size(); ++i)
2983 uint inputid = inputs[i];
2991 auto recstarttime = std::chrono::seconds(now.secsTo(
p->GetRecordingStartTime()));
2992 if (recstarttime > prerollseconds + 60s)
2994 if (
p->GetInputID() != inputid)
3011 LOG(VB_SCHEDULE, LOG_DEBUG,
3012 QString(
"Input %1 has a pending recording").arg(inputid));
3021 LOG(VB_SCHEDULE, LOG_DEBUG,
3022 QString(
"Input %1 is recording").arg(inputid));
3027 LOG(VB_SCHEDULE, LOG_DEBUG,
3028 QString(
"Input %1 is recording but will be free")
3037 LOG(VB_SCHEDULE, LOG_DEBUG,
3038 QString(
"Input %1 is recording but has to stop")
3044 LOG(VB_SCHEDULE, LOG_DEBUG,
3045 QString(
"Input %1 is recording but could be free")
3057 bool isbusy = rctv->
IsBusy(&busy_info, -1s);
3063 LOG(VB_SCHEDULE, LOG_DEBUG,
3064 QString(
"Input %1 is free").arg(inputid));
3070 LOG(VB_SCHEDULE, LOG_DEBUG,
3071 QString(
"Input %1 is on livetv but has to stop")
3082 LOG(VB_SCHEDULE, LOG_INFO,
3083 QString(
"Assigned input %1 for %2/%3/\"%4\"")
3091 LOG(VB_SCHEDULE, LOG_WARNING,
3092 QString(
"Failed to assign input for %1/%2/\"%3\"")
3098 return bestid != 0U;
3108 bool &blockShutdown, QDateTime &idleSince,
3109 std::chrono::seconds prerollseconds,
3115 uint logmask = VB_IDLE;
3116 int now = QTime::currentTime().msecsSinceStartOfDay();
3117 int tm = std::chrono::milliseconds(now) / 15min;
3120 logmask = VB_GENERAL;
3139 LOG(VB_GENERAL, LOG_NOTICE,
"Client is connected, removing startup block on shutdown");
3140 blockShutdown =
false;
3151 bool recording =
false;
3154 QMap<int, EncoderLink *>::const_iterator it;
3158 if ((*it)->IsBusy())
3172 if (!blocking && !recording && !activeJobs && !delay)
3179 if (idleSince.isValid())
3181 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN -1"));
3184 idleSince = QDateTime();
3189 if (statuschanged || !idleSince.isValid())
3191 bool wasValid = idleSince.isValid();
3193 idleSince = curtime;
3196 for ( ; idleIter !=
m_recList.end(); ++idleIter)
3198 if ((*idleIter)->GetRecordingStatus() ==
3200 (*idleIter)->GetRecordingStatus() ==
3207 auto recstarttime = std::chrono::seconds(curtime.secsTo((*idleIter)->GetRecordingStartTime()));
3210 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3211 "a recording is due to "
3213 idleSince = QDateTime();
3224 if (guideRunTime.isValid() &&
3228 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3229 "mythfilldatabase is due to "
3231 idleSince = QDateTime();
3236 if (idleSince.isValid())
3239 if (wasValid && !idleSince.isValid())
3241 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN -1"));
3246 if (idleSince.isValid())
3258 LOG(VB_GENERAL, LOG_WARNING,
3259 "Waited more than 60"
3260 " seconds for shutdown to complete"
3261 " - resetting idle time");
3262 idleSince = QDateTime();
3268 blockShutdown, logmask))
3274 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN -1"));
3280 auto itime = std::chrono::seconds(idleSince.secsTo(curtime));
3284 msg = QString(
"I\'m idle now... shutdown will "
3285 "occur in %1 seconds.")
3287 LOG(VB_GENERAL, LOG_NOTICE, msg);
3288 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN %1")
3295 msg = QString(
"%1 secs left to system shutdown!").arg(remain);
3296 LOG(logmask, LOG_NOTICE, msg);
3297 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN %1").arg(remain));
3306 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3307 "of an active encoder");
3309 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3310 "of a connected client");
3313 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3317 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3318 "of delay request from external application");
3321 if (idleSince.isValid())
3323 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN -1"));
3326 idleSince = QDateTime();
3333 QDateTime &idleSince,
3334 bool &blockShutdown,
uint logmask)
3336 bool retval =
false;
3346 LOG(logmask, LOG_INFO,
3347 "CheckShutdownServer returned - OK to shutdown");
3351 LOG(logmask, LOG_NOTICE,
3352 "CheckShutdownServer returned - Not OK to shutdown");
3354 idleSince = QDateTime();
3357 LOG(logmask, LOG_NOTICE,
3358 "CheckShutdownServer returned - Not OK to shutdown, "
3366 idleSince = QDateTime();
3371 m_noAutoShutdown =
true;
3375 LOG(VB_GENERAL, LOG_NOTICE,
3376 "CheckShutdownServer returned - Not OK");
3379 LOG(VB_GENERAL, LOG_NOTICE, QString(
3380 "CheckShutdownServer returned - Error %1").arg(state));
3393 QDateTime &idleSince)
3398 for ( ; recIter !=
m_recList.end(); ++recIter)
3406 QDateTime restarttime;
3411 .addSecs(-prerollseconds.count());
3419 && guideRefreshTime.isValid()
3421 && (restarttime.isNull() || guideRefreshTime < restarttime))
3422 restarttime = guideRefreshTime;
3424 if (restarttime.isValid())
3428 restarttime = restarttime.addSecs((-1LL) * add);
3431 "hh:mm yyyy-MM-dd");
3433 "echo \'Wakeuptime would "
3434 "be $time if command "
3437 if (setwakeup_cmd.isEmpty())
3439 LOG(VB_GENERAL, LOG_NOTICE,
3440 "SetWakeuptimeCommand is empty, shutdown aborted");
3441 idleSince = QDateTime();
3445 if (wakeup_timeformat ==
"time_t")
3448 setwakeup_cmd.replace(
"$time",
3449 time_ts.setNum(restarttime.toSecsSinceEpoch())
3454 setwakeup_cmd.replace(
3455 "$time", restarttime.toLocalTime().toString(wakeup_timeformat));
3458 LOG(VB_GENERAL, LOG_NOTICE,
3459 QString(
"Running the command to set the next "
3460 "scheduled wakeup time :-\n\t\t\t\t") + setwakeup_cmd);
3465 LOG(VB_GENERAL, LOG_ERR,
3466 "SetWakeuptimeCommand failed, shutdown aborted");
3467 idleSince = QDateTime();
3482 "sudo /sbin/halt -p");
3484 if (!halt_cmd.isEmpty())
3489 LOG(VB_GENERAL, LOG_NOTICE,
3490 QString(
"Running the command to shutdown "
3491 "this computer :-\n\t\t\t\t") + halt_cmd);
3498 LOG(VB_GENERAL, LOG_ERR,
"ServerHaltCommand failed, shutdown aborted");
3503 idleSince = QDateTime();
3509 std::chrono::seconds prerollseconds = 0s;
3510 std::chrono::seconds secsleft = 0s;
3514 bool someSlavesCanSleep =
false;
3515 for (
auto * enc : std::as_const(*
m_tvList))
3517 if (enc->CanSleep())
3518 someSlavesCanSleep =
true;
3521 if (!someSlavesCanSleep)
3524 LOG(VB_SCHEDULE, LOG_INFO,
3525 "Scheduler, Checking for slaves that can be shut down");
3527 auto sleepThreshold =
3530 LOG(VB_SCHEDULE, LOG_DEBUG,
3531 QString(
" Getting list of slaves that will be active in the "
3532 "next %1 minutes.") .arg(duration_cast<std::chrono::minutes>(sleepThreshold).count()));
3534 LOG(VB_SCHEDULE, LOG_DEBUG,
"Checking scheduler's reclist");
3536 QStringList SlavesInUse;
3546 auto recstarttime = std::chrono::seconds(curtime.secsTo(pginfo->GetRecordingStartTime()));
3547 secsleft = recstarttime - prerollseconds;
3548 if (secsleft > sleepThreshold)
3553 EncoderLink *enc = (*m_tvList)[pginfo->GetInputID()];
3560 LOG(VB_SCHEDULE, LOG_DEBUG,
3561 QString(
" Slave %1 will be in use in %2 minutes")
3563 .arg(duration_cast<std::chrono::minutes>(secsleft).count()));
3567 LOG(VB_SCHEDULE, LOG_DEBUG,
3568 QString(
" Slave %1 is in use currently "
3577 LOG(VB_SCHEDULE, LOG_DEBUG,
" Checking inuseprograms table:");
3580 query.
prepare(
"SELECT DISTINCT hostname, recusage FROM inuseprograms "
3581 "WHERE lastupdatetime > :ONEHOURAGO ;");
3582 query.
bindValue(
":ONEHOURAGO", oneHourAgo);
3585 while(query.
next()) {
3586 SlavesInUse << query.
value(0).toString();
3587 LOG(VB_SCHEDULE, LOG_DEBUG,
3588 QString(
" Slave %1 is marked as in use by a %2")
3589 .arg(query.
value(0).toString(),
3590 query.
value(1).toString()));
3594 LOG(VB_SCHEDULE, LOG_DEBUG, QString(
" Shutting down slaves which will "
3595 "be inactive for the next %1 minutes and can be put to sleep.")
3596 .arg(sleepThreshold.count() / 60));
3598 for (
auto * enc : std::as_const(*
m_tvList))
3600 if ((!enc->IsLocal()) &&
3602 (!SlavesInUse.contains(enc->GetHostName())) &&
3603 (!enc->IsFallingAsleep()))
3605 QString sleepCommand =
3607 enc->GetHostName());
3608 QString wakeUpCommand =
3610 enc->GetHostName());
3612 if (!sleepCommand.isEmpty() && !wakeUpCommand.isEmpty())
3614 QString thisHost = enc->GetHostName();
3616 LOG(VB_SCHEDULE, LOG_DEBUG,
3617 QString(
" Commanding %1 to go to sleep.")
3620 if (enc->GoToSleep())
3622 for (
auto * slv : std::as_const(*
m_tvList))
3624 if (slv->GetHostName() == thisHost)
3626 LOG(VB_SCHEDULE, LOG_DEBUG,
3627 QString(
" Marking card %1 on slave %2 "
3628 "as falling asleep.")
3629 .arg(slv->GetInputID())
3630 .arg(slv->GetHostName()));
3637 LOG(VB_GENERAL, LOG_ERR,
LOC +
3638 QString(
"Unable to shutdown %1 slave backend, setting "
3639 "sleep status to undefined.").arg(thisHost));
3640 for (
auto * slv : std::as_const(*
m_tvList))
3642 if (slv->GetHostName() == thisHost)
3655 LOG(VB_GENERAL, LOG_NOTICE,
3656 QString(
"Tried to Wake Up %1, but this is the "
3657 "master backend and it is not asleep.")
3658 .arg(slaveHostname));
3665 if (wakeUpCommand.isEmpty()) {
3666 LOG(VB_GENERAL, LOG_NOTICE,
3667 QString(
"Trying to Wake Up %1, but this slave "
3668 "does not have a WakeUpCommand set.").arg(slaveHostname));
3670 for (
auto * enc : std::as_const(*
m_tvList))
3672 if (enc->GetHostName() == slaveHostname)
3680 for (
auto * enc : std::as_const(*
m_tvList))
3682 if (setWakingStatus && (enc->GetHostName() == slaveHostname))
3684 enc->SetLastWakeTime(curtime);
3689 LOG(VB_SCHEDULE, LOG_NOTICE, QString(
"Executing '%1' to wake up slave.")
3690 .arg(wakeUpCommand));
3702 QStringList SlavesThatCanWake;
3704 for (
auto * enc : std::as_const(*
m_tvList))
3709 thisSlave = enc->GetHostName();
3713 (!SlavesThatCanWake.contains(thisSlave)))
3714 SlavesThatCanWake << thisSlave;
3718 for (; slave < SlavesThatCanWake.count(); slave++)
3720 thisSlave = SlavesThatCanWake[slave];
3721 LOG(VB_SCHEDULE, LOG_NOTICE,
3722 QString(
"Scheduler, Sending wakeup command to slave: %1")
3732 query.
prepare(QString(
"SELECT type,title,subtitle,description,"
3733 "station,startdate,starttime,"
3734 "enddate,endtime,season,episode,category,"
3735 "seriesid,programid,inetref,last_record "
3738 if (!query.
exec() || query.
size() != 1)
3748 QString title = query.
value(1).toString();
3749 QString subtitle = query.
value(2).toString();
3750 QString description = query.
value(3).toString();
3751 QString station = query.
value(4).toString();
3752#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
3753 QDateTime startdt = QDateTime(query.
value(5).toDate(),
3754 query.
value(6).toTime(), Qt::UTC);
3755 int duration = startdt.secsTo(
3756 QDateTime(query.
value(7).toDate(),
3757 query.
value(8).toTime(), Qt::UTC));
3759 QDateTime startdt = QDateTime(query.
value(5).toDate(),
3760 query.
value(6).toTime(),
3761 QTimeZone(QTimeZone::UTC));
3762 int duration = startdt.secsTo(
3763 QDateTime(query.
value(7).toDate(),
3764 query.
value(8).toTime(),
3765 QTimeZone(QTimeZone::UTC)));
3768 int season = query.
value(9).toInt();
3769 int episode = query.
value(10).toInt();
3770 QString category = query.
value(11).toString();
3771 QString seriesid = query.
value(12).toString();
3772 QString programid = query.
value(13).toString();
3773 QString inetref = query.
value(14).toString();
3777 QDate originalairdate = QDate(query.
value(15).toDate());
3779 if (description.isEmpty())
3780 description = startdt.toLocalTime().toString();
3782 query.
prepare(
"SELECT chanid from channel "
3783 "WHERE deleted IS NULL AND callsign = :STATION");
3791 std::vector<unsigned int> chanidlist;
3792 while (query.
next())
3793 chanidlist.push_back(query.
value(0).toUInt());
3797 bool weekday =
false;
3799 QDateTime lstartdt = startdt.toLocalTime();
3814 weekday = (lstartdt.date().dayOfWeek() < 6);
3815 daysoff = lstartdt.date().daysTo(
3817#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
3818 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3819 lstartdt.time(), Qt::LocalTime).toUTC();
3821 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3823 QTimeZone(QTimeZone::LocalTime)
3831 daysoff = lstartdt.date().daysTo(
3833 daysoff = (daysoff + 6) / 7 * 7;
3834#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
3835 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3836 lstartdt.time(), Qt::LocalTime).toUTC();
3838 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3840 QTimeZone(QTimeZone::LocalTime)
3845 LOG(VB_GENERAL, LOG_ERR,
3846 QString(
"Invalid rectype for manual recordid %1").arg(recordid));
3852 for (
uint id : chanidlist)
3854 if (weekday && startdt.toLocalTime().date().dayOfWeek() >= 6)
3857 query.
prepare(
"REPLACE INTO program (chanid, starttime, endtime,"
3858 " title, subtitle, description, manualid,"
3859 " season, episode, category, seriesid, programid,"
3860 " inetref, originalairdate, generic) "
3861 "VALUES (:CHANID, :STARTTIME, :ENDTIME, :TITLE,"
3862 " :SUBTITLE, :DESCRIPTION, :RECORDID, "
3863 " :SEASON, :EPISODE, :CATEGORY, :SERIESID,"
3864 " :PROGRAMID, :INETREF, :ORIGINALAIRDATE, 1)");
3867 query.
bindValue(
":ENDTIME", startdt.addSecs(duration));
3870 query.
bindValue(
":DESCRIPTION", description);
3875 query.
bindValue(
":PROGRAMID", programid);
3877 query.
bindValue(
":ORIGINALAIRDATE", originalairdate);
3886 daysoff += skipdays;
3887#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
3888 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3889 lstartdt.time(), Qt::LocalTime).toUTC();
3891 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3893 QTimeZone(QTimeZone::LocalTime)
3907 query = QString(
"SELECT recordid,search,subtitle,description "
3908 "FROM %1 WHERE search <> %2 AND "
3909 "(recordid = %3 OR %4 = 0) ")
3921 while (result.
next())
3923 QString
prefix = QString(
":NR%1").arg(count);
3924 qphrase = result.
value(3).toString();
3930 LOG(VB_GENERAL, LOG_ERR,
3931 QString(
"Invalid search key in recordid %1")
3932 .arg(result.
value(0).toString()));
3936 QString bindrecid =
prefix +
"RECID";
3937 QString bindphrase =
prefix +
"PHRASE";
3938 QString bindlikephrase1 =
prefix +
"LIKEPHRASE1";
3939 QString bindlikephrase2 =
prefix +
"LIKEPHRASE2";
3940 QString bindlikephrase3 =
prefix +
"LIKEPHRASE3";
3942 bindings[bindrecid] = result.
value(0).toString();
3948 qphrase.remove(
';');
3949 from << result.
value(2).toString();
3950 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
3951 QString(
" AND program.manualid = 0 AND ( %2 )")
3955 bindings[bindlikephrase1] = QString(
"%") + qphrase +
"%";
3957 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
" AND "
3958 "program.manualid = 0 AND "
3959 "program.title LIKE " + bindlikephrase1);
3962 bindings[bindlikephrase1] = QString(
"%") + qphrase +
"%";
3963 bindings[bindlikephrase2] = QString(
"%") + qphrase +
"%";
3964 bindings[bindlikephrase3] = QString(
"%") + qphrase +
"%";
3966 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
3967 " AND program.manualid = 0"
3968 " AND (program.title LIKE " + bindlikephrase1 +
3969 " OR program.subtitle LIKE " + bindlikephrase2 +
3970 " OR program.description LIKE " + bindlikephrase3 +
")");
3973 bindings[bindphrase] = qphrase;
3974 from <<
", people, credits";
3975 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
" AND "
3976 "program.manualid = 0 AND "
3977 "people.name LIKE " + bindphrase +
" AND "
3978 "credits.person = people.person AND "
3979 "program.chanid = credits.chanid AND "
3980 "program.starttime = credits.starttime");
3985 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
3987 QString(
"program.manualid = %1.recordid ")
3991 LOG(VB_GENERAL, LOG_ERR,
3992 QString(
"Unknown RecSearchType (%1) for recordid %2")
3993 .arg(result.
value(1).toInt())
3994 .arg(result.
value(0).toString()));
3995 bindings.remove(bindrecid);
4002 if (recordid == 0 || from.count() == 0)
4004 QString recidmatch =
"";
4006 recidmatch =
"RECTABLE.recordid = :NRRECORDID AND ";
4007 QString s1 = recidmatch +
4008 "RECTABLE.type <> :NRTEMPLATE AND "
4009 "RECTABLE.search = :NRST AND "
4010 "program.manualid = 0 AND "
4011 "program.title = RECTABLE.title ";
4013 QString s2 = recidmatch +
4014 "RECTABLE.type <> :NRTEMPLATE AND "
4015 "RECTABLE.search = :NRST AND "
4016 "program.manualid = 0 AND "
4017 "program.seriesid <> '' AND "
4018 "program.seriesid = RECTABLE.seriesid ";
4028 bindings[
":NRRECORDID"] = recordid;
4034" WHEN RECTABLE.type IN (%1, %2, %3) THEN 0 "
4035" WHEN RECTABLE.type IN (%4, %5, %6) THEN -1 "
4036" ELSE (program.generic - 1) "
4042"(CASE RECTABLE.type "
4044" THEN RECTABLE.findid "
4046" THEN to_days(date_sub(convert_tz(program.starttime, 'UTC', 'SYSTEM'), "
4047" interval time_format(RECTABLE.findtime, '%H:%i') hour_minute)) "
4049" THEN floor((to_days(date_sub(convert_tz(program.starttime, 'UTC', "
4050" 'SYSTEM'), interval time_format(RECTABLE.findtime, '%H:%i') "
4051" hour_minute)) - RECTABLE.findday)/7) * 7 + RECTABLE.findday "
4053" THEN RECTABLE.findid "
4062 const QDateTime &maxstarttime)
4066 QString deleteClause;
4067 QString filterClause = QString(
" AND program.endtime > "
4068 "(NOW() - INTERVAL 480 MINUTE)");
4072 deleteClause +=
" AND recordmatch.recordid = :RECORDID";
4073 bindings[
":RECORDID"] = recordid;
4077 deleteClause +=
" AND channel.sourceid = :SOURCEID";
4078 filterClause +=
" AND channel.sourceid = :SOURCEID";
4079 bindings[
":SOURCEID"] = sourceid;
4083 deleteClause +=
" AND channel.mplexid = :MPLEXID";
4084 filterClause +=
" AND channel.mplexid = :MPLEXID";
4085 bindings[
":MPLEXID"] = mplexid;
4087 if (maxstarttime.isValid())
4089 deleteClause +=
" AND recordmatch.starttime <= :MAXSTARTTIME";
4090 filterClause +=
" AND program.starttime <= :MAXSTARTTIME";
4091 bindings[
":MAXSTARTTIME"] = maxstarttime;
4094 query.
prepare(QString(
"DELETE recordmatch FROM recordmatch, channel "
4095 "WHERE recordmatch.chanid = channel.chanid")
4097 MSqlBindings::const_iterator it;
4098 for (it = bindings.cbegin(); it != bindings.cend(); ++it)
4106 bindings.remove(
":RECORDID");
4108 query.
prepare(
"SELECT filterid, clause FROM recordfilter "
4109 "WHERE filterid >= 0 AND filterid < :NUMFILTERS AND "
4110 " TRIM(clause) <> ''");
4117 while (query.
next())
4119 filterClause += QString(
" AND (((RECTABLE.filter & %1) = 0) OR (%2))")
4120 .arg(1 << query.
value(0).toInt()).arg(query.
value(1).toString());
4124 query.
prepare(
"SELECT NULL from record "
4125 "WHERE type = :FINDONE AND findid <= 0;");
4134 QDate epoch(1970, 1, 1);
4137 query.
prepare(
"UPDATE record set findid = :FINDID "
4138 "WHERE type = :FINDONE AND findid <= 0;");
4145 QStringList fromclauses;
4146 QStringList whereclauses;
4152 for (
int clause = 0; clause < fromclauses.count(); ++clause)
4154 LOG(VB_SCHEDULE, LOG_INFO, QString(
"Query %1: %2/%3")
4155 .arg(QString::number(clause), fromclauses[clause],
4156 whereclauses[clause]));
4160 for (
int clause = 0; clause < fromclauses.count(); ++clause)
4162 QString query2 = QString(
4163"REPLACE INTO recordmatch (recordid, chanid, starttime, manualid, "
4164" oldrecduplicate, findid) "
4165"SELECT RECTABLE.recordid, program.chanid, program.starttime, "
4166" IF(search = %1, RECTABLE.recordid, 0), ").arg(
kManualSearch) +
4168"FROM (RECTABLE, program INNER JOIN channel "
4169" ON channel.chanid = program.chanid) ") + fromclauses[clause] + QString(
4170" WHERE ") + whereclauses[clause] +
4171 QString(
" AND channel.deleted IS NULL "
4172 " AND channel.visible > 0 ") +
4173 filterClause + QString(
" AND "
4176" (RECTABLE.type = %1 "
4177" OR RECTABLE.type = %2 "
4178" OR RECTABLE.type = %3 "
4179" OR RECTABLE.type = %4) "
4181" ((RECTABLE.type = %6 "
4182" OR RECTABLE.type = %7 "
4183" OR RECTABLE.type = %8)"
4185" ADDTIME(RECTABLE.startdate, RECTABLE.starttime) = program.starttime "
4187" RECTABLE.station = channel.callsign) "
4199 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- Start DB Query %1...")
4202 auto dbstart = nowAsDuration<std::chrono::microseconds>();
4206 for (it = bindings.cbegin(); it != bindings.cend(); ++it)
4208 if (query2.contains(it.key()))
4212 bool ok = result.
exec();
4213 auto dbend = nowAsDuration<std::chrono::microseconds>();
4214 auto dbTime = dbend - dbstart;
4222 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- %1 results in %2 sec.")
4224 .arg(duration_cast<std::chrono::seconds>(dbTime).count()));
4228 LOG(VB_SCHEDULE, LOG_INFO,
" +-- Done.");
4237 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_record;");
4243 result.
prepare(
"CREATE TEMPORARY TABLE sched_temp_record "
4250 result.
prepare(
"INSERT sched_temp_record SELECT * from record;");
4258 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_recorded;");
4264 result.
prepare(
"CREATE TEMPORARY TABLE sched_temp_recorded "
4271 result.
prepare(
"INSERT sched_temp_recorded SELECT * from recorded;");
4285 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_record;");
4290 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_recorded;");
4298 if (schedTmpRecord ==
"record")
4299 schedTmpRecord =
"sched_temp_record";
4301 QString rmquery = QString(
4302"UPDATE recordmatch "
4303" INNER JOIN RECTABLE ON (recordmatch.recordid = RECTABLE.recordid) "
4304" INNER JOIN program p ON (recordmatch.chanid = p.chanid AND "
4305" recordmatch.starttime = p.starttime AND "
4306" recordmatch.manualid = p.manualid) "
4307" LEFT JOIN oldrecorded ON "
4309" RECTABLE.dupmethod > 1 AND "
4310" oldrecorded.duplicate <> 0 AND "
4311" p.title = oldrecorded.title AND "
4315" (p.programid <> '' "
4316" AND p.programid = oldrecorded.programid) "
4320" (p.programid = '' OR oldrecorded.programid = '' OR "
4321" LEFT(p.programid, LOCATE('/', p.programid)) <> "
4322" LEFT(oldrecorded.programid, LOCATE('/', oldrecorded.programid))) " :
4323" (p.programid = '' OR oldrecorded.programid = '') " )
4326" (((RECTABLE.dupmethod & 0x02) = 0) OR (p.subtitle <> '' "
4327" AND p.subtitle = oldrecorded.subtitle)) "
4329" (((RECTABLE.dupmethod & 0x04) = 0) OR (p.description <> '' "
4330" AND p.description = oldrecorded.description)) "
4332" (((RECTABLE.dupmethod & 0x08) = 0) OR "
4333" (p.subtitle <> '' AND "
4334" (p.subtitle = oldrecorded.subtitle OR "
4335" (oldrecorded.subtitle = '' AND "
4336" p.subtitle = oldrecorded.description))) OR "
4337" (p.subtitle = '' AND p.description <> '' AND "
4338" (p.description = oldrecorded.subtitle OR "
4339" (oldrecorded.subtitle = '' AND "
4340" p.description = oldrecorded.description)))) "
4344" LEFT JOIN sched_temp_recorded recorded ON "
4346" RECTABLE.dupmethod > 1 AND "
4347" recorded.duplicate <> 0 AND "
4348" p.title = recorded.title AND "
4349" p.generic = 0 AND "
4350" recorded.recgroup NOT IN ('LiveTV','Deleted') "
4353" (p.programid <> '' "
4354" AND p.programid = recorded.programid) "
4358" (p.programid = '' OR recorded.programid = '' OR "
4359" LEFT(p.programid, LOCATE('/', p.programid)) <> "
4360" LEFT(recorded.programid, LOCATE('/', recorded.programid))) " :
4361" (p.programid = '' OR recorded.programid = '') ")
4364" (((RECTABLE.dupmethod & 0x02) = 0) OR (p.subtitle <> '' "
4365" AND p.subtitle = recorded.subtitle)) "
4367" (((RECTABLE.dupmethod & 0x04) = 0) OR (p.description <> '' "
4368" AND p.description = recorded.description)) "
4370" (((RECTABLE.dupmethod & 0x08) = 0) OR "
4371" (p.subtitle <> '' AND "
4372" (p.subtitle = recorded.subtitle OR "
4373" (recorded.subtitle = '' AND "
4374" p.subtitle = recorded.description))) OR "
4375" (p.subtitle = '' AND p.description <> '' AND "
4376" (p.description = recorded.subtitle OR "
4377" (recorded.subtitle = '' AND "
4378" p.description = recorded.description)))) "
4382" LEFT JOIN oldfind ON "
4383" (oldfind.recordid = recordmatch.recordid AND "
4384" oldfind.findid = recordmatch.findid) "
4385" SET oldrecduplicate = (oldrecorded.endtime IS NOT NULL), "
4386" recduplicate = (recorded.endtime IS NOT NULL), "
4387" findduplicate = (oldfind.findid IS NOT NULL), "
4388" oldrecstatus = oldrecorded.recstatus "
4389" WHERE p.endtime >= (NOW() - INTERVAL 480 MINUTE) "
4390" AND oldrecduplicate = -1 "
4392 rmquery.replace(
"RECTABLE", schedTmpRecord);
4406 if (schedTmpRecord ==
"record")
4407 schedTmpRecord =
"sched_temp_record";
4411 QMap<int, bool> cardMap;
4412 for (
auto * enc : std::as_const(*
m_tvList))
4414 if (enc->IsConnected() || enc->IsAsleep())
4415 cardMap[enc->GetInputID()] =
true;
4418 QMap<int, bool> tooManyMap;
4419 bool checkTooMany =
false;
4423 rlist.
prepare(QString(
"SELECT recordid, title, maxepisodes, maxnewest "
4424 "FROM %1").arg(schedTmpRecord));
4432 while (rlist.
next())
4434 int recid = rlist.
value(0).toInt();
4436 int maxEpisodes = rlist.
value(2).toInt();
4437 int maxNewest = rlist.
value(3).toInt();
4439 tooManyMap[recid] =
false;
4442 if (maxEpisodes && !maxNewest)
4446 epicnt.
prepare(
"SELECT DISTINCT chanid, progstart, progend "
4448 "WHERE recordid = :RECID AND preserve = 0 "
4449 "AND recgroup NOT IN ('LiveTV','Deleted');");
4454 if (epicnt.
size() >= maxEpisodes - 1)
4457 if (epicnt.
size() >= maxEpisodes)
4459 tooManyMap[recid] =
true;
4460 checkTooMany =
true;
4476 QString pwrpri =
"channel.recpriority + capturecard.recpriority";
4480 pwrpri += QString(
" + "
4481 "IF(capturecard.cardid = RECTABLE.prefinput, 1, 0) * %1")
4487 pwrpri += QString(
" + IF(program.hdtv > 0 OR "
4488 "FIND_IN_SET('HDTV', program.videoprop) > 0, 1, 0) * %1")
4494 pwrpri += QString(
" + "
4495 "IF(FIND_IN_SET('WIDESCREEN', program.videoprop) > 0, 1, 0) * %1")
4501 pwrpri += QString(
" + "
4502 "IF(FIND_IN_SET('SIGNED', program.subtitletypes) > 0, 1, 0) * %1")
4508 pwrpri += QString(
" + "
4509 "IF(FIND_IN_SET('ONSCREEN', program.subtitletypes) > 0, 1, 0) * %1")
4510 .arg(onscrpriority);
4515 pwrpri += QString(
" + "
4516 "IF(FIND_IN_SET('NORMAL', program.subtitletypes) > 0 OR "
4517 "program.closecaptioned > 0 OR program.subtitled > 0, 1, 0) * %1")
4523 pwrpri += QString(
" + "
4524 "IF(FIND_IN_SET('HARDHEAR', program.subtitletypes) > 0 OR "
4525 "FIND_IN_SET('HARDHEAR', program.audioprop) > 0, 1, 0) * %1")
4531 pwrpri += QString(
" + "
4532 "IF(FIND_IN_SET('VISUALIMPAIR', program.audioprop) > 0, 1, 0) * %1")
4538 result.
prepare(QString(
"SELECT recpriority, selectclause FROM %1;")
4547 while (result.
next())
4549 if (result.
value(0).toBool())
4551 QString sclause = result.
value(1).toString();
4553 sclause.remove(
';');
4554 pwrpri += QString(
" + IF(%1, 1, 0) * %2")
4555 .arg(sclause).arg(result.
value(0).toInt());
4558 pwrpri += QString(
" AS powerpriority ");
4560 pwrpri.replace(
"program.",
"p.");
4561 pwrpri.replace(
"channel.",
"c.");
4562 QString query = QString(
4564 " c.chanid, c.sourceid, p.starttime, "
4565 " p.endtime, p.title, p.subtitle, "
4566 " p.description, c.channum, c.callsign, "
4567 " c.name, oldrecduplicate, p.category, "
4568 " RECTABLE.recpriority, RECTABLE.dupin, recduplicate, "
4569 " findduplicate, RECTABLE.type, RECTABLE.recordid, "
4570 " p.starttime - INTERVAL RECTABLE.startoffset "
4571 " minute AS recstartts, "
4572 " p.endtime + INTERVAL RECTABLE.endoffset "
4573 " minute AS recendts, "
4574 " p.previouslyshown, "
4575 " RECTABLE.recgroup, RECTABLE.dupmethod, c.commmethod, "
4576 " capturecard.cardid, 0, p.seriesid, "
4577 " p.programid, RECTABLE.inetref, p.category_type, "
4578 " p.airdate, p.stars, p.originalairdate, "
4579 " RECTABLE.inactive, RECTABLE.parentid, recordmatch.findid, "
4580 " RECTABLE.playgroup, oldrecstatus.recstatus, "
4581 " oldrecstatus.reactivate, p.videoprop+0, "
4582 " p.subtitletypes+0, p.audioprop+0, RECTABLE.storagegroup, "
4583 " capturecard.hostname, recordmatch.oldrecstatus, NULL, "
4584 " oldrecstatus.future, capturecard.schedorder, "
4585 " p.syndicatedepisodenumber, p.partnumber, p.parttotal, "
4586 " c.mplexid, capturecard.displayname, "
4587 " p.season, p.episode, p.totalepisodes, ") +
4590 "INNER JOIN RECTABLE ON (recordmatch.recordid = RECTABLE.recordid) "
4591 "INNER JOIN program AS p "
4592 "ON ( recordmatch.chanid = p.chanid AND "
4593 " recordmatch.starttime = p.starttime AND "
4594 " recordmatch.manualid = p.manualid ) "
4595 "INNER JOIN channel AS c "
4596 "ON ( c.chanid = p.chanid ) "
4597 "INNER JOIN capturecard "
4598 "ON ( c.sourceid = capturecard.sourceid AND "
4599 " ( capturecard.schedorder <> 0 OR "
4600 " capturecard.parentid = 0 ) ) "
4601 "LEFT JOIN oldrecorded as oldrecstatus "
4602 "ON ( oldrecstatus.station = c.callsign AND "
4603 " oldrecstatus.starttime = p.starttime AND "
4604 " oldrecstatus.title = p.title ) "
4605 "WHERE p.endtime > (NOW() - INTERVAL 480 MINUTE) "
4606 "ORDER BY RECTABLE.recordid DESC, p.starttime, p.title, c.callsign, "
4608 query.replace(
"RECTABLE", schedTmpRecord);
4610 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- Start DB Query..."));
4612 auto dbstart = nowAsDuration<std::chrono::microseconds>();
4619 auto dbend = nowAsDuration<std::chrono::microseconds>();
4620 auto dbTime = dbend - dbstart;
4622 LOG(VB_SCHEDULE, LOG_INFO,
4623 QString(
" |-- %1 results in %2 sec. Processing...")
4625 .arg(duration_cast<std::chrono::seconds>(dbTime).count()));
4629 while (result.
next())
4635 uint recordid = result.
value(17).toUInt();
4637 QString title = result.
value(4).toString();
4638 QString callsign = result.
value(8).toString();
4648 uint mplexid = result.
value(51).toUInt();
4649 if (mplexid == 32767)
4652 QString inputname = result.
value(52).toString();
4653 if (inputname.isEmpty())
4654 inputname = QString(
"Input %1").arg(result.
value(24).toUInt());
4659 result.
value(5).toString(),
4661 result.
value(6).toString(),
4662 result.
value(53).toInt(),
4663 result.
value(54).toInt(),
4664 result.
value(55).toInt(),
4665 result.
value(48).toString(),
4666 result.
value(11).toString(),
4668 result.
value(0).toUInt(),
4669 result.
value(7).toString(),
4671 result.
value(9).toString(),
4673 result.
value(21).toString(),
4674 result.
value(36).toString(),
4676 result.
value(43).toString(),
4677 result.
value(42).toString(),
4679 result.
value(30).toUInt(),
4680 result.
value(49).toUInt(),
4681 result.
value(50).toUInt(),
4683 result.
value(26).toString(),
4684 result.
value(27).toString(),
4685 result.
value(28).toString(),
4688 result.
value(12).toInt(),
4695 result.
value(31).toFloat(),
4696 (result.
value(32).isNull()) ? QDate() :
4700 result.
value(20).toBool(),
4703 result.
value(38).toBool(),
4706 result.
value(34).toUInt(),
4711 result.
value(1).toUInt(),
4712 result.
value(24).toUInt(),
4714 result.
value(35).toUInt(),
4717 result.
value(40).toUInt(),
4718 result.
value(39).toUInt(),
4719 result.
value(41).toUInt(),
4720 result.
value(46).toBool(),
4721 result.
value(47).toInt(),
4723 result.
value(24).toUInt(),
4726 if (!
p->m_future && !
p->IsReactivated() &&
4730 p->SetRecordingStatus(
p->m_oldrecstatus);
4733 p->SetRecordingPriority2(result.
value(56).toInt());
4742 if (
p->IsSameTitleStartTimeAndChannel(*r))
4744 if (r->m_sgroupId ==
p->m_sgroupId &&
4745 r->GetRecordingEndTime() !=
p->GetRecordingEndTime() &&
4746 (r->GetRecordingRuleID() ==
p->GetRecordingRuleID() ||
4761 tmpList.push_back(
p);
4768 (!cardMap.contains(
p->GetInputID()) || (
p->m_schedOrder == 0)))
4771 if (
p->m_schedOrder == 0 &&
4774 LOG(VB_GENERAL, LOG_WARNING,
LOC +
4775 QString(
"Channel %1, Title %2 %3 cardinput.schedorder = %4, "
4776 "it must be >0 to record from this input.")
4777 .arg(
p->GetChannelName(),
p->GetTitle(),
4778 p->GetScheduledStartTime().toString(),
4779 QString::number(
p->m_schedOrder)));
4785 if (checkTooMany && tooManyMap[
p->GetRecordingRuleID()] &&
4786 !
p->IsReactivated())
4794 else if (result.
value(15).toBool() && !
p->IsReactivated())
4798 !
p->IsReactivated() &&
4818 bool inactive = result.
value(33).toBool();
4833 p->SetRecordingStatus(newrecstatus);
4835 tmpList.push_back(
p);
4838 LOG(VB_SCHEDULE, LOG_INFO,
" +-- Cleanup...");
4839 for (
auto & tmp : tmpList)
4847 QString query = QString(
4848 "SELECT RECTABLE.title, RECTABLE.subtitle, "
4849 " RECTABLE.description, RECTABLE.season, "
4850 " RECTABLE.episode, RECTABLE.category, "
4851 " RECTABLE.chanid, channel.channum, "
4852 " RECTABLE.station, channel.name, "
4853 " RECTABLE.recgroup, RECTABLE.playgroup, "
4854 " RECTABLE.seriesid, RECTABLE.programid, "
4855 " RECTABLE.inetref, RECTABLE.recpriority, "
4856 " RECTABLE.startdate, RECTABLE.starttime, "
4857 " RECTABLE.enddate, RECTABLE.endtime, "
4858 " RECTABLE.recordid, RECTABLE.type, "
4859 " RECTABLE.dupin, RECTABLE.dupmethod, "
4860 " RECTABLE.findid, "
4861 " RECTABLE.startoffset, RECTABLE.endoffset, "
4862 " channel.commmethod "
4864 "INNER JOIN channel ON (channel.chanid = RECTABLE.chanid) "
4865 "LEFT JOIN recordmatch on RECTABLE.recordid = recordmatch.recordid "
4866 "WHERE (type = %1 OR type = %2) AND "
4867 " recordmatch.chanid IS NULL")
4873 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- Start DB Query..."));
4875 auto dbstart = nowAsDuration<std::chrono::microseconds>();
4878 bool ok = result.
exec();
4879 auto dbend = nowAsDuration<std::chrono::microseconds>();
4880 auto dbTime = dbend - dbstart;
4888 LOG(VB_SCHEDULE, LOG_INFO,
4889 QString(
" |-- %1 results in %2 sec. Processing...")
4891 .arg(duration_cast<std::chrono::seconds>(dbTime).count()));
4893 while (result.
next())
4896#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
4898 result.
value(16).toDate(), result.
value(17).toTime(), Qt::UTC);
4900 result.
value(18).toDate(), result.
value(19).toTime(), Qt::UTC);
4902 static const QTimeZone utc(QTimeZone::UTC);
4904 result.
value(16).toDate(), result.
value(17).toTime(), utc);
4906 result.
value(18).toDate(), result.
value(19).toTime(), utc);
4909 QDateTime recstartts = startts.addSecs(result.
value(25).toInt() * -60LL);
4910 QDateTime recendts = endts.addSecs( result.
value(26).toInt() * +60LL);
4912 if (recstartts >= recendts)
4915 recstartts = startts;
4926 result.
value(0).toString(),
4928 (sor) ? result.
value(1).toString() : QString(),
4930 (sor) ? result.
value(2).toString() : QString(),
4931 result.
value(3).toUInt(),
4932 result.
value(4).toUInt(),
4935 result.
value(6).toUInt(),
4936 result.
value(7).toString(),
4937 result.
value(8).toString(),
4938 result.
value(9).toString(),
4940 result.
value(10).toString(),
4941 result.
value(11).toString(),
4943 result.
value(12).toString(),
4944 result.
value(13).toString(),
4945 result.
value(14).toString(),
4947 result.
value(15).toInt(),
4950 recstartts, recendts,
4954 result.
value(20).toUInt(),
4960 result.
value(24).toUInt(),
4964 tmpList.push_back(
p);
4967 for (
auto & tmp : tmpList)
4978 QString sortColumn =
"title";
4988 QString prefixes = sh->getPrefixes();
4989 sortColumn =
"REGEXP_REPLACE(record.title,'" + prefixes +
"','')";
4993 sortColumn =
"record.recpriority";
4996 sortColumn =
"record.last_record";
5004 sortColumn =
"record.next_record IS NULL, record.next_record";
5007 sortColumn =
"record.type";
5011 QString order =
"ASC";
5015 QString query = QString(
5016 "SELECT record.title, record.subtitle, "
5017 " record.description, record.season, "
5018 " record.episode, record.category, "
5019 " record.chanid, channel.channum, "
5020 " record.station, channel.name, "
5021 " record.recgroup, record.playgroup, "
5022 " record.seriesid, record.programid, "
5023 " record.inetref, record.recpriority, "
5024 " record.startdate, record.starttime, "
5025 " record.enddate, record.endtime, "
5026 " record.recordid, record.type, "
5027 " record.dupin, record.dupmethod, "
5029 " channel.commmethod "
5031 "LEFT JOIN channel ON channel.callsign = record.station "
5032 " AND deleted IS NULL "
5033 "GROUP BY recordid "
5036 query = query.arg(sortColumn, order);
5047 while (result.
next())
5050#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
5051 QDateTime startts = QDateTime(result.
value(16).toDate(),
5052 result.
value(17).toTime(), Qt::UTC);
5053 QDateTime endts = QDateTime(result.
value(18).toDate(),
5054 result.
value(19).toTime(), Qt::UTC);
5056 static const QTimeZone utc(QTimeZone::UTC);
5057 QDateTime startts = QDateTime(result.
value(16).toDate(),
5058 result.
value(17).toTime(), utc);
5059 QDateTime endts = QDateTime(result.
value(18).toDate(),
5060 result.
value(19).toTime(), utc);
5063 if (!startts.isValid())
5065#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
5070 QTimeZone(QTimeZone::UTC));
5073 if (!endts.isValid())
5077 result.
value(0).toString(), QString(),
5078 result.
value(1).toString(), QString(),
5079 result.
value(2).toString(), result.
value(3).toUInt(),
5080 result.
value(4).toUInt(), result.
value(5).toString(),
5082 result.
value(6).toUInt(), result.
value(7).toString(),
5083 result.
value(8).toString(), result.
value(9).toString(),
5085 result.
value(10).toString(), result.
value(11).toString(),
5087 result.
value(12).toString(), result.
value(13).toString(),
5088 result.
value(14).toString(),
5090 result.
value(15).toInt(),
5097 result.
value(20).toUInt(), rectype,
5101 result.
value(24).toUInt(),
5200 QString recording_dir;
5204 "LiveTV", cur, cur.addSecs(3600), cardid,
5209 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"FindNextLiveTVDir: next dir is '%1'")
5210 .arg(recording_dir));
5217 const QString &title,
5219 const QString &storagegroup,
5220 const QDateTime &recstartts,
5221 const QDateTime &recendts,
5223 QString &recording_dir,
5226 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
"FillRecordingDir: Starting");
5231 if (cnt++ % 20 == 0)
5232 LOG(VB_SCHEDULE, LOG_WARNING,
"Waiting for main server.");
5233 std::this_thread::sleep_for(50ms);
5240 QStringList recsCounted;
5241 std::list<FileSystemInfo *> fsInfoList;
5242 std::list<FileSystemInfo *>::iterator fslistit;
5244 recording_dir.clear();
5246 if (dirlist.size() == 1)
5248 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5249 QString(
"FillRecordingDir: The only directory in the %1 Storage "
5250 "Group is %2, so it will be used by default.")
5251 .arg(storagegroup, dirlist[0]));
5252 recording_dir = dirlist[0];
5253 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
"FillRecordingDir: Finished");
5258 int weightPerRecording =
5260 int weightPerPlayback =
5262 int weightPerCommFlag =
5264 int weightPerTranscode =
5267 QString storageScheduler =
5269 int localStartingWeight =
5271 (storageScheduler !=
"Combination") ? 0
5272 : (
int)(-1.99 * weightPerRecording));
5273 int remoteStartingWeight =
5275 std::chrono::seconds maxOverlap =
5280 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5281 "FillRecordingDir: Calculating initial FS Weights.");
5292 tmpWeight = localStartingWeight;
5293 msg +=
" is local (" + QString::number(tmpWeight) +
")";
5297 tmpWeight = remoteStartingWeight;
5298 msg +=
" is remote (+" + QString::number(tmpWeight) +
")";
5308 msg +=
", has SGweightPerDir offset of "
5309 + QString::number(tmpWeight) +
")";
5311 msg +=
". initial dir weight = " + QString::number(fs->
getWeight());
5312 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, msg);
5314 fsInfoList.push_back(fs);
5317 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5318 "FillRecordingDir: Adjusting FS Weights from inuseprograms.");
5321 saveRecDir.
prepare(
"UPDATE inuseprograms "
5322 "SET recdir = :RECDIR "
5323 "WHERE chanid = :CHANID AND "
5324 " starttime = :STARTTIME");
5327 "SELECT i.chanid, i.starttime, r.endtime, recusage, rechost, recdir "
5328 "FROM inuseprograms i, recorded r "
5329 "WHERE DATE_ADD(lastupdatetime, INTERVAL 16 MINUTE) > NOW() AND "
5330 " i.chanid = r.chanid AND "
5331 " i.starttime = r.starttime");
5339 while (query.
next())
5341 uint recChanid = query.
value(0).toUInt();
5344 QString recUsage( query.
value(3).toString());
5345 QString recHost( query.
value(4).toString());
5346 QString recDir( query.
value(5).toString());
5348 if (recDir.isEmpty())
5352 recDir = recDir.isEmpty() ?
"_UNKNOWN_" : recDir;
5354 saveRecDir.
bindValue(
":RECDIR", recDir);
5355 saveRecDir.
bindValue(
":CHANID", recChanid);
5356 saveRecDir.
bindValue(
":STARTTIME", recStart);
5357 if (!saveRecDir.
exec())
5360 if (recDir ==
"_UNKNOWN_")
5363 for (fslistit = fsInfoList.begin();
5364 fslistit != fsInfoList.end(); ++fslistit)
5370 int weightOffset = 0;
5374 if (recEnd > recstartts.addSecs(maxOverlap.count()))
5376 weightOffset += weightPerRecording;
5377 recsCounted << QString::number(recChanid) +
":" +
5383 weightOffset += weightPerPlayback;
5387 weightOffset += weightPerCommFlag;
5391 weightOffset += weightPerTranscode;
5396 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5397 QString(
" %1 @ %2 in use by '%3' on %4:%5, FSID "
5398 "#%6, FSID weightOffset +%7.")
5399 .arg(QString::number(recChanid),
5401 recUsage, recHost, recDir,
5403 QString::number(weightOffset)));
5411 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5412 QString(
" %1:%2 => old weight %3 plus "
5414 .arg(
fs2->getHostname(),
5416 .arg(
fs2->getWeight())
5418 .arg(
fs2->getWeight() + weightOffset));
5420 fs2->setWeight(
fs2->getWeight() + weightOffset);
5430 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5431 "FillRecordingDir: Adjusting FS Weights from scheduler.");
5433 for (
auto *thispg : reclist)
5435 if ((recendts < thispg->GetRecordingStartTime()) ||
5436 (recstartts > thispg->GetRecordingEndTime()) ||
5439 (thispg->GetInputID() == 0) ||
5440 (recsCounted.contains(QString(
"%1:%2").arg(thispg->GetChanID())
5442 (thispg->GetPathname().isEmpty()))
5445 for (fslistit = fsInfoList.begin();
5446 fslistit != fsInfoList.end(); ++fslistit)
5449 if ((fs->
getHostname() == thispg->GetHostname()) &&
5450 (fs->
getPath() == thispg->GetPathname()))
5452 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5453 QString(
"%1 @ %2 will record on %3:%4, FSID #%5, "
5454 "weightPerRecording +%6.")
5455 .arg(thispg->GetChanID())
5458 .arg(fs->
getFSysID()).arg(weightPerRecording));
5467 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5468 QString(
" %1:%2 => old weight %3 plus %4 = %5")
5469 .arg(
fs2->getHostname(),
fs2->getPath())
5470 .arg(
fs2->getWeight()).arg(weightPerRecording)
5471 .arg(
fs2->getWeight() + weightPerRecording));
5473 fs2->setWeight(
fs2->getWeight() + weightPerRecording);
5481 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5482 QString(
"Using '%1' Storage Scheduler directory sorting algorithm.")
5483 .arg(storageScheduler));
5485 if (storageScheduler ==
"BalancedFreeSpace")
5487 else if (storageScheduler ==
"BalancedPercFreeSpace")
5489 else if (storageScheduler ==
"BalancedDiskIO")
5496 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5497 "--- FillRecordingDir Sorted fsInfoList start ---");
5498 for (fslistit = fsInfoList.begin();fslistit != fsInfoList.end();
5502 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
"%1:%2")
5504 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
" Location : %1")
5505 .arg((fs->
isLocal()) ?
"local" :
"remote"));
5506 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
" weight : %1")
5508 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
" free space : %5")
5511 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5512 "--- FillRecordingDir Sorted fsInfoList end ---");
5521 long long maxSizeKB = (maxByterate + maxByterate/3) *
5522 recstartts.secsTo(recendts) / 1024;
5524 bool simulateAutoExpire =
5527 (fsInfoList.size() > 1));
5541 for (
unsigned int pass = 1; pass <= 3; pass++)
5543 bool foundDir =
false;
5545 if ((pass == 2) && simulateAutoExpire)
5548 QMap <int , long long> remainingSpaceKB;
5549 for (fslistit = fsInfoList.begin();
5550 fslistit != fsInfoList.end(); ++fslistit)
5552 remainingSpaceKB[(*fslistit)->getFSysID()] =
5553 (*fslistit)->getFreeSpace();
5560 for (
auto & expire : expiring)
5564 for (fslistit = fsInfoList.begin();
5565 fslistit != fsInfoList.end(); ++fslistit)
5568 if (expire->GetHostname() != (*fslistit)->getHostname())
5572 if (!dirlist.contains((*fslistit)->getPath()))
5576 (*fslistit)->getPath() +
"/" + expire->GetPathname();
5583 if (checkFile.exists())
5591 QString backuppath = expire->GetPathname();
5593 bool foundSlave =
false;
5595 for (
auto * enc : std::as_const(*
m_tvList))
5597 if (enc->GetHostName() ==
5600 enc->CheckFile(programinfo);
5618 LOG(VB_GENERAL, LOG_ERR,
5619 QString(
"Unable to match '%1' "
5620 "to any file system. Ignoring it.")
5621 .arg(expire->GetBasename()));
5627 expire->GetFilesize() / 1024;
5630 long long desiredSpaceKB =
5634 (desiredSpaceKB + maxSizeKB))
5636 recording_dir = fs->
getPath();
5639 LOG(VB_FILE, LOG_INFO,
5640 QString(
"pass 2: '%1' will record in '%2' "
5641 "although there is only %3 MB free and the "
5642 "AutoExpirer wants at least %4 MB. This "
5643 "directory has the highest priority files "
5644 "to be expired from the AutoExpire list and "
5645 "there are enough that the Expirer should "
5646 "be able to free up space for this recording.")
5647 .arg(title, recording_dir)
5649 .arg(desiredSpaceKB / 1024));
5660 for (fslistit = fsInfoList.begin();
5661 fslistit != fsInfoList.end(); ++fslistit)
5663 long long desiredSpaceKB = 0;
5670 (dirlist.contains(fs->
getPath())) &&
5674 recording_dir = fs->
getPath();
5679 LOG(VB_FILE, LOG_INFO,
5680 QString(
"pass 1: '%1' will record in "
5681 "'%2' which has %3 MB free. This recording "
5682 "could use a max of %4 MB and the "
5683 "AutoExpirer wants to keep %5 MB free.")
5684 .arg(title, recording_dir)
5686 .arg(maxSizeKB / 1024)
5687 .arg(desiredSpaceKB / 1024));
5691 LOG(VB_FILE, LOG_INFO,
5692 QString(
"pass %1: '%2' will record in "
5693 "'%3' although there is only %4 MB free and "
5694 "the AutoExpirer wants at least %5 MB. "
5695 "Something will have to be deleted or expired "
5696 "in order for this recording to complete "
5698 .arg(pass).arg(title, recording_dir)
5700 .arg(desiredSpaceKB / 1024));
5713 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
"FillRecordingDir: Finished");
5726 QMap <int, bool> fsMap;
5727 for (
const auto&
fs1 : std::as_const(fsInfos))
5729 fsMap[
fs1.getFSysID()] =
true;
5733 LOG(VB_FILE, LOG_INFO,
LOC +
5734 QString(
"FillDirectoryInfoCache: found %1 unique filesystems")
5735 .arg(fsMap.size()));
5742 auto secsleft = std::chrono::seconds(curtime.secsTo(
m_livetvTime));
5746 if (secsleft - prerollseconds > 120s)
5750 for (
auto * enc : std::as_const(*
m_tvList))
5766 if (
m_schedTime.secsTo(dummy->GetRecordingEndTime()) < 1800)
5767 dummy->SetRecordingEndTime(
m_schedTime.addSecs(1800));
5768 dummy->SetInputID(enc->GetInputID());
5769 dummy->m_mplexId = dummy->QueryMplexID();
5792 bool autoStart =
false;
5794 QDateTime startupTime = QDateTime();
5800 if (startupTime.isValid())
5803 startupSecs = std::max(startupSecs, 15 * 60s);
5810 LOG(VB_GENERAL, LOG_INFO,
5811 "Close to auto-start time, AUTO-Startup assumed");
5817 LOG(VB_GENERAL, LOG_INFO,
5818 "Close to MythFillDB suggested run time, AUTO-Startup to fetch guide data?");
5824 LOG(VB_GENERAL, LOG_DEBUG,
5825 "NOT close to auto-start time, USER-initiated startup assumed");
5828 else if (!s.isEmpty())
5830 LOG(VB_GENERAL, LOG_ERR,
LOC +
5831 QString(
"Invalid MythShutdownWakeupTime specified in database (%1)")
5843 QMap<uint, QSet<uint> > inputSets;
5844 query.
prepare(
"SELECT DISTINCT ci1.cardid, ci2.cardid "
5845 "FROM capturecard ci1, capturecard ci2, "
5846 " inputgroup ig1, inputgroup ig2 "
5847 "WHERE ci1.cardid = ig1.cardinputid AND "
5848 " ci2.cardid = ig2.cardinputid AND"
5849 " ig1.inputgroupid = ig2.inputgroupid AND "
5850 " ci1.cardid <= ci2.cardid "
5851 "ORDER BY ci1.cardid, ci2.cardid");
5857 while (query.
next())
5861 inputSets[id0].insert(id1);
5862 inputSets[id1].insert(id0);
5865 QMap<uint, QSet<uint> >::iterator mit;
5866 for (mit = inputSets.begin(); mit != inputSets.end(); ++mit)
5868 uint inputid = mit.key();
5876 QSet<uint> fullset = mit.value();
5877 QSet<uint> checkset;
5878 QSet<uint>::const_iterator sit;
5879 while (checkset != fullset)
5882 for (
int item : std::as_const(checkset))
5883 fullset += inputSets[item];
5888 auto *conflictlist =
new RecList();
5890 for (
int item : std::as_const(checkset))
5892 LOG(VB_SCHEDULE, LOG_INFO,
5893 QString(
"Assigning input %1 to conflict set %2")
5901 query.
prepare(
"SELECT ci.cardid "
5902 "FROM capturecard ci "
5903 "LEFT JOIN inputgroup ig "
5904 " ON ci.cardid = ig.cardinputid "
5905 "WHERE ig.cardinputid IS NULL");
5911 while (query.
next())
5915 LOG(VB_GENERAL, LOG_ERR,
LOC +
5916 QString(
"Input %1 is not assigned to any input group").arg(
id));
5917 auto *conflictlist =
new RecList();
5919 LOG(VB_SCHEDULE, LOG_INFO,
5920 QString(
"Assigning input %1 to conflict set %2")
5934 query.
prepare(
"SELECT cardid, parentid, schedgroup "
5936 "WHERE sourceid > 0 "
5944 while (query.
next())
5947 uint parentid = query.
value(1).toUInt();
5964 LOG(VB_SCHEDULE, LOG_INFO,
5965 QString(
"Added SchedInputInfo i=%1, g=%2, sg=%3")
5974 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
5975 QString(
"AddChildInput: Handling parent = %1, input = %2")
5976 .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().
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="")
T GetDurSetting(const QString &key, T defaultval=T::zero())
void dispatch(const MythEvent &event)
int GetNumSetting(const QString &key, int defaultval=0)
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...