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))
1249 if (q->GetRecordingStartTime() <
p->GetRecordingStartTime())
1261 p->m_savedrecstatus =
p->GetRecordingStatus();
1269 p->SetRecordingStatus(
p->m_savedrecstatus);
1290 uint bestaffinity = 0;
1292 for (
auto *q : *showinglist)
1298 (q->GetRecordingPriority() <
p->GetRecordingPriority() ||
1299 (q->GetRecordingPriority() ==
p->GetRecordingPriority() &&
1300 q->GetRecordingPriority2() <
p->GetRecordingPriority2())))
1312 if (!
p->IsSameTitleStartTimeAndChannel(*q))
1347 PrintRec(q, QString(
" %1:").arg(affinity));
1348 if (!best || affinity > bestaffinity)
1351 bestaffinity = affinity;
1359 QString msg = QString(
1360 "Moved \"%1\" on chanid: %2 from card: %3 to %4 at %5 "
1361 "to avoid LiveTV conflict")
1362 .arg(
p->GetTitle()).arg(
p->GetChanID())
1365 LOG(VB_GENERAL, LOG_INFO, msg);
1377 p->SetRecordingStatus(oldstatus);
1385 LOG(VB_SCHEDULE, LOG_DEBUG,
1386 "+ = schedule this showing to be recorded");
1387 LOG(VB_SCHEDULE, LOG_DEBUG,
1388 "n: = could schedule this showing with affinity");
1389 LOG(VB_SCHEDULE, LOG_DEBUG,
1390 "n# = could not schedule this showing, with affinity");
1391 LOG(VB_SCHEDULE, LOG_DEBUG,
1392 "! = conflict caused by this showing");
1393 LOG(VB_SCHEDULE, LOG_DEBUG,
1394 "/ = retry this showing, same priority pass");
1395 LOG(VB_SCHEDULE, LOG_DEBUG,
1396 "? = retry this showing, lower priority pass");
1397 LOG(VB_SCHEDULE, LOG_DEBUG,
1398 "> = try another showing for this program");
1399 LOG(VB_SCHEDULE, LOG_DEBUG,
1400 "- = unschedule a showing in favor of another one");
1419 auto levelStart = i;
1420 int recpriority = (*i)->GetRecordingPriority();
1425 (*i)->GetRecordingPriority() != recpriority)
1428 auto sublevelStart = i;
1429 int recpriority2 = (*i)->GetRecordingPriority2();
1430 LOG(VB_SCHEDULE, LOG_DEBUG, QString(
"Trying priority %1/%2...")
1431 .arg(recpriority).arg(recpriority2));
1435 LOG(VB_SCHEDULE, LOG_DEBUG, QString(
"Retrying priority %1/%2...")
1436 .arg(recpriority).arg(recpriority2));
1441 LOG(VB_SCHEDULE, LOG_DEBUG, QString(
"Retrying priority %1/*...")
1451 int recpriority,
int recpriority2)
1457 for ( ; i != end; ++i)
1459 if ((*i)->GetRecordingPriority() != recpriority ||
1460 (*i)->GetRecordingPriority2() != recpriority2 ||
1467 (*i)->GetRecordingPriority() != recpriority ||
1468 (*i)->GetRecordingPriority2() != recpriority2)
1473 uint bestaffinity = 0;
1476 for ( ; i != end; ++i)
1478 if ((*i)->GetRecordingPriority() != recpriority ||
1479 (*i)->GetRecordingPriority2() != recpriority2 ||
1480 (*i)->GetRecordingStartTime() !=
1482 (*i)->GetRecordingRuleID() !=
1484 (*i)->GetTitle() != first->
GetTitle() ||
1499 PrintRec(*i, QString(
" %1#").arg(affinity));
1504 PrintRec(*i, QString(
" %1:").arg(affinity));
1505 if (!best || affinity > bestaffinity)
1508 bestaffinity = affinity;
1529 bool samePriority,
bool livetv)
1533 for ( ; i != end; ++i)
1536 retry_list.push_back(*i);
1538 std::ranges::stable_sort(retry_list,
comp_retry);
1540 for (
auto *
p : retry_list)
1559 auto k = conflictlist.cbegin();
1581 int lastrecpri2 = 0;
1616 p->SetRecordingStatus(
p->m_oldrecstatus);
1627 p->ClearInputName();
1645 p->GetRecordingPriority2() >
1649 lastrecpri2 -
p->GetRecordingPriority2());
1664 QMap<int, QDateTime> nextRecMap;
1672 nextRecMap[
p->GetRecordingRuleID()].isNull())
1674 nextRecMap[
p->GetRecordingRuleID()] =
p->GetRecordingStartTime();
1678 p->GetParentRecordingRuleID() > 0 &&
1681 nextRecMap[
p->GetParentRecordingRuleID()].isNull())
1683 nextRecMap[
p->GetParentRecordingRuleID()] =
1684 p->GetRecordingStartTime();
1690 query.
prepare(
"SELECT recordid, next_record FROM record;");
1696 while (query.
next())
1698 int recid = query.
value(0).toInt();
1701 if (next_record == nextRecMap[recid])
1704 if (nextRecMap[recid].isValid())
1706 subquery.
prepare(
"UPDATE record SET next_record = :NEXTREC "
1707 "WHERE recordid = :RECORDID;");
1709 subquery.
bindValue(
":NEXTREC", nextRecMap[recid]);
1710 if (!subquery.
exec())
1713 else if (next_record.isValid())
1715 subquery.
prepare(
"UPDATE record "
1716 "SET next_record = NULL "
1717 "WHERE recordid = :RECORDID;");
1719 if (!subquery.
exec())
1731 strlist << QString::number(retlist.size());
1733 while (!retlist.empty())
1736 p->ToStringList(strlist);
1738 retlist.pop_front();
1749 nullptr,
true); ++i)
1760 bool hasconflicts =
false;
1764 if (recRuleId > 0 &&
1765 p->GetRecordingRuleID() !=
static_cast<uint>(recRuleId))
1768 hasconflicts =
true;
1772 return hasconflicts;
1779 bool hasconflicts =
false;
1783 if (recRuleId > 0 &&
1784 p->GetRecordingRuleID() !=
static_cast<uint>(recRuleId))
1788 hasconflicts =
true;
1792 return hasconflicts;
1799 QMap<QString,ProgramInfo*> recMap;
1816 if (recordedid ==
p->GetRecordingID())
1845 strList << QString::number(static_cast<int>(hasconflicts));
1846 strList << QString::number(retlist.size());
1848 while (!retlist.empty())
1851 p->ToStringList(strList);
1853 retlist.pop_front();
1865 strList << QString::number(schedlist.size());
1867 while (!schedlist.empty())
1872 schedlist.pop_front();
1887 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"AddRecording() recid: %1")
1893 p->IsSameTitleTimeslotAndChannel(pi))
1895 LOG(VB_GENERAL, LOG_INFO,
LOC +
"Not adding recording, " +
1896 QString(
"'%1' is already in reclist.")
1902 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
1903 QString(
"Adding '%1' to reclist.").arg(pi.
GetTitle()));
1906 new_pi->m_mplexId = new_pi->QueryMplexID();
1907 new_pi->m_sgroupId =
m_sinputInfoMap[new_pi->GetInputID()].m_sgroupId;
1913 new_pi->AddHistory(
false);
1916 new_pi->GetRecordingRule();
1920 QString(
"AddRecording %1").arg(pi.
GetTitle()));
1928 LOG(VB_GENERAL, LOG_ERR,
LOC +
1929 "IsBusyRecording() -> true, no tvList or no rcinfo");
1940 bool is_busy = rctv1->
IsBusy(&busy_input, -1s);
1952 const std::vector<unsigned int> &inputids =
m_sinputInfoMap[inputid].m_conflictingInputs;
1953 std::vector<unsigned int> &group_inputs =
m_sinputInfoMap[inputid].m_groupInputs;
1954 for (
uint id : inputids)
1959 LOG(VB_SCHEDULE, LOG_ERR,
LOC +
1960 QString(
"IsBusyRecording() -> true, rctv(NULL) for input %2")
1967 if (rctv2->
IsBusy(&busy_input, -1s))
1988#ifdef __cpp_lib_ranges_contains
1989 std::ranges::contains(group_inputs,
id))
2009 query.
prepare(
"UPDATE oldrecorded SET recstatus = :RSABORTED "
2010 " WHERE recstatus = :RSRECORDING OR "
2011 " recstatus = :RSTUNING OR "
2012 " recstatus = :RSFAILING");
2021 query.
prepare(
"UPDATE oldrecorded SET recstatus = :RSMISSED "
2022 "WHERE recstatus = :RSWILLRECORD OR "
2023 " recstatus = :RSPENDING");
2032 query.
prepare(
"UPDATE oldrecorded SET recstatus = :RSPREVIOUS "
2033 "WHERE recstatus = :RSCURRENT");
2042 query.
prepare(
"UPDATE oldrecorded SET future = 0 "
2043 "WHERE future > 0 AND "
2044 " endtime < (NOW() - INTERVAL 475 MINUTE)");
2064 std::this_thread::sleep_for(3s);
2071 std::chrono::seconds prerollseconds = 0s;
2072 std::chrono::seconds wakeThreshold = 5min;
2075 bool blockShutdown =
2077 bool firstRun =
true;
2080 QDateTime idleSince = QDateTime();
2081 std::chrono::seconds schedRunTime = 0s;
2082 bool statuschanged =
false;
2084 QDateTime nextWakeTime = nextStartTime;
2098 nextWakeTime = std::min(nextWakeTime, nextStartTime);
2100 auto secs_to_next = std::chrono::seconds(curtime.secsTo(nextStartTime));
2101 auto sched_sleep = std::max(std::chrono::milliseconds(curtime.msecsTo(nextWakeTime)), 0ms);
2103 sched_sleep = std::min(sched_sleep, 15000ms);
2105 int const kSleepCheck = 300;
2106 bool checkSlaves = curtime >= nextSleepCheck;
2110 if ((secs_to_next > -60s && secs_to_next < schedRunTime) ||
2111 (!haveRequests && !checkSlaves))
2113 if (sched_sleep > 0ms)
2115 LOG(VB_SCHEDULE, LOG_INFO,
2116 QString(
"sleeping for %1 ms "
2117 "(s2n: %2 sr: %3 qr: %4 cs: %5)")
2118 .arg(sched_sleep.count()).arg(secs_to_next.count()).arg(schedRunTime.count())
2119 .arg(haveRequests).arg(checkSlaves));
2141 wakeThreshold = std::max(wakeThreshold, prerollseconds + 120s);
2143 QElapsedTimer
t;
t.start();
2146 statuschanged =
true;
2149 auto elapsed = std::chrono::ceil<std::chrono::seconds>(std::chrono::milliseconds(
t.elapsed()));
2150 schedRunTime = std::max(elapsed + elapsed/2 + 2s, schedRunTime);
2171 checkSlaves =
false;
2181 nextWakeTime = nextSleepCheck;
2185 for ( ; startIter !=
m_recList.end(); ++startIter)
2187 if ((*startIter)->GetRecordingStatus() !=
2188 (*startIter)->m_oldrecstatus)
2196 for (
auto it = startIter; it !=
m_recList.end(); ++it)
2198 auto secsleft = std::chrono::seconds(curtime.secsTo((*it)->GetRecordingStartTime()));
2199 auto timeBeforePreroll = secsleft - prerollseconds;
2200 if (timeBeforePreroll <= wakeThreshold)
2205 if (timeBeforePreroll > 0s)
2207 std::chrono::seconds waitpending;
2208 if (timeBeforePreroll > 120s)
2209 waitpending = timeBeforePreroll -120s;
2211 waitpending = std::min(timeBeforePreroll, 30s);
2225 for (
auto it = startIter; it !=
m_recList.end() && !done; ++it)
2228 **it, statuschanged, nextStartTime, nextWakeTime,
2252 if (idleSince.isValid())
2257 else if (idleSince.addSecs((
idleTimeoutSecs - 30s).count()) <= curtime)
2263 statuschanged =
false;
2270 const QString &title,
const QString &subtitle,
2271 const QString &descrip,
2272 const QString &programid)
2275 QString filterClause;
2278 if (!title.isEmpty())
2280 filterClause +=
"AND p.title = :TITLE ";
2281 bindings[
":TITLE"] = title;
2285 if (programid !=
"**any**")
2287 filterClause +=
"AND (0 ";
2288 if (!subtitle.isEmpty())
2291 filterClause +=
"OR p.subtitle = :SUBTITLE1 "
2292 "OR p.description = :SUBTITLE2 ";
2293 bindings[
":SUBTITLE1"] = subtitle;
2294 bindings[
":SUBTITLE2"] = subtitle;
2296 if (!descrip.isEmpty())
2299 filterClause +=
"OR p.description = :DESCRIP1 "
2300 "OR p.subtitle = :DESCRIP2 ";
2301 bindings[
":DESCRIP1"] = descrip;
2302 bindings[
":DESCRIP2"] = descrip;
2304 if (!programid.isEmpty())
2306 filterClause +=
"OR p.programid = :PROGRAMID ";
2307 bindings[
":PROGRAMID"] = programid;
2309 filterClause +=
") ";
2312 query.
prepare(QString(
"UPDATE recordmatch rm "
2314 " ON rm.recordid = r.recordid "
2315 "INNER JOIN program p "
2316 " ON rm.chanid = p.chanid "
2317 " AND rm.starttime = p.starttime "
2318 " AND rm.manualid = p.manualid "
2319 "SET oldrecduplicate = -1 "
2320 "WHERE p.generic = 0 "
2321 " AND r.type NOT IN (%2, %3, %4) ")
2327 MSqlBindings::const_iterator it;
2328 for (it = bindings.cbegin(); it != bindings.cend(); ++it)
2333 if (findid && programid !=
"**any**")
2335 query.
prepare(
"UPDATE recordmatch rm "
2336 "SET oldrecduplicate = -1 "
2337 "WHERE rm.recordid = :RECORDID "
2338 " AND rm.findid = :FINDID");
2352 auto fillstart = nowAsDuration<std::chrono::microseconds>();
2354 bool deleteFuture =
false;
2355 bool runCheck =
false;
2361 if (!request.empty())
2363 tokens = request[0].split(
' ', Qt::SkipEmptyParts);
2366 if (request.empty() || tokens.empty())
2368 LOG(VB_GENERAL, LOG_ERR,
"Empty Reschedule request received");
2372 LOG(VB_GENERAL, LOG_INFO, QString(
"Reschedule requested for %1")
2373 .arg(request.join(
" | ")));
2375 if (tokens[0] ==
"MATCH")
2377 if (tokens.size() < 5)
2379 LOG(VB_GENERAL, LOG_ERR,
2380 QString(
"Invalid RescheduleMatch request received (%1)")
2385 uint recordid = tokens[1].toUInt();
2386 uint sourceid = tokens[2].toUInt();
2387 uint mplexid = tokens[3].toUInt();
2389 deleteFuture =
true;
2397 else if (tokens[0] ==
"CHECK")
2399 if (tokens.size() < 4 || request.size() < 5)
2401 LOG(VB_GENERAL, LOG_ERR,
2402 QString(
"Invalid RescheduleCheck request received (%1)")
2407 uint recordid = tokens[2].toUInt();
2408 uint findid = tokens[3].toUInt();
2409 const QString& title = request[1];
2410 const QString& subtitle = request[2];
2411 const QString& descrip = request[3];
2412 const QString& programid = request[4];
2421 else if (tokens[0] !=
"PLACE")
2423 LOG(VB_GENERAL, LOG_ERR,
2424 QString(
"Unknown Reschedule request received (%1)")
2434 query.
prepare(
"DELETE oldrecorded FROM oldrecorded "
2435 "LEFT JOIN recordmatch ON "
2436 " recordmatch.chanid = oldrecorded.chanid AND "
2437 " recordmatch.starttime = oldrecorded.starttime "
2438 "WHERE oldrecorded.future > 0 AND "
2439 " recordmatch.recordid IS NULL");
2444 auto fillend = nowAsDuration<std::chrono::microseconds>();
2445 auto matchTime = fillend - fillstart;
2447 LOG(VB_SCHEDULE, LOG_INFO,
"CreateTempTables...");
2450 fillstart = nowAsDuration<std::chrono::microseconds>();
2453 LOG(VB_SCHEDULE, LOG_INFO,
"UpdateDuplicates...");
2456 fillend = nowAsDuration<std::chrono::microseconds>();
2457 auto checkTime = fillend - fillstart;
2459 fillstart = nowAsDuration<std::chrono::microseconds>();
2461 fillend = nowAsDuration<std::chrono::microseconds>();
2462 auto placeTime = fillend - fillstart;
2464 LOG(VB_SCHEDULE, LOG_INFO,
"DeleteTempTables...");
2474 LOG(VB_GENERAL, LOG_INFO,
"Reschedule interrupted, will retry");
2479 msg = QString(
"Scheduled %1 items in %2 "
2480 "= %3 match + %4 check + %5 place")
2482 .arg(duration_cast<floatsecs>(matchTime + checkTime + placeTime).count(), 0,
'f', 1)
2483 .arg(duration_cast<floatsecs>(matchTime).count(), 0,
'f', 2)
2484 .arg(duration_cast<floatsecs>(checkTime).count(), 0,
'f', 2)
2485 .arg(duration_cast<floatsecs>(placeTime).count(), 0,
'f', 2);
2486 LOG(VB_GENERAL, LOG_INFO, msg);
2491 if (
p->GetRecordingStatus() !=
p->m_oldrecstatus)
2494 p->AddHistory(
false,
false,
false);
2498 p->AddHistory(
false,
false,
false);
2500 p->AddHistory(
false,
false,
true);
2502 else if (
p->m_future)
2508 p->m_future =
false;
2517 std::chrono::seconds prerollseconds,
2520 bool blockShutdown =
true;
2525 QString startupParam =
"user";
2529 for ( ; firstRunIter !=
m_recList.end(); ++firstRunIter)
2540 ((std::chrono::seconds(curtime.secsTo((*firstRunIter)->GetRecordingStartTime())) -
2543 LOG(VB_GENERAL, LOG_INFO,
LOC +
"AUTO-Startup assumed");
2544 startupParam =
"auto";
2548 blockShutdown =
false;
2552 LOG(VB_GENERAL, LOG_INFO,
LOC +
"Seem to be woken up by USER");
2564 return blockShutdown;
2570 static constexpr std::array<const std::chrono::seconds,4> kSysEventSecs = { 120s, 90s, 60s, 30s };
2574 auto secsleft = std::chrono::seconds(curtime.secsTo(nextrectime));
2584 bool pendingEventSent =
false;
2585 for (
size_t i = 0; i < kSysEventSecs.size(); i++)
2587 auto pending_secs = std::max((secsleft - prerollseconds), 0s);
2588 if ((pending_secs <= kSysEventSecs[i]) &&
2591 if (!pendingEventSent)
2594 QString(
"REC_PENDING SECS %1").arg(pending_secs.count()), &ri);
2598 pendingEventSent =
true;
2604 for (
size_t i = 0; i < kSysEventSecs.size(); i++)
2612 keys.insert(rec->MakeUniqueKey());
2613 keys.insert(
"something");
2616 QSet<QString>::iterator sit =
m_sysEvents[i].begin();
2619 if (!keys.contains(*sit))
2630 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
2631 QString(
"Slave Backend %1 is being awakened to record: %2")
2638 ((secsleft - prerollseconds) < 210s) &&
2642 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
2643 QString(
"Slave Backend %1 not available yet, "
2644 "trying to wake it up again.")
2651 ((secsleft - prerollseconds) < 150s) &&
2654 LOG(VB_GENERAL, LOG_WARNING,
LOC +
2655 QString(
"Slave Backend %1 has NOT come "
2656 "back from sleep yet in 150 seconds. Setting "
2657 "slave status to unknown and attempting "
2658 "to reschedule around its tuners.")
2661 for (
auto * enc : std::as_const(*
m_tvList))
2673 QDateTime &nextStartTime, QDateTime &nextWakeTime,
2674 std::chrono::seconds prerollseconds)
2681 std::chrono::seconds origprerollseconds = prerollseconds;
2688 auto nextwake = std::chrono::seconds(nextWakeTime.secsTo(nextrectime));
2689 if (nextwake - prerollseconds > 5min)
2691 nextStartTime = std::min(nextStartTime, nextrectime);
2695 if (curtime < nextrectime)
2696 nextWakeTime = std::min(nextWakeTime, nextrectime);
2702 auto secsleft = std::chrono::seconds(curtime.secsTo(nextrectime));
2707 if (secsleft - prerollseconds > 1min)
2709 nextStartTime = std::min(nextStartTime, nextrectime.addSecs(-30));
2710 nextWakeTime = std::min(nextWakeTime,
2711 nextrectime.addSecs(-prerollseconds.count() - 60));
2724 if (secsleft - prerollseconds > 35s)
2726 nextStartTime = std::min(nextStartTime, nextrectime.addSecs(-30));
2727 nextWakeTime = std::min(nextWakeTime,
2728 nextrectime.addSecs(-prerollseconds.count() - 35));
2737 QString msg = QString(
"Invalid cardid [%1] for %2")
2739 LOG(VB_GENERAL, LOG_ERR,
LOC + msg);
2743 statuschanged =
true;
2751 QString msg = QString(
"SUPPRESSED recording \"%1\" on channel: "
2752 "%2 on cardid: [%3], sourceid %4. Tuner "
2753 "is locked by an external application.")
2758 LOG(VB_GENERAL, LOG_NOTICE, msg);
2762 statuschanged =
true;
2773 if (prerollseconds > 0s)
2781 if (isBusyRecording)
2784 nextWakeTime = std::min(nextWakeTime, curtime.addSecs(5));
2785 prerollseconds = 0s;
2789 if (secsleft - prerollseconds > 30s)
2791 nextStartTime = std::min(nextStartTime, nextrectime.addSecs(-30));
2792 nextWakeTime = std::min(nextWakeTime,
2793 nextrectime.addSecs(-prerollseconds.count() - 30));
2801 LOG(VB_SCHEDULE, LOG_WARNING,
2802 QString(
"WARNING: Slave Backend %1 has NOT come "
2803 "back from sleep yet. Recording can "
2804 "not begin yet for: %2")
2810 LOG(VB_SCHEDULE, LOG_WARNING,
2811 QString(
"WARNING: Slave Backend %1 has NOT come "
2812 "back from sleep yet. Setting slave "
2813 "status to unknown and attempting "
2814 "to reschedule around its tuners.")
2817 for (
auto * enc : std::as_const(*
m_tvList))
2826 nextStartTime = std::min(nextStartTime, nextrectime);
2827 nextWakeTime = std::min(nextWakeTime, curtime.addSecs(1));
2834 QString recording_dir;
2853 MythEvent me(QString(
"ADD_CHILD_INPUT %1")
2856 nextWakeTime = std::min(nextWakeTime, curtime.addSecs(1));
2866 nexttv->
RecordPending(&tempri, std::max(secsleft, 0s),
false);
2872 if (secsleft - prerollseconds > 0s)
2874 nextStartTime = std::min(nextStartTime, nextrectime);
2875 nextWakeTime = std::min(nextWakeTime,
2876 nextrectime.addSecs(-prerollseconds.count()));
2881#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
2882 recstartts = QDateTime(
2884 QTime(recstartts.time().hour(), recstartts.time().minute()), Qt::UTC);
2886 recstartts = QDateTime(
2888 QTime(recstartts.time().hour(), recstartts.time().minute()),
2889 QTimeZone(QTimeZone::UTC));
2894 QString details = QString(
"%1: channel %2 on cardid [%3], sourceid %4")
2921 statuschanged =
true;
2934 bool doSchedAfterStart =
2944 msg = QString(
"Started recording");
2946 msg = QString(
"Tuning recording");
2948 msg = QString(
"Canceled recording (%1)")
2950 LOG(VB_GENERAL, LOG_INFO, QString(
"%1: %2").arg(msg, details));
2958 MythEvent me(QString(
"FORCE_DELETE_RECORDING %1 %2")
2966 std::chrono::seconds prerollseconds)
2971 LOG(VB_SCHEDULE, LOG_DEBUG,
2972 QString(
"Assigning input for %1/%2/\"%3\"")
2983 for (
uint i = 0; !bestid && i < inputs.size(); ++i)
2985 uint inputid = inputs[i];
2993 auto recstarttime = std::chrono::seconds(now.secsTo(
p->GetRecordingStartTime()));
2994 if (recstarttime > prerollseconds + 60s)
2996 if (
p->GetInputID() != inputid)
3013 LOG(VB_SCHEDULE, LOG_DEBUG,
3014 QString(
"Input %1 has a pending recording").arg(inputid));
3023 LOG(VB_SCHEDULE, LOG_DEBUG,
3024 QString(
"Input %1 is recording").arg(inputid));
3029 LOG(VB_SCHEDULE, LOG_DEBUG,
3030 QString(
"Input %1 is recording but will be free")
3039 LOG(VB_SCHEDULE, LOG_DEBUG,
3040 QString(
"Input %1 is recording but has to stop")
3046 LOG(VB_SCHEDULE, LOG_DEBUG,
3047 QString(
"Input %1 is recording but could be free")
3059 bool isbusy = rctv->
IsBusy(&busy_info, -1s);
3065 LOG(VB_SCHEDULE, LOG_DEBUG,
3066 QString(
"Input %1 is free").arg(inputid));
3072 LOG(VB_SCHEDULE, LOG_DEBUG,
3073 QString(
"Input %1 is on livetv but has to stop")
3084 LOG(VB_SCHEDULE, LOG_INFO,
3085 QString(
"Assigned input %1 for %2/%3/\"%4\"")
3093 LOG(VB_SCHEDULE, LOG_WARNING,
3094 QString(
"Failed to assign input for %1/%2/\"%3\"")
3100 return bestid != 0U;
3110 bool &blockShutdown, QDateTime &idleSince,
3111 std::chrono::seconds prerollseconds,
3117 uint logmask = VB_IDLE;
3118 int now = QTime::currentTime().msecsSinceStartOfDay();
3119 int tm = std::chrono::milliseconds(now) / 15min;
3122 logmask = VB_GENERAL;
3141 LOG(VB_GENERAL, LOG_NOTICE,
"Client is connected, removing startup block on shutdown");
3142 blockShutdown =
false;
3153 bool recording =
false;
3156 QMap<int, EncoderLink *>::const_iterator it;
3160 if ((*it)->IsBusy())
3174 if (!blocking && !recording && !activeJobs && !delay)
3181 if (idleSince.isValid())
3183 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN -1"));
3186 idleSince = QDateTime();
3191 if (statuschanged || !idleSince.isValid())
3193 bool wasValid = idleSince.isValid();
3195 idleSince = curtime;
3198 for ( ; idleIter !=
m_recList.end(); ++idleIter)
3200 if ((*idleIter)->GetRecordingStatus() ==
3202 (*idleIter)->GetRecordingStatus() ==
3209 auto recstarttime = std::chrono::seconds(curtime.secsTo((*idleIter)->GetRecordingStartTime()));
3212 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3213 "a recording is due to "
3215 idleSince = QDateTime();
3226 if (guideRunTime.isValid() &&
3230 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3231 "mythfilldatabase is due to "
3233 idleSince = QDateTime();
3238 if (idleSince.isValid())
3241 if (wasValid && !idleSince.isValid())
3243 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN -1"));
3248 if (idleSince.isValid())
3260 LOG(VB_GENERAL, LOG_WARNING,
3261 "Waited more than 60"
3262 " seconds for shutdown to complete"
3263 " - resetting idle time");
3264 idleSince = QDateTime();
3270 blockShutdown, logmask))
3276 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN -1"));
3282 auto itime = std::chrono::seconds(idleSince.secsTo(curtime));
3286 msg = QString(
"I\'m idle now... shutdown will "
3287 "occur in %1 seconds.")
3289 LOG(VB_GENERAL, LOG_NOTICE, msg);
3290 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN %1")
3297 msg = QString(
"%1 secs left to system shutdown!").arg(remain);
3298 LOG(logmask, LOG_NOTICE, msg);
3299 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN %1").arg(remain));
3308 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3309 "of an active encoder");
3311 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3312 "of a connected client");
3315 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3319 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3320 "of delay request from external application");
3323 if (idleSince.isValid())
3325 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN -1"));
3328 idleSince = QDateTime();
3335 QDateTime &idleSince,
3336 bool &blockShutdown,
uint logmask)
3338 bool retval =
false;
3348 LOG(logmask, LOG_INFO,
3349 "CheckShutdownServer returned - OK to shutdown");
3353 LOG(logmask, LOG_NOTICE,
3354 "CheckShutdownServer returned - Not OK to shutdown");
3356 idleSince = QDateTime();
3359 LOG(logmask, LOG_NOTICE,
3360 "CheckShutdownServer returned - Not OK to shutdown, "
3368 idleSince = QDateTime();
3373 m_noAutoShutdown =
true;
3377 LOG(VB_GENERAL, LOG_NOTICE,
3378 "CheckShutdownServer returned - Not OK");
3381 LOG(VB_GENERAL, LOG_NOTICE, QString(
3382 "CheckShutdownServer returned - Error %1").arg(state));
3395 QDateTime &idleSince)
3400 for ( ; recIter !=
m_recList.end(); ++recIter)
3408 QDateTime restarttime;
3413 .addSecs(-prerollseconds.count());
3421 && guideRefreshTime.isValid()
3423 && (restarttime.isNull() || guideRefreshTime < restarttime))
3424 restarttime = guideRefreshTime;
3426 if (restarttime.isValid())
3430 restarttime = restarttime.addSecs((-1LL) * add);
3433 "hh:mm yyyy-MM-dd");
3435 "echo \'Wakeuptime would "
3436 "be $time if command "
3439 if (setwakeup_cmd.isEmpty())
3441 LOG(VB_GENERAL, LOG_NOTICE,
3442 "SetWakeuptimeCommand is empty, shutdown aborted");
3443 idleSince = QDateTime();
3447 if (wakeup_timeformat ==
"time_t")
3450 setwakeup_cmd.replace(
"$time",
3451 time_ts.setNum(restarttime.toSecsSinceEpoch())
3456 setwakeup_cmd.replace(
3457 "$time", restarttime.toLocalTime().toString(wakeup_timeformat));
3460 LOG(VB_GENERAL, LOG_NOTICE,
3461 QString(
"Running the command to set the next "
3462 "scheduled wakeup time :-\n\t\t\t\t") + setwakeup_cmd);
3467 LOG(VB_GENERAL, LOG_ERR,
3468 "SetWakeuptimeCommand failed, shutdown aborted");
3469 idleSince = QDateTime();
3484 "sudo /sbin/halt -p");
3486 if (!halt_cmd.isEmpty())
3491 LOG(VB_GENERAL, LOG_NOTICE,
3492 QString(
"Running the command to shutdown "
3493 "this computer :-\n\t\t\t\t") + halt_cmd);
3500 LOG(VB_GENERAL, LOG_ERR,
"ServerHaltCommand failed, shutdown aborted");
3505 idleSince = QDateTime();
3511 std::chrono::seconds prerollseconds = 0s;
3512 std::chrono::seconds secsleft = 0s;
3516 bool someSlavesCanSleep =
false;
3517 for (
auto * enc : std::as_const(*
m_tvList))
3519 if (enc->CanSleep())
3520 someSlavesCanSleep =
true;
3523 if (!someSlavesCanSleep)
3526 LOG(VB_SCHEDULE, LOG_INFO,
3527 "Scheduler, Checking for slaves that can be shut down");
3529 auto sleepThreshold =
3532 LOG(VB_SCHEDULE, LOG_DEBUG,
3533 QString(
" Getting list of slaves that will be active in the "
3534 "next %1 minutes.") .arg(duration_cast<std::chrono::minutes>(sleepThreshold).count()));
3536 LOG(VB_SCHEDULE, LOG_DEBUG,
"Checking scheduler's reclist");
3538 QStringList SlavesInUse;
3548 auto recstarttime = std::chrono::seconds(curtime.secsTo(pginfo->GetRecordingStartTime()));
3549 secsleft = recstarttime - prerollseconds;
3550 if (secsleft > sleepThreshold)
3555 EncoderLink *enc = (*m_tvList)[pginfo->GetInputID()];
3562 LOG(VB_SCHEDULE, LOG_DEBUG,
3563 QString(
" Slave %1 will be in use in %2 minutes")
3565 .arg(duration_cast<std::chrono::minutes>(secsleft).count()));
3569 LOG(VB_SCHEDULE, LOG_DEBUG,
3570 QString(
" Slave %1 is in use currently "
3579 LOG(VB_SCHEDULE, LOG_DEBUG,
" Checking inuseprograms table:");
3582 query.
prepare(
"SELECT DISTINCT hostname, recusage FROM inuseprograms "
3583 "WHERE lastupdatetime > :ONEHOURAGO ;");
3584 query.
bindValue(
":ONEHOURAGO", oneHourAgo);
3587 while(query.
next()) {
3588 SlavesInUse << query.
value(0).toString();
3589 LOG(VB_SCHEDULE, LOG_DEBUG,
3590 QString(
" Slave %1 is marked as in use by a %2")
3591 .arg(query.
value(0).toString(),
3592 query.
value(1).toString()));
3596 LOG(VB_SCHEDULE, LOG_DEBUG, QString(
" Shutting down slaves which will "
3597 "be inactive for the next %1 minutes and can be put to sleep.")
3598 .arg(sleepThreshold.count() / 60));
3600 for (
auto * enc : std::as_const(*
m_tvList))
3602 if ((!enc->IsLocal()) &&
3604 (!SlavesInUse.contains(enc->GetHostName())) &&
3605 (!enc->IsFallingAsleep()))
3607 QString sleepCommand =
3609 enc->GetHostName());
3610 QString wakeUpCommand =
3612 enc->GetHostName());
3614 if (!sleepCommand.isEmpty() && !wakeUpCommand.isEmpty())
3616 QString thisHost = enc->GetHostName();
3618 LOG(VB_SCHEDULE, LOG_DEBUG,
3619 QString(
" Commanding %1 to go to sleep.")
3622 if (enc->GoToSleep())
3624 for (
auto * slv : std::as_const(*
m_tvList))
3626 if (slv->GetHostName() == thisHost)
3628 LOG(VB_SCHEDULE, LOG_DEBUG,
3629 QString(
" Marking card %1 on slave %2 "
3630 "as falling asleep.")
3631 .arg(slv->GetInputID())
3632 .arg(slv->GetHostName()));
3639 LOG(VB_GENERAL, LOG_ERR,
LOC +
3640 QString(
"Unable to shutdown %1 slave backend, setting "
3641 "sleep status to undefined.").arg(thisHost));
3642 for (
auto * slv : std::as_const(*
m_tvList))
3644 if (slv->GetHostName() == thisHost)
3657 LOG(VB_GENERAL, LOG_NOTICE,
3658 QString(
"Tried to Wake Up %1, but this is the "
3659 "master backend and it is not asleep.")
3660 .arg(slaveHostname));
3667 if (wakeUpCommand.isEmpty()) {
3668 LOG(VB_GENERAL, LOG_NOTICE,
3669 QString(
"Trying to Wake Up %1, but this slave "
3670 "does not have a WakeUpCommand set.").arg(slaveHostname));
3672 for (
auto * enc : std::as_const(*
m_tvList))
3674 if (enc->GetHostName() == slaveHostname)
3682 for (
auto * enc : std::as_const(*
m_tvList))
3684 if (setWakingStatus && (enc->GetHostName() == slaveHostname))
3686 enc->SetLastWakeTime(curtime);
3691 LOG(VB_SCHEDULE, LOG_NOTICE, QString(
"Executing '%1' to wake up slave.")
3692 .arg(wakeUpCommand));
3704 QStringList SlavesThatCanWake;
3706 for (
auto * enc : std::as_const(*
m_tvList))
3711 thisSlave = enc->GetHostName();
3715 (!SlavesThatCanWake.contains(thisSlave)))
3716 SlavesThatCanWake << thisSlave;
3720 for (; slave < SlavesThatCanWake.count(); slave++)
3722 thisSlave = SlavesThatCanWake[slave];
3723 LOG(VB_SCHEDULE, LOG_NOTICE,
3724 QString(
"Scheduler, Sending wakeup command to slave: %1")
3734 query.
prepare(QString(
"SELECT type,title,subtitle,description,"
3735 "station,startdate,starttime,"
3736 "enddate,endtime,season,episode,category,"
3737 "seriesid,programid,inetref,last_record "
3740 if (!query.
exec() || query.
size() != 1)
3750 QString title = query.
value(1).toString();
3751 QString subtitle = query.
value(2).toString();
3752 QString description = query.
value(3).toString();
3753 QString station = query.
value(4).toString();
3754#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
3755 QDateTime startdt = QDateTime(query.
value(5).toDate(),
3756 query.
value(6).toTime(), Qt::UTC);
3757 int duration = startdt.secsTo(
3758 QDateTime(query.
value(7).toDate(),
3759 query.
value(8).toTime(), Qt::UTC));
3761 QDateTime startdt = QDateTime(query.
value(5).toDate(),
3762 query.
value(6).toTime(),
3763 QTimeZone(QTimeZone::UTC));
3764 int duration = startdt.secsTo(
3765 QDateTime(query.
value(7).toDate(),
3766 query.
value(8).toTime(),
3767 QTimeZone(QTimeZone::UTC)));
3770 int season = query.
value(9).toInt();
3771 int episode = query.
value(10).toInt();
3772 QString category = query.
value(11).toString();
3773 QString seriesid = query.
value(12).toString();
3774 QString programid = query.
value(13).toString();
3775 QString inetref = query.
value(14).toString();
3779 QDate originalairdate = QDate(query.
value(15).toDate());
3781 if (description.isEmpty())
3782 description = startdt.toLocalTime().toString();
3784 query.
prepare(
"SELECT chanid from channel "
3785 "WHERE deleted IS NULL AND callsign = :STATION");
3793 std::vector<unsigned int> chanidlist;
3794 while (query.
next())
3795 chanidlist.push_back(query.
value(0).toUInt());
3799 bool weekday =
false;
3801 QDateTime lstartdt = startdt.toLocalTime();
3816 weekday = (lstartdt.date().dayOfWeek() < 6);
3817 daysoff = lstartdt.date().daysTo(
3819#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
3820 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3821 lstartdt.time(), Qt::LocalTime).toUTC();
3823 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3825 QTimeZone(QTimeZone::LocalTime)
3833 daysoff = lstartdt.date().daysTo(
3835 daysoff = (daysoff + 6) / 7 * 7;
3836#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
3837 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3838 lstartdt.time(), Qt::LocalTime).toUTC();
3840 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3842 QTimeZone(QTimeZone::LocalTime)
3847 LOG(VB_GENERAL, LOG_ERR,
3848 QString(
"Invalid rectype for manual recordid %1").arg(recordid));
3854 for (
uint id : chanidlist)
3856 if (weekday && startdt.toLocalTime().date().dayOfWeek() >= 6)
3859 query.
prepare(
"REPLACE INTO program (chanid, starttime, endtime,"
3860 " title, subtitle, description, manualid,"
3861 " season, episode, category, seriesid, programid,"
3862 " inetref, originalairdate, generic) "
3863 "VALUES (:CHANID, :STARTTIME, :ENDTIME, :TITLE,"
3864 " :SUBTITLE, :DESCRIPTION, :RECORDID, "
3865 " :SEASON, :EPISODE, :CATEGORY, :SERIESID,"
3866 " :PROGRAMID, :INETREF, :ORIGINALAIRDATE, 1)");
3869 query.
bindValue(
":ENDTIME", startdt.addSecs(duration));
3872 query.
bindValue(
":DESCRIPTION", description);
3877 query.
bindValue(
":PROGRAMID", programid);
3879 query.
bindValue(
":ORIGINALAIRDATE", originalairdate);
3888 daysoff += skipdays;
3889#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
3890 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3891 lstartdt.time(), Qt::LocalTime).toUTC();
3893 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3895 QTimeZone(QTimeZone::LocalTime)
3909 query = QString(
"SELECT recordid,search,subtitle,description "
3910 "FROM %1 WHERE search <> %2 AND "
3911 "(recordid = %3 OR %4 = 0) ")
3923 while (result.
next())
3925 QString
prefix = QString(
":NR%1").arg(count);
3926 qphrase = result.
value(3).toString();
3932 LOG(VB_GENERAL, LOG_ERR,
3933 QString(
"Invalid search key in recordid %1")
3934 .arg(result.
value(0).toString()));
3938 QString bindrecid =
prefix +
"RECID";
3939 QString bindphrase =
prefix +
"PHRASE";
3940 QString bindlikephrase1 =
prefix +
"LIKEPHRASE1";
3941 QString bindlikephrase2 =
prefix +
"LIKEPHRASE2";
3942 QString bindlikephrase3 =
prefix +
"LIKEPHRASE3";
3944 bindings[bindrecid] = result.
value(0).toString();
3950 qphrase.remove(
';');
3951 from << result.
value(2).toString();
3952 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
3953 QString(
" AND program.manualid = 0 AND ( %2 )")
3957 bindings[bindlikephrase1] = QString(
"%") + qphrase +
"%";
3959 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
" AND "
3960 "program.manualid = 0 AND "
3961 "program.title LIKE " + bindlikephrase1);
3964 bindings[bindlikephrase1] = QString(
"%") + qphrase +
"%";
3965 bindings[bindlikephrase2] = QString(
"%") + qphrase +
"%";
3966 bindings[bindlikephrase3] = QString(
"%") + qphrase +
"%";
3968 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
3969 " AND program.manualid = 0"
3970 " AND (program.title LIKE " + bindlikephrase1 +
3971 " OR program.subtitle LIKE " + bindlikephrase2 +
3972 " OR program.description LIKE " + bindlikephrase3 +
")");
3975 bindings[bindphrase] = qphrase;
3976 from <<
", people, credits";
3977 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
" AND "
3978 "program.manualid = 0 AND "
3979 "people.name LIKE " + bindphrase +
" AND "
3980 "credits.person = people.person AND "
3981 "program.chanid = credits.chanid AND "
3982 "program.starttime = credits.starttime");
3987 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
3989 QString(
"program.manualid = %1.recordid ")
3993 LOG(VB_GENERAL, LOG_ERR,
3994 QString(
"Unknown RecSearchType (%1) for recordid %2")
3995 .arg(result.
value(1).toInt())
3996 .arg(result.
value(0).toString()));
3997 bindings.remove(bindrecid);
4004 if (recordid == 0 || from.count() == 0)
4006 QString recidmatch =
"";
4008 recidmatch =
"RECTABLE.recordid = :NRRECORDID AND ";
4009 QString s1 = recidmatch +
4010 "RECTABLE.type <> :NRTEMPLATE AND "
4011 "RECTABLE.search = :NRST AND "
4012 "program.manualid = 0 AND "
4013 "program.title = RECTABLE.title ";
4015 QString s2 = recidmatch +
4016 "RECTABLE.type <> :NRTEMPLATE AND "
4017 "RECTABLE.search = :NRST AND "
4018 "program.manualid = 0 AND "
4019 "program.seriesid <> '' AND "
4020 "program.seriesid = RECTABLE.seriesid ";
4030 bindings[
":NRRECORDID"] = recordid;
4036" WHEN RECTABLE.type IN (%1, %2, %3) THEN 0 "
4037" WHEN RECTABLE.type IN (%4, %5, %6) THEN -1 "
4038" ELSE (program.generic - 1) "
4044"(CASE RECTABLE.type "
4046" THEN RECTABLE.findid "
4048" THEN to_days(date_sub(convert_tz(program.starttime, 'UTC', 'SYSTEM'), "
4049" interval time_format(RECTABLE.findtime, '%H:%i') hour_minute)) "
4051" THEN floor((to_days(date_sub(convert_tz(program.starttime, 'UTC', "
4052" 'SYSTEM'), interval time_format(RECTABLE.findtime, '%H:%i') "
4053" hour_minute)) - RECTABLE.findday)/7) * 7 + RECTABLE.findday "
4055" THEN RECTABLE.findid "
4064 const QDateTime &maxstarttime)
4068 QString deleteClause;
4069 QString filterClause = QString(
" AND program.endtime > "
4070 "(NOW() - INTERVAL 480 MINUTE)");
4074 deleteClause +=
" AND recordmatch.recordid = :RECORDID";
4075 bindings[
":RECORDID"] = recordid;
4079 deleteClause +=
" AND channel.sourceid = :SOURCEID";
4080 filterClause +=
" AND channel.sourceid = :SOURCEID";
4081 bindings[
":SOURCEID"] = sourceid;
4085 deleteClause +=
" AND channel.mplexid = :MPLEXID";
4086 filterClause +=
" AND channel.mplexid = :MPLEXID";
4087 bindings[
":MPLEXID"] = mplexid;
4089 if (maxstarttime.isValid())
4091 deleteClause +=
" AND recordmatch.starttime <= :MAXSTARTTIME";
4092 filterClause +=
" AND program.starttime <= :MAXSTARTTIME";
4093 bindings[
":MAXSTARTTIME"] = maxstarttime;
4096 query.
prepare(QString(
"DELETE recordmatch FROM recordmatch, channel "
4097 "WHERE recordmatch.chanid = channel.chanid")
4099 MSqlBindings::const_iterator it;
4100 for (it = bindings.cbegin(); it != bindings.cend(); ++it)
4108 bindings.remove(
":RECORDID");
4110 query.
prepare(
"SELECT filterid, clause FROM recordfilter "
4111 "WHERE filterid >= 0 AND filterid < :NUMFILTERS AND "
4112 " TRIM(clause) <> ''");
4119 while (query.
next())
4121 filterClause += QString(
" AND (((RECTABLE.filter & %1) = 0) OR (%2))")
4122 .arg(1 << query.
value(0).toInt()).arg(query.
value(1).toString());
4126 query.
prepare(
"SELECT NULL from record "
4127 "WHERE type = :FINDONE AND findid <= 0;");
4136 QDate epoch(1970, 1, 1);
4139 query.
prepare(
"UPDATE record set findid = :FINDID "
4140 "WHERE type = :FINDONE AND findid <= 0;");
4147 QStringList fromclauses;
4148 QStringList whereclauses;
4154 for (
int clause = 0; clause < fromclauses.count(); ++clause)
4156 LOG(VB_SCHEDULE, LOG_INFO, QString(
"Query %1: %2/%3")
4157 .arg(QString::number(clause), fromclauses[clause],
4158 whereclauses[clause]));
4162 for (
int clause = 0; clause < fromclauses.count(); ++clause)
4164 QString query2 = QString(
4165"REPLACE INTO recordmatch (recordid, chanid, starttime, manualid, "
4166" oldrecduplicate, findid) "
4167"SELECT RECTABLE.recordid, program.chanid, program.starttime, "
4168" IF(search = %1, RECTABLE.recordid, 0), ").arg(
kManualSearch) +
4170"FROM (RECTABLE, program INNER JOIN channel "
4171" ON channel.chanid = program.chanid) ") + fromclauses[clause] + QString(
4172" WHERE ") + whereclauses[clause] +
4173 QString(
" AND channel.deleted IS NULL "
4174 " AND channel.visible > 0 ") +
4175 filterClause + QString(
" AND "
4178" (RECTABLE.type = %1 "
4179" OR RECTABLE.type = %2 "
4180" OR RECTABLE.type = %3 "
4181" OR RECTABLE.type = %4) "
4183" ((RECTABLE.type = %6 "
4184" OR RECTABLE.type = %7 "
4185" OR RECTABLE.type = %8)"
4187" ADDTIME(RECTABLE.startdate, RECTABLE.starttime) = program.starttime "
4189" RECTABLE.station = channel.callsign) "
4201 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- Start DB Query %1...")
4204 auto dbstart = nowAsDuration<std::chrono::microseconds>();
4208 for (it = bindings.cbegin(); it != bindings.cend(); ++it)
4210 if (query2.contains(it.key()))
4214 bool ok = result.
exec();
4215 auto dbend = nowAsDuration<std::chrono::microseconds>();
4216 auto dbTime = dbend - dbstart;
4224 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- %1 results in %2 sec.")
4226 .arg(duration_cast<std::chrono::seconds>(dbTime).count()));
4230 LOG(VB_SCHEDULE, LOG_INFO,
" +-- Done.");
4239 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_record;");
4245 result.
prepare(
"CREATE TEMPORARY TABLE sched_temp_record "
4252 result.
prepare(
"INSERT sched_temp_record SELECT * from record;");
4260 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_recorded;");
4266 result.
prepare(
"CREATE TEMPORARY TABLE sched_temp_recorded "
4273 result.
prepare(
"INSERT sched_temp_recorded SELECT * from recorded;");
4287 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_record;");
4292 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_recorded;");
4300 if (schedTmpRecord ==
"record")
4301 schedTmpRecord =
"sched_temp_record";
4303 QString rmquery = QString(
4304"UPDATE recordmatch "
4305" INNER JOIN RECTABLE ON (recordmatch.recordid = RECTABLE.recordid) "
4306" INNER JOIN program p ON (recordmatch.chanid = p.chanid AND "
4307" recordmatch.starttime = p.starttime AND "
4308" recordmatch.manualid = p.manualid) "
4309" LEFT JOIN oldrecorded ON "
4311" RECTABLE.dupmethod > 1 AND "
4312" oldrecorded.duplicate <> 0 AND "
4313" p.title = oldrecorded.title AND "
4317" (p.programid <> '' "
4318" AND p.programid = oldrecorded.programid) "
4322" (p.programid = '' OR oldrecorded.programid = '' OR "
4323" LEFT(p.programid, LOCATE('/', p.programid)) <> "
4324" LEFT(oldrecorded.programid, LOCATE('/', oldrecorded.programid))) " :
4325" (p.programid = '' OR oldrecorded.programid = '') " )
4328" (((RECTABLE.dupmethod & 0x02) = 0) OR (p.subtitle <> '' "
4329" AND p.subtitle = oldrecorded.subtitle)) "
4331" (((RECTABLE.dupmethod & 0x04) = 0) OR (p.description <> '' "
4332" AND p.description = oldrecorded.description)) "
4334" (((RECTABLE.dupmethod & 0x08) = 0) OR "
4335" (p.subtitle <> '' AND "
4336" (p.subtitle = oldrecorded.subtitle OR "
4337" (oldrecorded.subtitle = '' AND "
4338" p.subtitle = oldrecorded.description))) OR "
4339" (p.subtitle = '' AND p.description <> '' AND "
4340" (p.description = oldrecorded.subtitle OR "
4341" (oldrecorded.subtitle = '' AND "
4342" p.description = oldrecorded.description)))) "
4346" LEFT JOIN sched_temp_recorded recorded ON "
4348" RECTABLE.dupmethod > 1 AND "
4349" recorded.duplicate <> 0 AND "
4350" p.title = recorded.title AND "
4351" p.generic = 0 AND "
4352" recorded.recgroup NOT IN ('LiveTV','Deleted') "
4355" (p.programid <> '' "
4356" AND p.programid = recorded.programid) "
4360" (p.programid = '' OR recorded.programid = '' OR "
4361" LEFT(p.programid, LOCATE('/', p.programid)) <> "
4362" LEFT(recorded.programid, LOCATE('/', recorded.programid))) " :
4363" (p.programid = '' OR recorded.programid = '') ")
4366" (((RECTABLE.dupmethod & 0x02) = 0) OR (p.subtitle <> '' "
4367" AND p.subtitle = recorded.subtitle)) "
4369" (((RECTABLE.dupmethod & 0x04) = 0) OR (p.description <> '' "
4370" AND p.description = recorded.description)) "
4372" (((RECTABLE.dupmethod & 0x08) = 0) OR "
4373" (p.subtitle <> '' AND "
4374" (p.subtitle = recorded.subtitle OR "
4375" (recorded.subtitle = '' AND "
4376" p.subtitle = recorded.description))) OR "
4377" (p.subtitle = '' AND p.description <> '' AND "
4378" (p.description = recorded.subtitle OR "
4379" (recorded.subtitle = '' AND "
4380" p.description = recorded.description)))) "
4384" LEFT JOIN oldfind ON "
4385" (oldfind.recordid = recordmatch.recordid AND "
4386" oldfind.findid = recordmatch.findid) "
4387" SET oldrecduplicate = (oldrecorded.endtime IS NOT NULL), "
4388" recduplicate = (recorded.endtime IS NOT NULL), "
4389" findduplicate = (oldfind.findid IS NOT NULL), "
4390" oldrecstatus = oldrecorded.recstatus "
4391" WHERE p.endtime >= (NOW() - INTERVAL 480 MINUTE) "
4392" AND oldrecduplicate = -1 "
4394 rmquery.replace(
"RECTABLE", schedTmpRecord);
4408 if (schedTmpRecord ==
"record")
4409 schedTmpRecord =
"sched_temp_record";
4413 QMap<int, bool> cardMap;
4414 for (
auto * enc : std::as_const(*
m_tvList))
4416 if (enc->IsConnected() || enc->IsAsleep())
4417 cardMap[enc->GetInputID()] =
true;
4420 QMap<int, bool> tooManyMap;
4421 bool checkTooMany =
false;
4425 rlist.
prepare(QString(
"SELECT recordid, title, maxepisodes, maxnewest "
4426 "FROM %1").arg(schedTmpRecord));
4434 while (rlist.
next())
4436 int recid = rlist.
value(0).toInt();
4438 int maxEpisodes = rlist.
value(2).toInt();
4439 int maxNewest = rlist.
value(3).toInt();
4441 tooManyMap[recid] =
false;
4444 if (maxEpisodes && !maxNewest)
4448 epicnt.
prepare(
"SELECT DISTINCT chanid, progstart, progend "
4450 "WHERE recordid = :RECID AND preserve = 0 "
4451 "AND recgroup NOT IN ('LiveTV','Deleted');");
4456 if (epicnt.
size() >= maxEpisodes - 1)
4459 if (epicnt.
size() >= maxEpisodes)
4461 tooManyMap[recid] =
true;
4462 checkTooMany =
true;
4478 QString pwrpri =
"channel.recpriority + capturecard.recpriority";
4482 pwrpri += QString(
" + "
4483 "IF(capturecard.cardid = RECTABLE.prefinput, 1, 0) * %1")
4489 pwrpri += QString(
" + IF(program.hdtv > 0 OR "
4490 "FIND_IN_SET('HDTV', program.videoprop) > 0, 1, 0) * %1")
4496 pwrpri += QString(
" + "
4497 "IF(FIND_IN_SET('WIDESCREEN', program.videoprop) > 0, 1, 0) * %1")
4503 pwrpri += QString(
" + "
4504 "IF(FIND_IN_SET('SIGNED', program.subtitletypes) > 0, 1, 0) * %1")
4510 pwrpri += QString(
" + "
4511 "IF(FIND_IN_SET('ONSCREEN', program.subtitletypes) > 0, 1, 0) * %1")
4512 .arg(onscrpriority);
4517 pwrpri += QString(
" + "
4518 "IF(FIND_IN_SET('NORMAL', program.subtitletypes) > 0 OR "
4519 "program.closecaptioned > 0 OR program.subtitled > 0, 1, 0) * %1")
4525 pwrpri += QString(
" + "
4526 "IF(FIND_IN_SET('HARDHEAR', program.subtitletypes) > 0 OR "
4527 "FIND_IN_SET('HARDHEAR', program.audioprop) > 0, 1, 0) * %1")
4533 pwrpri += QString(
" + "
4534 "IF(FIND_IN_SET('VISUALIMPAIR', program.audioprop) > 0, 1, 0) * %1")
4540 result.
prepare(QString(
"SELECT recpriority, selectclause FROM %1;")
4549 while (result.
next())
4551 if (result.
value(0).toBool())
4553 QString sclause = result.
value(1).toString();
4555 sclause.remove(
';');
4556 pwrpri += QString(
" + IF(%1, 1, 0) * %2")
4557 .arg(sclause).arg(result.
value(0).toInt());
4560 pwrpri += QString(
" AS powerpriority ");
4562 pwrpri.replace(
"program.",
"p.");
4563 pwrpri.replace(
"channel.",
"c.");
4564 QString query = QString(
4566 " c.chanid, c.sourceid, p.starttime, "
4567 " p.endtime, p.title, p.subtitle, "
4568 " p.description, c.channum, c.callsign, "
4569 " c.name, oldrecduplicate, p.category, "
4570 " RECTABLE.recpriority, RECTABLE.dupin, recduplicate, "
4571 " findduplicate, RECTABLE.type, RECTABLE.recordid, "
4572 " p.starttime - INTERVAL RECTABLE.startoffset "
4573 " minute AS recstartts, "
4574 " p.endtime + INTERVAL RECTABLE.endoffset "
4575 " minute AS recendts, "
4576 " p.previouslyshown, "
4577 " RECTABLE.recgroup, RECTABLE.dupmethod, c.commmethod, "
4578 " capturecard.cardid, 0, p.seriesid, "
4579 " p.programid, RECTABLE.inetref, p.category_type, "
4580 " p.airdate, p.stars, p.originalairdate, "
4581 " RECTABLE.inactive, RECTABLE.parentid, recordmatch.findid, "
4582 " RECTABLE.playgroup, oldrecstatus.recstatus, "
4583 " oldrecstatus.reactivate, p.videoprop+0, "
4584 " p.subtitletypes+0, p.audioprop+0, RECTABLE.storagegroup, "
4585 " capturecard.hostname, recordmatch.oldrecstatus, NULL, "
4586 " oldrecstatus.future, capturecard.schedorder, "
4587 " p.syndicatedepisodenumber, p.partnumber, p.parttotal, "
4588 " c.mplexid, capturecard.displayname, "
4589 " p.season, p.episode, p.totalepisodes, ") +
4592 "INNER JOIN RECTABLE ON (recordmatch.recordid = RECTABLE.recordid) "
4593 "INNER JOIN program AS p "
4594 "ON ( recordmatch.chanid = p.chanid AND "
4595 " recordmatch.starttime = p.starttime AND "
4596 " recordmatch.manualid = p.manualid ) "
4597 "INNER JOIN channel AS c "
4598 "ON ( c.chanid = p.chanid ) "
4599 "INNER JOIN capturecard "
4600 "ON ( c.sourceid = capturecard.sourceid AND "
4601 " ( capturecard.schedorder <> 0 OR "
4602 " capturecard.parentid = 0 ) ) "
4603 "LEFT JOIN oldrecorded as oldrecstatus "
4604 "ON ( oldrecstatus.station = c.callsign AND "
4605 " oldrecstatus.starttime = p.starttime AND "
4606 " oldrecstatus.title = p.title ) "
4607 "WHERE p.endtime > (NOW() - INTERVAL 480 MINUTE) "
4608 "ORDER BY RECTABLE.recordid DESC, p.starttime, p.title, c.callsign, "
4610 query.replace(
"RECTABLE", schedTmpRecord);
4612 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- Start DB Query..."));
4614 auto dbstart = nowAsDuration<std::chrono::microseconds>();
4621 auto dbend = nowAsDuration<std::chrono::microseconds>();
4622 auto dbTime = dbend - dbstart;
4624 LOG(VB_SCHEDULE, LOG_INFO,
4625 QString(
" |-- %1 results in %2 sec. Processing...")
4627 .arg(duration_cast<std::chrono::seconds>(dbTime).count()));
4631 while (result.
next())
4637 uint recordid = result.
value(17).toUInt();
4639 QString title = result.
value(4).toString();
4640 QString callsign = result.
value(8).toString();
4650 uint mplexid = result.
value(51).toUInt();
4651 if (mplexid == 32767)
4654 QString inputname = result.
value(52).toString();
4655 if (inputname.isEmpty())
4656 inputname = QString(
"Input %1").arg(result.
value(24).toUInt());
4661 result.
value(5).toString(),
4663 result.
value(6).toString(),
4664 result.
value(53).toInt(),
4665 result.
value(54).toInt(),
4666 result.
value(55).toInt(),
4667 result.
value(48).toString(),
4668 result.
value(11).toString(),
4670 result.
value(0).toUInt(),
4671 result.
value(7).toString(),
4673 result.
value(9).toString(),
4675 result.
value(21).toString(),
4676 result.
value(36).toString(),
4678 result.
value(43).toString(),
4679 result.
value(42).toString(),
4681 result.
value(30).toUInt(),
4682 result.
value(49).toUInt(),
4683 result.
value(50).toUInt(),
4685 result.
value(26).toString(),
4686 result.
value(27).toString(),
4687 result.
value(28).toString(),
4690 result.
value(12).toInt(),
4697 result.
value(31).toFloat(),
4698 (result.
value(32).isNull()) ? QDate() :
4702 result.
value(20).toBool(),
4705 result.
value(38).toBool(),
4708 result.
value(34).toUInt(),
4713 result.
value(1).toUInt(),
4714 result.
value(24).toUInt(),
4716 result.
value(35).toUInt(),
4719 result.
value(40).toUInt(),
4720 result.
value(39).toUInt(),
4721 result.
value(41).toUInt(),
4722 result.
value(46).toBool(),
4723 result.
value(47).toInt(),
4725 result.
value(24).toUInt(),
4728 if (!
p->m_future && !
p->IsReactivated() &&
4732 p->SetRecordingStatus(
p->m_oldrecstatus);
4735 p->SetRecordingPriority2(result.
value(56).toInt());
4744 if (
p->IsSameTitleStartTimeAndChannel(*r))
4746 if (r->m_sgroupId ==
p->m_sgroupId &&
4747 r->GetRecordingEndTime() !=
p->GetRecordingEndTime() &&
4748 (r->GetRecordingRuleID() ==
p->GetRecordingRuleID() ||
4763 tmpList.push_back(
p);
4770 (!cardMap.contains(
p->GetInputID()) || (
p->m_schedOrder == 0)))
4773 if (
p->m_schedOrder == 0 &&
4776 LOG(VB_GENERAL, LOG_WARNING,
LOC +
4777 QString(
"Channel %1, Title %2 %3 cardinput.schedorder = %4, "
4778 "it must be >0 to record from this input.")
4779 .arg(
p->GetChannelName(),
p->GetTitle(),
4780 p->GetScheduledStartTime().toString(),
4781 QString::number(
p->m_schedOrder)));
4787 if (checkTooMany && tooManyMap[
p->GetRecordingRuleID()] &&
4788 !
p->IsReactivated())
4798 else if (result.
value(15).toBool() && !
p->IsReactivated())
4804 !
p->IsReactivated() &&
4824 bool inactive = result.
value(33).toBool();
4839 p->SetRecordingStatus(newrecstatus);
4841 tmpList.push_back(
p);
4844 LOG(VB_SCHEDULE, LOG_INFO,
" +-- Cleanup...");
4845 for (
auto & tmp : tmpList)
4853 QString query = QString(
4854 "SELECT RECTABLE.title, RECTABLE.subtitle, "
4855 " RECTABLE.description, RECTABLE.season, "
4856 " RECTABLE.episode, RECTABLE.category, "
4857 " RECTABLE.chanid, channel.channum, "
4858 " RECTABLE.station, channel.name, "
4859 " RECTABLE.recgroup, RECTABLE.playgroup, "
4860 " RECTABLE.seriesid, RECTABLE.programid, "
4861 " RECTABLE.inetref, RECTABLE.recpriority, "
4862 " RECTABLE.startdate, RECTABLE.starttime, "
4863 " RECTABLE.enddate, RECTABLE.endtime, "
4864 " RECTABLE.recordid, RECTABLE.type, "
4865 " RECTABLE.dupin, RECTABLE.dupmethod, "
4866 " RECTABLE.findid, "
4867 " RECTABLE.startoffset, RECTABLE.endoffset, "
4868 " channel.commmethod "
4870 "INNER JOIN channel ON (channel.chanid = RECTABLE.chanid) "
4871 "LEFT JOIN recordmatch on RECTABLE.recordid = recordmatch.recordid "
4872 "WHERE (type = %1 OR type = %2) AND "
4873 " recordmatch.chanid IS NULL")
4879 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- Start DB Query..."));
4881 auto dbstart = nowAsDuration<std::chrono::microseconds>();
4884 bool ok = result.
exec();
4885 auto dbend = nowAsDuration<std::chrono::microseconds>();
4886 auto dbTime = dbend - dbstart;
4894 LOG(VB_SCHEDULE, LOG_INFO,
4895 QString(
" |-- %1 results in %2 sec. Processing...")
4897 .arg(duration_cast<std::chrono::seconds>(dbTime).count()));
4899 while (result.
next())
4902#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
4904 result.
value(16).toDate(), result.
value(17).toTime(), Qt::UTC);
4906 result.
value(18).toDate(), result.
value(19).toTime(), Qt::UTC);
4908 static const QTimeZone utc(QTimeZone::UTC);
4910 result.
value(16).toDate(), result.
value(17).toTime(), utc);
4912 result.
value(18).toDate(), result.
value(19).toTime(), utc);
4915 QDateTime recstartts = startts.addSecs(result.
value(25).toInt() * -60LL);
4916 QDateTime recendts = endts.addSecs( result.
value(26).toInt() * +60LL);
4918 if (recstartts >= recendts)
4921 recstartts = startts;
4932 result.
value(0).toString(),
4934 sor ? result.
value(1).toString() : QString(),
4936 sor ? result.
value(2).toString() : QString(),
4937 result.
value(3).toUInt(),
4938 result.
value(4).toUInt(),
4941 result.
value(6).toUInt(),
4942 result.
value(7).toString(),
4943 result.
value(8).toString(),
4944 result.
value(9).toString(),
4946 result.
value(10).toString(),
4947 result.
value(11).toString(),
4949 result.
value(12).toString(),
4950 result.
value(13).toString(),
4951 result.
value(14).toString(),
4953 result.
value(15).toInt(),
4956 recstartts, recendts,
4960 result.
value(20).toUInt(),
4966 result.
value(24).toUInt(),
4970 tmpList.push_back(
p);
4973 for (
auto & tmp : tmpList)
4984 QString sortColumn =
"title";
4994 QString prefixes = sh->getPrefixes();
4995 sortColumn =
"REGEXP_REPLACE(record.title,'" + prefixes +
"','')";
4999 sortColumn =
"record.recpriority";
5002 sortColumn =
"record.last_record";
5010 sortColumn =
"record.next_record IS NULL, record.next_record";
5013 sortColumn =
"record.type";
5017 QString order =
"ASC";
5021 QString query = QString(
5022 "SELECT record.title, record.subtitle, "
5023 " record.description, record.season, "
5024 " record.episode, record.category, "
5025 " record.chanid, channel.channum, "
5026 " record.station, channel.name, "
5027 " record.recgroup, record.playgroup, "
5028 " record.seriesid, record.programid, "
5029 " record.inetref, record.recpriority, "
5030 " record.startdate, record.starttime, "
5031 " record.enddate, record.endtime, "
5032 " record.recordid, record.type, "
5033 " record.dupin, record.dupmethod, "
5035 " channel.commmethod "
5037 "LEFT JOIN channel ON channel.callsign = record.station "
5038 " AND deleted IS NULL "
5039 "GROUP BY recordid "
5042 query = query.arg(sortColumn, order);
5053 while (result.
next())
5056#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
5057 QDateTime startts = QDateTime(result.
value(16).toDate(),
5058 result.
value(17).toTime(), Qt::UTC);
5059 QDateTime endts = QDateTime(result.
value(18).toDate(),
5060 result.
value(19).toTime(), Qt::UTC);
5062 static const QTimeZone utc(QTimeZone::UTC);
5063 QDateTime startts = QDateTime(result.
value(16).toDate(),
5064 result.
value(17).toTime(), utc);
5065 QDateTime endts = QDateTime(result.
value(18).toDate(),
5066 result.
value(19).toTime(), utc);
5069 if (!startts.isValid())
5071#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
5076 QTimeZone(QTimeZone::UTC));
5079 if (!endts.isValid())
5083 result.
value(0).toString(), QString(),
5084 result.
value(1).toString(), QString(),
5085 result.
value(2).toString(), result.
value(3).toUInt(),
5086 result.
value(4).toUInt(), result.
value(5).toString(),
5088 result.
value(6).toUInt(), result.
value(7).toString(),
5089 result.
value(8).toString(), result.
value(9).toString(),
5091 result.
value(10).toString(), result.
value(11).toString(),
5093 result.
value(12).toString(), result.
value(13).toString(),
5094 result.
value(14).toString(),
5096 result.
value(15).toInt(),
5103 result.
value(20).toUInt(), rectype,
5107 result.
value(24).toUInt(),
5206 QString recording_dir;
5210 "LiveTV", cur, cur.addSecs(3600), cardid,
5215 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"FindNextLiveTVDir: next dir is '%1'")
5216 .arg(recording_dir));
5223 const QString &title,
5225 const QString &storagegroup,
5226 const QDateTime &recstartts,
5227 const QDateTime &recendts,
5229 QString &recording_dir,
5232 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
"FillRecordingDir: Starting");
5237 if (cnt++ % 20 == 0)
5238 LOG(VB_SCHEDULE, LOG_WARNING,
"Waiting for main server.");
5239 std::this_thread::sleep_for(50ms);
5246 QStringList recsCounted;
5247 std::list<FileSystemInfo *> fsInfoList;
5248 std::list<FileSystemInfo *>::iterator fslistit;
5250 recording_dir.clear();
5252 if (dirlist.size() == 1)
5254 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5255 QString(
"FillRecordingDir: The only directory in the %1 Storage "
5256 "Group is %2, so it will be used by default.")
5257 .arg(storagegroup, dirlist[0]));
5258 recording_dir = dirlist[0];
5259 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
"FillRecordingDir: Finished");
5264 int weightPerRecording =
5266 int weightPerPlayback =
5268 int weightPerCommFlag =
5270 int weightPerTranscode =
5273 QString storageScheduler =
5275 int localStartingWeight =
5277 (storageScheduler !=
"Combination") ? 0
5278 : (
int)(-1.99 * weightPerRecording));
5279 int remoteStartingWeight =
5281 std::chrono::seconds maxOverlap =
5286 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5287 "FillRecordingDir: Calculating initial FS Weights.");
5298 tmpWeight = localStartingWeight;
5299 msg +=
" is local (" + QString::number(tmpWeight) +
")";
5303 tmpWeight = remoteStartingWeight;
5304 msg +=
" is remote (+" + QString::number(tmpWeight) +
")";
5314 msg +=
", has SGweightPerDir offset of "
5315 + QString::number(tmpWeight) +
")";
5317 msg +=
". initial dir weight = " + QString::number(fs->
getWeight());
5318 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, msg);
5320 fsInfoList.push_back(fs);
5323 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5324 "FillRecordingDir: Adjusting FS Weights from inuseprograms.");
5327 saveRecDir.
prepare(
"UPDATE inuseprograms "
5328 "SET recdir = :RECDIR "
5329 "WHERE chanid = :CHANID AND "
5330 " starttime = :STARTTIME");
5333 "SELECT i.chanid, i.starttime, r.endtime, recusage, rechost, recdir "
5334 "FROM inuseprograms i, recorded r "
5335 "WHERE DATE_ADD(lastupdatetime, INTERVAL 16 MINUTE) > NOW() AND "
5336 " i.chanid = r.chanid AND "
5337 " i.starttime = r.starttime");
5345 while (query.
next())
5347 uint recChanid = query.
value(0).toUInt();
5350 QString recUsage( query.
value(3).toString());
5351 QString recHost( query.
value(4).toString());
5352 QString recDir( query.
value(5).toString());
5354 if (recDir.isEmpty())
5358 recDir = recDir.isEmpty() ?
"_UNKNOWN_" : recDir;
5360 saveRecDir.
bindValue(
":RECDIR", recDir);
5361 saveRecDir.
bindValue(
":CHANID", recChanid);
5362 saveRecDir.
bindValue(
":STARTTIME", recStart);
5363 if (!saveRecDir.
exec())
5366 if (recDir ==
"_UNKNOWN_")
5369 for (fslistit = fsInfoList.begin();
5370 fslistit != fsInfoList.end(); ++fslistit)
5376 int weightOffset = 0;
5380 if (recEnd > recstartts.addSecs(maxOverlap.count()))
5382 weightOffset += weightPerRecording;
5383 recsCounted << QString::number(recChanid) +
":" +
5389 weightOffset += weightPerPlayback;
5393 weightOffset += weightPerCommFlag;
5397 weightOffset += weightPerTranscode;
5402 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5403 QString(
" %1 @ %2 in use by '%3' on %4:%5, FSID "
5404 "#%6, FSID weightOffset +%7.")
5405 .arg(QString::number(recChanid),
5407 recUsage, recHost, recDir,
5409 QString::number(weightOffset)));
5417 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5418 QString(
" %1:%2 => old weight %3 plus "
5420 .arg(
fs2->getHostname(),
5422 .arg(
fs2->getWeight())
5424 .arg(
fs2->getWeight() + weightOffset));
5426 fs2->setWeight(
fs2->getWeight() + weightOffset);
5436 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5437 "FillRecordingDir: Adjusting FS Weights from scheduler.");
5439 for (
auto *thispg : reclist)
5441 if ((recendts < thispg->GetRecordingStartTime()) ||
5442 (recstartts > thispg->GetRecordingEndTime()) ||
5445 (thispg->GetInputID() == 0) ||
5446 (recsCounted.contains(QString(
"%1:%2").arg(thispg->GetChanID())
5448 (thispg->GetPathname().isEmpty()))
5451 for (fslistit = fsInfoList.begin();
5452 fslistit != fsInfoList.end(); ++fslistit)
5455 if ((fs->
getHostname() == thispg->GetHostname()) &&
5456 (fs->
getPath() == thispg->GetPathname()))
5458 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5459 QString(
"%1 @ %2 will record on %3:%4, FSID #%5, "
5460 "weightPerRecording +%6.")
5461 .arg(thispg->GetChanID())
5464 .arg(fs->
getFSysID()).arg(weightPerRecording));
5473 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5474 QString(
" %1:%2 => old weight %3 plus %4 = %5")
5475 .arg(
fs2->getHostname(),
fs2->getPath())
5476 .arg(
fs2->getWeight()).arg(weightPerRecording)
5477 .arg(
fs2->getWeight() + weightPerRecording));
5479 fs2->setWeight(
fs2->getWeight() + weightPerRecording);
5487 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5488 QString(
"Using '%1' Storage Scheduler directory sorting algorithm.")
5489 .arg(storageScheduler));
5491 if (storageScheduler ==
"BalancedFreeSpace")
5493 else if (storageScheduler ==
"BalancedPercFreeSpace")
5495 else if (storageScheduler ==
"BalancedDiskIO")
5502 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5503 "--- FillRecordingDir Sorted fsInfoList start ---");
5504 for (fslistit = fsInfoList.begin();fslistit != fsInfoList.end();
5508 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
"%1:%2")
5510 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
" Location : %1")
5511 .arg((fs->
isLocal()) ?
"local" :
"remote"));
5512 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
" weight : %1")
5514 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
" free space : %5")
5517 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5518 "--- FillRecordingDir Sorted fsInfoList end ---");
5527 long long maxSizeKB = (maxByterate + (maxByterate/3)) *
5528 recstartts.secsTo(recendts) / 1024;
5530 bool simulateAutoExpire =
5533 (fsInfoList.size() > 1));
5547 for (
unsigned int pass = 1; pass <= 3; pass++)
5549 bool foundDir =
false;
5551 if ((pass == 2) && simulateAutoExpire)
5554 QMap <int , long long> remainingSpaceKB;
5555 for (fslistit = fsInfoList.begin();
5556 fslistit != fsInfoList.end(); ++fslistit)
5558 remainingSpaceKB[(*fslistit)->getFSysID()] =
5559 (*fslistit)->getFreeSpace();
5566 for (
auto & expire : expiring)
5570 for (fslistit = fsInfoList.begin();
5571 fslistit != fsInfoList.end(); ++fslistit)
5574 if (expire->GetHostname() != (*fslistit)->getHostname())
5578 if (!dirlist.contains((*fslistit)->getPath()))
5582 (*fslistit)->getPath() +
"/" + expire->GetPathname();
5589 if (checkFile.exists())
5597 QString backuppath = expire->GetPathname();
5599 bool foundSlave =
false;
5601 for (
auto * enc : std::as_const(*
m_tvList))
5603 if (enc->GetHostName() ==
5606 enc->CheckFile(programinfo);
5624 LOG(VB_GENERAL, LOG_ERR,
5625 QString(
"Unable to match '%1' "
5626 "to any file system. Ignoring it.")
5627 .arg(expire->GetBasename()));
5633 expire->GetFilesize() / 1024;
5636 long long desiredSpaceKB =
5640 (desiredSpaceKB + maxSizeKB))
5642 recording_dir = fs->
getPath();
5645 LOG(VB_FILE, LOG_INFO,
5646 QString(
"pass 2: '%1' will record in '%2' "
5647 "although there is only %3 MB free and the "
5648 "AutoExpirer wants at least %4 MB. This "
5649 "directory has the highest priority files "
5650 "to be expired from the AutoExpire list and "
5651 "there are enough that the Expirer should "
5652 "be able to free up space for this recording.")
5653 .arg(title, recording_dir)
5655 .arg(desiredSpaceKB / 1024));
5666 for (fslistit = fsInfoList.begin();
5667 fslistit != fsInfoList.end(); ++fslistit)
5669 long long desiredSpaceKB = 0;
5676 (dirlist.contains(fs->
getPath())) &&
5680 recording_dir = fs->
getPath();
5685 LOG(VB_FILE, LOG_INFO,
5686 QString(
"pass 1: '%1' will record in "
5687 "'%2' which has %3 MB free. This recording "
5688 "could use a max of %4 MB and the "
5689 "AutoExpirer wants to keep %5 MB free.")
5690 .arg(title, recording_dir)
5692 .arg(maxSizeKB / 1024)
5693 .arg(desiredSpaceKB / 1024));
5697 LOG(VB_FILE, LOG_INFO,
5698 QString(
"pass %1: '%2' will record in "
5699 "'%3' although there is only %4 MB free and "
5700 "the AutoExpirer wants at least %5 MB. "
5701 "Something will have to be deleted or expired "
5702 "in order for this recording to complete "
5704 .arg(pass).arg(title, recording_dir)
5706 .arg(desiredSpaceKB / 1024));
5719 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
"FillRecordingDir: Finished");
5732 QMap <int, bool> fsMap;
5733 for (
const auto&
fs1 : std::as_const(fsInfos))
5735 fsMap[
fs1.getFSysID()] =
true;
5739 LOG(VB_FILE, LOG_INFO,
LOC +
5740 QString(
"FillDirectoryInfoCache: found %1 unique filesystems")
5741 .arg(fsMap.size()));
5748 auto secsleft = std::chrono::seconds(curtime.secsTo(
m_livetvTime));
5752 if (secsleft - prerollseconds > 120s)
5756 for (
auto * enc : std::as_const(*
m_tvList))
5772 if (
m_schedTime.secsTo(dummy->GetRecordingEndTime()) < 1800)
5773 dummy->SetRecordingEndTime(
m_schedTime.addSecs(1800));
5774 dummy->SetInputID(enc->GetInputID());
5775 dummy->m_mplexId = dummy->QueryMplexID();
5798 bool autoStart =
false;
5800 QDateTime startupTime = QDateTime();
5806 if (startupTime.isValid())
5809 startupSecs = std::max(startupSecs, 15 * 60s);
5816 LOG(VB_GENERAL, LOG_INFO,
5817 "Close to auto-start time, AUTO-Startup assumed");
5823 LOG(VB_GENERAL, LOG_INFO,
5824 "Close to MythFillDB suggested run time, AUTO-Startup to fetch guide data?");
5830 LOG(VB_GENERAL, LOG_DEBUG,
5831 "NOT close to auto-start time, USER-initiated startup assumed");
5834 else if (!s.isEmpty())
5836 LOG(VB_GENERAL, LOG_ERR,
LOC +
5837 QString(
"Invalid MythShutdownWakeupTime specified in database (%1)")
5849 QMap<uint, QSet<uint> > inputSets;
5850 query.
prepare(
"SELECT DISTINCT ci1.cardid, ci2.cardid "
5851 "FROM capturecard ci1, capturecard ci2, "
5852 " inputgroup ig1, inputgroup ig2 "
5853 "WHERE ci1.cardid = ig1.cardinputid AND "
5854 " ci2.cardid = ig2.cardinputid AND"
5855 " ig1.inputgroupid = ig2.inputgroupid AND "
5856 " ci1.cardid <= ci2.cardid "
5857 "ORDER BY ci1.cardid, ci2.cardid");
5863 while (query.
next())
5867 inputSets[id0].insert(id1);
5868 inputSets[id1].insert(id0);
5871 QMap<uint, QSet<uint> >::iterator mit;
5872 for (mit = inputSets.begin(); mit != inputSets.end(); ++mit)
5874 uint inputid = mit.key();
5882 QSet<uint> fullset = mit.value();
5883 QSet<uint> checkset;
5884 QSet<uint>::const_iterator sit;
5885 while (checkset != fullset)
5888 for (
int item : std::as_const(checkset))
5889 fullset += inputSets[item];
5894 auto *conflictlist =
new RecList();
5896 for (
int item : std::as_const(checkset))
5898 LOG(VB_SCHEDULE, LOG_INFO,
5899 QString(
"Assigning input %1 to conflict set %2")
5907 query.
prepare(
"SELECT ci.cardid "
5908 "FROM capturecard ci "
5909 "LEFT JOIN inputgroup ig "
5910 " ON ci.cardid = ig.cardinputid "
5911 "WHERE ig.cardinputid IS NULL");
5917 while (query.
next())
5921 LOG(VB_GENERAL, LOG_ERR,
LOC +
5922 QString(
"Input %1 is not assigned to any input group").arg(
id));
5923 auto *conflictlist =
new RecList();
5925 LOG(VB_SCHEDULE, LOG_INFO,
5926 QString(
"Assigning input %1 to conflict set %2")
5940 query.
prepare(
"SELECT cardid, parentid, schedgroup "
5942 "WHERE sourceid > 0 "
5950 while (query.
next())
5953 uint parentid = query.
value(1).toUInt();
5970 LOG(VB_SCHEDULE, LOG_INFO,
5971 QString(
"Added SchedInputInfo i=%1, g=%2, sg=%3")
5980 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
5981 QString(
"AddChildInput: Handling parent = %1, input = %2")
5982 .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...