2#if QT_VERSION >= QT_VERSION_CHECK(6,0,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)");
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,inetref,last_record "
3730 if (!query.
exec() || query.
size() != 1)
3740 QString title = query.
value(1).toString();
3741 QString subtitle = query.
value(2).toString();
3742 QString description = query.
value(3).toString();
3743 QString station = query.
value(4).toString();
3744#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
3745 QDateTime startdt = QDateTime(query.
value(5).toDate(),
3746 query.
value(6).toTime(), Qt::UTC);
3747 int duration = startdt.secsTo(
3748 QDateTime(query.
value(7).toDate(),
3749 query.
value(8).toTime(), Qt::UTC));
3751 QDateTime startdt = QDateTime(query.
value(5).toDate(),
3752 query.
value(6).toTime(),
3753 QTimeZone(QTimeZone::UTC));
3754 int duration = startdt.secsTo(
3755 QDateTime(query.
value(7).toDate(),
3756 query.
value(8).toTime(),
3757 QTimeZone(QTimeZone::UTC)));
3760 int season = query.
value(9).toInt();
3761 int episode = query.
value(10).toInt();
3762 QString inetref = query.
value(11).toString();
3766 QDate originalairdate = QDate(query.
value(12).toDate());
3768 if (description.isEmpty())
3769 description = startdt.toLocalTime().toString();
3771 query.
prepare(
"SELECT chanid from channel "
3772 "WHERE deleted IS NULL AND callsign = :STATION");
3780 std::vector<unsigned int> chanidlist;
3781 while (query.
next())
3782 chanidlist.push_back(query.
value(0).toUInt());
3786 bool weekday =
false;
3788 QDateTime lstartdt = startdt.toLocalTime();
3803 weekday = (lstartdt.date().dayOfWeek() < 6);
3804 daysoff = lstartdt.date().daysTo(
3806#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
3807 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3808 lstartdt.time(), Qt::LocalTime).toUTC();
3810 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3812 QTimeZone(QTimeZone::LocalTime)
3820 daysoff = lstartdt.date().daysTo(
3822 daysoff = (daysoff + 6) / 7 * 7;
3823#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
3824 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3825 lstartdt.time(), Qt::LocalTime).toUTC();
3827 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3829 QTimeZone(QTimeZone::LocalTime)
3834 LOG(VB_GENERAL, LOG_ERR,
3835 QString(
"Invalid rectype for manual recordid %1").arg(recordid));
3841 for (
uint id : chanidlist)
3843 if (weekday && startdt.toLocalTime().date().dayOfWeek() >= 6)
3846 query.
prepare(
"REPLACE INTO program (chanid, starttime, endtime,"
3847 " title, subtitle, description, manualid,"
3848 " season, episode, inetref, originalairdate, generic) "
3849 "VALUES (:CHANID, :STARTTIME, :ENDTIME, :TITLE,"
3850 " :SUBTITLE, :DESCRIPTION, :RECORDID, "
3851 " :SEASON, :EPISODE, :INETREF, :ORIGINALAIRDATE, 1)");
3854 query.
bindValue(
":ENDTIME", startdt.addSecs(duration));
3857 query.
bindValue(
":DESCRIPTION", description);
3861 query.
bindValue(
":ORIGINALAIRDATE", originalairdate);
3870 daysoff += skipdays;
3871#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
3872 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3873 lstartdt.time(), Qt::LocalTime).toUTC();
3875 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3877 QTimeZone(QTimeZone::LocalTime)
3891 query = QString(
"SELECT recordid,search,subtitle,description "
3892 "FROM %1 WHERE search <> %2 AND "
3893 "(recordid = %3 OR %4 = 0) ")
3905 while (result.
next())
3907 QString
prefix = QString(
":NR%1").arg(count);
3908 qphrase = result.
value(3).toString();
3914 LOG(VB_GENERAL, LOG_ERR,
3915 QString(
"Invalid search key in recordid %1")
3916 .arg(result.
value(0).toString()));
3920 QString bindrecid =
prefix +
"RECID";
3921 QString bindphrase =
prefix +
"PHRASE";
3922 QString bindlikephrase1 =
prefix +
"LIKEPHRASE1";
3923 QString bindlikephrase2 =
prefix +
"LIKEPHRASE2";
3924 QString bindlikephrase3 =
prefix +
"LIKEPHRASE3";
3926 bindings[bindrecid] = result.
value(0).toString();
3932 qphrase.remove(
';');
3933 from << result.
value(2).toString();
3934 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
3935 QString(
" AND program.manualid = 0 AND ( %2 )")
3939 bindings[bindlikephrase1] = QString(
"%") + qphrase +
"%";
3941 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
" AND "
3942 "program.manualid = 0 AND "
3943 "program.title LIKE " + bindlikephrase1);
3946 bindings[bindlikephrase1] = QString(
"%") + qphrase +
"%";
3947 bindings[bindlikephrase2] = QString(
"%") + qphrase +
"%";
3948 bindings[bindlikephrase3] = QString(
"%") + qphrase +
"%";
3950 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
3951 " AND program.manualid = 0"
3952 " AND (program.title LIKE " + bindlikephrase1 +
3953 " OR program.subtitle LIKE " + bindlikephrase2 +
3954 " OR program.description LIKE " + bindlikephrase3 +
")");
3957 bindings[bindphrase] = qphrase;
3958 from <<
", people, credits";
3959 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
" AND "
3960 "program.manualid = 0 AND "
3961 "people.name LIKE " + bindphrase +
" AND "
3962 "credits.person = people.person AND "
3963 "program.chanid = credits.chanid AND "
3964 "program.starttime = credits.starttime");
3969 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
3971 QString(
"program.manualid = %1.recordid ")
3975 LOG(VB_GENERAL, LOG_ERR,
3976 QString(
"Unknown RecSearchType (%1) for recordid %2")
3977 .arg(result.
value(1).toInt())
3978 .arg(result.
value(0).toString()));
3979 bindings.remove(bindrecid);
3986 if (recordid == 0 || from.count() == 0)
3988 QString recidmatch =
"";
3990 recidmatch =
"RECTABLE.recordid = :NRRECORDID AND ";
3991 QString s1 = recidmatch +
3992 "RECTABLE.type <> :NRTEMPLATE AND "
3993 "RECTABLE.search = :NRST AND "
3994 "program.manualid = 0 AND "
3995 "program.title = RECTABLE.title ";
3997 QString s2 = recidmatch +
3998 "RECTABLE.type <> :NRTEMPLATE AND "
3999 "RECTABLE.search = :NRST AND "
4000 "program.manualid = 0 AND "
4001 "program.seriesid <> '' AND "
4002 "program.seriesid = RECTABLE.seriesid ";
4012 bindings[
":NRRECORDID"] = recordid;
4018" WHEN RECTABLE.type IN (%1, %2, %3) THEN 0 "
4019" WHEN RECTABLE.type IN (%4, %5, %6) THEN -1 "
4020" ELSE (program.generic - 1) "
4026"(CASE RECTABLE.type "
4028" THEN RECTABLE.findid "
4030" THEN to_days(date_sub(convert_tz(program.starttime, 'UTC', 'SYSTEM'), "
4031" interval time_format(RECTABLE.findtime, '%H:%i') hour_minute)) "
4033" THEN floor((to_days(date_sub(convert_tz(program.starttime, 'UTC', "
4034" 'SYSTEM'), interval time_format(RECTABLE.findtime, '%H:%i') "
4035" hour_minute)) - RECTABLE.findday)/7) * 7 + RECTABLE.findday "
4037" THEN RECTABLE.findid "
4046 const QDateTime &maxstarttime)
4050 QString deleteClause;
4051 QString filterClause = QString(
" AND program.endtime > "
4052 "(NOW() - INTERVAL 480 MINUTE)");
4056 deleteClause +=
" AND recordmatch.recordid = :RECORDID";
4057 bindings[
":RECORDID"] = recordid;
4061 deleteClause +=
" AND channel.sourceid = :SOURCEID";
4062 filterClause +=
" AND channel.sourceid = :SOURCEID";
4063 bindings[
":SOURCEID"] = sourceid;
4067 deleteClause +=
" AND channel.mplexid = :MPLEXID";
4068 filterClause +=
" AND channel.mplexid = :MPLEXID";
4069 bindings[
":MPLEXID"] = mplexid;
4071 if (maxstarttime.isValid())
4073 deleteClause +=
" AND recordmatch.starttime <= :MAXSTARTTIME";
4074 filterClause +=
" AND program.starttime <= :MAXSTARTTIME";
4075 bindings[
":MAXSTARTTIME"] = maxstarttime;
4078 query.
prepare(QString(
"DELETE recordmatch FROM recordmatch, channel "
4079 "WHERE recordmatch.chanid = channel.chanid")
4081 MSqlBindings::const_iterator it;
4082 for (it = bindings.cbegin(); it != bindings.cend(); ++it)
4090 bindings.remove(
":RECORDID");
4092 query.
prepare(
"SELECT filterid, clause FROM recordfilter "
4093 "WHERE filterid >= 0 AND filterid < :NUMFILTERS AND "
4094 " TRIM(clause) <> ''");
4101 while (query.
next())
4103 filterClause += QString(
" AND (((RECTABLE.filter & %1) = 0) OR (%2))")
4104 .arg(1 << query.
value(0).toInt()).arg(query.
value(1).toString());
4108 query.
prepare(
"SELECT NULL from record "
4109 "WHERE type = :FINDONE AND findid <= 0;");
4118 QDate epoch(1970, 1, 1);
4121 query.
prepare(
"UPDATE record set findid = :FINDID "
4122 "WHERE type = :FINDONE AND findid <= 0;");
4129 QStringList fromclauses;
4130 QStringList whereclauses;
4136 for (
int clause = 0; clause < fromclauses.count(); ++clause)
4138 LOG(VB_SCHEDULE, LOG_INFO, QString(
"Query %1: %2/%3")
4139 .arg(QString::number(clause), fromclauses[clause],
4140 whereclauses[clause]));
4144 for (
int clause = 0; clause < fromclauses.count(); ++clause)
4146 QString query2 = QString(
4147"REPLACE INTO recordmatch (recordid, chanid, starttime, manualid, "
4148" oldrecduplicate, findid) "
4149"SELECT RECTABLE.recordid, program.chanid, program.starttime, "
4150" IF(search = %1, RECTABLE.recordid, 0), ").arg(
kManualSearch) +
4152"FROM (RECTABLE, program INNER JOIN channel "
4153" ON channel.chanid = program.chanid) ") + fromclauses[clause] + QString(
4154" WHERE ") + whereclauses[clause] +
4155 QString(
" AND channel.deleted IS NULL "
4156 " AND channel.visible > 0 ") +
4157 filterClause + QString(
" AND "
4160" (RECTABLE.type = %1 "
4161" OR RECTABLE.type = %2 "
4162" OR RECTABLE.type = %3 "
4163" OR RECTABLE.type = %4) "
4165" ((RECTABLE.type = %6 "
4166" OR RECTABLE.type = %7 "
4167" OR RECTABLE.type = %8)"
4169" ADDTIME(RECTABLE.startdate, RECTABLE.starttime) = program.starttime "
4171" RECTABLE.station = channel.callsign) "
4183 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- Start DB Query %1...")
4186 auto dbstart = nowAsDuration<std::chrono::microseconds>();
4190 for (it = bindings.cbegin(); it != bindings.cend(); ++it)
4192 if (query2.contains(it.key()))
4196 bool ok = result.
exec();
4197 auto dbend = nowAsDuration<std::chrono::microseconds>();
4198 auto dbTime = dbend - dbstart;
4206 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- %1 results in %2 sec.")
4208 .arg(duration_cast<std::chrono::seconds>(dbTime).count()));
4212 LOG(VB_SCHEDULE, LOG_INFO,
" +-- Done.");
4221 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_record;");
4227 result.
prepare(
"CREATE TEMPORARY TABLE sched_temp_record "
4234 result.
prepare(
"INSERT sched_temp_record SELECT * from record;");
4242 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_recorded;");
4248 result.
prepare(
"CREATE TEMPORARY TABLE sched_temp_recorded "
4255 result.
prepare(
"INSERT sched_temp_recorded SELECT * from recorded;");
4269 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_record;");
4274 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_recorded;");
4282 if (schedTmpRecord ==
"record")
4283 schedTmpRecord =
"sched_temp_record";
4285 QString rmquery = QString(
4286"UPDATE recordmatch "
4287" INNER JOIN RECTABLE ON (recordmatch.recordid = RECTABLE.recordid) "
4288" INNER JOIN program p ON (recordmatch.chanid = p.chanid AND "
4289" recordmatch.starttime = p.starttime AND "
4290" recordmatch.manualid = p.manualid) "
4291" LEFT JOIN oldrecorded ON "
4293" RECTABLE.dupmethod > 1 AND "
4294" oldrecorded.duplicate <> 0 AND "
4295" p.title = oldrecorded.title AND "
4299" (p.programid <> '' "
4300" AND p.programid = oldrecorded.programid) "
4304" (p.programid = '' OR oldrecorded.programid = '' OR "
4305" LEFT(p.programid, LOCATE('/', p.programid)) <> "
4306" LEFT(oldrecorded.programid, LOCATE('/', oldrecorded.programid))) " :
4307" (p.programid = '' OR oldrecorded.programid = '') " )
4310" (((RECTABLE.dupmethod & 0x02) = 0) OR (p.subtitle <> '' "
4311" AND p.subtitle = oldrecorded.subtitle)) "
4313" (((RECTABLE.dupmethod & 0x04) = 0) OR (p.description <> '' "
4314" AND p.description = oldrecorded.description)) "
4316" (((RECTABLE.dupmethod & 0x08) = 0) OR "
4317" (p.subtitle <> '' AND "
4318" (p.subtitle = oldrecorded.subtitle OR "
4319" (oldrecorded.subtitle = '' AND "
4320" p.subtitle = oldrecorded.description))) OR "
4321" (p.subtitle = '' AND p.description <> '' AND "
4322" (p.description = oldrecorded.subtitle OR "
4323" (oldrecorded.subtitle = '' AND "
4324" p.description = oldrecorded.description)))) "
4328" LEFT JOIN sched_temp_recorded recorded ON "
4330" RECTABLE.dupmethod > 1 AND "
4331" recorded.duplicate <> 0 AND "
4332" p.title = recorded.title AND "
4333" p.generic = 0 AND "
4334" recorded.recgroup NOT IN ('LiveTV','Deleted') "
4337" (p.programid <> '' "
4338" AND p.programid = recorded.programid) "
4342" (p.programid = '' OR recorded.programid = '' OR "
4343" LEFT(p.programid, LOCATE('/', p.programid)) <> "
4344" LEFT(recorded.programid, LOCATE('/', recorded.programid))) " :
4345" (p.programid = '' OR recorded.programid = '') ")
4348" (((RECTABLE.dupmethod & 0x02) = 0) OR (p.subtitle <> '' "
4349" AND p.subtitle = recorded.subtitle)) "
4351" (((RECTABLE.dupmethod & 0x04) = 0) OR (p.description <> '' "
4352" AND p.description = recorded.description)) "
4354" (((RECTABLE.dupmethod & 0x08) = 0) OR "
4355" (p.subtitle <> '' AND "
4356" (p.subtitle = recorded.subtitle OR "
4357" (recorded.subtitle = '' AND "
4358" p.subtitle = recorded.description))) OR "
4359" (p.subtitle = '' AND p.description <> '' AND "
4360" (p.description = recorded.subtitle OR "
4361" (recorded.subtitle = '' AND "
4362" p.description = recorded.description)))) "
4366" LEFT JOIN oldfind ON "
4367" (oldfind.recordid = recordmatch.recordid AND "
4368" oldfind.findid = recordmatch.findid) "
4369" SET oldrecduplicate = (oldrecorded.endtime IS NOT NULL), "
4370" recduplicate = (recorded.endtime IS NOT NULL), "
4371" findduplicate = (oldfind.findid IS NOT NULL), "
4372" oldrecstatus = oldrecorded.recstatus "
4373" WHERE p.endtime >= (NOW() - INTERVAL 480 MINUTE) "
4374" AND oldrecduplicate = -1 "
4376 rmquery.replace(
"RECTABLE", schedTmpRecord);
4390 if (schedTmpRecord ==
"record")
4391 schedTmpRecord =
"sched_temp_record";
4395 QMap<int, bool> cardMap;
4396 for (
auto * enc : std::as_const(*
m_tvList))
4398 if (enc->IsConnected() || enc->IsAsleep())
4399 cardMap[enc->GetInputID()] =
true;
4402 QMap<int, bool> tooManyMap;
4403 bool checkTooMany =
false;
4407 rlist.
prepare(QString(
"SELECT recordid, title, maxepisodes, maxnewest "
4408 "FROM %1").arg(schedTmpRecord));
4416 while (rlist.
next())
4418 int recid = rlist.
value(0).toInt();
4420 int maxEpisodes = rlist.
value(2).toInt();
4421 int maxNewest = rlist.
value(3).toInt();
4423 tooManyMap[recid] =
false;
4426 if (maxEpisodes && !maxNewest)
4430 epicnt.
prepare(
"SELECT DISTINCT chanid, progstart, progend "
4432 "WHERE recordid = :RECID AND preserve = 0 "
4433 "AND recgroup NOT IN ('LiveTV','Deleted');");
4438 if (epicnt.
size() >= maxEpisodes - 1)
4441 if (epicnt.
size() >= maxEpisodes)
4443 tooManyMap[recid] =
true;
4444 checkTooMany =
true;
4460 QString pwrpri =
"channel.recpriority + capturecard.recpriority";
4464 pwrpri += QString(
" + "
4465 "IF(capturecard.cardid = RECTABLE.prefinput, 1, 0) * %1")
4471 pwrpri += QString(
" + IF(program.hdtv > 0 OR "
4472 "FIND_IN_SET('HDTV', program.videoprop) > 0, 1, 0) * %1")
4478 pwrpri += QString(
" + "
4479 "IF(FIND_IN_SET('WIDESCREEN', program.videoprop) > 0, 1, 0) * %1")
4485 pwrpri += QString(
" + "
4486 "IF(FIND_IN_SET('SIGNED', program.subtitletypes) > 0, 1, 0) * %1")
4492 pwrpri += QString(
" + "
4493 "IF(FIND_IN_SET('ONSCREEN', program.subtitletypes) > 0, 1, 0) * %1")
4494 .arg(onscrpriority);
4499 pwrpri += QString(
" + "
4500 "IF(FIND_IN_SET('NORMAL', program.subtitletypes) > 0 OR "
4501 "program.closecaptioned > 0 OR program.subtitled > 0, 1, 0) * %1")
4507 pwrpri += QString(
" + "
4508 "IF(FIND_IN_SET('HARDHEAR', program.subtitletypes) > 0 OR "
4509 "FIND_IN_SET('HARDHEAR', program.audioprop) > 0, 1, 0) * %1")
4515 pwrpri += QString(
" + "
4516 "IF(FIND_IN_SET('VISUALIMPAIR', program.audioprop) > 0, 1, 0) * %1")
4522 result.
prepare(QString(
"SELECT recpriority, selectclause FROM %1;")
4531 while (result.
next())
4533 if (result.
value(0).toBool())
4535 QString sclause = result.
value(1).toString();
4537 sclause.remove(
';');
4538 pwrpri += QString(
" + IF(%1, 1, 0) * %2")
4539 .arg(sclause).arg(result.
value(0).toInt());
4542 pwrpri += QString(
" AS powerpriority ");
4544 pwrpri.replace(
"program.",
"p.");
4545 pwrpri.replace(
"channel.",
"c.");
4546 QString query = QString(
4548 " c.chanid, c.sourceid, p.starttime, "
4549 " p.endtime, p.title, p.subtitle, "
4550 " p.description, c.channum, c.callsign, "
4551 " c.name, oldrecduplicate, p.category, "
4552 " RECTABLE.recpriority, RECTABLE.dupin, recduplicate, "
4553 " findduplicate, RECTABLE.type, RECTABLE.recordid, "
4554 " p.starttime - INTERVAL RECTABLE.startoffset "
4555 " minute AS recstartts, "
4556 " p.endtime + INTERVAL RECTABLE.endoffset "
4557 " minute AS recendts, "
4558 " p.previouslyshown, "
4559 " RECTABLE.recgroup, RECTABLE.dupmethod, c.commmethod, "
4560 " capturecard.cardid, 0, p.seriesid, "
4561 " p.programid, RECTABLE.inetref, p.category_type, "
4562 " p.airdate, p.stars, p.originalairdate, "
4563 " RECTABLE.inactive, RECTABLE.parentid, recordmatch.findid, "
4564 " RECTABLE.playgroup, oldrecstatus.recstatus, "
4565 " oldrecstatus.reactivate, p.videoprop+0, "
4566 " p.subtitletypes+0, p.audioprop+0, RECTABLE.storagegroup, "
4567 " capturecard.hostname, recordmatch.oldrecstatus, NULL, "
4568 " oldrecstatus.future, capturecard.schedorder, "
4569 " p.syndicatedepisodenumber, p.partnumber, p.parttotal, "
4570 " c.mplexid, capturecard.displayname, "
4571 " p.season, p.episode, p.totalepisodes, ") +
4574 "INNER JOIN RECTABLE ON (recordmatch.recordid = RECTABLE.recordid) "
4575 "INNER JOIN program AS p "
4576 "ON ( recordmatch.chanid = p.chanid AND "
4577 " recordmatch.starttime = p.starttime AND "
4578 " recordmatch.manualid = p.manualid ) "
4579 "INNER JOIN channel AS c "
4580 "ON ( c.chanid = p.chanid ) "
4581 "INNER JOIN capturecard "
4582 "ON ( c.sourceid = capturecard.sourceid AND "
4583 " ( capturecard.schedorder <> 0 OR "
4584 " capturecard.parentid = 0 ) ) "
4585 "LEFT JOIN oldrecorded as oldrecstatus "
4586 "ON ( oldrecstatus.station = c.callsign AND "
4587 " oldrecstatus.starttime = p.starttime AND "
4588 " oldrecstatus.title = p.title ) "
4589 "WHERE p.endtime > (NOW() - INTERVAL 480 MINUTE) "
4590 "ORDER BY RECTABLE.recordid DESC, p.starttime, p.title, c.callsign, "
4592 query.replace(
"RECTABLE", schedTmpRecord);
4594 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- Start DB Query..."));
4596 auto dbstart = nowAsDuration<std::chrono::microseconds>();
4603 auto dbend = nowAsDuration<std::chrono::microseconds>();
4604 auto dbTime = dbend - dbstart;
4606 LOG(VB_SCHEDULE, LOG_INFO,
4607 QString(
" |-- %1 results in %2 sec. Processing...")
4609 .arg(duration_cast<std::chrono::seconds>(dbTime).count()));
4613 while (result.
next())
4619 uint recordid = result.
value(17).toUInt();
4621 QString title = result.
value(4).toString();
4622 QString callsign = result.
value(8).toString();
4632 uint mplexid = result.
value(51).toUInt();
4633 if (mplexid == 32767)
4636 QString inputname = result.
value(52).toString();
4637 if (inputname.isEmpty())
4638 inputname = QString(
"Input %1").arg(result.
value(24).toUInt());
4643 result.
value(5).toString(),
4645 result.
value(6).toString(),
4646 result.
value(53).toInt(),
4647 result.
value(54).toInt(),
4648 result.
value(55).toInt(),
4649 result.
value(48).toString(),
4650 result.
value(11).toString(),
4652 result.
value(0).toUInt(),
4653 result.
value(7).toString(),
4655 result.
value(9).toString(),
4657 result.
value(21).toString(),
4658 result.
value(36).toString(),
4660 result.
value(43).toString(),
4661 result.
value(42).toString(),
4663 result.
value(30).toUInt(),
4664 result.
value(49).toUInt(),
4665 result.
value(50).toUInt(),
4667 result.
value(26).toString(),
4668 result.
value(27).toString(),
4669 result.
value(28).toString(),
4672 result.
value(12).toInt(),
4679 result.
value(31).toFloat(),
4680 (result.
value(32).isNull()) ? QDate() :
4684 result.
value(20).toBool(),
4687 result.
value(38).toBool(),
4690 result.
value(34).toUInt(),
4695 result.
value(1).toUInt(),
4696 result.
value(24).toUInt(),
4698 result.
value(35).toUInt(),
4701 result.
value(40).toUInt(),
4702 result.
value(39).toUInt(),
4703 result.
value(41).toUInt(),
4704 result.
value(46).toBool(),
4705 result.
value(47).toInt(),
4707 result.
value(24).toUInt(),
4710 if (!
p->m_future && !
p->IsReactivated() &&
4714 p->SetRecordingStatus(
p->m_oldrecstatus);
4717 p->SetRecordingPriority2(result.
value(56).toInt());
4726 if (
p->IsSameTitleStartTimeAndChannel(*r))
4728 if (r->m_sgroupId ==
p->m_sgroupId &&
4729 r->GetRecordingEndTime() !=
p->GetRecordingEndTime() &&
4730 (r->GetRecordingRuleID() ==
p->GetRecordingRuleID() ||
4745 tmpList.push_back(
p);
4752 (!cardMap.contains(
p->GetInputID()) || (
p->m_schedOrder == 0)))
4755 if (
p->m_schedOrder == 0 &&
4758 LOG(VB_GENERAL, LOG_WARNING,
LOC +
4759 QString(
"Channel %1, Title %2 %3 cardinput.schedorder = %4, "
4760 "it must be >0 to record from this input.")
4761 .arg(
p->GetChannelName(),
p->GetTitle(),
4762 p->GetScheduledStartTime().toString(),
4763 QString::number(
p->m_schedOrder)));
4769 if (checkTooMany && tooManyMap[
p->GetRecordingRuleID()] &&
4770 !
p->IsReactivated())
4778 else if (result.
value(15).toBool() && !
p->IsReactivated())
4782 !
p->IsReactivated() &&
4802 bool inactive = result.
value(33).toBool();
4817 p->SetRecordingStatus(newrecstatus);
4819 tmpList.push_back(
p);
4822 LOG(VB_SCHEDULE, LOG_INFO,
" +-- Cleanup...");
4823 for (
auto &
tmp : tmpList)
4831 QString query = QString(
4832 "SELECT RECTABLE.title, RECTABLE.subtitle, "
4833 " RECTABLE.description, RECTABLE.season, "
4834 " RECTABLE.episode, RECTABLE.category, "
4835 " RECTABLE.chanid, channel.channum, "
4836 " RECTABLE.station, channel.name, "
4837 " RECTABLE.recgroup, RECTABLE.playgroup, "
4838 " RECTABLE.seriesid, RECTABLE.programid, "
4839 " RECTABLE.inetref, RECTABLE.recpriority, "
4840 " RECTABLE.startdate, RECTABLE.starttime, "
4841 " RECTABLE.enddate, RECTABLE.endtime, "
4842 " RECTABLE.recordid, RECTABLE.type, "
4843 " RECTABLE.dupin, RECTABLE.dupmethod, "
4844 " RECTABLE.findid, "
4845 " RECTABLE.startoffset, RECTABLE.endoffset, "
4846 " channel.commmethod "
4848 "INNER JOIN channel ON (channel.chanid = RECTABLE.chanid) "
4849 "LEFT JOIN recordmatch on RECTABLE.recordid = recordmatch.recordid "
4850 "WHERE (type = %1 OR type = %2) AND "
4851 " recordmatch.chanid IS NULL")
4857 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- Start DB Query..."));
4859 auto dbstart = nowAsDuration<std::chrono::microseconds>();
4862 bool ok = result.
exec();
4863 auto dbend = nowAsDuration<std::chrono::microseconds>();
4864 auto dbTime = dbend - dbstart;
4872 LOG(VB_SCHEDULE, LOG_INFO,
4873 QString(
" |-- %1 results in %2 sec. Processing...")
4875 .arg(duration_cast<std::chrono::seconds>(dbTime).count()));
4877 while (result.
next())
4880#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
4882 result.
value(16).toDate(), result.
value(17).toTime(), Qt::UTC);
4884 result.
value(18).toDate(), result.
value(19).toTime(), Qt::UTC);
4886 static const QTimeZone utc(QTimeZone::UTC);
4888 result.
value(16).toDate(), result.
value(17).toTime(), utc);
4890 result.
value(18).toDate(), result.
value(19).toTime(), utc);
4893 QDateTime recstartts = startts.addSecs(result.
value(25).toInt() * -60LL);
4894 QDateTime recendts = endts.addSecs( result.
value(26).toInt() * +60LL);
4896 if (recstartts >= recendts)
4899 recstartts = startts;
4910 result.
value(0).toString(),
4912 (sor) ? result.
value(1).toString() : QString(),
4914 (sor) ? result.
value(2).toString() : QString(),
4915 result.
value(3).toUInt(),
4916 result.
value(4).toUInt(),
4919 result.
value(6).toUInt(),
4920 result.
value(7).toString(),
4921 result.
value(8).toString(),
4922 result.
value(9).toString(),
4924 result.
value(10).toString(),
4925 result.
value(11).toString(),
4927 result.
value(12).toString(),
4928 result.
value(13).toString(),
4929 result.
value(14).toString(),
4931 result.
value(15).toInt(),
4934 recstartts, recendts,
4938 result.
value(20).toUInt(),
4944 result.
value(24).toUInt(),
4948 tmpList.push_back(
p);
4951 for (
auto &
tmp : tmpList)
4962 QString sortColumn =
"title";
4972 QString prefixes = sh->getPrefixes();
4973 sortColumn =
"REGEXP_REPLACE(record.title,'" + prefixes +
"','')";
4977 sortColumn =
"record.recpriority";
4980 sortColumn =
"record.last_record";
4988 sortColumn =
"record.next_record IS NULL, record.next_record";
4991 sortColumn =
"record.type";
4995 QString order =
"ASC";
4999 QString query = QString(
5000 "SELECT record.title, record.subtitle, "
5001 " record.description, record.season, "
5002 " record.episode, record.category, "
5003 " record.chanid, channel.channum, "
5004 " record.station, channel.name, "
5005 " record.recgroup, record.playgroup, "
5006 " record.seriesid, record.programid, "
5007 " record.inetref, record.recpriority, "
5008 " record.startdate, record.starttime, "
5009 " record.enddate, record.endtime, "
5010 " record.recordid, record.type, "
5011 " record.dupin, record.dupmethod, "
5013 " channel.commmethod "
5015 "LEFT JOIN channel ON channel.callsign = record.station "
5016 " AND deleted IS NULL "
5017 "GROUP BY recordid "
5020 query = query.arg(sortColumn, order);
5031 while (result.
next())
5034#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
5035 QDateTime startts = QDateTime(result.
value(16).toDate(),
5036 result.
value(17).toTime(), Qt::UTC);
5037 QDateTime endts = QDateTime(result.
value(18).toDate(),
5038 result.
value(19).toTime(), Qt::UTC);
5040 static const QTimeZone utc(QTimeZone::UTC);
5041 QDateTime startts = QDateTime(result.
value(16).toDate(),
5042 result.
value(17).toTime(), utc);
5043 QDateTime endts = QDateTime(result.
value(18).toDate(),
5044 result.
value(19).toTime(), utc);
5047 if (!startts.isValid())
5049#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
5054 QTimeZone(QTimeZone::UTC));
5057 if (!endts.isValid())
5061 result.
value(0).toString(), QString(),
5062 result.
value(1).toString(), QString(),
5063 result.
value(2).toString(), result.
value(3).toUInt(),
5064 result.
value(4).toUInt(), result.
value(5).toString(),
5066 result.
value(6).toUInt(), result.
value(7).toString(),
5067 result.
value(8).toString(), result.
value(9).toString(),
5069 result.
value(10).toString(), result.
value(11).toString(),
5071 result.
value(12).toString(), result.
value(13).toString(),
5072 result.
value(14).toString(),
5074 result.
value(15).toInt(),
5081 result.
value(20).toUInt(), rectype,
5085 result.
value(24).toUInt(),
5184 QString recording_dir;
5188 "LiveTV", cur, cur.addSecs(3600), cardid,
5193 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"FindNextLiveTVDir: next dir is '%1'")
5194 .arg(recording_dir));
5201 const QString &title,
5203 const QString &storagegroup,
5204 const QDateTime &recstartts,
5205 const QDateTime &recendts,
5207 QString &recording_dir,
5210 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
"FillRecordingDir: Starting");
5215 if (cnt++ % 20 == 0)
5216 LOG(VB_SCHEDULE, LOG_WARNING,
"Waiting for main server.");
5217 std::this_thread::sleep_for(50ms);
5224 QStringList recsCounted;
5225 std::list<FileSystemInfo *> fsInfoList;
5226 std::list<FileSystemInfo *>::iterator fslistit;
5228 recording_dir.clear();
5230 if (dirlist.size() == 1)
5232 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5233 QString(
"FillRecordingDir: The only directory in the %1 Storage "
5234 "Group is %2, so it will be used by default.")
5235 .arg(storagegroup, dirlist[0]));
5236 recording_dir = dirlist[0];
5237 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
"FillRecordingDir: Finished");
5242 int weightPerRecording =
5244 int weightPerPlayback =
5246 int weightPerCommFlag =
5248 int weightPerTranscode =
5251 QString storageScheduler =
5253 int localStartingWeight =
5255 (storageScheduler !=
"Combination") ? 0
5256 : (
int)(-1.99 * weightPerRecording));
5257 int remoteStartingWeight =
5259 std::chrono::seconds maxOverlap =
5264 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5265 "FillRecordingDir: Calculating initial FS Weights.");
5276 tmpWeight = localStartingWeight;
5277 msg +=
" is local (" + QString::number(tmpWeight) +
")";
5281 tmpWeight = remoteStartingWeight;
5282 msg +=
" is remote (+" + QString::number(tmpWeight) +
")";
5292 msg +=
", has SGweightPerDir offset of "
5293 + QString::number(tmpWeight) +
")";
5295 msg +=
". initial dir weight = " + QString::number(fs->
getWeight());
5296 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, msg);
5298 fsInfoList.push_back(fs);
5301 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5302 "FillRecordingDir: Adjusting FS Weights from inuseprograms.");
5305 saveRecDir.
prepare(
"UPDATE inuseprograms "
5306 "SET recdir = :RECDIR "
5307 "WHERE chanid = :CHANID AND "
5308 " starttime = :STARTTIME");
5311 "SELECT i.chanid, i.starttime, r.endtime, recusage, rechost, recdir "
5312 "FROM inuseprograms i, recorded r "
5313 "WHERE DATE_ADD(lastupdatetime, INTERVAL 16 MINUTE) > NOW() AND "
5314 " i.chanid = r.chanid AND "
5315 " i.starttime = r.starttime");
5323 while (query.
next())
5325 uint recChanid = query.
value(0).toUInt();
5328 QString recUsage( query.
value(3).toString());
5329 QString recHost( query.
value(4).toString());
5330 QString recDir( query.
value(5).toString());
5332 if (recDir.isEmpty())
5336 recDir = recDir.isEmpty() ?
"_UNKNOWN_" : recDir;
5338 saveRecDir.
bindValue(
":RECDIR", recDir);
5339 saveRecDir.
bindValue(
":CHANID", recChanid);
5340 saveRecDir.
bindValue(
":STARTTIME", recStart);
5341 if (!saveRecDir.
exec())
5344 if (recDir ==
"_UNKNOWN_")
5347 for (fslistit = fsInfoList.begin();
5348 fslistit != fsInfoList.end(); ++fslistit)
5354 int weightOffset = 0;
5358 if (recEnd > recstartts.addSecs(maxOverlap.count()))
5360 weightOffset += weightPerRecording;
5361 recsCounted << QString::number(recChanid) +
":" +
5367 weightOffset += weightPerPlayback;
5371 weightOffset += weightPerCommFlag;
5375 weightOffset += weightPerTranscode;
5380 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5381 QString(
" %1 @ %2 in use by '%3' on %4:%5, FSID "
5382 "#%6, FSID weightOffset +%7.")
5383 .arg(QString::number(recChanid),
5385 recUsage, recHost, recDir,
5387 QString::number(weightOffset)));
5395 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5396 QString(
" %1:%2 => old weight %3 plus "
5398 .arg(
fs2->getHostname(),
5400 .arg(
fs2->getWeight())
5402 .arg(
fs2->getWeight() + weightOffset));
5404 fs2->setWeight(
fs2->getWeight() + weightOffset);
5414 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5415 "FillRecordingDir: Adjusting FS Weights from scheduler.");
5417 for (
auto *thispg : reclist)
5419 if ((recendts < thispg->GetRecordingStartTime()) ||
5420 (recstartts > thispg->GetRecordingEndTime()) ||
5423 (thispg->GetInputID() == 0) ||
5424 (recsCounted.contains(QString(
"%1:%2").arg(thispg->GetChanID())
5426 (thispg->GetPathname().isEmpty()))
5429 for (fslistit = fsInfoList.begin();
5430 fslistit != fsInfoList.end(); ++fslistit)
5433 if ((fs->
getHostname() == thispg->GetHostname()) &&
5434 (fs->
getPath() == thispg->GetPathname()))
5436 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5437 QString(
"%1 @ %2 will record on %3:%4, FSID #%5, "
5438 "weightPerRecording +%6.")
5439 .arg(thispg->GetChanID())
5442 .arg(fs->
getFSysID()).arg(weightPerRecording));
5451 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5452 QString(
" %1:%2 => old weight %3 plus %4 = %5")
5453 .arg(
fs2->getHostname(),
fs2->getPath())
5454 .arg(
fs2->getWeight()).arg(weightPerRecording)
5455 .arg(
fs2->getWeight() + weightPerRecording));
5457 fs2->setWeight(
fs2->getWeight() + weightPerRecording);
5465 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5466 QString(
"Using '%1' Storage Scheduler directory sorting algorithm.")
5467 .arg(storageScheduler));
5469 if (storageScheduler ==
"BalancedFreeSpace")
5471 else if (storageScheduler ==
"BalancedPercFreeSpace")
5473 else if (storageScheduler ==
"BalancedDiskIO")
5480 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5481 "--- FillRecordingDir Sorted fsInfoList start ---");
5482 for (fslistit = fsInfoList.begin();fslistit != fsInfoList.end();
5486 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
"%1:%2")
5488 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
" Location : %1")
5489 .arg((fs->
isLocal()) ?
"local" :
"remote"));
5490 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
" weight : %1")
5492 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
" free space : %5")
5495 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5496 "--- FillRecordingDir Sorted fsInfoList end ---");
5505 long long maxSizeKB = (maxByterate + maxByterate/3) *
5506 recstartts.secsTo(recendts) / 1024;
5508 bool simulateAutoExpire =
5511 (fsInfoList.size() > 1));
5525 for (
unsigned int pass = 1; pass <= 3; pass++)
5527 bool foundDir =
false;
5529 if ((pass == 2) && simulateAutoExpire)
5532 QMap <int , long long> remainingSpaceKB;
5533 for (fslistit = fsInfoList.begin();
5534 fslistit != fsInfoList.end(); ++fslistit)
5536 remainingSpaceKB[(*fslistit)->getFSysID()] =
5537 (*fslistit)->getFreeSpace();
5544 for (
auto & expire : expiring)
5548 for (fslistit = fsInfoList.begin();
5549 fslistit != fsInfoList.end(); ++fslistit)
5552 if (expire->GetHostname() != (*fslistit)->getHostname())
5556 if (!dirlist.contains((*fslistit)->getPath()))
5560 (*fslistit)->getPath() +
"/" + expire->GetPathname();
5567 if (checkFile.exists())
5575 QString backuppath = expire->GetPathname();
5577 bool foundSlave =
false;
5579 for (
auto * enc : std::as_const(*
m_tvList))
5581 if (enc->GetHostName() ==
5584 enc->CheckFile(programinfo);
5602 LOG(VB_GENERAL, LOG_ERR,
5603 QString(
"Unable to match '%1' "
5604 "to any file system. Ignoring it.")
5605 .arg(expire->GetBasename()));
5611 expire->GetFilesize() / 1024;
5614 long long desiredSpaceKB =
5618 (desiredSpaceKB + maxSizeKB))
5620 recording_dir = fs->
getPath();
5623 LOG(VB_FILE, LOG_INFO,
5624 QString(
"pass 2: '%1' will record in '%2' "
5625 "although there is only %3 MB free and the "
5626 "AutoExpirer wants at least %4 MB. This "
5627 "directory has the highest priority files "
5628 "to be expired from the AutoExpire list and "
5629 "there are enough that the Expirer should "
5630 "be able to free up space for this recording.")
5631 .arg(title, recording_dir)
5633 .arg(desiredSpaceKB / 1024));
5644 for (fslistit = fsInfoList.begin();
5645 fslistit != fsInfoList.end(); ++fslistit)
5647 long long desiredSpaceKB = 0;
5654 (dirlist.contains(fs->
getPath())) &&
5658 recording_dir = fs->
getPath();
5663 LOG(VB_FILE, LOG_INFO,
5664 QString(
"pass 1: '%1' will record in "
5665 "'%2' which has %3 MB free. This recording "
5666 "could use a max of %4 MB and the "
5667 "AutoExpirer wants to keep %5 MB free.")
5668 .arg(title, recording_dir)
5670 .arg(maxSizeKB / 1024)
5671 .arg(desiredSpaceKB / 1024));
5675 LOG(VB_FILE, LOG_INFO,
5676 QString(
"pass %1: '%2' will record in "
5677 "'%3' although there is only %4 MB free and "
5678 "the AutoExpirer wants at least %5 MB. "
5679 "Something will have to be deleted or expired "
5680 "in order for this recording to complete "
5682 .arg(pass).arg(title, recording_dir)
5684 .arg(desiredSpaceKB / 1024));
5697 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
"FillRecordingDir: Finished");
5710 QMap <int, bool> fsMap;
5711 for (
const auto&
fs1 : std::as_const(fsInfos))
5713 fsMap[
fs1.getFSysID()] =
true;
5717 LOG(VB_FILE, LOG_INFO,
LOC +
5718 QString(
"FillDirectoryInfoCache: found %1 unique filesystems")
5719 .arg(fsMap.size()));
5726 auto secsleft = std::chrono::seconds(curtime.secsTo(
m_livetvTime));
5730 if (secsleft - prerollseconds > 120s)
5734 for (
auto * enc : std::as_const(*
m_tvList))
5750 if (
m_schedTime.secsTo(dummy->GetRecordingEndTime()) < 1800)
5751 dummy->SetRecordingEndTime(
m_schedTime.addSecs(1800));
5752 dummy->SetInputID(enc->GetInputID());
5753 dummy->m_mplexId = dummy->QueryMplexID();
5776 bool autoStart =
false;
5778 QDateTime startupTime = QDateTime();
5784 if (startupTime.isValid())
5787 startupSecs = std::max(startupSecs, 15 * 60s);
5794 LOG(VB_GENERAL, LOG_INFO,
5795 "Close to auto-start time, AUTO-Startup assumed");
5801 LOG(VB_GENERAL, LOG_INFO,
5802 "Close to MythFillDB suggested run time, AUTO-Startup to fetch guide data?");
5808 LOG(VB_GENERAL, LOG_DEBUG,
5809 "NOT close to auto-start time, USER-initiated startup assumed");
5812 else if (!s.isEmpty())
5814 LOG(VB_GENERAL, LOG_ERR,
LOC +
5815 QString(
"Invalid MythShutdownWakeupTime specified in database (%1)")
5827 QMap<uint, QSet<uint> > inputSets;
5828 query.
prepare(
"SELECT DISTINCT ci1.cardid, ci2.cardid "
5829 "FROM capturecard ci1, capturecard ci2, "
5830 " inputgroup ig1, inputgroup ig2 "
5831 "WHERE ci1.cardid = ig1.cardinputid AND "
5832 " ci2.cardid = ig2.cardinputid AND"
5833 " ig1.inputgroupid = ig2.inputgroupid AND "
5834 " ci1.cardid <= ci2.cardid "
5835 "ORDER BY ci1.cardid, ci2.cardid");
5841 while (query.
next())
5845 inputSets[id0].insert(id1);
5846 inputSets[id1].insert(id0);
5849 QMap<uint, QSet<uint> >::iterator mit;
5850 for (mit = inputSets.begin(); mit != inputSets.end(); ++mit)
5852 uint inputid = mit.key();
5860 QSet<uint> fullset = mit.value();
5861 QSet<uint> checkset;
5862 QSet<uint>::const_iterator sit;
5863 while (checkset != fullset)
5866 for (
int item : std::as_const(checkset))
5867 fullset += inputSets[item];
5872 auto *conflictlist =
new RecList();
5874 for (
int item : std::as_const(checkset))
5876 LOG(VB_SCHEDULE, LOG_INFO,
5877 QString(
"Assigning input %1 to conflict set %2")
5885 query.
prepare(
"SELECT ci.cardid "
5886 "FROM capturecard ci "
5887 "LEFT JOIN inputgroup ig "
5888 " ON ci.cardid = ig.cardinputid "
5889 "WHERE ig.cardinputid IS NULL");
5895 while (query.
next())
5899 LOG(VB_GENERAL, LOG_ERR,
LOC +
5900 QString(
"Input %1 is not assigned to any input group").arg(
id));
5901 auto *conflictlist =
new RecList();
5903 LOG(VB_SCHEDULE, LOG_INFO,
5904 QString(
"Assigning input %1 to conflict set %2")
5918 query.
prepare(
"SELECT cardid, parentid, schedgroup "
5920 "WHERE sourceid > 0 "
5928 while (query.
next())
5931 uint parentid = query.
value(1).toUInt();
5948 LOG(VB_SCHEDULE, LOG_INFO,
5949 QString(
"Added SchedInputInfo i=%1, g=%2, sg=%3")
5958 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
5959 QString(
"AddChildInput: Handling parent = %1, input = %2")
5960 .arg(parentid).arg(childid));
std::vector< ProgramInfo * > pginfolist_t
static GlobalSpinBoxSetting * idleTimeoutSecs()
static GlobalSpinBoxSetting * idleWaitForRecordingTime()
static GlobalTextEditSetting * startupCommand()
static GlobalTextEditSetting * preSDWUCheckCommand()
static void Update(int encoder, int fsID, bool immediately)
This is used to update the global AutoExpire instance "expirer".
void GetAllExpiring(QStringList &strList)
Gets the full list of programs that can expire in expiration order.
uint64_t GetDesiredSpace(int fsID) const
Used by the scheduler to select the next recording dir.
static void ClearExpireList(pginfolist_t &expireList, bool deleteProg=true)
Clears expireList, freeing any ProgramInfo's if necessary.
static std::vector< uint > GetChildInputIDs(uint inputid)
static std::vector< uint > GetConflictingInputs(uint inputid)
Provides an interface to both local and remote TVRec's for the mythbackend.
void SetNextLiveTVDir(const QString &dir)
Tells TVRec where to put the next LiveTV recording.
RecStatus::Type StartRecording(ProgramInfo *rec)
Tells TVRec to Start recording the program "rec" as soon as possible.
bool IsConnected(void) const
Returns true if the EncoderLink instance is usable.
long long GetMaxBitrate(void)
Returns maximum bits per second this recorder might output.
bool IsLocal(void) const
Returns true for a local EncoderLink.
bool IsWaking(void) const
Returns true if the encoder is waking up.
bool IsBusy(InputInfo *busy_input=nullptr, std::chrono::seconds time_buffer=5s)
Returns true if the recorder is busy, or will be within the next time_buffer seconds.
bool IsTunerLocked(void) const
Returns true iff the tuner is locked.
QDateTime GetLastWakeTime(void) const
Get the last time the encoder was awakened.
void RecordPending(const ProgramInfo *rec, std::chrono::seconds secsleft, bool hasLater)
Tells TVRec there is a pending recording "rec" in "secsleft" seconds.
QDateTime GetSleepStatusTime(void) const
Get the last time the sleep status was changed.
bool IsAsleep(void) const
Returns true if the encoder is asleep.
QString GetHostName(void) const
Returns the remote host for a non-local EncoderLink.
void setWeight(int weight)
QString getHostname() const
int64_t getTotalSpace() const
int64_t getFreeSpace() const
static bool HasRunningOrPendingJobs(std::chrono::minutes startingWithinMins=0min)
QSqlQuery wrapper that fetches a DB connection from the connection pool.
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
QVariant value(int i) const
static MSqlQueryInfo SchedCon()
Returns dedicated connection. (Required for using temporary SQL tables.)
bool isActive(void) const
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
static MSqlQueryInfo ChannelCon()
Returns dedicated connection. (Required for using temporary SQL tables.)
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
This is a wrapper around QThread that does several additional things.
bool isRunning(void) const
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
static void usleep(std::chrono::microseconds time)
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
void ShutSlaveBackendsDown(const QString &haltcmd)
Sends the Slavebackends the request to shut down using haltcmd.
bool isClientConnected(bool onlyBlockingClients=false)
void GetFilesystemInfos(FileSystemInfoList &fsInfos, bool useCache=true)
QString GetHostName(void)
QString GetSetting(const QString &key, const QString &defaultval="")
void SendSystemEvent(const QString &msg)
bool SaveSettingOnHost(const QString &key, const QString &newValue, const QString &host)
QString GetSettingOnHost(const QString &key, const QString &host, const QString &defaultval="")
void dispatch(const MythEvent &event)
int GetNumSetting(const QString &key, int defaultval=0)
std::enable_if_t< std::chrono::__is_duration< T >::value, T > GetDurSetting(const QString &key, T defaultval=T::zero())
bool GetBoolSetting(const QString &key, bool defaultval=false)
static void DBError(const QString &where, const MSqlQuery &query)
T dequeue()
Removes item from front of list and returns a copy. O(1).
void enqueue(const T &d)
Adds item to the back of the list. O(1).
This class is used as a container for messages.
Holds information on recordings and videos.
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
uint GetRecordingRuleID(void) const
QString toString(Verbosity v=kLongDescription, const QString &sep=":", const QString &grp="\"") const
void SetRecordingPriority2(int priority)
bool IsSameTitleStartTimeAndChannel(const ProgramInfo &other) const
Checks title, chanid or callsign and start times for equality.
QString GetProgramID(void) const
bool IsDuplicateProgram(const ProgramInfo &other) const
Checks for duplicates according to dupmethod.
void SetRecordingRuleType(RecordingType type)
uint GetRecordingID(void) const
QDateTime GetScheduledEndTime(void) const
The scheduled end time of the program.
void SetRecordingStatus(RecStatus::Type status)
QString GetHostname(void) const
static bool UsingProgramIDAuthority(void)
uint GetSourceID(void) const
QString DiscoverRecordingDirectory(void)
bool IsReactivated(void) const
QString GetDescription(void) const
QString GetStorageGroup(void) const
void SetRecordingStartTime(const QDateTime &dt)
QString GetTitle(void) const
static void CheckProgramIDAuthorities(void)
QDateTime GetRecordingStartTime(void) const
Approximate time the recording started.
QDateTime GetScheduledStartTime(void) const
The scheduled start time of program.
QString GetChanNum(void) const
This is the channel "number", in the form 1, 1_2, 1-2, 1#1, etc.
void SetRecordingRuleID(uint id)
QString MakeUniqueKey(void) const
Creates a unique string that can be used to identify an existing recording.
bool IsSameRecording(const ProgramInfo &other) const
int GetRecordingPriority(void) const
QString GetPathname(void) const
uint GetInputID(void) const
int GetRecordingPriority2(void) const
uint GetParentRecordingRuleID(void) const
void ToStringList(QStringList &list) const
Serializes ProgramInfo into a QStringList which can be passed over a socket.
void SetRecordingEndTime(const QDateTime &dt)
RecStatus::Type GetRecordingStatus(void) const
QDateTime GetRecordingEndTime(void) const
Approximate time the recording should have ended, did end, or is intended to end.
QString GetSubtitle(void) const
void SetPathname(const QString &pn)
RecordingType GetRecordingRuleType(void) const
QString GetChannelSchedulingID(void) const
This is the unique programming identifier of a channel.
static QString toString(RecStatus::Type recstatus, uint id)
Converts "recstatus" into a short (unreadable) string.
static QString toUIState(RecStatus::Type recstatus)
static void create(Scheduler *scheduler, RecordingInfo &ri)
Create an instance of the RecordingExtender if necessary, and add this recording to the list of new r...
Holds information on a TV Program one might wish to record.
RecStatus::Type m_oldrecstatus
static const QRegularExpression kReLeadingAnd
void AddHistory(bool resched=true, bool forcedup=false, bool future=false)
Adds recording history, creating "record" it if necessary.
void SetRecordingID(uint _recordedid) override
static const int kNumFilters
QWaitCondition m_reschedWait
QMap< int, bool > m_schedAfterStartMap
const RecordingInfo * FindConflict(const RecordingInfo *p, OpenEndType openEnd=openEndNever, uint *affinity=nullptr, bool checkAll=false) const
QMap< int, EncoderLink * > * m_tvList
bool WakeUpSlave(const QString &slaveHostname, bool setWakingStatus=true)
void SlaveConnected(const RecordingList &slavelist)
void FillDirectoryInfoCache(void)
static void PrintRec(const RecordingInfo *p, const QString &prefix="")
bool IsSameProgram(const RecordingInfo *a, const RecordingInfo *b) const
QMap< QString, FileSystemInfo > m_fsInfoCache
bool AssignGroupInput(RecordingInfo &ri, std::chrono::seconds prerollseconds)
void BackupRecStatus(void)
bool IsBusyRecording(const RecordingInfo *rcinfo)
MythDeque< QStringList > m_reschedQueue
void MarkOtherShowings(RecordingInfo *p)
bool HaveQueuedRequests(void)
static bool VerifyCards(void)
QDateTime m_lastPrepareTime
bool GetAllPending(RecList &retList, int recRuleId=0) const
std::chrono::milliseconds m_delayShutdownTime
void RestoreRecStatus(void)
bool CreateConflictLists(void)
void CreateTempTables(void)
void EnqueueCheck(const RecordingInfo &recinfo, const QString &why)
std::pair< const RecordingInfo *, const RecordingInfo * > IsSameKey
void AddChildInput(uint parentid, uint childid)
void FillRecordListFromDB(uint recordid=0)
void UpdateMatches(uint recordid, uint sourceid, uint mplexid, const QDateTime &maxstarttime)
void ShutdownServer(std::chrono::seconds prerollseconds, QDateTime &idleSince)
void SchedNewFirstPass(RecIter &start, const RecIter &end, int recpriority, int recpriority2)
QMutex m_resetIdleTimeLock
bool HandleRecording(RecordingInfo &ri, bool &statuschanged, QDateTime &nextStartTime, QDateTime &nextWakeTime, std::chrono::seconds prerollseconds)
bool HandleRunSchedulerStartup(std::chrono::seconds prerollseconds, std::chrono::minutes idleWaitForRecordingTime)
void BuildNewRecordsQueries(uint recordid, QStringList &from, QStringList &where, MSqlBindings &bindings)
void UpdateNextRecord(void)
QSet< uint > m_schedOrderWarned
void EnqueuePlace(const QString &why)
Scheduler(bool runthread, QMap< int, EncoderLink * > *_tvList, const QString &tmptable="record", Scheduler *master_sched=nullptr)
static bool WasStartedAutomatically()
bool FindNextConflict(const RecList &cardlist, const RecordingInfo *p, RecConstIter &iter, OpenEndType openEnd=openEndNever, uint *paffinity=nullptr, bool ignoreinput=false) const
int FillRecordingDir(const QString &title, const QString &hostname, const QString &storagegroup, const QDateTime &recstartts, const QDateTime &recendts, uint cardid, QString &recording_dir, const RecList &reclist)
void ResetDuplicates(uint recordid, uint findid, const QString &title, const QString &subtitle, const QString &descrip, const QString &programid)
void PrintList(bool onlyFutureRecordings=false)
bool FillRecordList(void)
static void GetAllScheduled(QStringList &strList, SchedSortColumn sortBy=kSortTitle, bool ascending=true)
Returns all scheduled programs serialized into a QStringList.
void SchedNewRetryPass(const RecIter &start, const RecIter &end, bool samePriority, bool livetv=false)
void HandleWakeSlave(RecordingInfo &ri, std::chrono::seconds prerollseconds)
void getConflicting(RecordingInfo *pginfo, QStringList &strlist)
std::vector< RecList * > m_conflictLists
bool ChangeRecordingEnd(RecordingInfo *oldp, RecordingInfo *newp)
RecStatus::Type GetRecStatus(const ProgramInfo &pginfo)
void run(void) override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
void DeleteTempTables(void)
void EnqueueMatch(uint recordid, uint sourceid, uint mplexid, const QDateTime &maxstarttime, const QString &why)
void Reschedule(const QStringList &request)
bool InitInputInfoMap(void)
QMap< uint, RecList > m_recordIdListMap
void PruneRedundants(void)
void SlaveDisconnected(uint cardid)
MainServer * m_mainServer
void PutInactiveSlavesToSleep(void)
QMap< QString, ProgramInfo * > GetRecording(void) const override
void OldRecordedFixups(void)
bool TryAnotherShowing(RecordingInfo *p, bool samePriority, bool livetv=false)
void GetNextLiveTVDir(uint cardid)
void FillRecordListFromMaster(void)
void ClearRequestQueue(void)
void UpdateManuals(uint recordid)
IsSameCacheType m_cacheIsSameProgram
std::array< QSet< QString >, 4 > m_sysEvents
void AddRecording(const RecordingInfo &pi)
bool HandleReschedule(void)
void UpdateDuplicates(void)
void HandleIdleShutdown(bool &blockShutdown, QDateTime &idleSince, std::chrono::seconds prerollseconds, std::chrono::seconds idleTimeoutSecs, std::chrono::minutes idleWaitForRecordingTime, bool statuschanged)
void MarkShowingsList(const RecList &showinglist, RecordingInfo *p)
QMap< uint, SchedInputInfo > m_sinputInfoMap
void HandleRecordingStatusChange(RecordingInfo &ri, RecStatus::Type recStatus, const QString &details)
void SetMainServer(MainServer *ms)
void UpdateRecStatus(RecordingInfo *pginfo)
QMap< QString, RecList > m_titleListMap
void SchedNewRecords(void)
static bool CheckShutdownServer(std::chrono::seconds prerollseconds, QDateTime &idleSince, bool &blockShutdown, uint logmask)
QStringList GetDirList(void) const
static QReadWriteLock s_inputsLock
static pid_list_t::iterator find(const PIDInfoMap &map, pid_list_t &list, pid_list_t::iterator begin, pid_list_t::iterator end, bool find_open)
@ GENERIC_EXIT_OK
Exited with no error.
@ GENERIC_EXIT_NOT_OK
Exited with error.
QVector< FileSystemInfo > FileSystemInfoList
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
QMap< QString, QVariant > MSqlBindings
typedef for a map of string -> string bindings for generic queries.
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
bool IsMACAddress(const QString &MAC)
bool WakeOnLAN(const QString &MAC)
RecList::const_iterator RecConstIter
RecList::iterator RecIter
std::deque< RecordingInfo * > RecList
std::shared_ptr< MythSortHelper > getMythSortHelper(void)
Get a pointer to the MythSortHelper singleton.
void SendMythSystemRecEvent(const QString &msg, const RecordingInfo *pginfo)
uint myth_system(const QString &command, uint flags, std::chrono::seconds timeout)
QDateTime as_utc(const QDateTime &old_dt)
Returns copy of QDateTime with TimeSpec set to UTC.
std::chrono::seconds secsInPast(const QDateTime &past)
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
@ kDatabase
Default UTC, database format.
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
ProgramInfo::CategoryType string_to_myth_category_type(const QString &category_type)
bool LoadFromScheduler(AutoDeleteDeque< TYPE * > &destination, bool &hasConflicts, const QString &altTable="", int recordid=-1)
const QString kTranscoderInUseID
const QString kPlayerInUseID
const QString kFlaggerInUseID
const QString kRecorderInUseID
QChar toQChar(RecordingType rectype)
Converts "rectype" into a human readable character.
int RecTypePrecedence(RecordingType rectype)
Converts a RecordingType to a simple integer so it's specificity can be compared to another.
static QString fs1(QT_TRANSLATE_NOOP("SchedFilterEditor", "Identifiable episode"))
static QString fs2(QT_TRANSLATE_NOOP("SchedFilterEditor", "First showing"))
static bool comp_retry(RecordingInfo *a, RecordingInfo *b)
static bool comp_storage_combination(FileSystemInfo *a, FileSystemInfo *b)
static bool comp_redundant(RecordingInfo *a, RecordingInfo *b)
static QString progfindid
static bool comp_overlap(RecordingInfo *a, RecordingInfo *b)
static void erase_nulls(RecList &reclist)
static bool comp_recstart(RecordingInfo *a, RecordingInfo *b)
static bool comp_storage_disk_io(FileSystemInfo *a, FileSystemInfo *b)
static bool comp_storage_perc_free_space(FileSystemInfo *a, FileSystemInfo *b)
static bool comp_storage_free_space(FileSystemInfo *a, FileSystemInfo *b)
static bool comp_priority(RecordingInfo *a, RecordingInfo *b)
static QString progdupinit
static bool Recording(const RecordingInfo *p)
static constexpr int64_t kProgramInUseInterval
@ sStatus_Waking
A slave is marked as waking when the master runs the slave's wakeup command.
@ sStatus_Undefined
A slave's sleep status is undefined when it has never connected to the master backend or is not able ...
@ sStatus_FallingAsleep
A slave is marked as falling asleep when told to shutdown by the master.
@ kState_WatchingLiveTV
Watching LiveTV is the state for when we are watching a recording and the user has control over the c...