11# include <sys/param.h>
13# include <sys/mount.h>
55#define LOC QString("Scheduler: ")
56#define LOC_WARN QString("Scheduler, Warning: ")
57#define LOC_ERR QString("Scheduler, Error: ")
64 const QString& tmptable,
Scheduler *master_sched) :
66 m_recordTable(tmptable),
67 m_priorityTable(
"powerpriority"),
68 m_specSched(master_sched),
80 if (tmptable ==
"powerpriority_tmp")
95 start(QThread::LowPriority);
161 if (!query.
exec(
"SELECT count(*) FROM capturecard") || !query.
next())
170 LOG(VB_GENERAL, LOG_ERR,
LOC +
171 "No capture cards are defined in the database.\n\t\t\t"
172 "Perhaps you should re-read the installation instructions?");
176 query.
prepare(
"SELECT sourceid,name FROM videosource ORDER BY sourceid;");
191 "WHERE sourceid = :SOURCEID "
195 if (!subquery.
exec())
199 else if (!subquery.
next())
201 LOG(VB_GENERAL, LOG_WARNING,
LOC +
202 QString(
"Video source '%1' is defined, "
203 "but is not attached to a card input.")
204 .arg(query.
value(1).toString()));
214 LOG(VB_GENERAL, LOG_ERR,
LOC +
215 "No channel sources defined in the database");
264 return aprec < bprec;
287 Qt::CaseInsensitive);
301 Qt::CaseInsensitive);
315 int arec =
static_cast<int>
320 int brec =
static_cast<int>
335 int atype =
static_cast<int>
338 int btype =
static_cast<int>
342 return atype > btype;
345 int apast =
static_cast<int>
347 int bpast =
static_cast<int>
350 return apast < bpast;
381 int arec =
static_cast<int>
384 int brec =
static_cast<int>
397 int atype =
static_cast<int>
400 int btype =
static_cast<int>
404 return atype > btype;
407 int apast =
static_cast<int>
409 int bpast =
static_cast<int>
412 return apast < bpast;
447 LOG(VB_SCHEDULE, LOG_INFO,
"BuildWorkList...");
452 LOG(VB_SCHEDULE, LOG_INFO,
"AddNewRecords...");
454 LOG(VB_SCHEDULE, LOG_INFO,
"AddNotListed...");
457 LOG(VB_SCHEDULE, LOG_INFO,
"Sort by time...");
459 LOG(VB_SCHEDULE, LOG_INFO,
"PruneOverlaps...");
462 LOG(VB_SCHEDULE, LOG_INFO,
"Sort by priority...");
464 LOG(VB_SCHEDULE, LOG_INFO,
"BuildListMaps...");
466 LOG(VB_SCHEDULE, LOG_INFO,
"SchedNewRecords...");
468 LOG(VB_SCHEDULE, LOG_INFO,
"SchedLiveTV...");
470 LOG(VB_SCHEDULE, LOG_INFO,
"ClearListMaps...");
475 LOG(VB_SCHEDULE, LOG_INFO,
"Sort by time...");
477 LOG(VB_SCHEDULE, LOG_INFO,
"PruneRedundants...");
480 LOG(VB_SCHEDULE, LOG_INFO,
"Sort by time...");
482 LOG(VB_SCHEDULE, LOG_INFO,
"ClearWorkList...");
500 where =
"WHERE recordid IS NULL ";
502 thequery = QString(
"CREATE TEMPORARY TABLE recordmatch ") +
503 "SELECT * FROM recordmatch " + where +
"; ";
507 bool ok = query.
exec();
515 thequery =
"ALTER TABLE recordmatch "
516 " ADD UNIQUE INDEX (recordid, chanid, starttime); ";
524 thequery =
"ALTER TABLE recordmatch "
525 " ADD INDEX (chanid, starttime, manualid); ";
535 auto fillstart = nowAsDuration<std::chrono::microseconds>();
537 auto fillend = nowAsDuration<std::chrono::microseconds>();
538 auto matchTime = fillend - fillstart;
540 LOG(VB_SCHEDULE, LOG_INFO,
"CreateTempTables...");
543 fillstart = nowAsDuration<std::chrono::microseconds>();
544 LOG(VB_SCHEDULE, LOG_INFO,
"UpdateDuplicates...");
546 fillend = nowAsDuration<std::chrono::microseconds>();
547 auto checkTime = fillend - fillstart;
549 fillstart = nowAsDuration<std::chrono::microseconds>();
551 fillend = nowAsDuration<std::chrono::microseconds>();
552 auto placeTime = fillend - fillstart;
554 LOG(VB_SCHEDULE, LOG_INFO,
"DeleteTempTables...");
558 queryDrop.
prepare(
"DROP TABLE recordmatch;");
559 if (!queryDrop.
exec())
565 QString msg = QString(
"Speculative scheduled %1 items in %2 "
566 "= %3 match + %4 check + %5 place")
568 .arg(duration_cast<floatsecs>(matchTime + checkTime + placeTime).count(), 0,
'f', 1)
569 .arg(duration_cast<floatsecs>(matchTime).count(), 0,
'f', 2)
570 .arg(duration_cast<floatsecs>(checkTime).count(), 0,
'f', 2)
571 .arg(duration_cast<floatsecs>(placeTime).count(), 0,
'f', 2);
572 LOG(VB_GENERAL, LOG_INFO, msg);
583 for (
auto & it : schedList)
594 LOG(VB_SCHEDULE, LOG_INFO,
"--- print list start ---");
595 LOG(VB_SCHEDULE, LOG_INFO,
"Title - Subtitle Ch Station "
596 "Day Start End G I T N Pri");
598 for (
auto *first : list)
600 if (onlyFutureRecordings &&
601 ((first->GetRecordingEndTime() < now &&
602 first->GetScheduledEndTime() < now) ||
603 (first->GetRecordingStartTime() < now && !
Recording(first))))
609 LOG(VB_SCHEDULE, LOG_INFO,
"--- print list end ---");
622 static QString initialOutstr =
" ";
624 static QString initialOutstr =
"";
627 QString outstr = initialOutstr +
prefix;
630 .leftJustified(34 -
prefix.length(),
' ',
true);
632 outstr += QString(
"%1 %2 %3 %4-%5 %6 %7 ")
634 p->GetChanNum().rightJustified(5,
' '),
635 p->GetChannelSchedulingID().leftJustified(7,
' ',
true),
636 p->GetRecordingStartTime().toLocalTime().toString(
"dd hh:mm"),
637 p->GetRecordingEndTime().toLocalTime().toString(
"hh:mm"),
638 p->GetShortInputName().rightJustified(2,
' '),
639 QString::number(
p->GetInputID()).rightJustified(2,
' '));
640 outstr += QString(
"%1 %2 %3")
641 .arg(
toQChar(
p->GetRecordingRuleType()))
643 .arg(
p->GetRecordingPriority());
644 if (
p->GetRecordingPriority2())
645 outstr += QString(
"/%1").arg(
p->GetRecordingPriority2());
647 LOG(VB_SCHEDULE, LOG_INFO, outstr);
656 if (
p->IsSameTitleTimeslotAndChannel(*pginfo))
675 LOG(VB_GENERAL, LOG_INFO,
676 QString(
"Updating status for %1 on cardid [%2] (%3 => %4)")
678 QString::number(
p->GetInputID()),
680 p->GetRecordingRuleType()),
682 p->GetRecordingRuleType())));
690 p->AddHistory(
false);
708 const QDateTime &startts,
710 const QDateTime &recendts)
716 if (
p->GetInputID() == cardid &&
p->GetChanID() == chanid &&
717 p->GetScheduledStartTime() == startts)
719 p->SetRecordingEndTime(recendts);
721 if (
p->GetRecordingStatus() != recstatus)
723 LOG(VB_GENERAL, LOG_INFO,
724 QString(
"Updating status for %1 on cardid [%2] (%3 => %4)")
726 QString::number(
p->GetInputID()),
728 p->GetRecordingRuleType()),
730 p->GetRecordingRuleType())));
736 p->SetRecordingStatus(recstatus);
738 p->AddHistory(
false);
789 LOG(VB_GENERAL, LOG_ERR,
790 QString(
"Failed to change end time on card %1 to %2")
837 for (
auto *sp : slavelist)
843 if (!sp->GetTitle().isEmpty() &&
844 sp->GetScheduledStartTime() == rp->GetScheduledStartTime() &&
845 sp->GetChannelSchedulingID().compare(
846 rp->GetChannelSchedulingID(), Qt::CaseInsensitive) == 0 &&
847 sp->GetTitle().compare(rp->GetTitle(),
848 Qt::CaseInsensitive) == 0)
850 if (sp->GetInputID() == rp->GetInputID() ||
855 rp->SetRecordingStatus(sp->GetRecordingStatus());
857 rp->AddHistory(
false);
858 LOG(VB_GENERAL, LOG_INFO,
859 QString(
"setting %1/%2/\"%3\" as %4")
860 .arg(QString::number(sp->GetInputID()),
861 sp->GetChannelSchedulingID(),
867 LOG(VB_GENERAL, LOG_NOTICE,
868 QString(
"%1/%2/\"%3\" is already recording on card %4")
869 .arg(sp->GetInputID())
870 .arg(sp->GetChannelSchedulingID(),
872 .arg(rp->GetInputID()));
875 else if (sp->GetInputID() == rp->GetInputID() &&
882 rp->AddHistory(
false);
883 LOG(VB_GENERAL, LOG_INFO,
884 QString(
"setting %1/%2/\"%3\" as aborted")
885 .arg(QString::number(rp->GetInputID()),
886 rp->GetChannelSchedulingID(),
891 if (sp->GetInputID() && !found)
893 sp->m_mplexId = sp->QueryMplexID();
897 sp->AddHistory(
false);
898 LOG(VB_GENERAL, LOG_INFO,
899 QString(
"adding %1/%2/\"%3\" as recording")
900 .arg(QString::number(sp->GetInputID()),
901 sp->GetChannelSchedulingID(),
913 if (rp->GetInputID() == cardid &&
922 rp->AddHistory(
false,
false,
true);
927 rp->AddHistory(
false);
930 LOG(VB_GENERAL, LOG_INFO, QString(
"setting %1/%2/\"%3\" as aborted")
931 .arg(QString::number(rp->GetInputID()), rp->GetChannelSchedulingID(),
983 for (
auto it = reclist.begin(); it != reclist.end(); ++it)
1011 *(dreciter++) =
nullptr;
1020 QMap<uint, uint> badinputs;
1035 ++badinputs[
p->GetInputID()];
1038 conflictlist->push_back(
p);
1044 QMap<uint, uint>::iterator it;
1045 for (it = badinputs.begin(); it != badinputs.end(); ++it)
1048 QString(
"Ignored %1 entries for invalid input %2")
1049 .arg(badinputs[it.value()]).arg(it.key()));
1084 bool ignoreinput)
const
1087 for ( ; iter != cardlist.end(); ++iter)
1100 msg = QString(
"comparing '%1' on %2 with '%3' on %4")
1101 .arg(
p->GetTitle(),
p->GetChanNum(),
1105 if (
p->GetInputID() != q->
GetInputID() && !ignoreinput)
1107 const std::vector<unsigned int> &conflicting_inputs =
1109 if (
find(conflicting_inputs.begin(), conflicting_inputs.end(),
1110 q->
GetInputID()) == conflicting_inputs.end())
1113 msg +=
" cardid== ";
1122 msg +=
" no-overlap ";
1129 (((
p->m_mplexId != 0U) &&
p->m_mplexId == q->
m_mplexId) ||
1130 ((
p->m_mplexId == 0U) &&
p->GetChanID() == q->
GetChanID()));
1142 msg +=
" no-overlap ";
1151 LOG(VB_SCHEDULE, LOG_INFO, msg);
1152 LOG(VB_SCHEDULE, LOG_INFO,
1153 QString(
" cardid's: [%1], [%2] Share an input group, "
1154 "mplexid's: %3, %4")
1168 LOG(VB_SCHEDULE, LOG_INFO,
"Found conflict");
1171 *paffinity += affinity;
1176 LOG(VB_SCHEDULE, LOG_INFO,
"No conflict");
1179 *paffinity += affinity;
1187 bool checkAll)
const
1190 auto k = conflictlist.cbegin();
1197 return firstConflict;
1224 for (
auto *q : showinglist)
1233 if (q->IsSameTitleStartTimeAndChannel(*
p))
1239 if (q->GetRecordingStartTime() <
p->GetRecordingStartTime())
1251 p->m_savedrecstatus =
p->GetRecordingStatus();
1259 p->SetRecordingStatus(
p->m_savedrecstatus);
1280 uint bestaffinity = 0;
1282 for (
auto *q : *showinglist)
1288 (q->GetRecordingPriority() <
p->GetRecordingPriority() ||
1289 (q->GetRecordingPriority() ==
p->GetRecordingPriority() &&
1290 q->GetRecordingPriority2() <
p->GetRecordingPriority2())))
1302 if (!
p->IsSameTitleStartTimeAndChannel(*q))
1337 PrintRec(q, QString(
" %1:").arg(affinity));
1338 if (!best || affinity > bestaffinity)
1341 bestaffinity = affinity;
1349 QString msg = QString(
1350 "Moved \"%1\" on chanid: %2 from card: %3 to %4 at %5 "
1351 "to avoid LiveTV conflict")
1352 .arg(
p->GetTitle()).arg(
p->GetChanID())
1355 LOG(VB_GENERAL, LOG_INFO, msg);
1367 p->SetRecordingStatus(oldstatus);
1375 LOG(VB_SCHEDULE, LOG_DEBUG,
1376 "+ = schedule this showing to be recorded");
1377 LOG(VB_SCHEDULE, LOG_DEBUG,
1378 "n: = could schedule this showing with affinity");
1379 LOG(VB_SCHEDULE, LOG_DEBUG,
1380 "n# = could not schedule this showing, with affinity");
1381 LOG(VB_SCHEDULE, LOG_DEBUG,
1382 "! = conflict caused by this showing");
1383 LOG(VB_SCHEDULE, LOG_DEBUG,
1384 "/ = retry this showing, same priority pass");
1385 LOG(VB_SCHEDULE, LOG_DEBUG,
1386 "? = retry this showing, lower priority pass");
1387 LOG(VB_SCHEDULE, LOG_DEBUG,
1388 "> = try another showing for this program");
1389 LOG(VB_SCHEDULE, LOG_DEBUG,
1390 "- = unschedule a showing in favor of another one");
1409 auto levelStart = i;
1410 int recpriority = (*i)->GetRecordingPriority();
1415 (*i)->GetRecordingPriority() != recpriority)
1418 auto sublevelStart = i;
1419 int recpriority2 = (*i)->GetRecordingPriority2();
1420 LOG(VB_SCHEDULE, LOG_DEBUG, QString(
"Trying priority %1/%2...")
1421 .arg(recpriority).arg(recpriority2));
1425 LOG(VB_SCHEDULE, LOG_DEBUG, QString(
"Retrying priority %1/%2...")
1426 .arg(recpriority).arg(recpriority2));
1431 LOG(VB_SCHEDULE, LOG_DEBUG, QString(
"Retrying priority %1/*...")
1441 int recpriority,
int recpriority2)
1447 for ( ; i != end; ++i)
1449 if ((*i)->GetRecordingPriority() != recpriority ||
1450 (*i)->GetRecordingPriority2() != recpriority2 ||
1457 (*i)->GetRecordingPriority() != recpriority ||
1458 (*i)->GetRecordingPriority2() != recpriority2)
1463 uint bestaffinity = 0;
1466 for ( ; i != end; ++i)
1468 if ((*i)->GetRecordingPriority() != recpriority ||
1469 (*i)->GetRecordingPriority2() != recpriority2 ||
1470 (*i)->GetRecordingStartTime() !=
1472 (*i)->GetRecordingRuleID() !=
1474 (*i)->GetTitle() != first->
GetTitle() ||
1489 PrintRec(*i, QString(
" %1#").arg(affinity));
1494 PrintRec(*i, QString(
" %1:").arg(affinity));
1495 if (!best || affinity > bestaffinity)
1498 bestaffinity = affinity;
1519 bool samePriority,
bool livetv)
1523 for ( ; i != end; ++i)
1526 retry_list.push_back(*i);
1528 std::stable_sort(retry_list.begin(), retry_list.end(),
comp_retry);
1530 for (
auto *
p : retry_list)
1549 auto k = conflictlist.cbegin();
1571 int lastrecpri2 = 0;
1606 p->SetRecordingStatus(
p->m_oldrecstatus);
1617 p->ClearInputName();
1635 p->GetRecordingPriority2() >
1639 lastrecpri2 -
p->GetRecordingPriority2());
1654 QMap<int, QDateTime> nextRecMap;
1662 nextRecMap[
p->GetRecordingRuleID()].isNull())
1664 nextRecMap[
p->GetRecordingRuleID()] =
p->GetRecordingStartTime();
1668 p->GetParentRecordingRuleID() > 0 &&
1671 nextRecMap[
p->GetParentRecordingRuleID()].isNull())
1673 nextRecMap[
p->GetParentRecordingRuleID()] =
1674 p->GetRecordingStartTime();
1680 query.
prepare(
"SELECT recordid, next_record FROM record;");
1686 while (query.
next())
1688 int recid = query.
value(0).toInt();
1691 if (next_record == nextRecMap[recid])
1694 if (nextRecMap[recid].isValid())
1696 subquery.
prepare(
"UPDATE record SET next_record = :NEXTREC "
1697 "WHERE recordid = :RECORDID;");
1699 subquery.
bindValue(
":NEXTREC", nextRecMap[recid]);
1700 if (!subquery.
exec())
1703 else if (next_record.isValid())
1705 subquery.
prepare(
"UPDATE record "
1706 "SET next_record = NULL "
1707 "WHERE recordid = :RECORDID;");
1709 if (!subquery.
exec())
1721 strlist << QString::number(retlist.size());
1723 while (!retlist.empty())
1726 p->ToStringList(strlist);
1728 retlist.pop_front();
1739 nullptr,
true); ++i)
1750 bool hasconflicts =
false;
1754 if (recRuleId > 0 &&
1755 p->GetRecordingRuleID() !=
static_cast<uint>(recRuleId))
1758 hasconflicts =
true;
1762 return hasconflicts;
1769 bool hasconflicts =
false;
1773 if (recRuleId > 0 &&
1774 p->GetRecordingRuleID() !=
static_cast<uint>(recRuleId))
1778 hasconflicts =
true;
1782 return hasconflicts;
1789 QMap<QString,ProgramInfo*> recMap;
1806 if (recordedid ==
p->GetRecordingID())
1835 strList << QString::number(static_cast<int>(hasconflicts));
1836 strList << QString::number(retlist.size());
1838 while (!retlist.empty())
1841 p->ToStringList(strList);
1843 retlist.pop_front();
1855 strList << QString::number(schedlist.size());
1857 while (!schedlist.empty())
1862 schedlist.pop_front();
1877 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"AddRecording() recid: %1")
1883 p->IsSameTitleTimeslotAndChannel(pi))
1885 LOG(VB_GENERAL, LOG_INFO,
LOC +
"Not adding recording, " +
1886 QString(
"'%1' is already in reclist.")
1892 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
1893 QString(
"Adding '%1' to reclist.").arg(pi.
GetTitle()));
1896 new_pi->m_mplexId = new_pi->QueryMplexID();
1897 new_pi->m_sgroupId =
m_sinputInfoMap[new_pi->GetInputID()].m_sgroupId;
1903 new_pi->AddHistory(
false);
1906 new_pi->GetRecordingRule();
1910 QString(
"AddRecording %1").arg(pi.
GetTitle()));
1918 LOG(VB_GENERAL, LOG_ERR,
LOC +
1919 "IsBusyRecording() -> true, no tvList or no rcinfo");
1930 bool is_busy = rctv1->
IsBusy(&busy_input, -1s);
1942 const std::vector<unsigned int> &inputids =
m_sinputInfoMap[inputid].m_conflictingInputs;
1943 std::vector<unsigned int> &group_inputs =
m_sinputInfoMap[inputid].m_groupInputs;
1944 for (
uint id : inputids)
1949 LOG(VB_SCHEDULE, LOG_ERR,
LOC +
1950 QString(
"IsBusyRecording() -> true, rctv(NULL) for input %2")
1957 if (rctv2->
IsBusy(&busy_input, -1s))
1978 std::find(group_inputs.begin(), group_inputs.end(),
1979 id) != group_inputs.end())
1996 query.
prepare(
"UPDATE oldrecorded SET recstatus = :RSABORTED "
1997 " WHERE recstatus = :RSRECORDING OR "
1998 " recstatus = :RSTUNING OR "
1999 " recstatus = :RSFAILING");
2008 query.
prepare(
"UPDATE oldrecorded SET recstatus = :RSMISSED "
2009 "WHERE recstatus = :RSWILLRECORD OR "
2010 " recstatus = :RSPENDING");
2019 query.
prepare(
"UPDATE oldrecorded SET recstatus = :RSPREVIOUS "
2020 "WHERE recstatus = :RSCURRENT");
2029 query.
prepare(
"UPDATE oldrecorded SET future = 0 "
2030 "WHERE future > 0 AND "
2031 " endtime < (NOW() - INTERVAL 475 MINUTE)");
2058 std::chrono::seconds prerollseconds = 0s;
2059 std::chrono::seconds wakeThreshold = 5min;
2062 bool blockShutdown =
2064 bool firstRun =
true;
2067 QDateTime idleSince = QDateTime();
2068 std::chrono::seconds schedRunTime = 0s;
2069 bool statuschanged =
false;
2071 QDateTime nextWakeTime = nextStartTime;
2085 nextWakeTime = std::min(nextWakeTime, nextStartTime);
2087 auto secs_to_next = std::chrono::seconds(curtime.secsTo(nextStartTime));
2088 auto sched_sleep = std::max(std::chrono::milliseconds(curtime.msecsTo(nextWakeTime)), 0ms);
2090 sched_sleep = std::min(sched_sleep, 15000ms);
2092 int const kSleepCheck = 300;
2093 bool checkSlaves = curtime >= nextSleepCheck;
2097 if ((secs_to_next > -60s && secs_to_next < schedRunTime) ||
2098 (!haveRequests && !checkSlaves))
2100 if (sched_sleep > 0ms)
2102 LOG(VB_SCHEDULE, LOG_INFO,
2103 QString(
"sleeping for %1 ms "
2104 "(s2n: %2 sr: %3 qr: %4 cs: %5)")
2105 .arg(sched_sleep.count()).arg(secs_to_next.count()).arg(schedRunTime.count())
2106 .arg(haveRequests).arg(checkSlaves));
2128 wakeThreshold = std::max(wakeThreshold, prerollseconds + 120s);
2130 QElapsedTimer
t;
t.start();
2133 statuschanged =
true;
2136 auto elapsed = std::chrono::ceil<std::chrono::seconds>(std::chrono::milliseconds(
t.elapsed()));
2137 schedRunTime = std::max(elapsed + elapsed/2 + 2s, schedRunTime);
2158 checkSlaves =
false;
2168 nextWakeTime = nextSleepCheck;
2172 for ( ; startIter !=
m_recList.end(); ++startIter)
2174 if ((*startIter)->GetRecordingStatus() !=
2175 (*startIter)->m_oldrecstatus)
2183 for (
auto it = startIter; it !=
m_recList.end(); ++it)
2185 auto secsleft = std::chrono::seconds(curtime.secsTo((*it)->GetRecordingStartTime()));
2186 auto timeBeforePreroll = secsleft - prerollseconds;
2187 if (timeBeforePreroll <= wakeThreshold)
2192 if (timeBeforePreroll > 0s)
2194 std::chrono::seconds waitpending;
2195 if (timeBeforePreroll > 120s)
2196 waitpending = timeBeforePreroll -120s;
2198 waitpending = std::min(timeBeforePreroll, 30s);
2212 for (
auto it = startIter; it !=
m_recList.end() && !done; ++it)
2215 **it, statuschanged, nextStartTime, nextWakeTime,
2239 if (idleSince.isValid())
2244 else if (idleSince.addSecs((
idleTimeoutSecs - 30s).count()) <= curtime)
2250 statuschanged =
false;
2257 const QString &title,
const QString &subtitle,
2258 const QString &descrip,
2259 const QString &programid)
2262 QString filterClause;
2265 if (!title.isEmpty())
2267 filterClause +=
"AND p.title = :TITLE ";
2268 bindings[
":TITLE"] = title;
2272 if (programid !=
"**any**")
2274 filterClause +=
"AND (0 ";
2275 if (!subtitle.isEmpty())
2278 filterClause +=
"OR p.subtitle = :SUBTITLE1 "
2279 "OR p.description = :SUBTITLE2 ";
2280 bindings[
":SUBTITLE1"] = subtitle;
2281 bindings[
":SUBTITLE2"] = subtitle;
2283 if (!descrip.isEmpty())
2286 filterClause +=
"OR p.description = :DESCRIP1 "
2287 "OR p.subtitle = :DESCRIP2 ";
2288 bindings[
":DESCRIP1"] = descrip;
2289 bindings[
":DESCRIP2"] = descrip;
2291 if (!programid.isEmpty())
2293 filterClause +=
"OR p.programid = :PROGRAMID ";
2294 bindings[
":PROGRAMID"] = programid;
2296 filterClause +=
") ";
2299 query.
prepare(QString(
"UPDATE recordmatch rm "
2301 " ON rm.recordid = r.recordid "
2302 "INNER JOIN program p "
2303 " ON rm.chanid = p.chanid "
2304 " AND rm.starttime = p.starttime "
2305 " AND rm.manualid = p.manualid "
2306 "SET oldrecduplicate = -1 "
2307 "WHERE p.generic = 0 "
2308 " AND r.type NOT IN (%2, %3, %4) ")
2314 MSqlBindings::const_iterator it;
2315 for (it = bindings.cbegin(); it != bindings.cend(); ++it)
2320 if (findid && programid !=
"**any**")
2322 query.
prepare(
"UPDATE recordmatch rm "
2323 "SET oldrecduplicate = -1 "
2324 "WHERE rm.recordid = :RECORDID "
2325 " AND rm.findid = :FINDID");
2339 auto fillstart = nowAsDuration<std::chrono::microseconds>();
2341 bool deleteFuture =
false;
2342 bool runCheck =
false;
2348 if (!request.empty())
2350 tokens = request[0].split(
' ', Qt::SkipEmptyParts);
2353 if (request.empty() || tokens.empty())
2355 LOG(VB_GENERAL, LOG_ERR,
"Empty Reschedule request received");
2359 LOG(VB_GENERAL, LOG_INFO, QString(
"Reschedule requested for %1")
2360 .arg(request.join(
" | ")));
2362 if (tokens[0] ==
"MATCH")
2364 if (tokens.size() < 5)
2366 LOG(VB_GENERAL, LOG_ERR,
2367 QString(
"Invalid RescheduleMatch request received (%1)")
2372 uint recordid = tokens[1].toUInt();
2373 uint sourceid = tokens[2].toUInt();
2374 uint mplexid = tokens[3].toUInt();
2376 deleteFuture =
true;
2384 else if (tokens[0] ==
"CHECK")
2386 if (tokens.size() < 4 || request.size() < 5)
2388 LOG(VB_GENERAL, LOG_ERR,
2389 QString(
"Invalid RescheduleCheck request received (%1)")
2394 uint recordid = tokens[2].toUInt();
2395 uint findid = tokens[3].toUInt();
2396 const QString& title = request[1];
2397 const QString& subtitle = request[2];
2398 const QString& descrip = request[3];
2399 const QString& programid = request[4];
2408 else if (tokens[0] !=
"PLACE")
2410 LOG(VB_GENERAL, LOG_ERR,
2411 QString(
"Unknown Reschedule request received (%1)")
2421 query.
prepare(
"DELETE oldrecorded FROM oldrecorded "
2422 "LEFT JOIN recordmatch ON "
2423 " recordmatch.chanid = oldrecorded.chanid AND "
2424 " recordmatch.starttime = oldrecorded.starttime "
2425 "WHERE oldrecorded.future > 0 AND "
2426 " recordmatch.recordid IS NULL");
2431 auto fillend = nowAsDuration<std::chrono::microseconds>();
2432 auto matchTime = fillend - fillstart;
2434 LOG(VB_SCHEDULE, LOG_INFO,
"CreateTempTables...");
2437 fillstart = nowAsDuration<std::chrono::microseconds>();
2440 LOG(VB_SCHEDULE, LOG_INFO,
"UpdateDuplicates...");
2443 fillend = nowAsDuration<std::chrono::microseconds>();
2444 auto checkTime = fillend - fillstart;
2446 fillstart = nowAsDuration<std::chrono::microseconds>();
2448 fillend = nowAsDuration<std::chrono::microseconds>();
2449 auto placeTime = fillend - fillstart;
2451 LOG(VB_SCHEDULE, LOG_INFO,
"DeleteTempTables...");
2461 LOG(VB_GENERAL, LOG_INFO,
"Reschedule interrupted, will retry");
2466 msg = QString(
"Scheduled %1 items in %2 "
2467 "= %3 match + %4 check + %5 place")
2469 .arg(duration_cast<floatsecs>(matchTime + checkTime + placeTime).count(), 0,
'f', 1)
2470 .arg(duration_cast<floatsecs>(matchTime).count(), 0,
'f', 2)
2471 .arg(duration_cast<floatsecs>(checkTime).count(), 0,
'f', 2)
2472 .arg(duration_cast<floatsecs>(placeTime).count(), 0,
'f', 2);
2473 LOG(VB_GENERAL, LOG_INFO, msg);
2478 if (
p->GetRecordingStatus() !=
p->m_oldrecstatus)
2481 p->AddHistory(
false,
false,
false);
2485 p->AddHistory(
false,
false,
false);
2487 p->AddHistory(
false,
false,
true);
2489 else if (
p->m_future)
2495 p->m_future =
false;
2504 std::chrono::seconds prerollseconds,
2507 bool blockShutdown =
true;
2512 QString startupParam =
"user";
2516 for ( ; firstRunIter !=
m_recList.end(); ++firstRunIter)
2527 ((std::chrono::seconds(curtime.secsTo((*firstRunIter)->GetRecordingStartTime())) -
2530 LOG(VB_GENERAL, LOG_INFO,
LOC +
"AUTO-Startup assumed");
2531 startupParam =
"auto";
2535 blockShutdown =
false;
2539 LOG(VB_GENERAL, LOG_INFO,
LOC +
"Seem to be woken up by USER");
2551 return blockShutdown;
2557 static constexpr std::array<const std::chrono::seconds,4> kSysEventSecs = { 120s, 90s, 60s, 30s };
2561 auto secsleft = std::chrono::seconds(curtime.secsTo(nextrectime));
2571 bool pendingEventSent =
false;
2572 for (
size_t i = 0; i < kSysEventSecs.size(); i++)
2574 auto pending_secs = std::max((secsleft - prerollseconds), 0s);
2575 if ((pending_secs <= kSysEventSecs[i]) &&
2578 if (!pendingEventSent)
2581 QString(
"REC_PENDING SECS %1").arg(pending_secs.count()), &ri);
2585 pendingEventSent =
true;
2591 for (
size_t i = 0; i < kSysEventSecs.size(); i++)
2599 keys.insert(rec->MakeUniqueKey());
2600 keys.insert(
"something");
2603 QSet<QString>::iterator sit =
m_sysEvents[i].begin();
2606 if (!keys.contains(*sit))
2617 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
2618 QString(
"Slave Backend %1 is being awakened to record: %2")
2625 ((secsleft - prerollseconds) < 210s) &&
2629 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
2630 QString(
"Slave Backend %1 not available yet, "
2631 "trying to wake it up again.")
2638 ((secsleft - prerollseconds) < 150s) &&
2641 LOG(VB_GENERAL, LOG_WARNING,
LOC +
2642 QString(
"Slave Backend %1 has NOT come "
2643 "back from sleep yet in 150 seconds. Setting "
2644 "slave status to unknown and attempting "
2645 "to reschedule around its tuners.")
2648 for (
auto * enc : std::as_const(*
m_tvList))
2660 QDateTime &nextStartTime, QDateTime &nextWakeTime,
2661 std::chrono::seconds prerollseconds)
2668 std::chrono::seconds origprerollseconds = prerollseconds;
2675 auto nextwake = std::chrono::seconds(nextWakeTime.secsTo(nextrectime));
2676 if (nextwake - prerollseconds > 5min)
2678 nextStartTime = std::min(nextStartTime, nextrectime);
2682 if (curtime < nextrectime)
2683 nextWakeTime = std::min(nextWakeTime, nextrectime);
2689 auto secsleft = std::chrono::seconds(curtime.secsTo(nextrectime));
2694 if (secsleft - prerollseconds > 1min)
2696 nextStartTime = std::min(nextStartTime, nextrectime.addSecs(-30));
2697 nextWakeTime = std::min(nextWakeTime,
2698 nextrectime.addSecs(-prerollseconds.count() - 60));
2711 if (secsleft - prerollseconds > 35s)
2713 nextStartTime = std::min(nextStartTime, nextrectime.addSecs(-30));
2714 nextWakeTime = std::min(nextWakeTime,
2715 nextrectime.addSecs(-prerollseconds.count() - 35));
2724 QString msg = QString(
"Invalid cardid [%1] for %2")
2726 LOG(VB_GENERAL, LOG_ERR,
LOC + msg);
2730 statuschanged =
true;
2738 QString msg = QString(
"SUPPRESSED recording \"%1\" on channel: "
2739 "%2 on cardid: [%3], sourceid %4. Tuner "
2740 "is locked by an external application.")
2745 LOG(VB_GENERAL, LOG_NOTICE, msg);
2749 statuschanged =
true;
2760 if (prerollseconds > 0s)
2768 if (isBusyRecording)
2771 nextWakeTime = std::min(nextWakeTime, curtime.addSecs(5));
2772 prerollseconds = 0s;
2776 if (secsleft - prerollseconds > 30s)
2778 nextStartTime = std::min(nextStartTime, nextrectime.addSecs(-30));
2779 nextWakeTime = std::min(nextWakeTime,
2780 nextrectime.addSecs(-prerollseconds.count() - 30));
2788 LOG(VB_SCHEDULE, LOG_WARNING,
2789 QString(
"WARNING: Slave Backend %1 has NOT come "
2790 "back from sleep yet. Recording can "
2791 "not begin yet for: %2")
2797 LOG(VB_SCHEDULE, LOG_WARNING,
2798 QString(
"WARNING: Slave Backend %1 has NOT come "
2799 "back from sleep yet. Setting slave "
2800 "status to unknown and attempting "
2801 "to reschedule around its tuners.")
2804 for (
auto * enc : std::as_const(*
m_tvList))
2813 nextStartTime = std::min(nextStartTime, nextrectime);
2814 nextWakeTime = std::min(nextWakeTime, curtime.addSecs(1));
2821 QString recording_dir;
2840 MythEvent me(QString(
"ADD_CHILD_INPUT %1")
2843 nextWakeTime = std::min(nextWakeTime, curtime.addSecs(1));
2853 nexttv->
RecordPending(&tempri, std::max(secsleft, 0s),
false);
2859 if (secsleft - prerollseconds > 0s)
2861 nextStartTime = std::min(nextStartTime, nextrectime);
2862 nextWakeTime = std::min(nextWakeTime,
2863 nextrectime.addSecs(-prerollseconds.count()));
2868#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
2869 recstartts = QDateTime(
2871 QTime(recstartts.time().hour(), recstartts.time().minute()), Qt::UTC);
2873 recstartts = QDateTime(
2875 QTime(recstartts.time().hour(), recstartts.time().minute()),
2876 QTimeZone(QTimeZone::UTC));
2881 QString details = QString(
"%1: channel %2 on cardid [%3], sourceid %4")
2908 statuschanged =
true;
2921 bool doSchedAfterStart =
2931 msg = QString(
"Started recording");
2933 msg = QString(
"Tuning recording");
2935 msg = QString(
"Canceled recording (%1)")
2937 LOG(VB_GENERAL, LOG_INFO, QString(
"%1: %2").arg(msg, details));
2945 MythEvent me(QString(
"FORCE_DELETE_RECORDING %1 %2")
2953 std::chrono::seconds prerollseconds)
2958 LOG(VB_SCHEDULE, LOG_DEBUG,
2959 QString(
"Assigning input for %1/%2/\"%3\"")
2970 for (
uint i = 0; !bestid && i < inputs.size(); ++i)
2972 uint inputid = inputs[i];
2980 auto recstarttime = std::chrono::seconds(now.secsTo(
p->GetRecordingStartTime()));
2981 if (recstarttime > prerollseconds + 60s)
2983 if (
p->GetInputID() != inputid)
3000 LOG(VB_SCHEDULE, LOG_DEBUG,
3001 QString(
"Input %1 has a pending recording").arg(inputid));
3010 LOG(VB_SCHEDULE, LOG_DEBUG,
3011 QString(
"Input %1 is recording").arg(inputid));
3016 LOG(VB_SCHEDULE, LOG_DEBUG,
3017 QString(
"Input %1 is recording but will be free")
3026 LOG(VB_SCHEDULE, LOG_DEBUG,
3027 QString(
"Input %1 is recording but has to stop")
3033 LOG(VB_SCHEDULE, LOG_DEBUG,
3034 QString(
"Input %1 is recording but could be free")
3046 bool isbusy = rctv->
IsBusy(&busy_info, -1s);
3052 LOG(VB_SCHEDULE, LOG_DEBUG,
3053 QString(
"Input %1 is free").arg(inputid));
3059 LOG(VB_SCHEDULE, LOG_DEBUG,
3060 QString(
"Input %1 is on livetv but has to stop")
3071 LOG(VB_SCHEDULE, LOG_INFO,
3072 QString(
"Assigned input %1 for %2/%3/\"%4\"")
3080 LOG(VB_SCHEDULE, LOG_WARNING,
3081 QString(
"Failed to assign input for %1/%2/\"%3\"")
3087 return bestid != 0U;
3097 bool &blockShutdown, QDateTime &idleSince,
3098 std::chrono::seconds prerollseconds,
3104 uint logmask = VB_IDLE;
3105 int now = QTime::currentTime().msecsSinceStartOfDay();
3106 int tm = std::chrono::milliseconds(now) / 15min;
3109 logmask = VB_GENERAL;
3128 LOG(VB_GENERAL, LOG_NOTICE,
"Client is connected, removing startup block on shutdown");
3129 blockShutdown =
false;
3140 bool recording =
false;
3143 QMap<int, EncoderLink *>::const_iterator it;
3147 if ((*it)->IsBusy())
3161 if (!blocking && !recording && !activeJobs && !delay)
3168 if (idleSince.isValid())
3170 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN -1"));
3173 idleSince = QDateTime();
3178 if (statuschanged || !idleSince.isValid())
3180 bool wasValid = idleSince.isValid();
3182 idleSince = curtime;
3185 for ( ; idleIter !=
m_recList.end(); ++idleIter)
3187 if ((*idleIter)->GetRecordingStatus() ==
3189 (*idleIter)->GetRecordingStatus() ==
3196 auto recstarttime = std::chrono::seconds(curtime.secsTo((*idleIter)->GetRecordingStartTime()));
3199 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3200 "a recording is due to "
3202 idleSince = QDateTime();
3213 if (guideRunTime.isValid() &&
3217 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3218 "mythfilldatabase is due to "
3220 idleSince = QDateTime();
3225 if (idleSince.isValid())
3228 if (wasValid && !idleSince.isValid())
3230 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN -1"));
3235 if (idleSince.isValid())
3247 LOG(VB_GENERAL, LOG_WARNING,
3248 "Waited more than 60"
3249 " seconds for shutdown to complete"
3250 " - resetting idle time");
3251 idleSince = QDateTime();
3257 blockShutdown, logmask))
3263 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN -1"));
3269 auto itime = std::chrono::seconds(idleSince.secsTo(curtime));
3273 msg = QString(
"I\'m idle now... shutdown will "
3274 "occur in %1 seconds.")
3276 LOG(VB_GENERAL, LOG_NOTICE, msg);
3277 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN %1")
3284 msg = QString(
"%1 secs left to system shutdown!").arg(remain);
3285 LOG(logmask, LOG_NOTICE, msg);
3286 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN %1").arg(remain));
3295 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3296 "of an active encoder");
3298 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3299 "of a connected client");
3302 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3306 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3307 "of delay request from external application");
3310 if (idleSince.isValid())
3312 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN -1"));
3315 idleSince = QDateTime();
3322 QDateTime &idleSince,
3323 bool &blockShutdown,
uint logmask)
3325 bool retval =
false;
3335 LOG(logmask, LOG_INFO,
3336 "CheckShutdownServer returned - OK to shutdown");
3340 LOG(logmask, LOG_NOTICE,
3341 "CheckShutdownServer returned - Not OK to shutdown");
3343 idleSince = QDateTime();
3346 LOG(logmask, LOG_NOTICE,
3347 "CheckShutdownServer returned - Not OK to shutdown, "
3355 idleSince = QDateTime();
3360 m_noAutoShutdown =
true;
3364 LOG(VB_GENERAL, LOG_NOTICE,
3365 "CheckShutdownServer returned - Not OK");
3368 LOG(VB_GENERAL, LOG_NOTICE, QString(
3369 "CheckShutdownServer returned - Error %1").arg(state));
3382 QDateTime &idleSince)
3387 for ( ; recIter !=
m_recList.end(); ++recIter)
3395 QDateTime restarttime;
3400 .addSecs(-prerollseconds.count());
3408 && guideRefreshTime.isValid()
3410 && (restarttime.isNull() || guideRefreshTime < restarttime))
3411 restarttime = guideRefreshTime;
3413 if (restarttime.isValid())
3417 restarttime = restarttime.addSecs((-1LL) * add);
3420 "hh:mm yyyy-MM-dd");
3422 "echo \'Wakeuptime would "
3423 "be $time if command "
3426 if (setwakeup_cmd.isEmpty())
3428 LOG(VB_GENERAL, LOG_NOTICE,
3429 "SetWakeuptimeCommand is empty, shutdown aborted");
3430 idleSince = QDateTime();
3434 if (wakeup_timeformat ==
"time_t")
3437 setwakeup_cmd.replace(
"$time",
3438 time_ts.setNum(restarttime.toSecsSinceEpoch())
3443 setwakeup_cmd.replace(
3444 "$time", restarttime.toLocalTime().toString(wakeup_timeformat));
3447 LOG(VB_GENERAL, LOG_NOTICE,
3448 QString(
"Running the command to set the next "
3449 "scheduled wakeup time :-\n\t\t\t\t") + setwakeup_cmd);
3454 LOG(VB_GENERAL, LOG_ERR,
3455 "SetWakeuptimeCommand failed, shutdown aborted");
3456 idleSince = QDateTime();
3471 "sudo /sbin/halt -p");
3473 if (!halt_cmd.isEmpty())
3478 LOG(VB_GENERAL, LOG_NOTICE,
3479 QString(
"Running the command to shutdown "
3480 "this computer :-\n\t\t\t\t") + halt_cmd);
3487 LOG(VB_GENERAL, LOG_ERR,
"ServerHaltCommand failed, shutdown aborted");
3492 idleSince = QDateTime();
3498 std::chrono::seconds prerollseconds = 0s;
3499 std::chrono::seconds secsleft = 0s;
3503 bool someSlavesCanSleep =
false;
3504 for (
auto * enc : std::as_const(*
m_tvList))
3506 if (enc->CanSleep())
3507 someSlavesCanSleep =
true;
3510 if (!someSlavesCanSleep)
3513 LOG(VB_SCHEDULE, LOG_INFO,
3514 "Scheduler, Checking for slaves that can be shut down");
3516 auto sleepThreshold =
3519 LOG(VB_SCHEDULE, LOG_DEBUG,
3520 QString(
" Getting list of slaves that will be active in the "
3521 "next %1 minutes.") .arg(duration_cast<std::chrono::minutes>(sleepThreshold).count()));
3523 LOG(VB_SCHEDULE, LOG_DEBUG,
"Checking scheduler's reclist");
3525 QStringList SlavesInUse;
3535 auto recstarttime = std::chrono::seconds(curtime.secsTo(pginfo->GetRecordingStartTime()));
3536 secsleft = recstarttime - prerollseconds;
3537 if (secsleft > sleepThreshold)
3542 EncoderLink *enc = (*m_tvList)[pginfo->GetInputID()];
3549 LOG(VB_SCHEDULE, LOG_DEBUG,
3550 QString(
" Slave %1 will be in use in %2 minutes")
3552 .arg(duration_cast<std::chrono::minutes>(secsleft).count()));
3556 LOG(VB_SCHEDULE, LOG_DEBUG,
3557 QString(
" Slave %1 is in use currently "
3566 LOG(VB_SCHEDULE, LOG_DEBUG,
" Checking inuseprograms table:");
3569 query.
prepare(
"SELECT DISTINCT hostname, recusage FROM inuseprograms "
3570 "WHERE lastupdatetime > :ONEHOURAGO ;");
3571 query.
bindValue(
":ONEHOURAGO", oneHourAgo);
3574 while(query.
next()) {
3575 SlavesInUse << query.
value(0).toString();
3576 LOG(VB_SCHEDULE, LOG_DEBUG,
3577 QString(
" Slave %1 is marked as in use by a %2")
3578 .arg(query.
value(0).toString(),
3579 query.
value(1).toString()));
3583 LOG(VB_SCHEDULE, LOG_DEBUG, QString(
" Shutting down slaves which will "
3584 "be inactive for the next %1 minutes and can be put to sleep.")
3585 .arg(sleepThreshold.count() / 60));
3587 for (
auto * enc : std::as_const(*
m_tvList))
3589 if ((!enc->IsLocal()) &&
3591 (!SlavesInUse.contains(enc->GetHostName())) &&
3592 (!enc->IsFallingAsleep()))
3594 QString sleepCommand =
3596 enc->GetHostName());
3597 QString wakeUpCommand =
3599 enc->GetHostName());
3601 if (!sleepCommand.isEmpty() && !wakeUpCommand.isEmpty())
3603 QString thisHost = enc->GetHostName();
3605 LOG(VB_SCHEDULE, LOG_DEBUG,
3606 QString(
" Commanding %1 to go to sleep.")
3609 if (enc->GoToSleep())
3611 for (
auto * slv : std::as_const(*
m_tvList))
3613 if (slv->GetHostName() == thisHost)
3615 LOG(VB_SCHEDULE, LOG_DEBUG,
3616 QString(
" Marking card %1 on slave %2 "
3617 "as falling asleep.")
3618 .arg(slv->GetInputID())
3619 .arg(slv->GetHostName()));
3626 LOG(VB_GENERAL, LOG_ERR,
LOC +
3627 QString(
"Unable to shutdown %1 slave backend, setting "
3628 "sleep status to undefined.").arg(thisHost));
3629 for (
auto * slv : std::as_const(*
m_tvList))
3631 if (slv->GetHostName() == thisHost)
3644 LOG(VB_GENERAL, LOG_NOTICE,
3645 QString(
"Tried to Wake Up %1, but this is the "
3646 "master backend and it is not asleep.")
3647 .arg(slaveHostname));
3654 if (wakeUpCommand.isEmpty()) {
3655 LOG(VB_GENERAL, LOG_NOTICE,
3656 QString(
"Trying to Wake Up %1, but this slave "
3657 "does not have a WakeUpCommand set.").arg(slaveHostname));
3659 for (
auto * enc : std::as_const(*
m_tvList))
3661 if (enc->GetHostName() == slaveHostname)
3669 for (
auto * enc : std::as_const(*
m_tvList))
3671 if (setWakingStatus && (enc->GetHostName() == slaveHostname))
3673 enc->SetLastWakeTime(curtime);
3678 LOG(VB_SCHEDULE, LOG_NOTICE, QString(
"Executing '%1' to wake up slave.")
3679 .arg(wakeUpCommand));
3691 QStringList SlavesThatCanWake;
3693 for (
auto * enc : std::as_const(*
m_tvList))
3698 thisSlave = enc->GetHostName();
3702 (!SlavesThatCanWake.contains(thisSlave)))
3703 SlavesThatCanWake << thisSlave;
3707 for (; slave < SlavesThatCanWake.count(); slave++)
3709 thisSlave = SlavesThatCanWake[slave];
3710 LOG(VB_SCHEDULE, LOG_NOTICE,
3711 QString(
"Scheduler, Sending wakeup command to slave: %1")
3721 query.
prepare(QString(
"SELECT type,title,subtitle,description,"
3722 "station,startdate,starttime,"
3723 "enddate,endtime,season,episode,inetref,last_record "
3726 if (!query.
exec() || query.
size() != 1)
3736 QString title = query.
value(1).toString();
3737 QString subtitle = query.
value(2).toString();
3738 QString description = query.
value(3).toString();
3739 QString station = query.
value(4).toString();
3740#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
3741 QDateTime startdt = QDateTime(query.
value(5).toDate(),
3742 query.
value(6).toTime(), Qt::UTC);
3743 int duration = startdt.secsTo(
3744 QDateTime(query.
value(7).toDate(),
3745 query.
value(8).toTime(), Qt::UTC));
3747 QDateTime startdt = QDateTime(query.
value(5).toDate(),
3748 query.
value(6).toTime(),
3749 QTimeZone(QTimeZone::UTC));
3750 int duration = startdt.secsTo(
3751 QDateTime(query.
value(7).toDate(),
3752 query.
value(8).toTime(),
3753 QTimeZone(QTimeZone::UTC)));
3756 int season = query.
value(9).toInt();
3757 int episode = query.
value(10).toInt();
3758 QString inetref = query.
value(11).toString();
3762 QDate originalairdate = QDate(query.
value(12).toDate());
3764 if (description.isEmpty())
3765 description = startdt.toLocalTime().toString();
3767 query.
prepare(
"SELECT chanid from channel "
3768 "WHERE deleted IS NULL AND callsign = :STATION");
3776 std::vector<unsigned int> chanidlist;
3777 while (query.
next())
3778 chanidlist.push_back(query.
value(0).toUInt());
3782 bool weekday =
false;
3784 QDateTime lstartdt = startdt.toLocalTime();
3799 weekday = (lstartdt.date().dayOfWeek() < 6);
3800 daysoff = lstartdt.date().daysTo(
3802#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
3803 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3804 lstartdt.time(), Qt::LocalTime).toUTC();
3806 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3808 QTimeZone(QTimeZone::LocalTime)
3816 daysoff = lstartdt.date().daysTo(
3818 daysoff = (daysoff + 6) / 7 * 7;
3819#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
3820 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3821 lstartdt.time(), Qt::LocalTime).toUTC();
3823 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3825 QTimeZone(QTimeZone::LocalTime)
3830 LOG(VB_GENERAL, LOG_ERR,
3831 QString(
"Invalid rectype for manual recordid %1").arg(recordid));
3837 for (
uint id : chanidlist)
3839 if (weekday && startdt.toLocalTime().date().dayOfWeek() >= 6)
3842 query.
prepare(
"REPLACE INTO program (chanid, starttime, endtime,"
3843 " title, subtitle, description, manualid,"
3844 " season, episode, inetref, originalairdate, generic) "
3845 "VALUES (:CHANID, :STARTTIME, :ENDTIME, :TITLE,"
3846 " :SUBTITLE, :DESCRIPTION, :RECORDID, "
3847 " :SEASON, :EPISODE, :INETREF, :ORIGINALAIRDATE, 1)");
3850 query.
bindValue(
":ENDTIME", startdt.addSecs(duration));
3853 query.
bindValue(
":DESCRIPTION", description);
3857 query.
bindValue(
":ORIGINALAIRDATE", originalairdate);
3866 daysoff += skipdays;
3867#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
3868 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3869 lstartdt.time(), Qt::LocalTime).toUTC();
3871 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3873 QTimeZone(QTimeZone::LocalTime)
3887 query = QString(
"SELECT recordid,search,subtitle,description "
3888 "FROM %1 WHERE search <> %2 AND "
3889 "(recordid = %3 OR %4 = 0) ")
3901 while (result.
next())
3903 QString
prefix = QString(
":NR%1").arg(count);
3904 qphrase = result.
value(3).toString();
3910 LOG(VB_GENERAL, LOG_ERR,
3911 QString(
"Invalid search key in recordid %1")
3912 .arg(result.
value(0).toString()));
3916 QString bindrecid =
prefix +
"RECID";
3917 QString bindphrase =
prefix +
"PHRASE";
3918 QString bindlikephrase1 =
prefix +
"LIKEPHRASE1";
3919 QString bindlikephrase2 =
prefix +
"LIKEPHRASE2";
3920 QString bindlikephrase3 =
prefix +
"LIKEPHRASE3";
3922 bindings[bindrecid] = result.
value(0).toString();
3928 qphrase.remove(
';');
3929 from << result.
value(2).toString();
3930 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
3931 QString(
" AND program.manualid = 0 AND ( %2 )")
3935 bindings[bindlikephrase1] = QString(
"%") + qphrase +
"%";
3937 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
" AND "
3938 "program.manualid = 0 AND "
3939 "program.title LIKE " + bindlikephrase1);
3942 bindings[bindlikephrase1] = QString(
"%") + qphrase +
"%";
3943 bindings[bindlikephrase2] = QString(
"%") + qphrase +
"%";
3944 bindings[bindlikephrase3] = QString(
"%") + qphrase +
"%";
3946 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
3947 " AND program.manualid = 0"
3948 " AND (program.title LIKE " + bindlikephrase1 +
3949 " OR program.subtitle LIKE " + bindlikephrase2 +
3950 " OR program.description LIKE " + bindlikephrase3 +
")");
3953 bindings[bindphrase] = qphrase;
3954 from <<
", people, credits";
3955 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
" AND "
3956 "program.manualid = 0 AND "
3957 "people.name LIKE " + bindphrase +
" AND "
3958 "credits.person = people.person AND "
3959 "program.chanid = credits.chanid AND "
3960 "program.starttime = credits.starttime");
3965 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
3967 QString(
"program.manualid = %1.recordid ")
3971 LOG(VB_GENERAL, LOG_ERR,
3972 QString(
"Unknown RecSearchType (%1) for recordid %2")
3973 .arg(result.
value(1).toInt())
3974 .arg(result.
value(0).toString()));
3975 bindings.remove(bindrecid);
3982 if (recordid == 0 || from.count() == 0)
3984 QString recidmatch =
"";
3986 recidmatch =
"RECTABLE.recordid = :NRRECORDID AND ";
3987 QString s1 = recidmatch +
3988 "RECTABLE.type <> :NRTEMPLATE AND "
3989 "RECTABLE.search = :NRST AND "
3990 "program.manualid = 0 AND "
3991 "program.title = RECTABLE.title ";
3993 QString s2 = recidmatch +
3994 "RECTABLE.type <> :NRTEMPLATE AND "
3995 "RECTABLE.search = :NRST AND "
3996 "program.manualid = 0 AND "
3997 "program.seriesid <> '' AND "
3998 "program.seriesid = RECTABLE.seriesid ";
4008 bindings[
":NRRECORDID"] = recordid;
4014" WHEN RECTABLE.type IN (%1, %2, %3) THEN 0 "
4015" WHEN RECTABLE.type IN (%4, %5, %6) THEN -1 "
4016" ELSE (program.generic - 1) "
4022"(CASE RECTABLE.type "
4024" THEN RECTABLE.findid "
4026" THEN to_days(date_sub(convert_tz(program.starttime, 'UTC', 'SYSTEM'), "
4027" interval time_format(RECTABLE.findtime, '%H:%i') hour_minute)) "
4029" THEN floor((to_days(date_sub(convert_tz(program.starttime, 'UTC', "
4030" 'SYSTEM'), interval time_format(RECTABLE.findtime, '%H:%i') "
4031" hour_minute)) - RECTABLE.findday)/7) * 7 + RECTABLE.findday "
4033" THEN RECTABLE.findid "
4042 const QDateTime &maxstarttime)
4046 QString deleteClause;
4047 QString filterClause = QString(
" AND program.endtime > "
4048 "(NOW() - INTERVAL 480 MINUTE)");
4052 deleteClause +=
" AND recordmatch.recordid = :RECORDID";
4053 bindings[
":RECORDID"] = recordid;
4057 deleteClause +=
" AND channel.sourceid = :SOURCEID";
4058 filterClause +=
" AND channel.sourceid = :SOURCEID";
4059 bindings[
":SOURCEID"] = sourceid;
4063 deleteClause +=
" AND channel.mplexid = :MPLEXID";
4064 filterClause +=
" AND channel.mplexid = :MPLEXID";
4065 bindings[
":MPLEXID"] = mplexid;
4067 if (maxstarttime.isValid())
4069 deleteClause +=
" AND recordmatch.starttime <= :MAXSTARTTIME";
4070 filterClause +=
" AND program.starttime <= :MAXSTARTTIME";
4071 bindings[
":MAXSTARTTIME"] = maxstarttime;
4074 query.
prepare(QString(
"DELETE recordmatch FROM recordmatch, channel "
4075 "WHERE recordmatch.chanid = channel.chanid")
4077 MSqlBindings::const_iterator it;
4078 for (it = bindings.cbegin(); it != bindings.cend(); ++it)
4086 bindings.remove(
":RECORDID");
4088 query.
prepare(
"SELECT filterid, clause FROM recordfilter "
4089 "WHERE filterid >= 0 AND filterid < :NUMFILTERS AND "
4090 " TRIM(clause) <> ''");
4097 while (query.
next())
4099 filterClause += QString(
" AND (((RECTABLE.filter & %1) = 0) OR (%2))")
4100 .arg(1 << query.
value(0).toInt()).arg(query.
value(1).toString());
4104 query.
prepare(
"SELECT NULL from record "
4105 "WHERE type = :FINDONE AND findid <= 0;");
4114 QDate epoch(1970, 1, 1);
4117 query.
prepare(
"UPDATE record set findid = :FINDID "
4118 "WHERE type = :FINDONE AND findid <= 0;");
4125 QStringList fromclauses;
4126 QStringList whereclauses;
4132 for (
int clause = 0; clause < fromclauses.count(); ++clause)
4134 LOG(VB_SCHEDULE, LOG_INFO, QString(
"Query %1: %2/%3")
4135 .arg(QString::number(clause), fromclauses[clause],
4136 whereclauses[clause]));
4140 for (
int clause = 0; clause < fromclauses.count(); ++clause)
4142 QString query2 = QString(
4143"REPLACE INTO recordmatch (recordid, chanid, starttime, manualid, "
4144" oldrecduplicate, findid) "
4145"SELECT RECTABLE.recordid, program.chanid, program.starttime, "
4146" IF(search = %1, RECTABLE.recordid, 0), ").arg(
kManualSearch) +
4148"FROM (RECTABLE, program INNER JOIN channel "
4149" ON channel.chanid = program.chanid) ") + fromclauses[clause] + QString(
4150" WHERE ") + whereclauses[clause] +
4151 QString(
" AND channel.deleted IS NULL "
4152 " AND channel.visible > 0 ") +
4153 filterClause + QString(
" AND "
4156" (RECTABLE.type = %1 "
4157" OR RECTABLE.type = %2 "
4158" OR RECTABLE.type = %3 "
4159" OR RECTABLE.type = %4) "
4161" ((RECTABLE.type = %6 "
4162" OR RECTABLE.type = %7 "
4163" OR RECTABLE.type = %8)"
4165" ADDTIME(RECTABLE.startdate, RECTABLE.starttime) = program.starttime "
4167" RECTABLE.station = channel.callsign) "
4179 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- Start DB Query %1...")
4182 auto dbstart = nowAsDuration<std::chrono::microseconds>();
4186 for (it = bindings.cbegin(); it != bindings.cend(); ++it)
4188 if (query2.contains(it.key()))
4192 bool ok = result.
exec();
4193 auto dbend = nowAsDuration<std::chrono::microseconds>();
4194 auto dbTime = dbend - dbstart;
4202 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- %1 results in %2 sec.")
4204 .arg(duration_cast<std::chrono::seconds>(dbTime).count()));
4208 LOG(VB_SCHEDULE, LOG_INFO,
" +-- Done.");
4217 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_record;");
4223 result.
prepare(
"CREATE TEMPORARY TABLE sched_temp_record "
4230 result.
prepare(
"INSERT sched_temp_record SELECT * from record;");
4238 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_recorded;");
4244 result.
prepare(
"CREATE TEMPORARY TABLE sched_temp_recorded "
4251 result.
prepare(
"INSERT sched_temp_recorded SELECT * from recorded;");
4265 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_record;");
4270 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_recorded;");
4278 if (schedTmpRecord ==
"record")
4279 schedTmpRecord =
"sched_temp_record";
4281 QString rmquery = QString(
4282"UPDATE recordmatch "
4283" INNER JOIN RECTABLE ON (recordmatch.recordid = RECTABLE.recordid) "
4284" INNER JOIN program p ON (recordmatch.chanid = p.chanid AND "
4285" recordmatch.starttime = p.starttime AND "
4286" recordmatch.manualid = p.manualid) "
4287" LEFT JOIN oldrecorded ON "
4289" RECTABLE.dupmethod > 1 AND "
4290" oldrecorded.duplicate <> 0 AND "
4291" p.title = oldrecorded.title AND "
4295" (p.programid <> '' "
4296" AND p.programid = oldrecorded.programid) "
4300" (p.programid = '' OR oldrecorded.programid = '' OR "
4301" LEFT(p.programid, LOCATE('/', p.programid)) <> "
4302" LEFT(oldrecorded.programid, LOCATE('/', oldrecorded.programid))) " :
4303" (p.programid = '' OR oldrecorded.programid = '') " )
4306" (((RECTABLE.dupmethod & 0x02) = 0) OR (p.subtitle <> '' "
4307" AND p.subtitle = oldrecorded.subtitle)) "
4309" (((RECTABLE.dupmethod & 0x04) = 0) OR (p.description <> '' "
4310" AND p.description = oldrecorded.description)) "
4312" (((RECTABLE.dupmethod & 0x08) = 0) OR "
4313" (p.subtitle <> '' AND "
4314" (p.subtitle = oldrecorded.subtitle OR "
4315" (oldrecorded.subtitle = '' AND "
4316" p.subtitle = oldrecorded.description))) OR "
4317" (p.subtitle = '' AND p.description <> '' AND "
4318" (p.description = oldrecorded.subtitle OR "
4319" (oldrecorded.subtitle = '' AND "
4320" p.description = oldrecorded.description)))) "
4324" LEFT JOIN sched_temp_recorded recorded ON "
4326" RECTABLE.dupmethod > 1 AND "
4327" recorded.duplicate <> 0 AND "
4328" p.title = recorded.title AND "
4329" p.generic = 0 AND "
4330" recorded.recgroup NOT IN ('LiveTV','Deleted') "
4333" (p.programid <> '' "
4334" AND p.programid = recorded.programid) "
4338" (p.programid = '' OR recorded.programid = '' OR "
4339" LEFT(p.programid, LOCATE('/', p.programid)) <> "
4340" LEFT(recorded.programid, LOCATE('/', recorded.programid))) " :
4341" (p.programid = '' OR recorded.programid = '') ")
4344" (((RECTABLE.dupmethod & 0x02) = 0) OR (p.subtitle <> '' "
4345" AND p.subtitle = recorded.subtitle)) "
4347" (((RECTABLE.dupmethod & 0x04) = 0) OR (p.description <> '' "
4348" AND p.description = recorded.description)) "
4350" (((RECTABLE.dupmethod & 0x08) = 0) OR "
4351" (p.subtitle <> '' AND "
4352" (p.subtitle = recorded.subtitle OR "
4353" (recorded.subtitle = '' AND "
4354" p.subtitle = recorded.description))) OR "
4355" (p.subtitle = '' AND p.description <> '' AND "
4356" (p.description = recorded.subtitle OR "
4357" (recorded.subtitle = '' AND "
4358" p.description = recorded.description)))) "
4362" LEFT JOIN oldfind ON "
4363" (oldfind.recordid = recordmatch.recordid AND "
4364" oldfind.findid = recordmatch.findid) "
4365" SET oldrecduplicate = (oldrecorded.endtime IS NOT NULL), "
4366" recduplicate = (recorded.endtime IS NOT NULL), "
4367" findduplicate = (oldfind.findid IS NOT NULL), "
4368" oldrecstatus = oldrecorded.recstatus "
4369" WHERE p.endtime >= (NOW() - INTERVAL 480 MINUTE) "
4370" AND oldrecduplicate = -1 "
4372 rmquery.replace(
"RECTABLE", schedTmpRecord);
4386 if (schedTmpRecord ==
"record")
4387 schedTmpRecord =
"sched_temp_record";
4391 QMap<int, bool> cardMap;
4392 for (
auto * enc : std::as_const(*
m_tvList))
4394 if (enc->IsConnected() || enc->IsAsleep())
4395 cardMap[enc->GetInputID()] =
true;
4398 QMap<int, bool> tooManyMap;
4399 bool checkTooMany =
false;
4403 rlist.
prepare(QString(
"SELECT recordid, title, maxepisodes, maxnewest "
4404 "FROM %1").arg(schedTmpRecord));
4412 while (rlist.
next())
4414 int recid = rlist.
value(0).toInt();
4416 int maxEpisodes = rlist.
value(2).toInt();
4417 int maxNewest = rlist.
value(3).toInt();
4419 tooManyMap[recid] =
false;
4422 if (maxEpisodes && !maxNewest)
4426 epicnt.
prepare(
"SELECT DISTINCT chanid, progstart, progend "
4428 "WHERE recordid = :RECID AND preserve = 0 "
4429 "AND recgroup NOT IN ('LiveTV','Deleted');");
4434 if (epicnt.
size() >= maxEpisodes - 1)
4437 if (epicnt.
size() >= maxEpisodes)
4439 tooManyMap[recid] =
true;
4440 checkTooMany =
true;
4456 QString pwrpri =
"channel.recpriority + capturecard.recpriority";
4460 pwrpri += QString(
" + "
4461 "IF(capturecard.cardid = RECTABLE.prefinput, 1, 0) * %1")
4467 pwrpri += QString(
" + IF(program.hdtv > 0 OR "
4468 "FIND_IN_SET('HDTV', program.videoprop) > 0, 1, 0) * %1")
4474 pwrpri += QString(
" + "
4475 "IF(FIND_IN_SET('WIDESCREEN', program.videoprop) > 0, 1, 0) * %1")
4481 pwrpri += QString(
" + "
4482 "IF(FIND_IN_SET('SIGNED', program.subtitletypes) > 0, 1, 0) * %1")
4488 pwrpri += QString(
" + "
4489 "IF(FIND_IN_SET('ONSCREEN', program.subtitletypes) > 0, 1, 0) * %1")
4490 .arg(onscrpriority);
4495 pwrpri += QString(
" + "
4496 "IF(FIND_IN_SET('NORMAL', program.subtitletypes) > 0 OR "
4497 "program.closecaptioned > 0 OR program.subtitled > 0, 1, 0) * %1")
4503 pwrpri += QString(
" + "
4504 "IF(FIND_IN_SET('HARDHEAR', program.subtitletypes) > 0 OR "
4505 "FIND_IN_SET('HARDHEAR', program.audioprop) > 0, 1, 0) * %1")
4511 pwrpri += QString(
" + "
4512 "IF(FIND_IN_SET('VISUALIMPAIR', program.audioprop) > 0, 1, 0) * %1")
4518 result.
prepare(QString(
"SELECT recpriority, selectclause FROM %1;")
4527 while (result.
next())
4529 if (result.
value(0).toBool())
4531 QString sclause = result.
value(1).toString();
4533 sclause.remove(
';');
4534 pwrpri += QString(
" + IF(%1, 1, 0) * %2")
4535 .arg(sclause).arg(result.
value(0).toInt());
4538 pwrpri += QString(
" AS powerpriority ");
4540 pwrpri.replace(
"program.",
"p.");
4541 pwrpri.replace(
"channel.",
"c.");
4542 QString query = QString(
4544 " c.chanid, c.sourceid, p.starttime, "
4545 " p.endtime, p.title, p.subtitle, "
4546 " p.description, c.channum, c.callsign, "
4547 " c.name, oldrecduplicate, p.category, "
4548 " RECTABLE.recpriority, RECTABLE.dupin, recduplicate, "
4549 " findduplicate, RECTABLE.type, RECTABLE.recordid, "
4550 " p.starttime - INTERVAL RECTABLE.startoffset "
4551 " minute AS recstartts, "
4552 " p.endtime + INTERVAL RECTABLE.endoffset "
4553 " minute AS recendts, "
4554 " p.previouslyshown, "
4555 " RECTABLE.recgroup, RECTABLE.dupmethod, c.commmethod, "
4556 " capturecard.cardid, 0, p.seriesid, "
4557 " p.programid, RECTABLE.inetref, p.category_type, "
4558 " p.airdate, p.stars, p.originalairdate, "
4559 " RECTABLE.inactive, RECTABLE.parentid, recordmatch.findid, "
4560 " RECTABLE.playgroup, oldrecstatus.recstatus, "
4561 " oldrecstatus.reactivate, p.videoprop+0, "
4562 " p.subtitletypes+0, p.audioprop+0, RECTABLE.storagegroup, "
4563 " capturecard.hostname, recordmatch.oldrecstatus, NULL, "
4564 " oldrecstatus.future, capturecard.schedorder, "
4565 " p.syndicatedepisodenumber, p.partnumber, p.parttotal, "
4566 " c.mplexid, capturecard.displayname, "
4567 " p.season, p.episode, p.totalepisodes, ") +
4570 "INNER JOIN RECTABLE ON (recordmatch.recordid = RECTABLE.recordid) "
4571 "INNER JOIN program AS p "
4572 "ON ( recordmatch.chanid = p.chanid AND "
4573 " recordmatch.starttime = p.starttime AND "
4574 " recordmatch.manualid = p.manualid ) "
4575 "INNER JOIN channel AS c "
4576 "ON ( c.chanid = p.chanid ) "
4577 "INNER JOIN capturecard "
4578 "ON ( c.sourceid = capturecard.sourceid AND "
4579 " ( capturecard.schedorder <> 0 OR "
4580 " capturecard.parentid = 0 ) ) "
4581 "LEFT JOIN oldrecorded as oldrecstatus "
4582 "ON ( oldrecstatus.station = c.callsign AND "
4583 " oldrecstatus.starttime = p.starttime AND "
4584 " oldrecstatus.title = p.title ) "
4585 "WHERE p.endtime > (NOW() - INTERVAL 480 MINUTE) "
4586 "ORDER BY RECTABLE.recordid DESC, p.starttime, p.title, c.callsign, "
4588 query.replace(
"RECTABLE", schedTmpRecord);
4590 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- Start DB Query..."));
4592 auto dbstart = nowAsDuration<std::chrono::microseconds>();
4599 auto dbend = nowAsDuration<std::chrono::microseconds>();
4600 auto dbTime = dbend - dbstart;
4602 LOG(VB_SCHEDULE, LOG_INFO,
4603 QString(
" |-- %1 results in %2 sec. Processing...")
4605 .arg(duration_cast<std::chrono::seconds>(dbTime).count()));
4609 while (result.
next())
4615 uint recordid = result.
value(17).toUInt();
4617 QString title = result.
value(4).toString();
4618 QString callsign = result.
value(8).toString();
4628 uint mplexid = result.
value(51).toUInt();
4629 if (mplexid == 32767)
4632 QString inputname = result.
value(52).toString();
4633 if (inputname.isEmpty())
4634 inputname = QString(
"Input %1").arg(result.
value(24).toUInt());
4639 result.
value(5).toString(),
4641 result.
value(6).toString(),
4642 result.
value(53).toInt(),
4643 result.
value(54).toInt(),
4644 result.
value(55).toInt(),
4645 result.
value(48).toString(),
4646 result.
value(11).toString(),
4648 result.
value(0).toUInt(),
4649 result.
value(7).toString(),
4651 result.
value(9).toString(),
4653 result.
value(21).toString(),
4654 result.
value(36).toString(),
4656 result.
value(43).toString(),
4657 result.
value(42).toString(),
4659 result.
value(30).toUInt(),
4660 result.
value(49).toUInt(),
4661 result.
value(50).toUInt(),
4663 result.
value(26).toString(),
4664 result.
value(27).toString(),
4665 result.
value(28).toString(),
4668 result.
value(12).toInt(),
4675 result.
value(31).toFloat(),
4676 (result.
value(32).isNull()) ? QDate() :
4680 result.
value(20).toBool(),
4683 result.
value(38).toBool(),
4686 result.
value(34).toUInt(),
4691 result.
value(1).toUInt(),
4692 result.
value(24).toUInt(),
4694 result.
value(35).toUInt(),
4697 result.
value(40).toUInt(),
4698 result.
value(39).toUInt(),
4699 result.
value(41).toUInt(),
4700 result.
value(46).toBool(),
4701 result.
value(47).toInt(),
4703 result.
value(24).toUInt(),
4706 if (!
p->m_future && !
p->IsReactivated() &&
4710 p->SetRecordingStatus(
p->m_oldrecstatus);
4713 p->SetRecordingPriority2(result.
value(56).toInt());
4722 if (
p->IsSameTitleStartTimeAndChannel(*r))
4724 if (r->m_sgroupId ==
p->m_sgroupId &&
4725 r->GetRecordingEndTime() !=
p->GetRecordingEndTime() &&
4726 (r->GetRecordingRuleID() ==
p->GetRecordingRuleID() ||
4741 tmpList.push_back(
p);
4748 (!cardMap.contains(
p->GetInputID()) || (
p->m_schedOrder == 0)))
4751 if (
p->m_schedOrder == 0 &&
4754 LOG(VB_GENERAL, LOG_WARNING,
LOC +
4755 QString(
"Channel %1, Title %2 %3 cardinput.schedorder = %4, "
4756 "it must be >0 to record from this input.")
4757 .arg(
p->GetChannelName(),
p->GetTitle(),
4758 p->GetScheduledStartTime().toString(),
4759 QString::number(
p->m_schedOrder)));
4765 if (checkTooMany && tooManyMap[
p->GetRecordingRuleID()] &&
4766 !
p->IsReactivated())
4774 else if (result.
value(15).toBool() && !
p->IsReactivated())
4778 !
p->IsReactivated() &&
4798 bool inactive = result.
value(33).toBool();
4813 p->SetRecordingStatus(newrecstatus);
4815 tmpList.push_back(
p);
4818 LOG(VB_SCHEDULE, LOG_INFO,
" +-- Cleanup...");
4819 for (
auto &
tmp : tmpList)
4827 QString query = QString(
4828 "SELECT RECTABLE.title, RECTABLE.subtitle, "
4829 " RECTABLE.description, RECTABLE.season, "
4830 " RECTABLE.episode, RECTABLE.category, "
4831 " RECTABLE.chanid, channel.channum, "
4832 " RECTABLE.station, channel.name, "
4833 " RECTABLE.recgroup, RECTABLE.playgroup, "
4834 " RECTABLE.seriesid, RECTABLE.programid, "
4835 " RECTABLE.inetref, RECTABLE.recpriority, "
4836 " RECTABLE.startdate, RECTABLE.starttime, "
4837 " RECTABLE.enddate, RECTABLE.endtime, "
4838 " RECTABLE.recordid, RECTABLE.type, "
4839 " RECTABLE.dupin, RECTABLE.dupmethod, "
4840 " RECTABLE.findid, "
4841 " RECTABLE.startoffset, RECTABLE.endoffset, "
4842 " channel.commmethod "
4844 "INNER JOIN channel ON (channel.chanid = RECTABLE.chanid) "
4845 "LEFT JOIN recordmatch on RECTABLE.recordid = recordmatch.recordid "
4846 "WHERE (type = %1 OR type = %2) AND "
4847 " recordmatch.chanid IS NULL")
4853 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- Start DB Query..."));
4855 auto dbstart = nowAsDuration<std::chrono::microseconds>();
4858 bool ok = result.
exec();
4859 auto dbend = nowAsDuration<std::chrono::microseconds>();
4860 auto dbTime = dbend - dbstart;
4868 LOG(VB_SCHEDULE, LOG_INFO,
4869 QString(
" |-- %1 results in %2 sec. Processing...")
4871 .arg(duration_cast<std::chrono::seconds>(dbTime).count()));
4873 while (result.
next())
4876#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
4878 result.
value(16).toDate(), result.
value(17).toTime(), Qt::UTC);
4880 result.
value(18).toDate(), result.
value(19).toTime(), Qt::UTC);
4882 static const QTimeZone utc(QTimeZone::UTC);
4884 result.
value(16).toDate(), result.
value(17).toTime(), utc);
4886 result.
value(18).toDate(), result.
value(19).toTime(), utc);
4889 QDateTime recstartts = startts.addSecs(result.
value(25).toInt() * -60LL);
4890 QDateTime recendts = endts.addSecs( result.
value(26).toInt() * +60LL);
4892 if (recstartts >= recendts)
4895 recstartts = startts;
4906 result.
value(0).toString(),
4908 (sor) ? result.
value(1).toString() : QString(),
4910 (sor) ? result.
value(2).toString() : QString(),
4911 result.
value(3).toUInt(),
4912 result.
value(4).toUInt(),
4915 result.
value(6).toUInt(),
4916 result.
value(7).toString(),
4917 result.
value(8).toString(),
4918 result.
value(9).toString(),
4920 result.
value(10).toString(),
4921 result.
value(11).toString(),
4923 result.
value(12).toString(),
4924 result.
value(13).toString(),
4925 result.
value(14).toString(),
4927 result.
value(15).toInt(),
4930 recstartts, recendts,
4934 result.
value(20).toUInt(),
4940 result.
value(24).toUInt(),
4944 tmpList.push_back(
p);
4947 for (
auto &
tmp : tmpList)
4958 QString sortColumn =
"title";
4968 QString prefixes = sh->getPrefixes();
4969 sortColumn =
"REGEXP_REPLACE(record.title,'" + prefixes +
"','')";
4973 sortColumn =
"record.recpriority";
4976 sortColumn =
"record.last_record";
4984 sortColumn =
"record.next_record IS NULL, record.next_record";
4987 sortColumn =
"record.type";
4991 QString order =
"ASC";
4995 QString query = QString(
4996 "SELECT record.title, record.subtitle, "
4997 " record.description, record.season, "
4998 " record.episode, record.category, "
4999 " record.chanid, channel.channum, "
5000 " record.station, channel.name, "
5001 " record.recgroup, record.playgroup, "
5002 " record.seriesid, record.programid, "
5003 " record.inetref, record.recpriority, "
5004 " record.startdate, record.starttime, "
5005 " record.enddate, record.endtime, "
5006 " record.recordid, record.type, "
5007 " record.dupin, record.dupmethod, "
5009 " channel.commmethod "
5011 "LEFT JOIN channel ON channel.callsign = record.station "
5012 " AND deleted IS NULL "
5013 "GROUP BY recordid "
5016 query = query.arg(sortColumn, order);
5027 while (result.
next())
5030#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
5031 QDateTime startts = QDateTime(result.
value(16).toDate(),
5032 result.
value(17).toTime(), Qt::UTC);
5033 QDateTime endts = QDateTime(result.
value(18).toDate(),
5034 result.
value(19).toTime(), Qt::UTC);
5036 static const QTimeZone utc(QTimeZone::UTC);
5037 QDateTime startts = QDateTime(result.
value(16).toDate(),
5038 result.
value(17).toTime(), utc);
5039 QDateTime endts = QDateTime(result.
value(18).toDate(),
5040 result.
value(19).toTime(), utc);
5043 if (!startts.isValid())
5045#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
5050 QTimeZone(QTimeZone::UTC));
5053 if (!endts.isValid())
5057 result.
value(0).toString(), QString(),
5058 result.
value(1).toString(), QString(),
5059 result.
value(2).toString(), result.
value(3).toUInt(),
5060 result.
value(4).toUInt(), result.
value(5).toString(),
5062 result.
value(6).toUInt(), result.
value(7).toString(),
5063 result.
value(8).toString(), result.
value(9).toString(),
5065 result.
value(10).toString(), result.
value(11).toString(),
5067 result.
value(12).toString(), result.
value(13).toString(),
5068 result.
value(14).toString(),
5070 result.
value(15).toInt(),
5077 result.
value(20).toUInt(), rectype,
5081 result.
value(24).toUInt(),
5180 QString recording_dir;
5184 "LiveTV", cur, cur.addSecs(3600), cardid,
5189 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"FindNextLiveTVDir: next dir is '%1'")
5190 .arg(recording_dir));
5197 const QString &title,
5199 const QString &storagegroup,
5200 const QDateTime &recstartts,
5201 const QDateTime &recendts,
5203 QString &recording_dir,
5206 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
"FillRecordingDir: Starting");
5211 if (cnt++ % 20 == 0)
5212 LOG(VB_SCHEDULE, LOG_WARNING,
"Waiting for main server.");
5213 std::this_thread::sleep_for(50ms);
5220 QStringList recsCounted;
5221 std::list<FileSystemInfo *> fsInfoList;
5222 std::list<FileSystemInfo *>::iterator fslistit;
5224 recording_dir.clear();
5226 if (dirlist.size() == 1)
5228 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5229 QString(
"FillRecordingDir: The only directory in the %1 Storage "
5230 "Group is %2, so it will be used by default.")
5231 .arg(storagegroup, dirlist[0]));
5232 recording_dir = dirlist[0];
5233 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
"FillRecordingDir: Finished");
5238 int weightPerRecording =
5240 int weightPerPlayback =
5242 int weightPerCommFlag =
5244 int weightPerTranscode =
5247 QString storageScheduler =
5249 int localStartingWeight =
5251 (storageScheduler !=
"Combination") ? 0
5252 : (
int)(-1.99 * weightPerRecording));
5253 int remoteStartingWeight =
5255 std::chrono::seconds maxOverlap =
5260 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5261 "FillRecordingDir: Calculating initial FS Weights.");
5272 tmpWeight = localStartingWeight;
5273 msg +=
" is local (" + QString::number(tmpWeight) +
")";
5277 tmpWeight = remoteStartingWeight;
5278 msg +=
" is remote (+" + QString::number(tmpWeight) +
")";
5288 msg +=
", has SGweightPerDir offset of "
5289 + QString::number(tmpWeight) +
")";
5291 msg +=
". initial dir weight = " + QString::number(fs->
getWeight());
5292 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, msg);
5294 fsInfoList.push_back(fs);
5297 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5298 "FillRecordingDir: Adjusting FS Weights from inuseprograms.");
5301 saveRecDir.
prepare(
"UPDATE inuseprograms "
5302 "SET recdir = :RECDIR "
5303 "WHERE chanid = :CHANID AND "
5304 " starttime = :STARTTIME");
5307 "SELECT i.chanid, i.starttime, r.endtime, recusage, rechost, recdir "
5308 "FROM inuseprograms i, recorded r "
5309 "WHERE DATE_ADD(lastupdatetime, INTERVAL 16 MINUTE) > NOW() AND "
5310 " i.chanid = r.chanid AND "
5311 " i.starttime = r.starttime");
5319 while (query.
next())
5321 uint recChanid = query.
value(0).toUInt();
5324 QString recUsage( query.
value(3).toString());
5325 QString recHost( query.
value(4).toString());
5326 QString recDir( query.
value(5).toString());
5328 if (recDir.isEmpty())
5332 recDir = recDir.isEmpty() ?
"_UNKNOWN_" : recDir;
5334 saveRecDir.
bindValue(
":RECDIR", recDir);
5335 saveRecDir.
bindValue(
":CHANID", recChanid);
5336 saveRecDir.
bindValue(
":STARTTIME", recStart);
5337 if (!saveRecDir.
exec())
5340 if (recDir ==
"_UNKNOWN_")
5343 for (fslistit = fsInfoList.begin();
5344 fslistit != fsInfoList.end(); ++fslistit)
5350 int weightOffset = 0;
5354 if (recEnd > recstartts.addSecs(maxOverlap.count()))
5356 weightOffset += weightPerRecording;
5357 recsCounted << QString::number(recChanid) +
":" +
5363 weightOffset += weightPerPlayback;
5367 weightOffset += weightPerCommFlag;
5371 weightOffset += weightPerTranscode;
5376 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5377 QString(
" %1 @ %2 in use by '%3' on %4:%5, FSID "
5378 "#%6, FSID weightOffset +%7.")
5379 .arg(QString::number(recChanid),
5381 recUsage, recHost, recDir,
5383 QString::number(weightOffset)));
5391 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5392 QString(
" %1:%2 => old weight %3 plus "
5394 .arg(
fs2->getHostname(),
5396 .arg(
fs2->getWeight())
5398 .arg(
fs2->getWeight() + weightOffset));
5400 fs2->setWeight(
fs2->getWeight() + weightOffset);
5410 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5411 "FillRecordingDir: Adjusting FS Weights from scheduler.");
5413 for (
auto *thispg : reclist)
5415 if ((recendts < thispg->GetRecordingStartTime()) ||
5416 (recstartts > thispg->GetRecordingEndTime()) ||
5419 (thispg->GetInputID() == 0) ||
5420 (recsCounted.contains(QString(
"%1:%2").arg(thispg->GetChanID())
5422 (thispg->GetPathname().isEmpty()))
5425 for (fslistit = fsInfoList.begin();
5426 fslistit != fsInfoList.end(); ++fslistit)
5429 if ((fs->
getHostname() == thispg->GetHostname()) &&
5430 (fs->
getPath() == thispg->GetPathname()))
5432 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5433 QString(
"%1 @ %2 will record on %3:%4, FSID #%5, "
5434 "weightPerRecording +%6.")
5435 .arg(thispg->GetChanID())
5438 .arg(fs->
getFSysID()).arg(weightPerRecording));
5447 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5448 QString(
" %1:%2 => old weight %3 plus %4 = %5")
5449 .arg(
fs2->getHostname(),
fs2->getPath())
5450 .arg(
fs2->getWeight()).arg(weightPerRecording)
5451 .arg(
fs2->getWeight() + weightPerRecording));
5453 fs2->setWeight(
fs2->getWeight() + weightPerRecording);
5461 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5462 QString(
"Using '%1' Storage Scheduler directory sorting algorithm.")
5463 .arg(storageScheduler));
5465 if (storageScheduler ==
"BalancedFreeSpace")
5467 else if (storageScheduler ==
"BalancedPercFreeSpace")
5469 else if (storageScheduler ==
"BalancedDiskIO")
5476 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5477 "--- FillRecordingDir Sorted fsInfoList start ---");
5478 for (fslistit = fsInfoList.begin();fslistit != fsInfoList.end();
5482 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
"%1:%2")
5484 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
" Location : %1")
5485 .arg((fs->
isLocal()) ?
"local" :
"remote"));
5486 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
" weight : %1")
5488 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
" free space : %5")
5491 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5492 "--- FillRecordingDir Sorted fsInfoList end ---");
5501 long long maxSizeKB = (maxByterate + maxByterate/3) *
5502 recstartts.secsTo(recendts) / 1024;
5504 bool simulateAutoExpire =
5507 (fsInfoList.size() > 1));
5521 for (
unsigned int pass = 1; pass <= 3; pass++)
5523 bool foundDir =
false;
5525 if ((pass == 2) && simulateAutoExpire)
5528 QMap <int , long long> remainingSpaceKB;
5529 for (fslistit = fsInfoList.begin();
5530 fslistit != fsInfoList.end(); ++fslistit)
5532 remainingSpaceKB[(*fslistit)->getFSysID()] =
5533 (*fslistit)->getFreeSpace();
5540 for (
auto & expire : expiring)
5544 for (fslistit = fsInfoList.begin();
5545 fslistit != fsInfoList.end(); ++fslistit)
5548 if (expire->GetHostname() != (*fslistit)->getHostname())
5552 if (!dirlist.contains((*fslistit)->getPath()))
5556 (*fslistit)->getPath() +
"/" + expire->GetPathname();
5563 if (checkFile.exists())
5571 QString backuppath = expire->GetPathname();
5573 bool foundSlave =
false;
5575 for (
auto * enc : std::as_const(*
m_tvList))
5577 if (enc->GetHostName() ==
5580 enc->CheckFile(programinfo);
5598 LOG(VB_GENERAL, LOG_ERR,
5599 QString(
"Unable to match '%1' "
5600 "to any file system. Ignoring it.")
5601 .arg(expire->GetBasename()));
5607 expire->GetFilesize() / 1024;
5610 long long desiredSpaceKB =
5614 (desiredSpaceKB + maxSizeKB))
5616 recording_dir = fs->
getPath();
5619 LOG(VB_FILE, LOG_INFO,
5620 QString(
"pass 2: '%1' will record in '%2' "
5621 "although there is only %3 MB free and the "
5622 "AutoExpirer wants at least %4 MB. This "
5623 "directory has the highest priority files "
5624 "to be expired from the AutoExpire list and "
5625 "there are enough that the Expirer should "
5626 "be able to free up space for this recording.")
5627 .arg(title, recording_dir)
5629 .arg(desiredSpaceKB / 1024));
5640 for (fslistit = fsInfoList.begin();
5641 fslistit != fsInfoList.end(); ++fslistit)
5643 long long desiredSpaceKB = 0;
5650 (dirlist.contains(fs->
getPath())) &&
5654 recording_dir = fs->
getPath();
5659 LOG(VB_FILE, LOG_INFO,
5660 QString(
"pass 1: '%1' will record in "
5661 "'%2' which has %3 MB free. This recording "
5662 "could use a max of %4 MB and the "
5663 "AutoExpirer wants to keep %5 MB free.")
5664 .arg(title, recording_dir)
5666 .arg(maxSizeKB / 1024)
5667 .arg(desiredSpaceKB / 1024));
5671 LOG(VB_FILE, LOG_INFO,
5672 QString(
"pass %1: '%2' will record in "
5673 "'%3' although there is only %4 MB free and "
5674 "the AutoExpirer wants at least %5 MB. "
5675 "Something will have to be deleted or expired "
5676 "in order for this recording to complete "
5678 .arg(pass).arg(title, recording_dir)
5680 .arg(desiredSpaceKB / 1024));
5693 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
"FillRecordingDir: Finished");
5706 QMap <int, bool> fsMap;
5707 for (
const auto&
fs1 : std::as_const(fsInfos))
5709 fsMap[
fs1.getFSysID()] =
true;
5713 LOG(VB_FILE, LOG_INFO,
LOC +
5714 QString(
"FillDirectoryInfoCache: found %1 unique filesystems")
5715 .arg(fsMap.size()));
5722 auto secsleft = std::chrono::seconds(curtime.secsTo(
m_livetvTime));
5726 if (secsleft - prerollseconds > 120s)
5730 for (
auto * enc : std::as_const(*
m_tvList))
5746 if (
m_schedTime.secsTo(dummy->GetRecordingEndTime()) < 1800)
5747 dummy->SetRecordingEndTime(
m_schedTime.addSecs(1800));
5748 dummy->SetInputID(enc->GetInputID());
5749 dummy->m_mplexId = dummy->QueryMplexID();
5772 bool autoStart =
false;
5774 QDateTime startupTime = QDateTime();
5780 if (startupTime.isValid())
5783 startupSecs = std::max(startupSecs, 15 * 60s);
5790 LOG(VB_GENERAL, LOG_INFO,
5791 "Close to auto-start time, AUTO-Startup assumed");
5797 LOG(VB_GENERAL, LOG_INFO,
5798 "Close to MythFillDB suggested run time, AUTO-Startup to fetch guide data?");
5804 LOG(VB_GENERAL, LOG_DEBUG,
5805 "NOT close to auto-start time, USER-initiated startup assumed");
5808 else if (!s.isEmpty())
5810 LOG(VB_GENERAL, LOG_ERR,
LOC +
5811 QString(
"Invalid MythShutdownWakeupTime specified in database (%1)")
5823 QMap<uint, QSet<uint> > inputSets;
5824 query.
prepare(
"SELECT DISTINCT ci1.cardid, ci2.cardid "
5825 "FROM capturecard ci1, capturecard ci2, "
5826 " inputgroup ig1, inputgroup ig2 "
5827 "WHERE ci1.cardid = ig1.cardinputid AND "
5828 " ci2.cardid = ig2.cardinputid AND"
5829 " ig1.inputgroupid = ig2.inputgroupid AND "
5830 " ci1.cardid <= ci2.cardid "
5831 "ORDER BY ci1.cardid, ci2.cardid");
5837 while (query.
next())
5841 inputSets[id0].insert(id1);
5842 inputSets[id1].insert(id0);
5845 QMap<uint, QSet<uint> >::iterator mit;
5846 for (mit = inputSets.begin(); mit != inputSets.end(); ++mit)
5848 uint inputid = mit.key();
5856 QSet<uint> fullset = mit.value();
5857 QSet<uint> checkset;
5858 QSet<uint>::const_iterator sit;
5859 while (checkset != fullset)
5862 for (
int item : std::as_const(checkset))
5863 fullset += inputSets[item];
5868 auto *conflictlist =
new RecList();
5870 for (
int item : std::as_const(checkset))
5872 LOG(VB_SCHEDULE, LOG_INFO,
5873 QString(
"Assigning input %1 to conflict set %2")
5881 query.
prepare(
"SELECT ci.cardid "
5882 "FROM capturecard ci "
5883 "LEFT JOIN inputgroup ig "
5884 " ON ci.cardid = ig.cardinputid "
5885 "WHERE ig.cardinputid IS NULL");
5891 while (query.
next())
5895 LOG(VB_GENERAL, LOG_ERR,
LOC +
5896 QString(
"Input %1 is not assigned to any input group").arg(
id));
5897 auto *conflictlist =
new RecList();
5899 LOG(VB_SCHEDULE, LOG_INFO,
5900 QString(
"Assigning input %1 to conflict set %2")
5914 query.
prepare(
"SELECT cardid, parentid, schedgroup "
5916 "WHERE sourceid > 0 "
5924 while (query.
next())
5927 uint parentid = query.
value(1).toUInt();
5944 LOG(VB_SCHEDULE, LOG_INFO,
5945 QString(
"Added SchedInputInfo i=%1, g=%2, sg=%3")
5954 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
5955 QString(
"AddChildInput: Handling parent = %1, input = %2")
5956 .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...