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 if (
find(conflicting_inputs.begin(), conflicting_inputs.end(),
1114 q->
GetInputID()) == conflicting_inputs.end())
1117 msg +=
" cardid== ";
1126 msg +=
" no-overlap ";
1133 (((
p->m_mplexId != 0U) &&
p->m_mplexId == q->
m_mplexId) ||
1134 ((
p->m_mplexId == 0U) &&
p->GetChanID() == q->
GetChanID()));
1146 msg +=
" no-overlap ";
1155 LOG(VB_SCHEDULE, LOG_INFO, msg);
1156 LOG(VB_SCHEDULE, LOG_INFO,
1157 QString(
" cardid's: [%1], [%2] Share an input group, "
1158 "mplexid's: %3, %4")
1172 LOG(VB_SCHEDULE, LOG_INFO,
"Found conflict");
1175 *paffinity += affinity;
1180 LOG(VB_SCHEDULE, LOG_INFO,
"No conflict");
1183 *paffinity += affinity;
1191 bool checkAll)
const
1194 auto k = conflictlist.cbegin();
1201 return firstConflict;
1228 for (
auto *q : showinglist)
1237 if (q->IsSameTitleStartTimeAndChannel(*
p))
1243 if (q->GetRecordingStartTime() <
p->GetRecordingStartTime())
1255 p->m_savedrecstatus =
p->GetRecordingStatus();
1263 p->SetRecordingStatus(
p->m_savedrecstatus);
1284 uint bestaffinity = 0;
1286 for (
auto *q : *showinglist)
1292 (q->GetRecordingPriority() <
p->GetRecordingPriority() ||
1293 (q->GetRecordingPriority() ==
p->GetRecordingPriority() &&
1294 q->GetRecordingPriority2() <
p->GetRecordingPriority2())))
1306 if (!
p->IsSameTitleStartTimeAndChannel(*q))
1341 PrintRec(q, QString(
" %1:").arg(affinity));
1342 if (!best || affinity > bestaffinity)
1345 bestaffinity = affinity;
1353 QString msg = QString(
1354 "Moved \"%1\" on chanid: %2 from card: %3 to %4 at %5 "
1355 "to avoid LiveTV conflict")
1356 .arg(
p->GetTitle()).arg(
p->GetChanID())
1359 LOG(VB_GENERAL, LOG_INFO, msg);
1371 p->SetRecordingStatus(oldstatus);
1379 LOG(VB_SCHEDULE, LOG_DEBUG,
1380 "+ = schedule this showing to be recorded");
1381 LOG(VB_SCHEDULE, LOG_DEBUG,
1382 "n: = could schedule this showing with affinity");
1383 LOG(VB_SCHEDULE, LOG_DEBUG,
1384 "n# = could not schedule this showing, with affinity");
1385 LOG(VB_SCHEDULE, LOG_DEBUG,
1386 "! = conflict caused by this showing");
1387 LOG(VB_SCHEDULE, LOG_DEBUG,
1388 "/ = retry this showing, same priority pass");
1389 LOG(VB_SCHEDULE, LOG_DEBUG,
1390 "? = retry this showing, lower priority pass");
1391 LOG(VB_SCHEDULE, LOG_DEBUG,
1392 "> = try another showing for this program");
1393 LOG(VB_SCHEDULE, LOG_DEBUG,
1394 "- = unschedule a showing in favor of another one");
1413 auto levelStart = i;
1414 int recpriority = (*i)->GetRecordingPriority();
1419 (*i)->GetRecordingPriority() != recpriority)
1422 auto sublevelStart = i;
1423 int recpriority2 = (*i)->GetRecordingPriority2();
1424 LOG(VB_SCHEDULE, LOG_DEBUG, QString(
"Trying priority %1/%2...")
1425 .arg(recpriority).arg(recpriority2));
1429 LOG(VB_SCHEDULE, LOG_DEBUG, QString(
"Retrying priority %1/%2...")
1430 .arg(recpriority).arg(recpriority2));
1435 LOG(VB_SCHEDULE, LOG_DEBUG, QString(
"Retrying priority %1/*...")
1445 int recpriority,
int recpriority2)
1451 for ( ; i != end; ++i)
1453 if ((*i)->GetRecordingPriority() != recpriority ||
1454 (*i)->GetRecordingPriority2() != recpriority2 ||
1461 (*i)->GetRecordingPriority() != recpriority ||
1462 (*i)->GetRecordingPriority2() != recpriority2)
1467 uint bestaffinity = 0;
1470 for ( ; i != end; ++i)
1472 if ((*i)->GetRecordingPriority() != recpriority ||
1473 (*i)->GetRecordingPriority2() != recpriority2 ||
1474 (*i)->GetRecordingStartTime() !=
1476 (*i)->GetRecordingRuleID() !=
1478 (*i)->GetTitle() != first->
GetTitle() ||
1493 PrintRec(*i, QString(
" %1#").arg(affinity));
1498 PrintRec(*i, QString(
" %1:").arg(affinity));
1499 if (!best || affinity > bestaffinity)
1502 bestaffinity = affinity;
1523 bool samePriority,
bool livetv)
1527 for ( ; i != end; ++i)
1530 retry_list.push_back(*i);
1532 std::stable_sort(retry_list.begin(), retry_list.end(),
comp_retry);
1534 for (
auto *
p : retry_list)
1553 auto k = conflictlist.cbegin();
1575 int lastrecpri2 = 0;
1610 p->SetRecordingStatus(
p->m_oldrecstatus);
1621 p->ClearInputName();
1639 p->GetRecordingPriority2() >
1643 lastrecpri2 -
p->GetRecordingPriority2());
1658 QMap<int, QDateTime> nextRecMap;
1666 nextRecMap[
p->GetRecordingRuleID()].isNull())
1668 nextRecMap[
p->GetRecordingRuleID()] =
p->GetRecordingStartTime();
1672 p->GetParentRecordingRuleID() > 0 &&
1675 nextRecMap[
p->GetParentRecordingRuleID()].isNull())
1677 nextRecMap[
p->GetParentRecordingRuleID()] =
1678 p->GetRecordingStartTime();
1684 query.
prepare(
"SELECT recordid, next_record FROM record;");
1690 while (query.
next())
1692 int recid = query.
value(0).toInt();
1695 if (next_record == nextRecMap[recid])
1698 if (nextRecMap[recid].isValid())
1700 subquery.
prepare(
"UPDATE record SET next_record = :NEXTREC "
1701 "WHERE recordid = :RECORDID;");
1703 subquery.
bindValue(
":NEXTREC", nextRecMap[recid]);
1704 if (!subquery.
exec())
1707 else if (next_record.isValid())
1709 subquery.
prepare(
"UPDATE record "
1710 "SET next_record = NULL "
1711 "WHERE recordid = :RECORDID;");
1713 if (!subquery.
exec())
1725 strlist << QString::number(retlist.size());
1727 while (!retlist.empty())
1730 p->ToStringList(strlist);
1732 retlist.pop_front();
1743 nullptr,
true); ++i)
1754 bool hasconflicts =
false;
1758 if (recRuleId > 0 &&
1759 p->GetRecordingRuleID() !=
static_cast<uint>(recRuleId))
1762 hasconflicts =
true;
1766 return hasconflicts;
1773 bool hasconflicts =
false;
1777 if (recRuleId > 0 &&
1778 p->GetRecordingRuleID() !=
static_cast<uint>(recRuleId))
1782 hasconflicts =
true;
1786 return hasconflicts;
1793 QMap<QString,ProgramInfo*> recMap;
1810 if (recordedid ==
p->GetRecordingID())
1839 strList << QString::number(static_cast<int>(hasconflicts));
1840 strList << QString::number(retlist.size());
1842 while (!retlist.empty())
1845 p->ToStringList(strList);
1847 retlist.pop_front();
1859 strList << QString::number(schedlist.size());
1861 while (!schedlist.empty())
1866 schedlist.pop_front();
1881 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"AddRecording() recid: %1")
1887 p->IsSameTitleTimeslotAndChannel(pi))
1889 LOG(VB_GENERAL, LOG_INFO,
LOC +
"Not adding recording, " +
1890 QString(
"'%1' is already in reclist.")
1896 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
1897 QString(
"Adding '%1' to reclist.").arg(pi.
GetTitle()));
1900 new_pi->m_mplexId = new_pi->QueryMplexID();
1901 new_pi->m_sgroupId =
m_sinputInfoMap[new_pi->GetInputID()].m_sgroupId;
1907 new_pi->AddHistory(
false);
1910 new_pi->GetRecordingRule();
1914 QString(
"AddRecording %1").arg(pi.
GetTitle()));
1922 LOG(VB_GENERAL, LOG_ERR,
LOC +
1923 "IsBusyRecording() -> true, no tvList or no rcinfo");
1934 bool is_busy = rctv1->
IsBusy(&busy_input, -1s);
1946 const std::vector<unsigned int> &inputids =
m_sinputInfoMap[inputid].m_conflictingInputs;
1947 std::vector<unsigned int> &group_inputs =
m_sinputInfoMap[inputid].m_groupInputs;
1948 for (
uint id : inputids)
1953 LOG(VB_SCHEDULE, LOG_ERR,
LOC +
1954 QString(
"IsBusyRecording() -> true, rctv(NULL) for input %2")
1961 if (rctv2->
IsBusy(&busy_input, -1s))
1982 std::find(group_inputs.begin(), group_inputs.end(),
1983 id) != group_inputs.end())
2000 query.
prepare(
"UPDATE oldrecorded SET recstatus = :RSABORTED "
2001 " WHERE recstatus = :RSRECORDING OR "
2002 " recstatus = :RSTUNING OR "
2003 " recstatus = :RSFAILING");
2012 query.
prepare(
"UPDATE oldrecorded SET recstatus = :RSMISSED "
2013 "WHERE recstatus = :RSWILLRECORD OR "
2014 " recstatus = :RSPENDING");
2023 query.
prepare(
"UPDATE oldrecorded SET recstatus = :RSPREVIOUS "
2024 "WHERE recstatus = :RSCURRENT");
2033 query.
prepare(
"UPDATE oldrecorded SET future = 0 "
2034 "WHERE future > 0 AND "
2035 " endtime < (NOW() - INTERVAL 475 MINUTE)");
2055 std::this_thread::sleep_for(3s);
2062 std::chrono::seconds prerollseconds = 0s;
2063 std::chrono::seconds wakeThreshold = 5min;
2066 bool blockShutdown =
2068 bool firstRun =
true;
2071 QDateTime idleSince = QDateTime();
2072 std::chrono::seconds schedRunTime = 0s;
2073 bool statuschanged =
false;
2075 QDateTime nextWakeTime = nextStartTime;
2089 nextWakeTime = std::min(nextWakeTime, nextStartTime);
2091 auto secs_to_next = std::chrono::seconds(curtime.secsTo(nextStartTime));
2092 auto sched_sleep = std::max(std::chrono::milliseconds(curtime.msecsTo(nextWakeTime)), 0ms);
2094 sched_sleep = std::min(sched_sleep, 15000ms);
2096 int const kSleepCheck = 300;
2097 bool checkSlaves = curtime >= nextSleepCheck;
2101 if ((secs_to_next > -60s && secs_to_next < schedRunTime) ||
2102 (!haveRequests && !checkSlaves))
2104 if (sched_sleep > 0ms)
2106 LOG(VB_SCHEDULE, LOG_INFO,
2107 QString(
"sleeping for %1 ms "
2108 "(s2n: %2 sr: %3 qr: %4 cs: %5)")
2109 .arg(sched_sleep.count()).arg(secs_to_next.count()).arg(schedRunTime.count())
2110 .arg(haveRequests).arg(checkSlaves));
2132 wakeThreshold = std::max(wakeThreshold, prerollseconds + 120s);
2134 QElapsedTimer
t;
t.start();
2137 statuschanged =
true;
2140 auto elapsed = std::chrono::ceil<std::chrono::seconds>(std::chrono::milliseconds(
t.elapsed()));
2141 schedRunTime = std::max(elapsed + elapsed/2 + 2s, schedRunTime);
2162 checkSlaves =
false;
2172 nextWakeTime = nextSleepCheck;
2176 for ( ; startIter !=
m_recList.end(); ++startIter)
2178 if ((*startIter)->GetRecordingStatus() !=
2179 (*startIter)->m_oldrecstatus)
2187 for (
auto it = startIter; it !=
m_recList.end(); ++it)
2189 auto secsleft = std::chrono::seconds(curtime.secsTo((*it)->GetRecordingStartTime()));
2190 auto timeBeforePreroll = secsleft - prerollseconds;
2191 if (timeBeforePreroll <= wakeThreshold)
2196 if (timeBeforePreroll > 0s)
2198 std::chrono::seconds waitpending;
2199 if (timeBeforePreroll > 120s)
2200 waitpending = timeBeforePreroll -120s;
2202 waitpending = std::min(timeBeforePreroll, 30s);
2216 for (
auto it = startIter; it !=
m_recList.end() && !done; ++it)
2219 **it, statuschanged, nextStartTime, nextWakeTime,
2243 if (idleSince.isValid())
2248 else if (idleSince.addSecs((
idleTimeoutSecs - 30s).count()) <= curtime)
2254 statuschanged =
false;
2261 const QString &title,
const QString &subtitle,
2262 const QString &descrip,
2263 const QString &programid)
2266 QString filterClause;
2269 if (!title.isEmpty())
2271 filterClause +=
"AND p.title = :TITLE ";
2272 bindings[
":TITLE"] = title;
2276 if (programid !=
"**any**")
2278 filterClause +=
"AND (0 ";
2279 if (!subtitle.isEmpty())
2282 filterClause +=
"OR p.subtitle = :SUBTITLE1 "
2283 "OR p.description = :SUBTITLE2 ";
2284 bindings[
":SUBTITLE1"] = subtitle;
2285 bindings[
":SUBTITLE2"] = subtitle;
2287 if (!descrip.isEmpty())
2290 filterClause +=
"OR p.description = :DESCRIP1 "
2291 "OR p.subtitle = :DESCRIP2 ";
2292 bindings[
":DESCRIP1"] = descrip;
2293 bindings[
":DESCRIP2"] = descrip;
2295 if (!programid.isEmpty())
2297 filterClause +=
"OR p.programid = :PROGRAMID ";
2298 bindings[
":PROGRAMID"] = programid;
2300 filterClause +=
") ";
2303 query.
prepare(QString(
"UPDATE recordmatch rm "
2305 " ON rm.recordid = r.recordid "
2306 "INNER JOIN program p "
2307 " ON rm.chanid = p.chanid "
2308 " AND rm.starttime = p.starttime "
2309 " AND rm.manualid = p.manualid "
2310 "SET oldrecduplicate = -1 "
2311 "WHERE p.generic = 0 "
2312 " AND r.type NOT IN (%2, %3, %4) ")
2318 MSqlBindings::const_iterator it;
2319 for (it = bindings.cbegin(); it != bindings.cend(); ++it)
2324 if (findid && programid !=
"**any**")
2326 query.
prepare(
"UPDATE recordmatch rm "
2327 "SET oldrecduplicate = -1 "
2328 "WHERE rm.recordid = :RECORDID "
2329 " AND rm.findid = :FINDID");
2343 auto fillstart = nowAsDuration<std::chrono::microseconds>();
2345 bool deleteFuture =
false;
2346 bool runCheck =
false;
2352 if (!request.empty())
2354 tokens = request[0].split(
' ', Qt::SkipEmptyParts);
2357 if (request.empty() || tokens.empty())
2359 LOG(VB_GENERAL, LOG_ERR,
"Empty Reschedule request received");
2363 LOG(VB_GENERAL, LOG_INFO, QString(
"Reschedule requested for %1")
2364 .arg(request.join(
" | ")));
2366 if (tokens[0] ==
"MATCH")
2368 if (tokens.size() < 5)
2370 LOG(VB_GENERAL, LOG_ERR,
2371 QString(
"Invalid RescheduleMatch request received (%1)")
2376 uint recordid = tokens[1].toUInt();
2377 uint sourceid = tokens[2].toUInt();
2378 uint mplexid = tokens[3].toUInt();
2380 deleteFuture =
true;
2388 else if (tokens[0] ==
"CHECK")
2390 if (tokens.size() < 4 || request.size() < 5)
2392 LOG(VB_GENERAL, LOG_ERR,
2393 QString(
"Invalid RescheduleCheck request received (%1)")
2398 uint recordid = tokens[2].toUInt();
2399 uint findid = tokens[3].toUInt();
2400 const QString& title = request[1];
2401 const QString& subtitle = request[2];
2402 const QString& descrip = request[3];
2403 const QString& programid = request[4];
2412 else if (tokens[0] !=
"PLACE")
2414 LOG(VB_GENERAL, LOG_ERR,
2415 QString(
"Unknown Reschedule request received (%1)")
2425 query.
prepare(
"DELETE oldrecorded FROM oldrecorded "
2426 "LEFT JOIN recordmatch ON "
2427 " recordmatch.chanid = oldrecorded.chanid AND "
2428 " recordmatch.starttime = oldrecorded.starttime "
2429 "WHERE oldrecorded.future > 0 AND "
2430 " recordmatch.recordid IS NULL");
2435 auto fillend = nowAsDuration<std::chrono::microseconds>();
2436 auto matchTime = fillend - fillstart;
2438 LOG(VB_SCHEDULE, LOG_INFO,
"CreateTempTables...");
2441 fillstart = nowAsDuration<std::chrono::microseconds>();
2444 LOG(VB_SCHEDULE, LOG_INFO,
"UpdateDuplicates...");
2447 fillend = nowAsDuration<std::chrono::microseconds>();
2448 auto checkTime = fillend - fillstart;
2450 fillstart = nowAsDuration<std::chrono::microseconds>();
2452 fillend = nowAsDuration<std::chrono::microseconds>();
2453 auto placeTime = fillend - fillstart;
2455 LOG(VB_SCHEDULE, LOG_INFO,
"DeleteTempTables...");
2465 LOG(VB_GENERAL, LOG_INFO,
"Reschedule interrupted, will retry");
2470 msg = QString(
"Scheduled %1 items in %2 "
2471 "= %3 match + %4 check + %5 place")
2473 .arg(duration_cast<floatsecs>(matchTime + checkTime + placeTime).count(), 0,
'f', 1)
2474 .arg(duration_cast<floatsecs>(matchTime).count(), 0,
'f', 2)
2475 .arg(duration_cast<floatsecs>(checkTime).count(), 0,
'f', 2)
2476 .arg(duration_cast<floatsecs>(placeTime).count(), 0,
'f', 2);
2477 LOG(VB_GENERAL, LOG_INFO, msg);
2482 if (
p->GetRecordingStatus() !=
p->m_oldrecstatus)
2485 p->AddHistory(
false,
false,
false);
2489 p->AddHistory(
false,
false,
false);
2491 p->AddHistory(
false,
false,
true);
2493 else if (
p->m_future)
2499 p->m_future =
false;
2508 std::chrono::seconds prerollseconds,
2511 bool blockShutdown =
true;
2516 QString startupParam =
"user";
2520 for ( ; firstRunIter !=
m_recList.end(); ++firstRunIter)
2531 ((std::chrono::seconds(curtime.secsTo((*firstRunIter)->GetRecordingStartTime())) -
2534 LOG(VB_GENERAL, LOG_INFO,
LOC +
"AUTO-Startup assumed");
2535 startupParam =
"auto";
2539 blockShutdown =
false;
2543 LOG(VB_GENERAL, LOG_INFO,
LOC +
"Seem to be woken up by USER");
2555 return blockShutdown;
2561 static constexpr std::array<const std::chrono::seconds,4> kSysEventSecs = { 120s, 90s, 60s, 30s };
2565 auto secsleft = std::chrono::seconds(curtime.secsTo(nextrectime));
2575 bool pendingEventSent =
false;
2576 for (
size_t i = 0; i < kSysEventSecs.size(); i++)
2578 auto pending_secs = std::max((secsleft - prerollseconds), 0s);
2579 if ((pending_secs <= kSysEventSecs[i]) &&
2582 if (!pendingEventSent)
2585 QString(
"REC_PENDING SECS %1").arg(pending_secs.count()), &ri);
2589 pendingEventSent =
true;
2595 for (
size_t i = 0; i < kSysEventSecs.size(); i++)
2603 keys.insert(rec->MakeUniqueKey());
2604 keys.insert(
"something");
2607 QSet<QString>::iterator sit =
m_sysEvents[i].begin();
2610 if (!keys.contains(*sit))
2621 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
2622 QString(
"Slave Backend %1 is being awakened to record: %2")
2629 ((secsleft - prerollseconds) < 210s) &&
2633 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
2634 QString(
"Slave Backend %1 not available yet, "
2635 "trying to wake it up again.")
2642 ((secsleft - prerollseconds) < 150s) &&
2645 LOG(VB_GENERAL, LOG_WARNING,
LOC +
2646 QString(
"Slave Backend %1 has NOT come "
2647 "back from sleep yet in 150 seconds. Setting "
2648 "slave status to unknown and attempting "
2649 "to reschedule around its tuners.")
2652 for (
auto * enc : std::as_const(*
m_tvList))
2664 QDateTime &nextStartTime, QDateTime &nextWakeTime,
2665 std::chrono::seconds prerollseconds)
2672 std::chrono::seconds origprerollseconds = prerollseconds;
2679 auto nextwake = std::chrono::seconds(nextWakeTime.secsTo(nextrectime));
2680 if (nextwake - prerollseconds > 5min)
2682 nextStartTime = std::min(nextStartTime, nextrectime);
2686 if (curtime < nextrectime)
2687 nextWakeTime = std::min(nextWakeTime, nextrectime);
2693 auto secsleft = std::chrono::seconds(curtime.secsTo(nextrectime));
2698 if (secsleft - prerollseconds > 1min)
2700 nextStartTime = std::min(nextStartTime, nextrectime.addSecs(-30));
2701 nextWakeTime = std::min(nextWakeTime,
2702 nextrectime.addSecs(-prerollseconds.count() - 60));
2715 if (secsleft - prerollseconds > 35s)
2717 nextStartTime = std::min(nextStartTime, nextrectime.addSecs(-30));
2718 nextWakeTime = std::min(nextWakeTime,
2719 nextrectime.addSecs(-prerollseconds.count() - 35));
2728 QString msg = QString(
"Invalid cardid [%1] for %2")
2730 LOG(VB_GENERAL, LOG_ERR,
LOC + msg);
2734 statuschanged =
true;
2742 QString msg = QString(
"SUPPRESSED recording \"%1\" on channel: "
2743 "%2 on cardid: [%3], sourceid %4. Tuner "
2744 "is locked by an external application.")
2749 LOG(VB_GENERAL, LOG_NOTICE, msg);
2753 statuschanged =
true;
2764 if (prerollseconds > 0s)
2772 if (isBusyRecording)
2775 nextWakeTime = std::min(nextWakeTime, curtime.addSecs(5));
2776 prerollseconds = 0s;
2780 if (secsleft - prerollseconds > 30s)
2782 nextStartTime = std::min(nextStartTime, nextrectime.addSecs(-30));
2783 nextWakeTime = std::min(nextWakeTime,
2784 nextrectime.addSecs(-prerollseconds.count() - 30));
2792 LOG(VB_SCHEDULE, LOG_WARNING,
2793 QString(
"WARNING: Slave Backend %1 has NOT come "
2794 "back from sleep yet. Recording can "
2795 "not begin yet for: %2")
2801 LOG(VB_SCHEDULE, LOG_WARNING,
2802 QString(
"WARNING: Slave Backend %1 has NOT come "
2803 "back from sleep yet. Setting slave "
2804 "status to unknown and attempting "
2805 "to reschedule around its tuners.")
2808 for (
auto * enc : std::as_const(*
m_tvList))
2817 nextStartTime = std::min(nextStartTime, nextrectime);
2818 nextWakeTime = std::min(nextWakeTime, curtime.addSecs(1));
2825 QString recording_dir;
2844 MythEvent me(QString(
"ADD_CHILD_INPUT %1")
2847 nextWakeTime = std::min(nextWakeTime, curtime.addSecs(1));
2857 nexttv->
RecordPending(&tempri, std::max(secsleft, 0s),
false);
2863 if (secsleft - prerollseconds > 0s)
2865 nextStartTime = std::min(nextStartTime, nextrectime);
2866 nextWakeTime = std::min(nextWakeTime,
2867 nextrectime.addSecs(-prerollseconds.count()));
2872#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
2873 recstartts = QDateTime(
2875 QTime(recstartts.time().hour(), recstartts.time().minute()), Qt::UTC);
2877 recstartts = QDateTime(
2879 QTime(recstartts.time().hour(), recstartts.time().minute()),
2880 QTimeZone(QTimeZone::UTC));
2885 QString details = QString(
"%1: channel %2 on cardid [%3], sourceid %4")
2912 statuschanged =
true;
2925 bool doSchedAfterStart =
2935 msg = QString(
"Started recording");
2937 msg = QString(
"Tuning recording");
2939 msg = QString(
"Canceled recording (%1)")
2941 LOG(VB_GENERAL, LOG_INFO, QString(
"%1: %2").arg(msg, details));
2949 MythEvent me(QString(
"FORCE_DELETE_RECORDING %1 %2")
2957 std::chrono::seconds prerollseconds)
2962 LOG(VB_SCHEDULE, LOG_DEBUG,
2963 QString(
"Assigning input for %1/%2/\"%3\"")
2974 for (
uint i = 0; !bestid && i < inputs.size(); ++i)
2976 uint inputid = inputs[i];
2984 auto recstarttime = std::chrono::seconds(now.secsTo(
p->GetRecordingStartTime()));
2985 if (recstarttime > prerollseconds + 60s)
2987 if (
p->GetInputID() != inputid)
3004 LOG(VB_SCHEDULE, LOG_DEBUG,
3005 QString(
"Input %1 has a pending recording").arg(inputid));
3014 LOG(VB_SCHEDULE, LOG_DEBUG,
3015 QString(
"Input %1 is recording").arg(inputid));
3020 LOG(VB_SCHEDULE, LOG_DEBUG,
3021 QString(
"Input %1 is recording but will be free")
3030 LOG(VB_SCHEDULE, LOG_DEBUG,
3031 QString(
"Input %1 is recording but has to stop")
3037 LOG(VB_SCHEDULE, LOG_DEBUG,
3038 QString(
"Input %1 is recording but could be free")
3050 bool isbusy = rctv->
IsBusy(&busy_info, -1s);
3056 LOG(VB_SCHEDULE, LOG_DEBUG,
3057 QString(
"Input %1 is free").arg(inputid));
3063 LOG(VB_SCHEDULE, LOG_DEBUG,
3064 QString(
"Input %1 is on livetv but has to stop")
3075 LOG(VB_SCHEDULE, LOG_INFO,
3076 QString(
"Assigned input %1 for %2/%3/\"%4\"")
3084 LOG(VB_SCHEDULE, LOG_WARNING,
3085 QString(
"Failed to assign input for %1/%2/\"%3\"")
3091 return bestid != 0U;
3101 bool &blockShutdown, QDateTime &idleSince,
3102 std::chrono::seconds prerollseconds,
3108 uint logmask = VB_IDLE;
3109 int now = QTime::currentTime().msecsSinceStartOfDay();
3110 int tm = std::chrono::milliseconds(now) / 15min;
3113 logmask = VB_GENERAL;
3132 LOG(VB_GENERAL, LOG_NOTICE,
"Client is connected, removing startup block on shutdown");
3133 blockShutdown =
false;
3144 bool recording =
false;
3147 QMap<int, EncoderLink *>::const_iterator it;
3151 if ((*it)->IsBusy())
3165 if (!blocking && !recording && !activeJobs && !delay)
3172 if (idleSince.isValid())
3174 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN -1"));
3177 idleSince = QDateTime();
3182 if (statuschanged || !idleSince.isValid())
3184 bool wasValid = idleSince.isValid();
3186 idleSince = curtime;
3189 for ( ; idleIter !=
m_recList.end(); ++idleIter)
3191 if ((*idleIter)->GetRecordingStatus() ==
3193 (*idleIter)->GetRecordingStatus() ==
3200 auto recstarttime = std::chrono::seconds(curtime.secsTo((*idleIter)->GetRecordingStartTime()));
3203 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3204 "a recording is due to "
3206 idleSince = QDateTime();
3217 if (guideRunTime.isValid() &&
3221 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3222 "mythfilldatabase is due to "
3224 idleSince = QDateTime();
3229 if (idleSince.isValid())
3232 if (wasValid && !idleSince.isValid())
3234 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN -1"));
3239 if (idleSince.isValid())
3251 LOG(VB_GENERAL, LOG_WARNING,
3252 "Waited more than 60"
3253 " seconds for shutdown to complete"
3254 " - resetting idle time");
3255 idleSince = QDateTime();
3261 blockShutdown, logmask))
3267 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN -1"));
3273 auto itime = std::chrono::seconds(idleSince.secsTo(curtime));
3277 msg = QString(
"I\'m idle now... shutdown will "
3278 "occur in %1 seconds.")
3280 LOG(VB_GENERAL, LOG_NOTICE, msg);
3281 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN %1")
3288 msg = QString(
"%1 secs left to system shutdown!").arg(remain);
3289 LOG(logmask, LOG_NOTICE, msg);
3290 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN %1").arg(remain));
3299 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3300 "of an active encoder");
3302 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3303 "of a connected client");
3306 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3310 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3311 "of delay request from external application");
3314 if (idleSince.isValid())
3316 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN -1"));
3319 idleSince = QDateTime();
3326 QDateTime &idleSince,
3327 bool &blockShutdown,
uint logmask)
3329 bool retval =
false;
3339 LOG(logmask, LOG_INFO,
3340 "CheckShutdownServer returned - OK to shutdown");
3344 LOG(logmask, LOG_NOTICE,
3345 "CheckShutdownServer returned - Not OK to shutdown");
3347 idleSince = QDateTime();
3350 LOG(logmask, LOG_NOTICE,
3351 "CheckShutdownServer returned - Not OK to shutdown, "
3359 idleSince = QDateTime();
3364 m_noAutoShutdown =
true;
3368 LOG(VB_GENERAL, LOG_NOTICE,
3369 "CheckShutdownServer returned - Not OK");
3372 LOG(VB_GENERAL, LOG_NOTICE, QString(
3373 "CheckShutdownServer returned - Error %1").arg(state));
3386 QDateTime &idleSince)
3391 for ( ; recIter !=
m_recList.end(); ++recIter)
3399 QDateTime restarttime;
3404 .addSecs(-prerollseconds.count());
3412 && guideRefreshTime.isValid()
3414 && (restarttime.isNull() || guideRefreshTime < restarttime))
3415 restarttime = guideRefreshTime;
3417 if (restarttime.isValid())
3421 restarttime = restarttime.addSecs((-1LL) * add);
3424 "hh:mm yyyy-MM-dd");
3426 "echo \'Wakeuptime would "
3427 "be $time if command "
3430 if (setwakeup_cmd.isEmpty())
3432 LOG(VB_GENERAL, LOG_NOTICE,
3433 "SetWakeuptimeCommand is empty, shutdown aborted");
3434 idleSince = QDateTime();
3438 if (wakeup_timeformat ==
"time_t")
3441 setwakeup_cmd.replace(
"$time",
3442 time_ts.setNum(restarttime.toSecsSinceEpoch())
3447 setwakeup_cmd.replace(
3448 "$time", restarttime.toLocalTime().toString(wakeup_timeformat));
3451 LOG(VB_GENERAL, LOG_NOTICE,
3452 QString(
"Running the command to set the next "
3453 "scheduled wakeup time :-\n\t\t\t\t") + setwakeup_cmd);
3458 LOG(VB_GENERAL, LOG_ERR,
3459 "SetWakeuptimeCommand failed, shutdown aborted");
3460 idleSince = QDateTime();
3475 "sudo /sbin/halt -p");
3477 if (!halt_cmd.isEmpty())
3482 LOG(VB_GENERAL, LOG_NOTICE,
3483 QString(
"Running the command to shutdown "
3484 "this computer :-\n\t\t\t\t") + halt_cmd);
3491 LOG(VB_GENERAL, LOG_ERR,
"ServerHaltCommand failed, shutdown aborted");
3496 idleSince = QDateTime();
3502 std::chrono::seconds prerollseconds = 0s;
3503 std::chrono::seconds secsleft = 0s;
3507 bool someSlavesCanSleep =
false;
3508 for (
auto * enc : std::as_const(*
m_tvList))
3510 if (enc->CanSleep())
3511 someSlavesCanSleep =
true;
3514 if (!someSlavesCanSleep)
3517 LOG(VB_SCHEDULE, LOG_INFO,
3518 "Scheduler, Checking for slaves that can be shut down");
3520 auto sleepThreshold =
3523 LOG(VB_SCHEDULE, LOG_DEBUG,
3524 QString(
" Getting list of slaves that will be active in the "
3525 "next %1 minutes.") .arg(duration_cast<std::chrono::minutes>(sleepThreshold).count()));
3527 LOG(VB_SCHEDULE, LOG_DEBUG,
"Checking scheduler's reclist");
3529 QStringList SlavesInUse;
3539 auto recstarttime = std::chrono::seconds(curtime.secsTo(pginfo->GetRecordingStartTime()));
3540 secsleft = recstarttime - prerollseconds;
3541 if (secsleft > sleepThreshold)
3546 EncoderLink *enc = (*m_tvList)[pginfo->GetInputID()];
3553 LOG(VB_SCHEDULE, LOG_DEBUG,
3554 QString(
" Slave %1 will be in use in %2 minutes")
3556 .arg(duration_cast<std::chrono::minutes>(secsleft).count()));
3560 LOG(VB_SCHEDULE, LOG_DEBUG,
3561 QString(
" Slave %1 is in use currently "
3570 LOG(VB_SCHEDULE, LOG_DEBUG,
" Checking inuseprograms table:");
3573 query.
prepare(
"SELECT DISTINCT hostname, recusage FROM inuseprograms "
3574 "WHERE lastupdatetime > :ONEHOURAGO ;");
3575 query.
bindValue(
":ONEHOURAGO", oneHourAgo);
3578 while(query.
next()) {
3579 SlavesInUse << query.
value(0).toString();
3580 LOG(VB_SCHEDULE, LOG_DEBUG,
3581 QString(
" Slave %1 is marked as in use by a %2")
3582 .arg(query.
value(0).toString(),
3583 query.
value(1).toString()));
3587 LOG(VB_SCHEDULE, LOG_DEBUG, QString(
" Shutting down slaves which will "
3588 "be inactive for the next %1 minutes and can be put to sleep.")
3589 .arg(sleepThreshold.count() / 60));
3591 for (
auto * enc : std::as_const(*
m_tvList))
3593 if ((!enc->IsLocal()) &&
3595 (!SlavesInUse.contains(enc->GetHostName())) &&
3596 (!enc->IsFallingAsleep()))
3598 QString sleepCommand =
3600 enc->GetHostName());
3601 QString wakeUpCommand =
3603 enc->GetHostName());
3605 if (!sleepCommand.isEmpty() && !wakeUpCommand.isEmpty())
3607 QString thisHost = enc->GetHostName();
3609 LOG(VB_SCHEDULE, LOG_DEBUG,
3610 QString(
" Commanding %1 to go to sleep.")
3613 if (enc->GoToSleep())
3615 for (
auto * slv : std::as_const(*
m_tvList))
3617 if (slv->GetHostName() == thisHost)
3619 LOG(VB_SCHEDULE, LOG_DEBUG,
3620 QString(
" Marking card %1 on slave %2 "
3621 "as falling asleep.")
3622 .arg(slv->GetInputID())
3623 .arg(slv->GetHostName()));
3630 LOG(VB_GENERAL, LOG_ERR,
LOC +
3631 QString(
"Unable to shutdown %1 slave backend, setting "
3632 "sleep status to undefined.").arg(thisHost));
3633 for (
auto * slv : std::as_const(*
m_tvList))
3635 if (slv->GetHostName() == thisHost)
3648 LOG(VB_GENERAL, LOG_NOTICE,
3649 QString(
"Tried to Wake Up %1, but this is the "
3650 "master backend and it is not asleep.")
3651 .arg(slaveHostname));
3658 if (wakeUpCommand.isEmpty()) {
3659 LOG(VB_GENERAL, LOG_NOTICE,
3660 QString(
"Trying to Wake Up %1, but this slave "
3661 "does not have a WakeUpCommand set.").arg(slaveHostname));
3663 for (
auto * enc : std::as_const(*
m_tvList))
3665 if (enc->GetHostName() == slaveHostname)
3673 for (
auto * enc : std::as_const(*
m_tvList))
3675 if (setWakingStatus && (enc->GetHostName() == slaveHostname))
3677 enc->SetLastWakeTime(curtime);
3682 LOG(VB_SCHEDULE, LOG_NOTICE, QString(
"Executing '%1' to wake up slave.")
3683 .arg(wakeUpCommand));
3695 QStringList SlavesThatCanWake;
3697 for (
auto * enc : std::as_const(*
m_tvList))
3702 thisSlave = enc->GetHostName();
3706 (!SlavesThatCanWake.contains(thisSlave)))
3707 SlavesThatCanWake << thisSlave;
3711 for (; slave < SlavesThatCanWake.count(); slave++)
3713 thisSlave = SlavesThatCanWake[slave];
3714 LOG(VB_SCHEDULE, LOG_NOTICE,
3715 QString(
"Scheduler, Sending wakeup command to slave: %1")
3725 query.
prepare(QString(
"SELECT type,title,subtitle,description,"
3726 "station,startdate,starttime,"
3727 "enddate,endtime,season,episode,category,"
3728 "seriesid,programid,inetref,last_record "
3731 if (!query.
exec() || query.
size() != 1)
3741 QString title = query.
value(1).toString();
3742 QString subtitle = query.
value(2).toString();
3743 QString description = query.
value(3).toString();
3744 QString station = query.
value(4).toString();
3745#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
3746 QDateTime startdt = QDateTime(query.
value(5).toDate(),
3747 query.
value(6).toTime(), Qt::UTC);
3748 int duration = startdt.secsTo(
3749 QDateTime(query.
value(7).toDate(),
3750 query.
value(8).toTime(), Qt::UTC));
3752 QDateTime startdt = QDateTime(query.
value(5).toDate(),
3753 query.
value(6).toTime(),
3754 QTimeZone(QTimeZone::UTC));
3755 int duration = startdt.secsTo(
3756 QDateTime(query.
value(7).toDate(),
3757 query.
value(8).toTime(),
3758 QTimeZone(QTimeZone::UTC)));
3761 int season = query.
value(9).toInt();
3762 int episode = query.
value(10).toInt();
3763 QString category = query.
value(11).toString();
3764 QString seriesid = query.
value(12).toString();
3765 QString programid = query.
value(13).toString();
3766 QString inetref = query.
value(14).toString();
3770 QDate originalairdate = QDate(query.
value(15).toDate());
3772 if (description.isEmpty())
3773 description = startdt.toLocalTime().toString();
3775 query.
prepare(
"SELECT chanid from channel "
3776 "WHERE deleted IS NULL AND callsign = :STATION");
3784 std::vector<unsigned int> chanidlist;
3785 while (query.
next())
3786 chanidlist.push_back(query.
value(0).toUInt());
3790 bool weekday =
false;
3792 QDateTime lstartdt = startdt.toLocalTime();
3807 weekday = (lstartdt.date().dayOfWeek() < 6);
3808 daysoff = lstartdt.date().daysTo(
3810#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
3811 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3812 lstartdt.time(), Qt::LocalTime).toUTC();
3814 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3816 QTimeZone(QTimeZone::LocalTime)
3824 daysoff = lstartdt.date().daysTo(
3826 daysoff = (daysoff + 6) / 7 * 7;
3827#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
3828 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3829 lstartdt.time(), Qt::LocalTime).toUTC();
3831 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3833 QTimeZone(QTimeZone::LocalTime)
3838 LOG(VB_GENERAL, LOG_ERR,
3839 QString(
"Invalid rectype for manual recordid %1").arg(recordid));
3845 for (
uint id : chanidlist)
3847 if (weekday && startdt.toLocalTime().date().dayOfWeek() >= 6)
3850 query.
prepare(
"REPLACE INTO program (chanid, starttime, endtime,"
3851 " title, subtitle, description, manualid,"
3852 " season, episode, category, seriesid, programid,"
3853 " inetref, originalairdate, generic) "
3854 "VALUES (:CHANID, :STARTTIME, :ENDTIME, :TITLE,"
3855 " :SUBTITLE, :DESCRIPTION, :RECORDID, "
3856 " :SEASON, :EPISODE, :CATEGORY, :SERIESID,"
3857 " :PROGRAMID, :INETREF, :ORIGINALAIRDATE, 1)");
3860 query.
bindValue(
":ENDTIME", startdt.addSecs(duration));
3863 query.
bindValue(
":DESCRIPTION", description);
3868 query.
bindValue(
":PROGRAMID", programid);
3870 query.
bindValue(
":ORIGINALAIRDATE", originalairdate);
3879 daysoff += skipdays;
3880#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
3881 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3882 lstartdt.time(), Qt::LocalTime).toUTC();
3884 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3886 QTimeZone(QTimeZone::LocalTime)
3900 query = QString(
"SELECT recordid,search,subtitle,description "
3901 "FROM %1 WHERE search <> %2 AND "
3902 "(recordid = %3 OR %4 = 0) ")
3914 while (result.
next())
3916 QString
prefix = QString(
":NR%1").arg(count);
3917 qphrase = result.
value(3).toString();
3923 LOG(VB_GENERAL, LOG_ERR,
3924 QString(
"Invalid search key in recordid %1")
3925 .arg(result.
value(0).toString()));
3929 QString bindrecid =
prefix +
"RECID";
3930 QString bindphrase =
prefix +
"PHRASE";
3931 QString bindlikephrase1 =
prefix +
"LIKEPHRASE1";
3932 QString bindlikephrase2 =
prefix +
"LIKEPHRASE2";
3933 QString bindlikephrase3 =
prefix +
"LIKEPHRASE3";
3935 bindings[bindrecid] = result.
value(0).toString();
3941 qphrase.remove(
';');
3942 from << result.
value(2).toString();
3943 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
3944 QString(
" AND program.manualid = 0 AND ( %2 )")
3948 bindings[bindlikephrase1] = QString(
"%") + qphrase +
"%";
3950 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
" AND "
3951 "program.manualid = 0 AND "
3952 "program.title LIKE " + bindlikephrase1);
3955 bindings[bindlikephrase1] = QString(
"%") + qphrase +
"%";
3956 bindings[bindlikephrase2] = QString(
"%") + qphrase +
"%";
3957 bindings[bindlikephrase3] = QString(
"%") + qphrase +
"%";
3959 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
3960 " AND program.manualid = 0"
3961 " AND (program.title LIKE " + bindlikephrase1 +
3962 " OR program.subtitle LIKE " + bindlikephrase2 +
3963 " OR program.description LIKE " + bindlikephrase3 +
")");
3966 bindings[bindphrase] = qphrase;
3967 from <<
", people, credits";
3968 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
" AND "
3969 "program.manualid = 0 AND "
3970 "people.name LIKE " + bindphrase +
" AND "
3971 "credits.person = people.person AND "
3972 "program.chanid = credits.chanid AND "
3973 "program.starttime = credits.starttime");
3978 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
3980 QString(
"program.manualid = %1.recordid ")
3984 LOG(VB_GENERAL, LOG_ERR,
3985 QString(
"Unknown RecSearchType (%1) for recordid %2")
3986 .arg(result.
value(1).toInt())
3987 .arg(result.
value(0).toString()));
3988 bindings.remove(bindrecid);
3995 if (recordid == 0 || from.count() == 0)
3997 QString recidmatch =
"";
3999 recidmatch =
"RECTABLE.recordid = :NRRECORDID AND ";
4000 QString s1 = recidmatch +
4001 "RECTABLE.type <> :NRTEMPLATE AND "
4002 "RECTABLE.search = :NRST AND "
4003 "program.manualid = 0 AND "
4004 "program.title = RECTABLE.title ";
4006 QString s2 = recidmatch +
4007 "RECTABLE.type <> :NRTEMPLATE AND "
4008 "RECTABLE.search = :NRST AND "
4009 "program.manualid = 0 AND "
4010 "program.seriesid <> '' AND "
4011 "program.seriesid = RECTABLE.seriesid ";
4021 bindings[
":NRRECORDID"] = recordid;
4027" WHEN RECTABLE.type IN (%1, %2, %3) THEN 0 "
4028" WHEN RECTABLE.type IN (%4, %5, %6) THEN -1 "
4029" ELSE (program.generic - 1) "
4035"(CASE RECTABLE.type "
4037" THEN RECTABLE.findid "
4039" THEN to_days(date_sub(convert_tz(program.starttime, 'UTC', 'SYSTEM'), "
4040" interval time_format(RECTABLE.findtime, '%H:%i') hour_minute)) "
4042" THEN floor((to_days(date_sub(convert_tz(program.starttime, 'UTC', "
4043" 'SYSTEM'), interval time_format(RECTABLE.findtime, '%H:%i') "
4044" hour_minute)) - RECTABLE.findday)/7) * 7 + RECTABLE.findday "
4046" THEN RECTABLE.findid "
4055 const QDateTime &maxstarttime)
4059 QString deleteClause;
4060 QString filterClause = QString(
" AND program.endtime > "
4061 "(NOW() - INTERVAL 480 MINUTE)");
4065 deleteClause +=
" AND recordmatch.recordid = :RECORDID";
4066 bindings[
":RECORDID"] = recordid;
4070 deleteClause +=
" AND channel.sourceid = :SOURCEID";
4071 filterClause +=
" AND channel.sourceid = :SOURCEID";
4072 bindings[
":SOURCEID"] = sourceid;
4076 deleteClause +=
" AND channel.mplexid = :MPLEXID";
4077 filterClause +=
" AND channel.mplexid = :MPLEXID";
4078 bindings[
":MPLEXID"] = mplexid;
4080 if (maxstarttime.isValid())
4082 deleteClause +=
" AND recordmatch.starttime <= :MAXSTARTTIME";
4083 filterClause +=
" AND program.starttime <= :MAXSTARTTIME";
4084 bindings[
":MAXSTARTTIME"] = maxstarttime;
4087 query.
prepare(QString(
"DELETE recordmatch FROM recordmatch, channel "
4088 "WHERE recordmatch.chanid = channel.chanid")
4090 MSqlBindings::const_iterator it;
4091 for (it = bindings.cbegin(); it != bindings.cend(); ++it)
4099 bindings.remove(
":RECORDID");
4101 query.
prepare(
"SELECT filterid, clause FROM recordfilter "
4102 "WHERE filterid >= 0 AND filterid < :NUMFILTERS AND "
4103 " TRIM(clause) <> ''");
4110 while (query.
next())
4112 filterClause += QString(
" AND (((RECTABLE.filter & %1) = 0) OR (%2))")
4113 .arg(1 << query.
value(0).toInt()).arg(query.
value(1).toString());
4117 query.
prepare(
"SELECT NULL from record "
4118 "WHERE type = :FINDONE AND findid <= 0;");
4127 QDate epoch(1970, 1, 1);
4130 query.
prepare(
"UPDATE record set findid = :FINDID "
4131 "WHERE type = :FINDONE AND findid <= 0;");
4138 QStringList fromclauses;
4139 QStringList whereclauses;
4145 for (
int clause = 0; clause < fromclauses.count(); ++clause)
4147 LOG(VB_SCHEDULE, LOG_INFO, QString(
"Query %1: %2/%3")
4148 .arg(QString::number(clause), fromclauses[clause],
4149 whereclauses[clause]));
4153 for (
int clause = 0; clause < fromclauses.count(); ++clause)
4155 QString query2 = QString(
4156"REPLACE INTO recordmatch (recordid, chanid, starttime, manualid, "
4157" oldrecduplicate, findid) "
4158"SELECT RECTABLE.recordid, program.chanid, program.starttime, "
4159" IF(search = %1, RECTABLE.recordid, 0), ").arg(
kManualSearch) +
4161"FROM (RECTABLE, program INNER JOIN channel "
4162" ON channel.chanid = program.chanid) ") + fromclauses[clause] + QString(
4163" WHERE ") + whereclauses[clause] +
4164 QString(
" AND channel.deleted IS NULL "
4165 " AND channel.visible > 0 ") +
4166 filterClause + QString(
" AND "
4169" (RECTABLE.type = %1 "
4170" OR RECTABLE.type = %2 "
4171" OR RECTABLE.type = %3 "
4172" OR RECTABLE.type = %4) "
4174" ((RECTABLE.type = %6 "
4175" OR RECTABLE.type = %7 "
4176" OR RECTABLE.type = %8)"
4178" ADDTIME(RECTABLE.startdate, RECTABLE.starttime) = program.starttime "
4180" RECTABLE.station = channel.callsign) "
4192 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- Start DB Query %1...")
4195 auto dbstart = nowAsDuration<std::chrono::microseconds>();
4199 for (it = bindings.cbegin(); it != bindings.cend(); ++it)
4201 if (query2.contains(it.key()))
4205 bool ok = result.
exec();
4206 auto dbend = nowAsDuration<std::chrono::microseconds>();
4207 auto dbTime = dbend - dbstart;
4215 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- %1 results in %2 sec.")
4217 .arg(duration_cast<std::chrono::seconds>(dbTime).count()));
4221 LOG(VB_SCHEDULE, LOG_INFO,
" +-- Done.");
4230 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_record;");
4236 result.
prepare(
"CREATE TEMPORARY TABLE sched_temp_record "
4243 result.
prepare(
"INSERT sched_temp_record SELECT * from record;");
4251 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_recorded;");
4257 result.
prepare(
"CREATE TEMPORARY TABLE sched_temp_recorded "
4264 result.
prepare(
"INSERT sched_temp_recorded SELECT * from recorded;");
4278 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_record;");
4283 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_recorded;");
4291 if (schedTmpRecord ==
"record")
4292 schedTmpRecord =
"sched_temp_record";
4294 QString rmquery = QString(
4295"UPDATE recordmatch "
4296" INNER JOIN RECTABLE ON (recordmatch.recordid = RECTABLE.recordid) "
4297" INNER JOIN program p ON (recordmatch.chanid = p.chanid AND "
4298" recordmatch.starttime = p.starttime AND "
4299" recordmatch.manualid = p.manualid) "
4300" LEFT JOIN oldrecorded ON "
4302" RECTABLE.dupmethod > 1 AND "
4303" oldrecorded.duplicate <> 0 AND "
4304" p.title = oldrecorded.title AND "
4308" (p.programid <> '' "
4309" AND p.programid = oldrecorded.programid) "
4313" (p.programid = '' OR oldrecorded.programid = '' OR "
4314" LEFT(p.programid, LOCATE('/', p.programid)) <> "
4315" LEFT(oldrecorded.programid, LOCATE('/', oldrecorded.programid))) " :
4316" (p.programid = '' OR oldrecorded.programid = '') " )
4319" (((RECTABLE.dupmethod & 0x02) = 0) OR (p.subtitle <> '' "
4320" AND p.subtitle = oldrecorded.subtitle)) "
4322" (((RECTABLE.dupmethod & 0x04) = 0) OR (p.description <> '' "
4323" AND p.description = oldrecorded.description)) "
4325" (((RECTABLE.dupmethod & 0x08) = 0) OR "
4326" (p.subtitle <> '' AND "
4327" (p.subtitle = oldrecorded.subtitle OR "
4328" (oldrecorded.subtitle = '' AND "
4329" p.subtitle = oldrecorded.description))) OR "
4330" (p.subtitle = '' AND p.description <> '' AND "
4331" (p.description = oldrecorded.subtitle OR "
4332" (oldrecorded.subtitle = '' AND "
4333" p.description = oldrecorded.description)))) "
4337" LEFT JOIN sched_temp_recorded recorded ON "
4339" RECTABLE.dupmethod > 1 AND "
4340" recorded.duplicate <> 0 AND "
4341" p.title = recorded.title AND "
4342" p.generic = 0 AND "
4343" recorded.recgroup NOT IN ('LiveTV','Deleted') "
4346" (p.programid <> '' "
4347" AND p.programid = recorded.programid) "
4351" (p.programid = '' OR recorded.programid = '' OR "
4352" LEFT(p.programid, LOCATE('/', p.programid)) <> "
4353" LEFT(recorded.programid, LOCATE('/', recorded.programid))) " :
4354" (p.programid = '' OR recorded.programid = '') ")
4357" (((RECTABLE.dupmethod & 0x02) = 0) OR (p.subtitle <> '' "
4358" AND p.subtitle = recorded.subtitle)) "
4360" (((RECTABLE.dupmethod & 0x04) = 0) OR (p.description <> '' "
4361" AND p.description = recorded.description)) "
4363" (((RECTABLE.dupmethod & 0x08) = 0) OR "
4364" (p.subtitle <> '' AND "
4365" (p.subtitle = recorded.subtitle OR "
4366" (recorded.subtitle = '' AND "
4367" p.subtitle = recorded.description))) OR "
4368" (p.subtitle = '' AND p.description <> '' AND "
4369" (p.description = recorded.subtitle OR "
4370" (recorded.subtitle = '' AND "
4371" p.description = recorded.description)))) "
4375" LEFT JOIN oldfind ON "
4376" (oldfind.recordid = recordmatch.recordid AND "
4377" oldfind.findid = recordmatch.findid) "
4378" SET oldrecduplicate = (oldrecorded.endtime IS NOT NULL), "
4379" recduplicate = (recorded.endtime IS NOT NULL), "
4380" findduplicate = (oldfind.findid IS NOT NULL), "
4381" oldrecstatus = oldrecorded.recstatus "
4382" WHERE p.endtime >= (NOW() - INTERVAL 480 MINUTE) "
4383" AND oldrecduplicate = -1 "
4385 rmquery.replace(
"RECTABLE", schedTmpRecord);
4399 if (schedTmpRecord ==
"record")
4400 schedTmpRecord =
"sched_temp_record";
4404 QMap<int, bool> cardMap;
4405 for (
auto * enc : std::as_const(*
m_tvList))
4407 if (enc->IsConnected() || enc->IsAsleep())
4408 cardMap[enc->GetInputID()] =
true;
4411 QMap<int, bool> tooManyMap;
4412 bool checkTooMany =
false;
4416 rlist.
prepare(QString(
"SELECT recordid, title, maxepisodes, maxnewest "
4417 "FROM %1").arg(schedTmpRecord));
4425 while (rlist.
next())
4427 int recid = rlist.
value(0).toInt();
4429 int maxEpisodes = rlist.
value(2).toInt();
4430 int maxNewest = rlist.
value(3).toInt();
4432 tooManyMap[recid] =
false;
4435 if (maxEpisodes && !maxNewest)
4439 epicnt.
prepare(
"SELECT DISTINCT chanid, progstart, progend "
4441 "WHERE recordid = :RECID AND preserve = 0 "
4442 "AND recgroup NOT IN ('LiveTV','Deleted');");
4447 if (epicnt.
size() >= maxEpisodes - 1)
4450 if (epicnt.
size() >= maxEpisodes)
4452 tooManyMap[recid] =
true;
4453 checkTooMany =
true;
4469 QString pwrpri =
"channel.recpriority + capturecard.recpriority";
4473 pwrpri += QString(
" + "
4474 "IF(capturecard.cardid = RECTABLE.prefinput, 1, 0) * %1")
4480 pwrpri += QString(
" + IF(program.hdtv > 0 OR "
4481 "FIND_IN_SET('HDTV', program.videoprop) > 0, 1, 0) * %1")
4487 pwrpri += QString(
" + "
4488 "IF(FIND_IN_SET('WIDESCREEN', program.videoprop) > 0, 1, 0) * %1")
4494 pwrpri += QString(
" + "
4495 "IF(FIND_IN_SET('SIGNED', program.subtitletypes) > 0, 1, 0) * %1")
4501 pwrpri += QString(
" + "
4502 "IF(FIND_IN_SET('ONSCREEN', program.subtitletypes) > 0, 1, 0) * %1")
4503 .arg(onscrpriority);
4508 pwrpri += QString(
" + "
4509 "IF(FIND_IN_SET('NORMAL', program.subtitletypes) > 0 OR "
4510 "program.closecaptioned > 0 OR program.subtitled > 0, 1, 0) * %1")
4516 pwrpri += QString(
" + "
4517 "IF(FIND_IN_SET('HARDHEAR', program.subtitletypes) > 0 OR "
4518 "FIND_IN_SET('HARDHEAR', program.audioprop) > 0, 1, 0) * %1")
4524 pwrpri += QString(
" + "
4525 "IF(FIND_IN_SET('VISUALIMPAIR', program.audioprop) > 0, 1, 0) * %1")
4531 result.
prepare(QString(
"SELECT recpriority, selectclause FROM %1;")
4540 while (result.
next())
4542 if (result.
value(0).toBool())
4544 QString sclause = result.
value(1).toString();
4546 sclause.remove(
';');
4547 pwrpri += QString(
" + IF(%1, 1, 0) * %2")
4548 .arg(sclause).arg(result.
value(0).toInt());
4551 pwrpri += QString(
" AS powerpriority ");
4553 pwrpri.replace(
"program.",
"p.");
4554 pwrpri.replace(
"channel.",
"c.");
4555 QString query = QString(
4557 " c.chanid, c.sourceid, p.starttime, "
4558 " p.endtime, p.title, p.subtitle, "
4559 " p.description, c.channum, c.callsign, "
4560 " c.name, oldrecduplicate, p.category, "
4561 " RECTABLE.recpriority, RECTABLE.dupin, recduplicate, "
4562 " findduplicate, RECTABLE.type, RECTABLE.recordid, "
4563 " p.starttime - INTERVAL RECTABLE.startoffset "
4564 " minute AS recstartts, "
4565 " p.endtime + INTERVAL RECTABLE.endoffset "
4566 " minute AS recendts, "
4567 " p.previouslyshown, "
4568 " RECTABLE.recgroup, RECTABLE.dupmethod, c.commmethod, "
4569 " capturecard.cardid, 0, p.seriesid, "
4570 " p.programid, RECTABLE.inetref, p.category_type, "
4571 " p.airdate, p.stars, p.originalairdate, "
4572 " RECTABLE.inactive, RECTABLE.parentid, recordmatch.findid, "
4573 " RECTABLE.playgroup, oldrecstatus.recstatus, "
4574 " oldrecstatus.reactivate, p.videoprop+0, "
4575 " p.subtitletypes+0, p.audioprop+0, RECTABLE.storagegroup, "
4576 " capturecard.hostname, recordmatch.oldrecstatus, NULL, "
4577 " oldrecstatus.future, capturecard.schedorder, "
4578 " p.syndicatedepisodenumber, p.partnumber, p.parttotal, "
4579 " c.mplexid, capturecard.displayname, "
4580 " p.season, p.episode, p.totalepisodes, ") +
4583 "INNER JOIN RECTABLE ON (recordmatch.recordid = RECTABLE.recordid) "
4584 "INNER JOIN program AS p "
4585 "ON ( recordmatch.chanid = p.chanid AND "
4586 " recordmatch.starttime = p.starttime AND "
4587 " recordmatch.manualid = p.manualid ) "
4588 "INNER JOIN channel AS c "
4589 "ON ( c.chanid = p.chanid ) "
4590 "INNER JOIN capturecard "
4591 "ON ( c.sourceid = capturecard.sourceid AND "
4592 " ( capturecard.schedorder <> 0 OR "
4593 " capturecard.parentid = 0 ) ) "
4594 "LEFT JOIN oldrecorded as oldrecstatus "
4595 "ON ( oldrecstatus.station = c.callsign AND "
4596 " oldrecstatus.starttime = p.starttime AND "
4597 " oldrecstatus.title = p.title ) "
4598 "WHERE p.endtime > (NOW() - INTERVAL 480 MINUTE) "
4599 "ORDER BY RECTABLE.recordid DESC, p.starttime, p.title, c.callsign, "
4601 query.replace(
"RECTABLE", schedTmpRecord);
4603 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- Start DB Query..."));
4605 auto dbstart = nowAsDuration<std::chrono::microseconds>();
4612 auto dbend = nowAsDuration<std::chrono::microseconds>();
4613 auto dbTime = dbend - dbstart;
4615 LOG(VB_SCHEDULE, LOG_INFO,
4616 QString(
" |-- %1 results in %2 sec. Processing...")
4618 .arg(duration_cast<std::chrono::seconds>(dbTime).count()));
4622 while (result.
next())
4628 uint recordid = result.
value(17).toUInt();
4630 QString title = result.
value(4).toString();
4631 QString callsign = result.
value(8).toString();
4641 uint mplexid = result.
value(51).toUInt();
4642 if (mplexid == 32767)
4645 QString inputname = result.
value(52).toString();
4646 if (inputname.isEmpty())
4647 inputname = QString(
"Input %1").arg(result.
value(24).toUInt());
4652 result.
value(5).toString(),
4654 result.
value(6).toString(),
4655 result.
value(53).toInt(),
4656 result.
value(54).toInt(),
4657 result.
value(55).toInt(),
4658 result.
value(48).toString(),
4659 result.
value(11).toString(),
4661 result.
value(0).toUInt(),
4662 result.
value(7).toString(),
4664 result.
value(9).toString(),
4666 result.
value(21).toString(),
4667 result.
value(36).toString(),
4669 result.
value(43).toString(),
4670 result.
value(42).toString(),
4672 result.
value(30).toUInt(),
4673 result.
value(49).toUInt(),
4674 result.
value(50).toUInt(),
4676 result.
value(26).toString(),
4677 result.
value(27).toString(),
4678 result.
value(28).toString(),
4681 result.
value(12).toInt(),
4688 result.
value(31).toFloat(),
4689 (result.
value(32).isNull()) ? QDate() :
4693 result.
value(20).toBool(),
4696 result.
value(38).toBool(),
4699 result.
value(34).toUInt(),
4704 result.
value(1).toUInt(),
4705 result.
value(24).toUInt(),
4707 result.
value(35).toUInt(),
4710 result.
value(40).toUInt(),
4711 result.
value(39).toUInt(),
4712 result.
value(41).toUInt(),
4713 result.
value(46).toBool(),
4714 result.
value(47).toInt(),
4716 result.
value(24).toUInt(),
4719 if (!
p->m_future && !
p->IsReactivated() &&
4723 p->SetRecordingStatus(
p->m_oldrecstatus);
4726 p->SetRecordingPriority2(result.
value(56).toInt());
4735 if (
p->IsSameTitleStartTimeAndChannel(*r))
4737 if (r->m_sgroupId ==
p->m_sgroupId &&
4738 r->GetRecordingEndTime() !=
p->GetRecordingEndTime() &&
4739 (r->GetRecordingRuleID() ==
p->GetRecordingRuleID() ||
4754 tmpList.push_back(
p);
4761 (!cardMap.contains(
p->GetInputID()) || (
p->m_schedOrder == 0)))
4764 if (
p->m_schedOrder == 0 &&
4767 LOG(VB_GENERAL, LOG_WARNING,
LOC +
4768 QString(
"Channel %1, Title %2 %3 cardinput.schedorder = %4, "
4769 "it must be >0 to record from this input.")
4770 .arg(
p->GetChannelName(),
p->GetTitle(),
4771 p->GetScheduledStartTime().toString(),
4772 QString::number(
p->m_schedOrder)));
4778 if (checkTooMany && tooManyMap[
p->GetRecordingRuleID()] &&
4779 !
p->IsReactivated())
4787 else if (result.
value(15).toBool() && !
p->IsReactivated())
4791 !
p->IsReactivated() &&
4811 bool inactive = result.
value(33).toBool();
4826 p->SetRecordingStatus(newrecstatus);
4828 tmpList.push_back(
p);
4831 LOG(VB_SCHEDULE, LOG_INFO,
" +-- Cleanup...");
4832 for (
auto & tmp : tmpList)
4840 QString query = QString(
4841 "SELECT RECTABLE.title, RECTABLE.subtitle, "
4842 " RECTABLE.description, RECTABLE.season, "
4843 " RECTABLE.episode, RECTABLE.category, "
4844 " RECTABLE.chanid, channel.channum, "
4845 " RECTABLE.station, channel.name, "
4846 " RECTABLE.recgroup, RECTABLE.playgroup, "
4847 " RECTABLE.seriesid, RECTABLE.programid, "
4848 " RECTABLE.inetref, RECTABLE.recpriority, "
4849 " RECTABLE.startdate, RECTABLE.starttime, "
4850 " RECTABLE.enddate, RECTABLE.endtime, "
4851 " RECTABLE.recordid, RECTABLE.type, "
4852 " RECTABLE.dupin, RECTABLE.dupmethod, "
4853 " RECTABLE.findid, "
4854 " RECTABLE.startoffset, RECTABLE.endoffset, "
4855 " channel.commmethod "
4857 "INNER JOIN channel ON (channel.chanid = RECTABLE.chanid) "
4858 "LEFT JOIN recordmatch on RECTABLE.recordid = recordmatch.recordid "
4859 "WHERE (type = %1 OR type = %2) AND "
4860 " recordmatch.chanid IS NULL")
4866 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- Start DB Query..."));
4868 auto dbstart = nowAsDuration<std::chrono::microseconds>();
4871 bool ok = result.
exec();
4872 auto dbend = nowAsDuration<std::chrono::microseconds>();
4873 auto dbTime = dbend - dbstart;
4881 LOG(VB_SCHEDULE, LOG_INFO,
4882 QString(
" |-- %1 results in %2 sec. Processing...")
4884 .arg(duration_cast<std::chrono::seconds>(dbTime).count()));
4886 while (result.
next())
4889#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
4891 result.
value(16).toDate(), result.
value(17).toTime(), Qt::UTC);
4893 result.
value(18).toDate(), result.
value(19).toTime(), Qt::UTC);
4895 static const QTimeZone utc(QTimeZone::UTC);
4897 result.
value(16).toDate(), result.
value(17).toTime(), utc);
4899 result.
value(18).toDate(), result.
value(19).toTime(), utc);
4902 QDateTime recstartts = startts.addSecs(result.
value(25).toInt() * -60LL);
4903 QDateTime recendts = endts.addSecs( result.
value(26).toInt() * +60LL);
4905 if (recstartts >= recendts)
4908 recstartts = startts;
4919 result.
value(0).toString(),
4921 (sor) ? result.
value(1).toString() : QString(),
4923 (sor) ? result.
value(2).toString() : QString(),
4924 result.
value(3).toUInt(),
4925 result.
value(4).toUInt(),
4928 result.
value(6).toUInt(),
4929 result.
value(7).toString(),
4930 result.
value(8).toString(),
4931 result.
value(9).toString(),
4933 result.
value(10).toString(),
4934 result.
value(11).toString(),
4936 result.
value(12).toString(),
4937 result.
value(13).toString(),
4938 result.
value(14).toString(),
4940 result.
value(15).toInt(),
4943 recstartts, recendts,
4947 result.
value(20).toUInt(),
4953 result.
value(24).toUInt(),
4957 tmpList.push_back(
p);
4960 for (
auto & tmp : tmpList)
4971 QString sortColumn =
"title";
4981 QString prefixes = sh->getPrefixes();
4982 sortColumn =
"REGEXP_REPLACE(record.title,'" + prefixes +
"','')";
4986 sortColumn =
"record.recpriority";
4989 sortColumn =
"record.last_record";
4997 sortColumn =
"record.next_record IS NULL, record.next_record";
5000 sortColumn =
"record.type";
5004 QString order =
"ASC";
5008 QString query = QString(
5009 "SELECT record.title, record.subtitle, "
5010 " record.description, record.season, "
5011 " record.episode, record.category, "
5012 " record.chanid, channel.channum, "
5013 " record.station, channel.name, "
5014 " record.recgroup, record.playgroup, "
5015 " record.seriesid, record.programid, "
5016 " record.inetref, record.recpriority, "
5017 " record.startdate, record.starttime, "
5018 " record.enddate, record.endtime, "
5019 " record.recordid, record.type, "
5020 " record.dupin, record.dupmethod, "
5022 " channel.commmethod "
5024 "LEFT JOIN channel ON channel.callsign = record.station "
5025 " AND deleted IS NULL "
5026 "GROUP BY recordid "
5029 query = query.arg(sortColumn, order);
5040 while (result.
next())
5043#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
5044 QDateTime startts = QDateTime(result.
value(16).toDate(),
5045 result.
value(17).toTime(), Qt::UTC);
5046 QDateTime endts = QDateTime(result.
value(18).toDate(),
5047 result.
value(19).toTime(), Qt::UTC);
5049 static const QTimeZone utc(QTimeZone::UTC);
5050 QDateTime startts = QDateTime(result.
value(16).toDate(),
5051 result.
value(17).toTime(), utc);
5052 QDateTime endts = QDateTime(result.
value(18).toDate(),
5053 result.
value(19).toTime(), utc);
5056 if (!startts.isValid())
5058#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
5063 QTimeZone(QTimeZone::UTC));
5066 if (!endts.isValid())
5070 result.
value(0).toString(), QString(),
5071 result.
value(1).toString(), QString(),
5072 result.
value(2).toString(), result.
value(3).toUInt(),
5073 result.
value(4).toUInt(), result.
value(5).toString(),
5075 result.
value(6).toUInt(), result.
value(7).toString(),
5076 result.
value(8).toString(), result.
value(9).toString(),
5078 result.
value(10).toString(), result.
value(11).toString(),
5080 result.
value(12).toString(), result.
value(13).toString(),
5081 result.
value(14).toString(),
5083 result.
value(15).toInt(),
5090 result.
value(20).toUInt(), rectype,
5094 result.
value(24).toUInt(),
5193 QString recording_dir;
5197 "LiveTV", cur, cur.addSecs(3600), cardid,
5202 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"FindNextLiveTVDir: next dir is '%1'")
5203 .arg(recording_dir));
5210 const QString &title,
5212 const QString &storagegroup,
5213 const QDateTime &recstartts,
5214 const QDateTime &recendts,
5216 QString &recording_dir,
5219 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
"FillRecordingDir: Starting");
5224 if (cnt++ % 20 == 0)
5225 LOG(VB_SCHEDULE, LOG_WARNING,
"Waiting for main server.");
5226 std::this_thread::sleep_for(50ms);
5233 QStringList recsCounted;
5234 std::list<FileSystemInfo *> fsInfoList;
5235 std::list<FileSystemInfo *>::iterator fslistit;
5237 recording_dir.clear();
5239 if (dirlist.size() == 1)
5241 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5242 QString(
"FillRecordingDir: The only directory in the %1 Storage "
5243 "Group is %2, so it will be used by default.")
5244 .arg(storagegroup, dirlist[0]));
5245 recording_dir = dirlist[0];
5246 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
"FillRecordingDir: Finished");
5251 int weightPerRecording =
5253 int weightPerPlayback =
5255 int weightPerCommFlag =
5257 int weightPerTranscode =
5260 QString storageScheduler =
5262 int localStartingWeight =
5264 (storageScheduler !=
"Combination") ? 0
5265 : (
int)(-1.99 * weightPerRecording));
5266 int remoteStartingWeight =
5268 std::chrono::seconds maxOverlap =
5273 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5274 "FillRecordingDir: Calculating initial FS Weights.");
5285 tmpWeight = localStartingWeight;
5286 msg +=
" is local (" + QString::number(tmpWeight) +
")";
5290 tmpWeight = remoteStartingWeight;
5291 msg +=
" is remote (+" + QString::number(tmpWeight) +
")";
5301 msg +=
", has SGweightPerDir offset of "
5302 + QString::number(tmpWeight) +
")";
5304 msg +=
". initial dir weight = " + QString::number(fs->
getWeight());
5305 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, msg);
5307 fsInfoList.push_back(fs);
5310 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5311 "FillRecordingDir: Adjusting FS Weights from inuseprograms.");
5314 saveRecDir.
prepare(
"UPDATE inuseprograms "
5315 "SET recdir = :RECDIR "
5316 "WHERE chanid = :CHANID AND "
5317 " starttime = :STARTTIME");
5320 "SELECT i.chanid, i.starttime, r.endtime, recusage, rechost, recdir "
5321 "FROM inuseprograms i, recorded r "
5322 "WHERE DATE_ADD(lastupdatetime, INTERVAL 16 MINUTE) > NOW() AND "
5323 " i.chanid = r.chanid AND "
5324 " i.starttime = r.starttime");
5332 while (query.
next())
5334 uint recChanid = query.
value(0).toUInt();
5337 QString recUsage( query.
value(3).toString());
5338 QString recHost( query.
value(4).toString());
5339 QString recDir( query.
value(5).toString());
5341 if (recDir.isEmpty())
5345 recDir = recDir.isEmpty() ?
"_UNKNOWN_" : recDir;
5347 saveRecDir.
bindValue(
":RECDIR", recDir);
5348 saveRecDir.
bindValue(
":CHANID", recChanid);
5349 saveRecDir.
bindValue(
":STARTTIME", recStart);
5350 if (!saveRecDir.
exec())
5353 if (recDir ==
"_UNKNOWN_")
5356 for (fslistit = fsInfoList.begin();
5357 fslistit != fsInfoList.end(); ++fslistit)
5363 int weightOffset = 0;
5367 if (recEnd > recstartts.addSecs(maxOverlap.count()))
5369 weightOffset += weightPerRecording;
5370 recsCounted << QString::number(recChanid) +
":" +
5376 weightOffset += weightPerPlayback;
5380 weightOffset += weightPerCommFlag;
5384 weightOffset += weightPerTranscode;
5389 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5390 QString(
" %1 @ %2 in use by '%3' on %4:%5, FSID "
5391 "#%6, FSID weightOffset +%7.")
5392 .arg(QString::number(recChanid),
5394 recUsage, recHost, recDir,
5396 QString::number(weightOffset)));
5404 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5405 QString(
" %1:%2 => old weight %3 plus "
5407 .arg(
fs2->getHostname(),
5409 .arg(
fs2->getWeight())
5411 .arg(
fs2->getWeight() + weightOffset));
5413 fs2->setWeight(
fs2->getWeight() + weightOffset);
5423 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5424 "FillRecordingDir: Adjusting FS Weights from scheduler.");
5426 for (
auto *thispg : reclist)
5428 if ((recendts < thispg->GetRecordingStartTime()) ||
5429 (recstartts > thispg->GetRecordingEndTime()) ||
5432 (thispg->GetInputID() == 0) ||
5433 (recsCounted.contains(QString(
"%1:%2").arg(thispg->GetChanID())
5435 (thispg->GetPathname().isEmpty()))
5438 for (fslistit = fsInfoList.begin();
5439 fslistit != fsInfoList.end(); ++fslistit)
5442 if ((fs->
getHostname() == thispg->GetHostname()) &&
5443 (fs->
getPath() == thispg->GetPathname()))
5445 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5446 QString(
"%1 @ %2 will record on %3:%4, FSID #%5, "
5447 "weightPerRecording +%6.")
5448 .arg(thispg->GetChanID())
5451 .arg(fs->
getFSysID()).arg(weightPerRecording));
5460 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5461 QString(
" %1:%2 => old weight %3 plus %4 = %5")
5462 .arg(
fs2->getHostname(),
fs2->getPath())
5463 .arg(
fs2->getWeight()).arg(weightPerRecording)
5464 .arg(
fs2->getWeight() + weightPerRecording));
5466 fs2->setWeight(
fs2->getWeight() + weightPerRecording);
5474 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5475 QString(
"Using '%1' Storage Scheduler directory sorting algorithm.")
5476 .arg(storageScheduler));
5478 if (storageScheduler ==
"BalancedFreeSpace")
5480 else if (storageScheduler ==
"BalancedPercFreeSpace")
5482 else if (storageScheduler ==
"BalancedDiskIO")
5489 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5490 "--- FillRecordingDir Sorted fsInfoList start ---");
5491 for (fslistit = fsInfoList.begin();fslistit != fsInfoList.end();
5495 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
"%1:%2")
5497 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
" Location : %1")
5498 .arg((fs->
isLocal()) ?
"local" :
"remote"));
5499 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
" weight : %1")
5501 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
" free space : %5")
5504 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5505 "--- FillRecordingDir Sorted fsInfoList end ---");
5514 long long maxSizeKB = (maxByterate + maxByterate/3) *
5515 recstartts.secsTo(recendts) / 1024;
5517 bool simulateAutoExpire =
5520 (fsInfoList.size() > 1));
5534 for (
unsigned int pass = 1; pass <= 3; pass++)
5536 bool foundDir =
false;
5538 if ((pass == 2) && simulateAutoExpire)
5541 QMap <int , long long> remainingSpaceKB;
5542 for (fslistit = fsInfoList.begin();
5543 fslistit != fsInfoList.end(); ++fslistit)
5545 remainingSpaceKB[(*fslistit)->getFSysID()] =
5546 (*fslistit)->getFreeSpace();
5553 for (
auto & expire : expiring)
5557 for (fslistit = fsInfoList.begin();
5558 fslistit != fsInfoList.end(); ++fslistit)
5561 if (expire->GetHostname() != (*fslistit)->getHostname())
5565 if (!dirlist.contains((*fslistit)->getPath()))
5569 (*fslistit)->getPath() +
"/" + expire->GetPathname();
5576 if (checkFile.exists())
5584 QString backuppath = expire->GetPathname();
5586 bool foundSlave =
false;
5588 for (
auto * enc : std::as_const(*
m_tvList))
5590 if (enc->GetHostName() ==
5593 enc->CheckFile(programinfo);
5611 LOG(VB_GENERAL, LOG_ERR,
5612 QString(
"Unable to match '%1' "
5613 "to any file system. Ignoring it.")
5614 .arg(expire->GetBasename()));
5620 expire->GetFilesize() / 1024;
5623 long long desiredSpaceKB =
5627 (desiredSpaceKB + maxSizeKB))
5629 recording_dir = fs->
getPath();
5632 LOG(VB_FILE, LOG_INFO,
5633 QString(
"pass 2: '%1' will record in '%2' "
5634 "although there is only %3 MB free and the "
5635 "AutoExpirer wants at least %4 MB. This "
5636 "directory has the highest priority files "
5637 "to be expired from the AutoExpire list and "
5638 "there are enough that the Expirer should "
5639 "be able to free up space for this recording.")
5640 .arg(title, recording_dir)
5642 .arg(desiredSpaceKB / 1024));
5653 for (fslistit = fsInfoList.begin();
5654 fslistit != fsInfoList.end(); ++fslistit)
5656 long long desiredSpaceKB = 0;
5663 (dirlist.contains(fs->
getPath())) &&
5667 recording_dir = fs->
getPath();
5672 LOG(VB_FILE, LOG_INFO,
5673 QString(
"pass 1: '%1' will record in "
5674 "'%2' which has %3 MB free. This recording "
5675 "could use a max of %4 MB and the "
5676 "AutoExpirer wants to keep %5 MB free.")
5677 .arg(title, recording_dir)
5679 .arg(maxSizeKB / 1024)
5680 .arg(desiredSpaceKB / 1024));
5684 LOG(VB_FILE, LOG_INFO,
5685 QString(
"pass %1: '%2' will record in "
5686 "'%3' although there is only %4 MB free and "
5687 "the AutoExpirer wants at least %5 MB. "
5688 "Something will have to be deleted or expired "
5689 "in order for this recording to complete "
5691 .arg(pass).arg(title, recording_dir)
5693 .arg(desiredSpaceKB / 1024));
5706 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
"FillRecordingDir: Finished");
5719 QMap <int, bool> fsMap;
5720 for (
const auto&
fs1 : std::as_const(fsInfos))
5722 fsMap[
fs1.getFSysID()] =
true;
5726 LOG(VB_FILE, LOG_INFO,
LOC +
5727 QString(
"FillDirectoryInfoCache: found %1 unique filesystems")
5728 .arg(fsMap.size()));
5735 auto secsleft = std::chrono::seconds(curtime.secsTo(
m_livetvTime));
5739 if (secsleft - prerollseconds > 120s)
5743 for (
auto * enc : std::as_const(*
m_tvList))
5759 if (
m_schedTime.secsTo(dummy->GetRecordingEndTime()) < 1800)
5760 dummy->SetRecordingEndTime(
m_schedTime.addSecs(1800));
5761 dummy->SetInputID(enc->GetInputID());
5762 dummy->m_mplexId = dummy->QueryMplexID();
5785 bool autoStart =
false;
5787 QDateTime startupTime = QDateTime();
5793 if (startupTime.isValid())
5796 startupSecs = std::max(startupSecs, 15 * 60s);
5803 LOG(VB_GENERAL, LOG_INFO,
5804 "Close to auto-start time, AUTO-Startup assumed");
5810 LOG(VB_GENERAL, LOG_INFO,
5811 "Close to MythFillDB suggested run time, AUTO-Startup to fetch guide data?");
5817 LOG(VB_GENERAL, LOG_DEBUG,
5818 "NOT close to auto-start time, USER-initiated startup assumed");
5821 else if (!s.isEmpty())
5823 LOG(VB_GENERAL, LOG_ERR,
LOC +
5824 QString(
"Invalid MythShutdownWakeupTime specified in database (%1)")
5836 QMap<uint, QSet<uint> > inputSets;
5837 query.
prepare(
"SELECT DISTINCT ci1.cardid, ci2.cardid "
5838 "FROM capturecard ci1, capturecard ci2, "
5839 " inputgroup ig1, inputgroup ig2 "
5840 "WHERE ci1.cardid = ig1.cardinputid AND "
5841 " ci2.cardid = ig2.cardinputid AND"
5842 " ig1.inputgroupid = ig2.inputgroupid AND "
5843 " ci1.cardid <= ci2.cardid "
5844 "ORDER BY ci1.cardid, ci2.cardid");
5850 while (query.
next())
5854 inputSets[id0].insert(id1);
5855 inputSets[id1].insert(id0);
5858 QMap<uint, QSet<uint> >::iterator mit;
5859 for (mit = inputSets.begin(); mit != inputSets.end(); ++mit)
5861 uint inputid = mit.key();
5869 QSet<uint> fullset = mit.value();
5870 QSet<uint> checkset;
5871 QSet<uint>::const_iterator sit;
5872 while (checkset != fullset)
5875 for (
int item : std::as_const(checkset))
5876 fullset += inputSets[item];
5881 auto *conflictlist =
new RecList();
5883 for (
int item : std::as_const(checkset))
5885 LOG(VB_SCHEDULE, LOG_INFO,
5886 QString(
"Assigning input %1 to conflict set %2")
5894 query.
prepare(
"SELECT ci.cardid "
5895 "FROM capturecard ci "
5896 "LEFT JOIN inputgroup ig "
5897 " ON ci.cardid = ig.cardinputid "
5898 "WHERE ig.cardinputid IS NULL");
5904 while (query.
next())
5908 LOG(VB_GENERAL, LOG_ERR,
LOC +
5909 QString(
"Input %1 is not assigned to any input group").arg(
id));
5910 auto *conflictlist =
new RecList();
5912 LOG(VB_SCHEDULE, LOG_INFO,
5913 QString(
"Assigning input %1 to conflict set %2")
5927 query.
prepare(
"SELECT cardid, parentid, schedgroup "
5929 "WHERE sourceid > 0 "
5937 while (query.
next())
5940 uint parentid = query.
value(1).toUInt();
5957 LOG(VB_SCHEDULE, LOG_INFO,
5958 QString(
"Added SchedInputInfo i=%1, g=%2, sg=%3")
5967 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
5968 QString(
"AddChildInput: Handling parent = %1, input = %2")
5969 .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="")
void dispatch(const MythEvent &event)
int GetNumSetting(const QString &key, int defaultval=0)
std::enable_if_t< std::chrono::__is_duration< T >::value, T > GetDurSetting(const QString &key, T defaultval=T::zero())
bool GetBoolSetting(const QString &key, bool defaultval=false)
static void DBError(const QString &where, const MSqlQuery &query)
T dequeue()
Removes item from front of list and returns a copy. O(1).
void enqueue(const T &d)
Adds item to the back of the list. O(1).
This class is used as a container for messages.
Holds information on recordings and videos.
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
uint GetRecordingRuleID(void) const
QString toString(Verbosity v=kLongDescription, const QString &sep=":", const QString &grp="\"") const
void SetRecordingPriority2(int priority)
bool IsSameTitleStartTimeAndChannel(const ProgramInfo &other) const
Checks title, chanid or callsign and start times for equality.
QString GetProgramID(void) const
bool IsDuplicateProgram(const ProgramInfo &other) const
Checks for duplicates according to dupmethod.
void SetRecordingRuleType(RecordingType type)
uint GetRecordingID(void) const
QDateTime GetScheduledEndTime(void) const
The scheduled end time of the program.
void SetRecordingStatus(RecStatus::Type status)
QString GetHostname(void) const
static bool UsingProgramIDAuthority(void)
uint GetSourceID(void) const
QString DiscoverRecordingDirectory(void)
bool IsReactivated(void) const
QString GetDescription(void) const
QString GetStorageGroup(void) const
void SetRecordingStartTime(const QDateTime &dt)
QString GetTitle(void) const
static void CheckProgramIDAuthorities(void)
QDateTime GetRecordingStartTime(void) const
Approximate time the recording started.
QDateTime GetScheduledStartTime(void) const
The scheduled start time of program.
QString GetChanNum(void) const
This is the channel "number", in the form 1, 1_2, 1-2, 1#1, etc.
void SetRecordingRuleID(uint id)
QString MakeUniqueKey(void) const
Creates a unique string that can be used to identify an existing recording.
bool IsSameRecording(const ProgramInfo &other) const
int GetRecordingPriority(void) const
QString GetPathname(void) const
uint GetInputID(void) const
int GetRecordingPriority2(void) const
uint GetParentRecordingRuleID(void) const
void ToStringList(QStringList &list) const
Serializes ProgramInfo into a QStringList which can be passed over a socket.
void SetRecordingEndTime(const QDateTime &dt)
RecStatus::Type GetRecordingStatus(void) const
QDateTime GetRecordingEndTime(void) const
Approximate time the recording should have ended, did end, or is intended to end.
QString GetSubtitle(void) const
void SetPathname(const QString &pn)
RecordingType GetRecordingRuleType(void) const
QString GetChannelSchedulingID(void) const
This is the unique programming identifier of a channel.
static QString toString(RecStatus::Type recstatus, uint id)
Converts "recstatus" into a short (unreadable) string.
static QString toUIState(RecStatus::Type recstatus)
static void create(Scheduler *scheduler, RecordingInfo &ri)
Create an instance of the RecordingExtender if necessary, and add this recording to the list of new r...
Holds information on a TV Program one might wish to record.
RecStatus::Type m_oldrecstatus
static const QRegularExpression kReLeadingAnd
void AddHistory(bool resched=true, bool forcedup=false, bool future=false)
Adds recording history, creating "record" it if necessary.
void SetRecordingID(uint _recordedid) override
static const int kNumFilters
QWaitCondition m_reschedWait
QMap< int, bool > m_schedAfterStartMap
const RecordingInfo * FindConflict(const RecordingInfo *p, OpenEndType openEnd=openEndNever, uint *affinity=nullptr, bool checkAll=false) const
QMap< int, EncoderLink * > * m_tvList
bool WakeUpSlave(const QString &slaveHostname, bool setWakingStatus=true)
void SlaveConnected(const RecordingList &slavelist)
void FillDirectoryInfoCache(void)
static void PrintRec(const RecordingInfo *p, const QString &prefix="")
bool IsSameProgram(const RecordingInfo *a, const RecordingInfo *b) const
QMap< QString, FileSystemInfo > m_fsInfoCache
bool AssignGroupInput(RecordingInfo &ri, std::chrono::seconds prerollseconds)
void BackupRecStatus(void)
bool IsBusyRecording(const RecordingInfo *rcinfo)
MythDeque< QStringList > m_reschedQueue
void MarkOtherShowings(RecordingInfo *p)
bool HaveQueuedRequests(void)
static bool VerifyCards(void)
QDateTime m_lastPrepareTime
bool GetAllPending(RecList &retList, int recRuleId=0) const
std::chrono::milliseconds m_delayShutdownTime
void RestoreRecStatus(void)
bool CreateConflictLists(void)
void CreateTempTables(void)
void EnqueueCheck(const RecordingInfo &recinfo, const QString &why)
std::pair< const RecordingInfo *, const RecordingInfo * > IsSameKey
void AddChildInput(uint parentid, uint childid)
void FillRecordListFromDB(uint recordid=0)
void UpdateMatches(uint recordid, uint sourceid, uint mplexid, const QDateTime &maxstarttime)
void ShutdownServer(std::chrono::seconds prerollseconds, QDateTime &idleSince)
void SchedNewFirstPass(RecIter &start, const RecIter &end, int recpriority, int recpriority2)
QMutex m_resetIdleTimeLock
bool HandleRecording(RecordingInfo &ri, bool &statuschanged, QDateTime &nextStartTime, QDateTime &nextWakeTime, std::chrono::seconds prerollseconds)
bool HandleRunSchedulerStartup(std::chrono::seconds prerollseconds, std::chrono::minutes idleWaitForRecordingTime)
void BuildNewRecordsQueries(uint recordid, QStringList &from, QStringList &where, MSqlBindings &bindings)
void UpdateNextRecord(void)
QSet< uint > m_schedOrderWarned
void EnqueuePlace(const QString &why)
Scheduler(bool runthread, QMap< int, EncoderLink * > *_tvList, const QString &tmptable="record", Scheduler *master_sched=nullptr)
static bool WasStartedAutomatically()
bool FindNextConflict(const RecList &cardlist, const RecordingInfo *p, RecConstIter &iter, OpenEndType openEnd=openEndNever, uint *paffinity=nullptr, bool ignoreinput=false) const
int FillRecordingDir(const QString &title, const QString &hostname, const QString &storagegroup, const QDateTime &recstartts, const QDateTime &recendts, uint cardid, QString &recording_dir, const RecList &reclist)
void ResetDuplicates(uint recordid, uint findid, const QString &title, const QString &subtitle, const QString &descrip, const QString &programid)
void PrintList(bool onlyFutureRecordings=false)
bool FillRecordList(void)
static void GetAllScheduled(QStringList &strList, SchedSortColumn sortBy=kSortTitle, bool ascending=true)
Returns all scheduled programs serialized into a QStringList.
void SchedNewRetryPass(const RecIter &start, const RecIter &end, bool samePriority, bool livetv=false)
void HandleWakeSlave(RecordingInfo &ri, std::chrono::seconds prerollseconds)
void getConflicting(RecordingInfo *pginfo, QStringList &strlist)
std::vector< RecList * > m_conflictLists
bool ChangeRecordingEnd(RecordingInfo *oldp, RecordingInfo *newp)
RecStatus::Type GetRecStatus(const ProgramInfo &pginfo)
void run(void) override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
void DeleteTempTables(void)
void EnqueueMatch(uint recordid, uint sourceid, uint mplexid, const QDateTime &maxstarttime, const QString &why)
void Reschedule(const QStringList &request)
bool InitInputInfoMap(void)
QMap< uint, RecList > m_recordIdListMap
void PruneRedundants(void)
void SlaveDisconnected(uint cardid)
MainServer * m_mainServer
void PutInactiveSlavesToSleep(void)
QMap< QString, ProgramInfo * > GetRecording(void) const override
void OldRecordedFixups(void)
bool TryAnotherShowing(RecordingInfo *p, bool samePriority, bool livetv=false)
void GetNextLiveTVDir(uint cardid)
void FillRecordListFromMaster(void)
void ClearRequestQueue(void)
void UpdateManuals(uint recordid)
IsSameCacheType m_cacheIsSameProgram
std::array< QSet< QString >, 4 > m_sysEvents
void AddRecording(const RecordingInfo &pi)
bool HandleReschedule(void)
void UpdateDuplicates(void)
void HandleIdleShutdown(bool &blockShutdown, QDateTime &idleSince, std::chrono::seconds prerollseconds, std::chrono::seconds idleTimeoutSecs, std::chrono::minutes idleWaitForRecordingTime, bool statuschanged)
void MarkShowingsList(const RecList &showinglist, RecordingInfo *p)
QMap< uint, SchedInputInfo > m_sinputInfoMap
void HandleRecordingStatusChange(RecordingInfo &ri, RecStatus::Type recStatus, const QString &details)
void SetMainServer(MainServer *ms)
void UpdateRecStatus(RecordingInfo *pginfo)
QMap< QString, RecList > m_titleListMap
void SchedNewRecords(void)
static bool CheckShutdownServer(std::chrono::seconds prerollseconds, QDateTime &idleSince, bool &blockShutdown, uint logmask)
QStringList GetDirList(void) const
static QReadWriteLock s_inputsLock
static pid_list_t::iterator find(const PIDInfoMap &map, pid_list_t &list, pid_list_t::iterator begin, pid_list_t::iterator end, bool find_open)
@ GENERIC_EXIT_OK
Exited with no error.
@ GENERIC_EXIT_NOT_OK
Exited with error.
QVector< FileSystemInfo > FileSystemInfoList
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
QMap< QString, QVariant > MSqlBindings
typedef for a map of string -> string bindings for generic queries.
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
bool IsMACAddress(const QString &MAC)
bool WakeOnLAN(const QString &MAC)
RecList::const_iterator RecConstIter
RecList::iterator RecIter
std::deque< RecordingInfo * > RecList
std::shared_ptr< MythSortHelper > getMythSortHelper(void)
Get a pointer to the MythSortHelper singleton.
void SendMythSystemRecEvent(const QString &msg, const RecordingInfo *pginfo)
uint myth_system(const QString &command, uint flags, std::chrono::seconds timeout)
QDateTime as_utc(const QDateTime &old_dt)
Returns copy of QDateTime with TimeSpec set to UTC.
std::chrono::seconds secsInPast(const QDateTime &past)
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
@ kDatabase
Default UTC, database format.
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
ProgramInfo::CategoryType string_to_myth_category_type(const QString &category_type)
bool LoadFromScheduler(AutoDeleteDeque< TYPE * > &destination, bool &hasConflicts, const QString &altTable="", int recordid=-1)
const QString kTranscoderInUseID
const QString kPlayerInUseID
const QString kFlaggerInUseID
const QString kRecorderInUseID
QChar toQChar(RecordingType rectype)
Converts "rectype" into a human readable character.
int RecTypePrecedence(RecordingType rectype)
Converts a RecordingType to a simple integer so it's specificity can be compared to another.
static QString fs1(QT_TRANSLATE_NOOP("SchedFilterEditor", "Identifiable episode"))
static QString fs2(QT_TRANSLATE_NOOP("SchedFilterEditor", "First showing"))
static bool comp_retry(RecordingInfo *a, RecordingInfo *b)
static bool comp_storage_combination(FileSystemInfo *a, FileSystemInfo *b)
static bool comp_redundant(RecordingInfo *a, RecordingInfo *b)
static QString progfindid
static bool comp_overlap(RecordingInfo *a, RecordingInfo *b)
static void erase_nulls(RecList &reclist)
static bool comp_recstart(RecordingInfo *a, RecordingInfo *b)
static bool comp_storage_disk_io(FileSystemInfo *a, FileSystemInfo *b)
static bool comp_storage_perc_free_space(FileSystemInfo *a, FileSystemInfo *b)
static bool comp_storage_free_space(FileSystemInfo *a, FileSystemInfo *b)
static bool comp_priority(RecordingInfo *a, RecordingInfo *b)
static QString progdupinit
static bool Recording(const RecordingInfo *p)
static constexpr int64_t kProgramInUseInterval
@ sStatus_Waking
A slave is marked as waking when the master runs the slave's wakeup command.
@ sStatus_Undefined
A slave's sleep status is undefined when it has never connected to the master backend or is not able ...
@ sStatus_FallingAsleep
A slave is marked as falling asleep when told to shutdown by the master.
@ kState_WatchingLiveTV
Watching LiveTV is the state for when we are watching a recording and the user has control over the c...