10 #else // if !__linux__
11 # include <sys/param.h>
13 # include <sys/mount.h>
19 #include <sys/types.h>
22 #include <QStringList>
54 #define LOC QString("Scheduler: ")
55 #define LOC_WARN QString("Scheduler, Warning: ")
56 #define LOC_ERR QString("Scheduler, Error: ")
63 const QString& tmptable,
Scheduler *master_sched) :
65 m_recordTable(tmptable),
66 m_priorityTable(
"powerpriority"),
67 m_specSched(master_sched),
79 if (tmptable ==
"powerpriority_tmp")
94 start(QThread::LowPriority);
160 if (!query.
exec(
"SELECT count(*) FROM capturecard") || !query.
next())
169 LOG(VB_GENERAL, LOG_ERR,
LOC +
170 "No capture cards are defined in the database.\n\t\t\t"
171 "Perhaps you should re-read the installation instructions?");
175 query.
prepare(
"SELECT sourceid,name FROM videosource ORDER BY sourceid;");
190 "WHERE sourceid = :SOURCEID "
194 if (!subquery.
exec())
198 else if (!subquery.
next())
200 LOG(VB_GENERAL, LOG_WARNING,
LOC +
201 QString(
"Video source '%1' is defined, "
202 "but is not attached to a card input.")
203 .arg(query.
value(1).toString()));
213 LOG(VB_GENERAL, LOG_ERR,
LOC +
214 "No channel sources defined in the database");
263 return aprec < bprec;
286 Qt::CaseInsensitive);
300 Qt::CaseInsensitive);
314 int arec =
static_cast<int>
319 int brec =
static_cast<int>
334 int atype =
static_cast<int>
337 int btype =
static_cast<int>
341 return atype > btype;
344 int apast =
static_cast<int>
346 int bpast =
static_cast<int>
349 return apast < bpast;
380 int arec =
static_cast<int>
383 int brec =
static_cast<int>
396 int atype =
static_cast<int>
399 int btype =
static_cast<int>
403 return atype > btype;
406 int apast =
static_cast<int>
408 int bpast =
static_cast<int>
411 return apast < bpast;
446 LOG(VB_SCHEDULE, LOG_INFO,
"BuildWorkList...");
451 LOG(VB_SCHEDULE, LOG_INFO,
"AddNewRecords...");
453 LOG(VB_SCHEDULE, LOG_INFO,
"AddNotListed...");
456 LOG(VB_SCHEDULE, LOG_INFO,
"Sort by time...");
458 LOG(VB_SCHEDULE, LOG_INFO,
"PruneOverlaps...");
461 LOG(VB_SCHEDULE, LOG_INFO,
"Sort by priority...");
463 LOG(VB_SCHEDULE, LOG_INFO,
"BuildListMaps...");
465 LOG(VB_SCHEDULE, LOG_INFO,
"SchedNewRecords...");
467 LOG(VB_SCHEDULE, LOG_INFO,
"SchedLiveTV...");
469 LOG(VB_SCHEDULE, LOG_INFO,
"ClearListMaps...");
474 LOG(VB_SCHEDULE, LOG_INFO,
"Sort by time...");
476 LOG(VB_SCHEDULE, LOG_INFO,
"PruneRedundants...");
479 LOG(VB_SCHEDULE, LOG_INFO,
"Sort by time...");
481 LOG(VB_SCHEDULE, LOG_INFO,
"ClearWorkList...");
499 where =
"WHERE recordid IS NULL ";
501 thequery = QString(
"CREATE TEMPORARY TABLE recordmatch ") +
502 "SELECT * FROM recordmatch " + where +
"; ";
506 bool ok = query.
exec();
514 thequery =
"ALTER TABLE recordmatch "
515 " ADD UNIQUE INDEX (recordid, chanid, starttime); ";
523 thequery =
"ALTER TABLE recordmatch "
524 " ADD INDEX (chanid, starttime, manualid); ";
534 auto fillstart = nowAsDuration<std::chrono::microseconds>();
536 auto fillend = nowAsDuration<std::chrono::microseconds>();
537 auto matchTime = fillend - fillstart;
539 LOG(VB_SCHEDULE, LOG_INFO,
"CreateTempTables...");
542 fillstart = nowAsDuration<std::chrono::microseconds>();
543 LOG(VB_SCHEDULE, LOG_INFO,
"UpdateDuplicates...");
545 fillend = nowAsDuration<std::chrono::microseconds>();
546 auto checkTime = fillend - fillstart;
548 fillstart = nowAsDuration<std::chrono::microseconds>();
550 fillend = nowAsDuration<std::chrono::microseconds>();
551 auto placeTime = fillend - fillstart;
553 LOG(VB_SCHEDULE, LOG_INFO,
"DeleteTempTables...");
557 queryDrop.
prepare(
"DROP TABLE recordmatch;");
558 if (!queryDrop.
exec())
564 QString msg = QString(
"Speculative scheduled %1 items in %2 "
565 "= %3 match + %4 check + %5 place")
567 .arg(duration_cast<floatsecs>(matchTime + checkTime + placeTime).count(), 0,
'f', 1)
568 .arg(duration_cast<floatsecs>(matchTime).count(), 0,
'f', 2)
569 .arg(duration_cast<floatsecs>(checkTime).count(), 0,
'f', 2)
570 .arg(duration_cast<floatsecs>(placeTime).count(), 0,
'f', 2);
571 LOG(VB_GENERAL, LOG_INFO, msg);
582 for (
auto & it : schedList)
593 LOG(VB_SCHEDULE, LOG_INFO,
"--- print list start ---");
594 LOG(VB_SCHEDULE, LOG_INFO,
"Title - Subtitle Ch Station "
595 "Day Start End G I T N Pri");
597 for (
auto *first : list)
599 if (onlyFutureRecordings &&
600 ((first->GetRecordingEndTime() < now &&
601 first->GetScheduledEndTime() < now) ||
602 (first->GetRecordingStartTime() < now && !
Recording(first))))
608 LOG(VB_SCHEDULE, LOG_INFO,
"--- print list end ---");
620 #ifndef NDEBUG // debug compile type
621 static QString initialOutstr =
" ";
622 #else // defined NDEBUG
623 static QString initialOutstr =
"";
626 QString outstr = initialOutstr +
prefix;
629 .leftJustified(34 -
prefix.length(),
' ',
true);
631 outstr += QString(
"%1 %2 %3 %4-%5 %6 %7 ")
633 p->GetChanNum().rightJustified(5,
' '),
634 p->GetChannelSchedulingID().leftJustified(7,
' ',
true),
635 p->GetRecordingStartTime().toLocalTime().toString(
"dd hh:mm"),
636 p->GetRecordingEndTime().toLocalTime().toString(
"hh:mm"),
637 p->GetShortInputName().rightJustified(2,
' '),
638 QString::number(
p->GetInputID()).rightJustified(2,
' '));
639 outstr += QString(
"%1 %2 %3")
640 .arg(
toQChar(
p->GetRecordingRuleType()))
642 .arg(
p->GetRecordingPriority());
643 if (
p->GetRecordingPriority2())
644 outstr += QString(
"/%1").arg(
p->GetRecordingPriority2());
646 LOG(VB_SCHEDULE, LOG_INFO, outstr);
655 if (
p->IsSameTitleTimeslotAndChannel(*pginfo))
674 LOG(VB_GENERAL, LOG_INFO,
675 QString(
"Updating status for %1 on cardid [%2] (%3 => %4)")
677 QString::number(
p->GetInputID()),
679 p->GetRecordingRuleType()),
681 p->GetRecordingRuleType())));
689 p->AddHistory(
false);
707 const QDateTime &startts,
709 const QDateTime &recendts)
715 if (
p->GetInputID() == cardid &&
p->GetChanID() == chanid &&
716 p->GetScheduledStartTime() == startts)
718 p->SetRecordingEndTime(recendts);
720 if (
p->GetRecordingStatus() != recstatus)
722 LOG(VB_GENERAL, LOG_INFO,
723 QString(
"Updating status for %1 on cardid [%2] (%3 => %4)")
725 QString::number(
p->GetInputID()),
727 p->GetRecordingRuleType()),
729 p->GetRecordingRuleType())));
735 p->SetRecordingStatus(recstatus);
737 p->AddHistory(
false);
788 LOG(VB_GENERAL, LOG_ERR,
789 QString(
"Failed to change end time on card %1 to %2")
836 for (
auto *sp : slavelist)
842 if (!sp->GetTitle().isEmpty() &&
843 sp->GetScheduledStartTime() == rp->GetScheduledStartTime() &&
844 sp->GetChannelSchedulingID().compare(
845 rp->GetChannelSchedulingID(), Qt::CaseInsensitive) == 0 &&
846 sp->GetTitle().compare(rp->GetTitle(),
847 Qt::CaseInsensitive) == 0)
849 if (sp->GetInputID() == rp->GetInputID() ||
854 rp->SetRecordingStatus(sp->GetRecordingStatus());
856 rp->AddHistory(
false);
857 LOG(VB_GENERAL, LOG_INFO,
858 QString(
"setting %1/%2/\"%3\" as %4")
859 .arg(QString::number(sp->GetInputID()),
860 sp->GetChannelSchedulingID(),
866 LOG(VB_GENERAL, LOG_NOTICE,
867 QString(
"%1/%2/\"%3\" is already recording on card %4")
868 .arg(sp->GetInputID())
869 .arg(sp->GetChannelSchedulingID(),
871 .arg(rp->GetInputID()));
874 else if (sp->GetInputID() == rp->GetInputID() &&
881 rp->AddHistory(
false);
882 LOG(VB_GENERAL, LOG_INFO,
883 QString(
"setting %1/%2/\"%3\" as aborted")
884 .arg(QString::number(rp->GetInputID()),
885 rp->GetChannelSchedulingID(),
890 if (sp->GetInputID() && !found)
892 sp->m_mplexId = sp->QueryMplexID();
896 sp->AddHistory(
false);
897 LOG(VB_GENERAL, LOG_INFO,
898 QString(
"adding %1/%2/\"%3\" as recording")
899 .arg(QString::number(sp->GetInputID()),
900 sp->GetChannelSchedulingID(),
912 if (rp->GetInputID() == cardid &&
921 rp->AddHistory(
false,
false,
true);
926 rp->AddHistory(
false);
929 LOG(VB_GENERAL, LOG_INFO, QString(
"setting %1/%2/\"%3\" as aborted")
930 .arg(QString::number(rp->GetInputID()), rp->GetChannelSchedulingID(),
982 for (
auto it = reclist.begin(); it != reclist.end(); ++it)
1010 *(dreciter++) =
nullptr;
1019 QMap<uint, uint> badinputs;
1034 ++badinputs[
p->GetInputID()];
1037 conflictlist->push_back(
p);
1043 QMap<uint, uint>::iterator it;
1044 for (it = badinputs.begin(); it != badinputs.end(); ++it)
1047 QString(
"Ignored %1 entries for invalid input %2")
1048 .arg(badinputs[it.value()]).arg(it.key()));
1083 bool ignoreinput)
const
1086 for ( ; iter != cardlist.end(); ++iter)
1098 msg = QString(
"comparing with '%1' ").arg(q->
GetTitle());
1100 if (
p->GetInputID() != q->
GetInputID() && !ignoreinput)
1102 const std::vector<unsigned int> &conflicting_inputs =
1104 if (
find(conflicting_inputs.begin(), conflicting_inputs.end(),
1105 q->
GetInputID()) == conflicting_inputs.end())
1108 msg +=
" cardid== ";
1117 msg +=
" no-overlap ";
1124 (((
p->m_mplexId != 0U) &&
p->m_mplexId == q->
m_mplexId) ||
1125 ((
p->m_mplexId == 0U) &&
p->GetChanID() == q->
GetChanID()));
1137 msg +=
" no-overlap ";
1146 LOG(VB_SCHEDULE, LOG_INFO, msg);
1147 LOG(VB_SCHEDULE, LOG_INFO,
1148 QString(
" cardid's: [%1], [%2] Share an input group"
1149 "mplexid's: %3, %4")
1163 LOG(VB_SCHEDULE, LOG_INFO,
"Found conflict");
1166 *paffinity += affinity;
1171 LOG(VB_SCHEDULE, LOG_INFO,
"No conflict");
1174 *paffinity += affinity;
1182 bool checkAll)
const
1185 auto k = conflictlist.cbegin();
1192 return firstConflict;
1219 for (
auto *q : showinglist)
1228 if (q->IsSameTitleStartTimeAndChannel(*
p))
1234 if (q->GetRecordingStartTime() <
p->GetRecordingStartTime())
1246 p->m_savedrecstatus =
p->GetRecordingStatus();
1254 p->SetRecordingStatus(
p->m_savedrecstatus);
1275 uint bestaffinity = 0;
1277 for (
auto *q : *showinglist)
1283 (q->GetRecordingPriority() <
p->GetRecordingPriority() ||
1284 (q->GetRecordingPriority() ==
p->GetRecordingPriority() &&
1285 q->GetRecordingPriority2() <
p->GetRecordingPriority2())))
1297 if (!
p->IsSameTitleStartTimeAndChannel(*q))
1332 PrintRec(q, QString(
" %1:").arg(affinity));
1333 if (!best || affinity > bestaffinity)
1336 bestaffinity = affinity;
1344 QString msg = QString(
1345 "Moved \"%1\" on chanid: %2 from card: %3 to %4 at %5 "
1346 "to avoid LiveTV conflict")
1347 .arg(
p->GetTitle()).arg(
p->GetChanID())
1350 LOG(VB_GENERAL, LOG_INFO, msg);
1362 p->SetRecordingStatus(oldstatus);
1370 LOG(VB_SCHEDULE, LOG_DEBUG,
1371 "+ = schedule this showing to be recorded");
1372 LOG(VB_SCHEDULE, LOG_DEBUG,
1373 "n: = could schedule this showing with affinity");
1374 LOG(VB_SCHEDULE, LOG_DEBUG,
1375 "n# = could not schedule this showing, with affinity");
1376 LOG(VB_SCHEDULE, LOG_DEBUG,
1377 "! = conflict caused by this showing");
1378 LOG(VB_SCHEDULE, LOG_DEBUG,
1379 "/ = retry this showing, same priority pass");
1380 LOG(VB_SCHEDULE, LOG_DEBUG,
1381 "? = retry this showing, lower priority pass");
1382 LOG(VB_SCHEDULE, LOG_DEBUG,
1383 "> = try another showing for this program");
1384 LOG(VB_SCHEDULE, LOG_DEBUG,
1385 "- = unschedule a showing in favor of another one");
1404 auto levelStart = i;
1405 int recpriority = (*i)->GetRecordingPriority();
1410 (*i)->GetRecordingPriority() != recpriority)
1413 auto sublevelStart = i;
1414 int recpriority2 = (*i)->GetRecordingPriority2();
1415 LOG(VB_SCHEDULE, LOG_DEBUG, QString(
"Trying priority %1/%2...")
1416 .arg(recpriority).arg(recpriority2));
1420 LOG(VB_SCHEDULE, LOG_DEBUG, QString(
"Retrying priority %1/%2...")
1421 .arg(recpriority).arg(recpriority2));
1426 LOG(VB_SCHEDULE, LOG_DEBUG, QString(
"Retrying priority %1/*...")
1436 int recpriority,
int recpriority2)
1442 for ( ; i != end; ++i)
1444 if ((*i)->GetRecordingPriority() != recpriority ||
1445 (*i)->GetRecordingPriority2() != recpriority2 ||
1452 (*i)->GetRecordingPriority() != recpriority ||
1453 (*i)->GetRecordingPriority2() != recpriority2)
1458 uint bestaffinity = 0;
1461 for ( ; i != end; ++i)
1463 if ((*i)->GetRecordingPriority() != recpriority ||
1464 (*i)->GetRecordingPriority2() != recpriority2 ||
1465 (*i)->GetRecordingStartTime() !=
1467 (*i)->GetRecordingRuleID() !=
1469 (*i)->GetTitle() != first->
GetTitle() ||
1484 PrintRec(*i, QString(
" %1#").arg(affinity));
1489 PrintRec(*i, QString(
" %1:").arg(affinity));
1490 if (!best || affinity > bestaffinity)
1493 bestaffinity = affinity;
1514 bool samePriority,
bool livetv)
1518 for ( ; i != end; ++i)
1521 retry_list.push_back(*i);
1523 std::stable_sort(retry_list.begin(), retry_list.end(),
comp_retry);
1525 for (
auto *
p : retry_list)
1544 auto k = conflictlist.cbegin();
1566 int lastrecpri2 = 0;
1601 p->SetRecordingStatus(
p->m_oldrecstatus);
1612 p->ClearInputName();
1630 p->GetRecordingPriority2() >
1634 lastrecpri2 -
p->GetRecordingPriority2());
1649 QMap<int, QDateTime> nextRecMap;
1657 nextRecMap[
p->GetRecordingRuleID()].isNull())
1659 nextRecMap[
p->GetRecordingRuleID()] =
p->GetRecordingStartTime();
1663 p->GetParentRecordingRuleID() > 0 &&
1666 nextRecMap[
p->GetParentRecordingRuleID()].isNull())
1668 nextRecMap[
p->GetParentRecordingRuleID()] =
1669 p->GetRecordingStartTime();
1675 query.
prepare(
"SELECT recordid, next_record FROM record;");
1681 while (query.
next())
1683 int recid = query.
value(0).toInt();
1686 if (next_record == nextRecMap[recid])
1689 if (nextRecMap[recid].isValid())
1691 subquery.
prepare(
"UPDATE record SET next_record = :NEXTREC "
1692 "WHERE recordid = :RECORDID;");
1694 subquery.
bindValue(
":NEXTREC", nextRecMap[recid]);
1695 if (!subquery.
exec())
1698 else if (next_record.isValid())
1700 subquery.
prepare(
"UPDATE record "
1701 "SET next_record = NULL "
1702 "WHERE recordid = :RECORDID;");
1704 if (!subquery.
exec())
1716 strlist << QString::number(retlist.size());
1718 while (!retlist.empty())
1721 p->ToStringList(strlist);
1723 retlist.pop_front();
1734 nullptr,
true); ++i)
1745 bool hasconflicts =
false;
1749 if (recRuleId > 0 &&
1750 p->GetRecordingRuleID() !=
static_cast<uint>(recRuleId))
1753 hasconflicts =
true;
1757 return hasconflicts;
1764 bool hasconflicts =
false;
1768 if (recRuleId > 0 &&
1769 p->GetRecordingRuleID() !=
static_cast<uint>(recRuleId))
1773 hasconflicts =
true;
1777 return hasconflicts;
1784 QMap<QString,ProgramInfo*> recMap;
1801 if (recordedid ==
p->GetRecordingID())
1830 strList << QString::number(static_cast<int>(hasconflicts));
1831 strList << QString::number(retlist.size());
1833 while (!retlist.empty())
1836 p->ToStringList(strList);
1838 retlist.pop_front();
1850 strList << QString::number(schedlist.size());
1852 while (!schedlist.empty())
1857 schedlist.pop_front();
1872 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"AddRecording() recid: %1")
1878 p->IsSameTitleTimeslotAndChannel(pi))
1880 LOG(VB_GENERAL, LOG_INFO,
LOC +
"Not adding recording, " +
1881 QString(
"'%1' is already in reclist.")
1887 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
1888 QString(
"Adding '%1' to reclist.").arg(pi.
GetTitle()));
1891 new_pi->m_mplexId = new_pi->QueryMplexID();
1892 new_pi->m_sgroupId =
m_sinputInfoMap[new_pi->GetInputID()].m_sgroupId;
1898 new_pi->AddHistory(
false);
1901 new_pi->GetRecordingRule();
1905 QString(
"AddRecording %1").arg(pi.
GetTitle()));
1913 LOG(VB_GENERAL, LOG_ERR,
LOC +
1914 "IsBusyRecording() -> true, no tvList or no rcinfo");
1925 bool is_busy = rctv1->
IsBusy(&busy_input, -1s);
1937 const std::vector<unsigned int> &inputids =
m_sinputInfoMap[inputid].m_conflictingInputs;
1938 std::vector<unsigned int> &group_inputs =
m_sinputInfoMap[inputid].m_groupInputs;
1939 for (
uint id : inputids)
1944 LOG(VB_SCHEDULE, LOG_ERR,
LOC +
1945 QString(
"IsBusyRecording() -> true, rctv(NULL) for input %2")
1952 if (rctv2->
IsBusy(&busy_input, -1s))
1973 std::find(group_inputs.begin(), group_inputs.end(),
1974 id) != group_inputs.end())
1991 query.
prepare(
"UPDATE oldrecorded SET recstatus = :RSABORTED "
1992 " WHERE recstatus = :RSRECORDING OR "
1993 " recstatus = :RSTUNING OR "
1994 " recstatus = :RSFAILING");
2003 query.
prepare(
"UPDATE oldrecorded SET recstatus = :RSMISSED "
2004 "WHERE recstatus = :RSWILLRECORD OR "
2005 " recstatus = :RSPENDING");
2014 query.
prepare(
"UPDATE oldrecorded SET recstatus = :RSPREVIOUS "
2015 "WHERE recstatus = :RSCURRENT");
2024 query.
prepare(
"UPDATE oldrecorded SET future = 0 "
2025 "WHERE future > 0 AND "
2026 " endtime < (NOW() - INTERVAL 475 MINUTE)");
2053 std::chrono::seconds prerollseconds = 0s;
2054 std::chrono::seconds wakeThreshold = 5min;
2057 bool blockShutdown =
2059 bool firstRun =
true;
2062 QDateTime idleSince = QDateTime();
2063 std::chrono::seconds schedRunTime = 0s;
2064 bool statuschanged =
false;
2066 QDateTime nextWakeTime = nextStartTime;
2080 nextWakeTime = std::min(nextWakeTime, nextStartTime);
2082 auto secs_to_next = std::chrono::seconds(curtime.secsTo(nextStartTime));
2083 auto sched_sleep = std::max(std::chrono::milliseconds(curtime.msecsTo(nextWakeTime)), 0ms);
2085 sched_sleep = std::min(sched_sleep, 15000ms);
2087 int const kSleepCheck = 300;
2088 bool checkSlaves = curtime >= nextSleepCheck;
2092 if ((secs_to_next > -60s && secs_to_next < schedRunTime) ||
2093 (!haveRequests && !checkSlaves))
2095 if (sched_sleep > 0ms)
2097 LOG(VB_SCHEDULE, LOG_INFO,
2098 QString(
"sleeping for %1 ms "
2099 "(s2n: %2 sr: %3 qr: %4 cs: %5)")
2100 .arg(sched_sleep.count()).arg(secs_to_next.count()).arg(schedRunTime.count())
2101 .arg(haveRequests).arg(checkSlaves));
2123 wakeThreshold = std::max(wakeThreshold, prerollseconds + 120s);
2125 QElapsedTimer
t;
t.start();
2128 statuschanged =
true;
2131 auto elapsed = std::chrono::ceil<std::chrono::seconds>(std::chrono::milliseconds(
t.elapsed()));
2132 schedRunTime = std::max(elapsed + elapsed/2 + 2s, schedRunTime);
2153 checkSlaves =
false;
2163 nextWakeTime = nextSleepCheck;
2167 for ( ; startIter !=
m_recList.end(); ++startIter)
2169 if ((*startIter)->GetRecordingStatus() !=
2170 (*startIter)->m_oldrecstatus)
2178 for (
auto it = startIter; it !=
m_recList.end(); ++it)
2180 auto secsleft = std::chrono::seconds(curtime.secsTo((*it)->GetRecordingStartTime()));
2181 auto timeBeforePreroll = secsleft - prerollseconds;
2182 if (timeBeforePreroll <= wakeThreshold)
2187 if (timeBeforePreroll > 0s)
2189 std::chrono::seconds waitpending;
2190 if (timeBeforePreroll > 120s)
2191 waitpending = timeBeforePreroll -120s;
2193 waitpending = std::min(timeBeforePreroll, 30s);
2205 for (
auto it = startIter; it !=
m_recList.end() && !done; ++it)
2208 **it, statuschanged, nextStartTime, nextWakeTime,
2232 if (idleSince.isValid())
2236 (idleSince.addSecs((
idleTimeoutSecs - 30s).count()) <= curtime) ? 5 : 10);
2240 statuschanged =
false;
2247 const QString &title,
const QString &subtitle,
2248 const QString &descrip,
2249 const QString &programid)
2252 QString filterClause;
2255 if (!title.isEmpty())
2257 filterClause +=
"AND p.title = :TITLE ";
2258 bindings[
":TITLE"] = title;
2262 if (programid !=
"**any**")
2264 filterClause +=
"AND (0 ";
2265 if (!subtitle.isEmpty())
2268 filterClause +=
"OR p.subtitle = :SUBTITLE1 "
2269 "OR p.description = :SUBTITLE2 ";
2270 bindings[
":SUBTITLE1"] = subtitle;
2271 bindings[
":SUBTITLE2"] = subtitle;
2273 if (!descrip.isEmpty())
2276 filterClause +=
"OR p.description = :DESCRIP1 "
2277 "OR p.subtitle = :DESCRIP2 ";
2278 bindings[
":DESCRIP1"] = descrip;
2279 bindings[
":DESCRIP2"] = descrip;
2281 if (!programid.isEmpty())
2283 filterClause +=
"OR p.programid = :PROGRAMID ";
2284 bindings[
":PROGRAMID"] = programid;
2286 filterClause +=
") ";
2289 query.
prepare(QString(
"UPDATE recordmatch rm "
2291 " ON rm.recordid = r.recordid "
2292 "INNER JOIN program p "
2293 " ON rm.chanid = p.chanid "
2294 " AND rm.starttime = p.starttime "
2295 " AND rm.manualid = p.manualid "
2296 "SET oldrecduplicate = -1 "
2297 "WHERE p.generic = 0 "
2298 " AND r.type NOT IN (%2, %3, %4) ")
2304 MSqlBindings::const_iterator it;
2305 for (it = bindings.cbegin(); it != bindings.cend(); ++it)
2310 if (findid && programid !=
"**any**")
2312 query.
prepare(
"UPDATE recordmatch rm "
2313 "SET oldrecduplicate = -1 "
2314 "WHERE rm.recordid = :RECORDID "
2315 " AND rm.findid = :FINDID");
2329 auto fillstart = nowAsDuration<std::chrono::microseconds>();
2331 bool deleteFuture =
false;
2332 bool runCheck =
false;
2338 if (!request.empty())
2340 tokens = request[0].split(
' ', Qt::SkipEmptyParts);
2343 if (request.empty() || tokens.empty())
2345 LOG(VB_GENERAL, LOG_ERR,
"Empty Reschedule request received");
2349 LOG(VB_GENERAL, LOG_INFO, QString(
"Reschedule requested for %1")
2350 .arg(request.join(
" | ")));
2352 if (tokens[0] ==
"MATCH")
2354 if (tokens.size() < 5)
2356 LOG(VB_GENERAL, LOG_ERR,
2357 QString(
"Invalid RescheduleMatch request received (%1)")
2362 uint recordid = tokens[1].toUInt();
2363 uint sourceid = tokens[2].toUInt();
2364 uint mplexid = tokens[3].toUInt();
2366 deleteFuture =
true;
2374 else if (tokens[0] ==
"CHECK")
2376 if (tokens.size() < 4 || request.size() < 5)
2378 LOG(VB_GENERAL, LOG_ERR,
2379 QString(
"Invalid RescheduleCheck request received (%1)")
2384 uint recordid = tokens[2].toUInt();
2385 uint findid = tokens[3].toUInt();
2386 QString title = request[1];
2387 QString subtitle = request[2];
2388 QString descrip = request[3];
2389 QString programid = request[4];
2398 else if (tokens[0] !=
"PLACE")
2400 LOG(VB_GENERAL, LOG_ERR,
2401 QString(
"Unknown Reschedule request received (%1)")
2411 query.
prepare(
"DELETE oldrecorded FROM oldrecorded "
2412 "LEFT JOIN recordmatch ON "
2413 " recordmatch.chanid = oldrecorded.chanid AND "
2414 " recordmatch.starttime = oldrecorded.starttime "
2415 "WHERE oldrecorded.future > 0 AND "
2416 " recordmatch.recordid IS NULL");
2421 auto fillend = nowAsDuration<std::chrono::microseconds>();
2422 auto matchTime = fillend - fillstart;
2424 LOG(VB_SCHEDULE, LOG_INFO,
"CreateTempTables...");
2427 fillstart = nowAsDuration<std::chrono::microseconds>();
2430 LOG(VB_SCHEDULE, LOG_INFO,
"UpdateDuplicates...");
2433 fillend = nowAsDuration<std::chrono::microseconds>();
2434 auto checkTime = fillend - fillstart;
2436 fillstart = nowAsDuration<std::chrono::microseconds>();
2438 fillend = nowAsDuration<std::chrono::microseconds>();
2439 auto placeTime = fillend - fillstart;
2441 LOG(VB_SCHEDULE, LOG_INFO,
"DeleteTempTables...");
2451 LOG(VB_GENERAL, LOG_INFO,
"Reschedule interrupted, will retry");
2456 msg = QString(
"Scheduled %1 items in %2 "
2457 "= %3 match + %4 check + %5 place")
2459 .arg(duration_cast<floatsecs>(matchTime + checkTime + placeTime).count(), 0,
'f', 1)
2460 .arg(duration_cast<floatsecs>(matchTime).count(), 0,
'f', 2)
2461 .arg(duration_cast<floatsecs>(checkTime).count(), 0,
'f', 2)
2462 .arg(duration_cast<floatsecs>(placeTime).count(), 0,
'f', 2);
2463 LOG(VB_GENERAL, LOG_INFO, msg);
2468 if (
p->GetRecordingStatus() !=
p->m_oldrecstatus)
2471 p->AddHistory(
false,
false,
false);
2475 p->AddHistory(
false,
false,
false);
2477 p->AddHistory(
false,
false,
true);
2479 else if (
p->m_future)
2485 p->m_future =
false;
2494 std::chrono::seconds prerollseconds,
2497 bool blockShutdown =
true;
2502 QString startupParam =
"user";
2506 for ( ; firstRunIter !=
m_recList.end(); ++firstRunIter)
2517 ((std::chrono::seconds(curtime.secsTo((*firstRunIter)->GetRecordingStartTime())) -
2520 LOG(VB_GENERAL, LOG_INFO,
LOC +
"AUTO-Startup assumed");
2521 startupParam =
"auto";
2525 blockShutdown =
false;
2529 LOG(VB_GENERAL, LOG_INFO,
LOC +
"Seem to be woken up by USER");
2541 return blockShutdown;
2547 static constexpr std::array<const std::chrono::seconds,4> kSysEventSecs = { 120s, 90s, 60s, 30s };
2551 auto secsleft = std::chrono::seconds(curtime.secsTo(nextrectime));
2561 bool pendingEventSent =
false;
2562 for (
size_t i = 0; i < kSysEventSecs.size(); i++)
2564 auto pending_secs = std::max((secsleft - prerollseconds), 0s);
2565 if ((pending_secs <= kSysEventSecs[i]) &&
2568 if (!pendingEventSent)
2571 QString(
"REC_PENDING SECS %1").arg(pending_secs.count()), &ri);
2575 pendingEventSent =
true;
2581 for (
size_t i = 0; i < kSysEventSecs.size(); i++)
2589 keys.insert(rec->MakeUniqueKey());
2590 keys.insert(
"something");
2593 QSet<QString>::iterator sit =
m_sysEvents[i].begin();
2596 if (!keys.contains(*sit))
2607 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
2608 QString(
"Slave Backend %1 is being awakened to record: %2")
2615 ((secsleft - prerollseconds) < 210s) &&
2619 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
2620 QString(
"Slave Backend %1 not available yet, "
2621 "trying to wake it up again.")
2628 ((secsleft - prerollseconds) < 150s) &&
2631 LOG(VB_GENERAL, LOG_WARNING,
LOC +
2632 QString(
"Slave Backend %1 has NOT come "
2633 "back from sleep yet in 150 seconds. Setting "
2634 "slave status to unknown and attempting "
2635 "to reschedule around its tuners.")
2638 for (
auto * enc : std::as_const(*
m_tvList))
2650 QDateTime &nextStartTime, QDateTime &nextWakeTime,
2651 std::chrono::seconds prerollseconds)
2658 std::chrono::seconds origprerollseconds = prerollseconds;
2665 auto nextwake = std::chrono::seconds(nextWakeTime.secsTo(nextrectime));
2666 if (nextwake - prerollseconds > 5min)
2668 nextStartTime = std::min(nextStartTime, nextrectime);
2672 if (curtime < nextrectime)
2673 nextWakeTime = std::min(nextWakeTime, nextrectime);
2679 auto secsleft = std::chrono::seconds(curtime.secsTo(nextrectime));
2684 if (secsleft - prerollseconds > 1min)
2686 nextStartTime = std::min(nextStartTime, nextrectime.addSecs(-30));
2687 nextWakeTime = std::min(nextWakeTime,
2688 nextrectime.addSecs(-prerollseconds.count() - 60));
2701 if (secsleft - prerollseconds > 35s)
2703 nextStartTime = std::min(nextStartTime, nextrectime.addSecs(-30));
2704 nextWakeTime = std::min(nextWakeTime,
2705 nextrectime.addSecs(-prerollseconds.count() - 35));
2714 QString msg = QString(
"Invalid cardid [%1] for %2")
2716 LOG(VB_GENERAL, LOG_ERR,
LOC + msg);
2720 statuschanged =
true;
2728 QString msg = QString(
"SUPPRESSED recording \"%1\" on channel: "
2729 "%2 on cardid: [%3], sourceid %4. Tuner "
2730 "is locked by an external application.")
2735 LOG(VB_GENERAL, LOG_NOTICE, msg);
2739 statuschanged =
true;
2750 if (prerollseconds > 0s)
2758 if (isBusyRecording)
2761 nextWakeTime = std::min(nextWakeTime, curtime.addSecs(5));
2762 prerollseconds = 0s;
2766 if (secsleft - prerollseconds > 30s)
2768 nextStartTime = std::min(nextStartTime, nextrectime.addSecs(-30));
2769 nextWakeTime = std::min(nextWakeTime,
2770 nextrectime.addSecs(-prerollseconds.count() - 30));
2778 LOG(VB_SCHEDULE, LOG_WARNING,
2779 QString(
"WARNING: Slave Backend %1 has NOT come "
2780 "back from sleep yet. Recording can "
2781 "not begin yet for: %2")
2787 LOG(VB_SCHEDULE, LOG_WARNING,
2788 QString(
"WARNING: Slave Backend %1 has NOT come "
2789 "back from sleep yet. Setting slave "
2790 "status to unknown and attempting "
2791 "to reschedule around its tuners.")
2794 for (
auto * enc : std::as_const(*
m_tvList))
2803 nextStartTime = std::min(nextStartTime, nextrectime);
2804 nextWakeTime = std::min(nextWakeTime, curtime.addSecs(1));
2811 QString recording_dir;
2830 MythEvent me(QString(
"ADD_CHILD_INPUT %1")
2833 nextWakeTime = std::min(nextWakeTime, curtime.addSecs(1));
2843 nexttv->
RecordPending(&tempri, std::max(secsleft, 0s),
false);
2849 if (secsleft - prerollseconds > 0s)
2851 nextStartTime = std::min(nextStartTime, nextrectime);
2852 nextWakeTime = std::min(nextWakeTime,
2853 nextrectime.addSecs(-prerollseconds.count()));
2858 recstartts = QDateTime(
2860 QTime(recstartts.time().hour(), recstartts.time().minute()), Qt::UTC);
2864 QString details = QString(
"%1: channel %2 on cardid [%3], sourceid %4")
2891 statuschanged =
true;
2904 bool doSchedAfterStart =
2913 QString(
"Started recording") :
2915 QString(
"Tuning recording") :
2916 QString(
"Canceled recording (%1)")
2919 LOG(VB_GENERAL, LOG_INFO, QString(
"%1: %2").arg(msg, details));
2927 MythEvent me(QString(
"FORCE_DELETE_RECORDING %1 %2")
2935 std::chrono::seconds prerollseconds)
2940 LOG(VB_SCHEDULE, LOG_DEBUG,
2941 QString(
"Assigning input for %1/%2/\"%3\"")
2952 for (
uint i = 0; !bestid && i < inputs.size(); ++i)
2954 uint inputid = inputs[i];
2962 auto recstarttime = std::chrono::seconds(now.secsTo(
p->GetRecordingStartTime()));
2963 if (recstarttime > prerollseconds + 60s)
2965 if (
p->GetInputID() != inputid)
2982 LOG(VB_SCHEDULE, LOG_DEBUG,
2983 QString(
"Input %1 has a pending recording").arg(inputid));
2992 LOG(VB_SCHEDULE, LOG_DEBUG,
2993 QString(
"Input %1 is recording").arg(inputid));
2998 LOG(VB_SCHEDULE, LOG_DEBUG,
2999 QString(
"Input %1 is recording but will be free")
3008 LOG(VB_SCHEDULE, LOG_DEBUG,
3009 QString(
"Input %1 is recording but has to stop")
3015 LOG(VB_SCHEDULE, LOG_DEBUG,
3016 QString(
"Input %1 is recording but could be free")
3028 bool isbusy = rctv->
IsBusy(&busy_info, -1s);
3034 LOG(VB_SCHEDULE, LOG_DEBUG,
3035 QString(
"Input %1 is free").arg(inputid));
3041 LOG(VB_SCHEDULE, LOG_DEBUG,
3042 QString(
"Input %1 is on livetv but has to stop")
3053 LOG(VB_SCHEDULE, LOG_INFO,
3054 QString(
"Assigned input %1 for %2/%3/\"%4\"")
3062 LOG(VB_SCHEDULE, LOG_WARNING,
3063 QString(
"Failed to assign input for %1/%2/\"%3\"")
3069 return bestid != 0U;
3079 bool &blockShutdown, QDateTime &idleSince,
3080 std::chrono::seconds prerollseconds,
3086 uint logmask = VB_IDLE;
3087 int now = QTime::currentTime().msecsSinceStartOfDay();
3088 int tm = std::chrono::milliseconds(now) / 15min;
3091 logmask = VB_GENERAL;
3110 LOG(VB_GENERAL, LOG_NOTICE,
"Client is connected, removing startup block on shutdown");
3111 blockShutdown =
false;
3122 bool recording =
false;
3125 QMap<int, EncoderLink *>::const_iterator it;
3129 if ((*it)->IsBusy())
3143 if (!blocking && !recording && !activeJobs && !delay)
3150 if (idleSince.isValid())
3152 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN -1"));
3155 idleSince = QDateTime();
3160 if (statuschanged || !idleSince.isValid())
3162 bool wasValid = idleSince.isValid();
3164 idleSince = curtime;
3167 for ( ; idleIter !=
m_recList.end(); ++idleIter)
3169 if ((*idleIter)->GetRecordingStatus() ==
3171 (*idleIter)->GetRecordingStatus() ==
3178 auto recstarttime = std::chrono::seconds(curtime.secsTo((*idleIter)->GetRecordingStartTime()));
3181 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3182 "a recording is due to "
3184 idleSince = QDateTime();
3195 if (guideRunTime.isValid() &&
3199 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3200 "mythfilldatabase is due to "
3202 idleSince = QDateTime();
3207 if (idleSince.isValid())
3210 if (wasValid && !idleSince.isValid())
3212 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN -1"));
3217 if (idleSince.isValid())
3229 LOG(VB_GENERAL, LOG_WARNING,
3230 "Waited more than 60"
3231 " seconds for shutdown to complete"
3232 " - resetting idle time");
3233 idleSince = QDateTime();
3239 blockShutdown, logmask))
3245 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN -1"));
3251 auto itime = std::chrono::seconds(idleSince.secsTo(curtime));
3255 msg = QString(
"I\'m idle now... shutdown will "
3256 "occur in %1 seconds.")
3258 LOG(VB_GENERAL, LOG_NOTICE, msg);
3259 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN %1")
3266 msg = QString(
"%1 secs left to system shutdown!").arg(remain);
3267 LOG(logmask, LOG_NOTICE, msg);
3268 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN %1").arg(remain));
3277 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3278 "of an active encoder");
3280 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3281 "of a connected client");
3284 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3288 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3289 "of delay request from external application");
3292 if (idleSince.isValid())
3294 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN -1"));
3297 idleSince = QDateTime();
3304 QDateTime &idleSince,
3305 bool &blockShutdown,
uint logmask)
3307 bool retval =
false;
3317 LOG(logmask, LOG_INFO,
3318 "CheckShutdownServer returned - OK to shutdown");
3322 LOG(logmask, LOG_NOTICE,
3323 "CheckShutdownServer returned - Not OK to shutdown");
3325 idleSince = QDateTime();
3328 LOG(logmask, LOG_NOTICE,
3329 "CheckShutdownServer returned - Not OK to shutdown, "
3337 idleSince = QDateTime();
3342 m_noAutoShutdown =
true;
3346 LOG(VB_GENERAL, LOG_NOTICE,
3347 "CheckShutdownServer returned - Not OK");
3350 LOG(VB_GENERAL, LOG_NOTICE, QString(
3351 "CheckShutdownServer returned - Error %1").arg(state));
3362 QDateTime &idleSince)
3367 for ( ; recIter !=
m_recList.end(); ++recIter)
3375 QDateTime restarttime;
3380 .addSecs(-prerollseconds.count());
3388 && guideRefreshTime.isValid()
3390 && (restarttime.isNull() || guideRefreshTime < restarttime))
3391 restarttime = guideRefreshTime;
3393 if (restarttime.isValid())
3397 restarttime = restarttime.addSecs((-1LL) * add);
3400 "hh:mm yyyy-MM-dd");
3402 "echo \'Wakeuptime would "
3403 "be $time if command "
3406 if (setwakeup_cmd.isEmpty())
3408 LOG(VB_GENERAL, LOG_NOTICE,
3409 "SetWakeuptimeCommand is empty, shutdown aborted");
3410 idleSince = QDateTime();
3414 if (wakeup_timeformat ==
"time_t")
3417 setwakeup_cmd.replace(
"$time",
3418 time_ts.setNum(restarttime.toSecsSinceEpoch())
3422 setwakeup_cmd.replace(
3423 "$time", restarttime.toLocalTime().toString(wakeup_timeformat));
3425 LOG(VB_GENERAL, LOG_NOTICE,
3426 QString(
"Running the command to set the next "
3427 "scheduled wakeup time :-\n\t\t\t\t") + setwakeup_cmd);
3432 LOG(VB_GENERAL, LOG_ERR,
3433 "SetWakeuptimeCommand failed, shutdown aborted");
3434 idleSince = QDateTime();
3449 "sudo /sbin/halt -p");
3451 if (!halt_cmd.isEmpty())
3456 LOG(VB_GENERAL, LOG_NOTICE,
3457 QString(
"Running the command to shutdown "
3458 "this computer :-\n\t\t\t\t") + halt_cmd);
3465 LOG(VB_GENERAL, LOG_ERR,
"ServerHaltCommand failed, shutdown aborted");
3470 idleSince = QDateTime();
3476 std::chrono::seconds prerollseconds = 0s;
3477 std::chrono::seconds secsleft = 0s;
3481 bool someSlavesCanSleep =
false;
3482 for (
auto * enc : std::as_const(*
m_tvList))
3484 if (enc->CanSleep())
3485 someSlavesCanSleep =
true;
3488 if (!someSlavesCanSleep)
3491 LOG(VB_SCHEDULE, LOG_INFO,
3492 "Scheduler, Checking for slaves that can be shut down");
3494 auto sleepThreshold =
3497 LOG(VB_SCHEDULE, LOG_DEBUG,
3498 QString(
" Getting list of slaves that will be active in the "
3499 "next %1 minutes.") .arg(duration_cast<std::chrono::minutes>(sleepThreshold).count()));
3501 LOG(VB_SCHEDULE, LOG_DEBUG,
"Checking scheduler's reclist");
3503 QStringList SlavesInUse;
3513 auto recstarttime = std::chrono::seconds(curtime.secsTo(pginfo->GetRecordingStartTime()));
3514 secsleft = recstarttime - prerollseconds;
3515 if (secsleft > sleepThreshold)
3520 EncoderLink *enc = (*m_tvList)[pginfo->GetInputID()];
3527 LOG(VB_SCHEDULE, LOG_DEBUG,
3528 QString(
" Slave %1 will be in use in %2 minutes")
3530 .arg(duration_cast<std::chrono::minutes>(secsleft).count()));
3534 LOG(VB_SCHEDULE, LOG_DEBUG,
3535 QString(
" Slave %1 is in use currently "
3544 LOG(VB_SCHEDULE, LOG_DEBUG,
" Checking inuseprograms table:");
3547 query.
prepare(
"SELECT DISTINCT hostname, recusage FROM inuseprograms "
3548 "WHERE lastupdatetime > :ONEHOURAGO ;");
3549 query.
bindValue(
":ONEHOURAGO", oneHourAgo);
3552 while(query.
next()) {
3553 SlavesInUse << query.
value(0).toString();
3554 LOG(VB_SCHEDULE, LOG_DEBUG,
3555 QString(
" Slave %1 is marked as in use by a %2")
3556 .arg(query.
value(0).toString(),
3557 query.
value(1).toString()));
3561 LOG(VB_SCHEDULE, LOG_DEBUG, QString(
" Shutting down slaves which will "
3562 "be inactive for the next %1 minutes and can be put to sleep.")
3563 .arg(sleepThreshold.count() / 60));
3565 for (
auto * enc : std::as_const(*
m_tvList))
3567 if ((!enc->IsLocal()) &&
3569 (!SlavesInUse.contains(enc->GetHostName())) &&
3570 (!enc->IsFallingAsleep()))
3572 QString sleepCommand =
3574 enc->GetHostName());
3575 QString wakeUpCommand =
3577 enc->GetHostName());
3579 if (!sleepCommand.isEmpty() && !wakeUpCommand.isEmpty())
3581 QString thisHost = enc->GetHostName();
3583 LOG(VB_SCHEDULE, LOG_DEBUG,
3584 QString(
" Commanding %1 to go to sleep.")
3587 if (enc->GoToSleep())
3589 for (
auto * slv : std::as_const(*
m_tvList))
3591 if (slv->GetHostName() == thisHost)
3593 LOG(VB_SCHEDULE, LOG_DEBUG,
3594 QString(
" Marking card %1 on slave %2 "
3595 "as falling asleep.")
3596 .arg(slv->GetInputID())
3597 .arg(slv->GetHostName()));
3604 LOG(VB_GENERAL, LOG_ERR,
LOC +
3605 QString(
"Unable to shutdown %1 slave backend, setting "
3606 "sleep status to undefined.").arg(thisHost));
3607 for (
auto * slv : std::as_const(*
m_tvList))
3609 if (slv->GetHostName() == thisHost)
3622 LOG(VB_GENERAL, LOG_NOTICE,
3623 QString(
"Tried to Wake Up %1, but this is the "
3624 "master backend and it is not asleep.")
3625 .arg(slaveHostname));
3632 if (wakeUpCommand.isEmpty()) {
3633 LOG(VB_GENERAL, LOG_NOTICE,
3634 QString(
"Trying to Wake Up %1, but this slave "
3635 "does not have a WakeUpCommand set.").arg(slaveHostname));
3637 for (
auto * enc : std::as_const(*
m_tvList))
3639 if (enc->GetHostName() == slaveHostname)
3647 for (
auto * enc : std::as_const(*
m_tvList))
3649 if (setWakingStatus && (enc->GetHostName() == slaveHostname))
3651 enc->SetLastWakeTime(curtime);
3656 LOG(VB_SCHEDULE, LOG_NOTICE, QString(
"Executing '%1' to wake up slave.")
3657 .arg(wakeUpCommand));
3669 QStringList SlavesThatCanWake;
3671 for (
auto * enc : std::as_const(*
m_tvList))
3676 thisSlave = enc->GetHostName();
3680 (!SlavesThatCanWake.contains(thisSlave)))
3681 SlavesThatCanWake << thisSlave;
3685 for (; slave < SlavesThatCanWake.count(); slave++)
3687 thisSlave = SlavesThatCanWake[slave];
3688 LOG(VB_SCHEDULE, LOG_NOTICE,
3689 QString(
"Scheduler, Sending wakeup command to slave: %1")
3699 query.
prepare(QString(
"SELECT type,title,subtitle,description,"
3700 "station,startdate,starttime,"
3701 "enddate,endtime,season,episode,inetref,last_record "
3704 if (!query.
exec() || query.
size() != 1)
3714 QString title = query.
value(1).toString();
3715 QString subtitle = query.
value(2).toString();
3716 QString description = query.
value(3).toString();
3717 QString station = query.
value(4).toString();
3718 QDateTime startdt = QDateTime(query.
value(5).toDate(),
3719 query.
value(6).toTime(), Qt::UTC);
3720 int duration = startdt.secsTo(
3721 QDateTime(query.
value(7).toDate(),
3722 query.
value(8).toTime(), Qt::UTC));
3724 int season = query.
value(9).toInt();
3725 int episode = query.
value(10).toInt();
3726 QString inetref = query.
value(11).toString();
3730 QDate originalairdate = QDate(query.
value(12).toDate());
3732 if (description.isEmpty())
3733 description = startdt.toLocalTime().toString();
3735 query.
prepare(
"SELECT chanid from channel "
3736 "WHERE deleted IS NULL AND callsign = :STATION");
3744 std::vector<unsigned int> chanidlist;
3745 while (query.
next())
3746 chanidlist.push_back(query.
value(0).toUInt());
3750 bool weekday =
false;
3752 QDateTime lstartdt = startdt.toLocalTime();
3767 weekday = (lstartdt.date().dayOfWeek() < 6);
3768 daysoff = lstartdt.date().daysTo(
3770 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3771 lstartdt.time(), Qt::LocalTime).toUTC();
3777 daysoff = lstartdt.date().daysTo(
3779 daysoff = (daysoff + 6) / 7 * 7;
3780 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3781 lstartdt.time(), Qt::LocalTime).toUTC();
3784 LOG(VB_GENERAL, LOG_ERR,
3785 QString(
"Invalid rectype for manual recordid %1").arg(recordid));
3791 for (
uint id : chanidlist)
3793 if (weekday && startdt.toLocalTime().date().dayOfWeek() >= 6)
3796 query.
prepare(
"REPLACE INTO program (chanid, starttime, endtime,"
3797 " title, subtitle, description, manualid,"
3798 " season, episode, inetref, originalairdate, generic) "
3799 "VALUES (:CHANID, :STARTTIME, :ENDTIME, :TITLE,"
3800 " :SUBTITLE, :DESCRIPTION, :RECORDID, "
3801 " :SEASON, :EPISODE, :INETREF, :ORIGINALAIRDATE, 1)");
3804 query.
bindValue(
":ENDTIME", startdt.addSecs(duration));
3807 query.
bindValue(
":DESCRIPTION", description);
3811 query.
bindValue(
":ORIGINALAIRDATE", originalairdate);
3820 daysoff += skipdays;
3821 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3822 lstartdt.time(), Qt::LocalTime).toUTC();
3834 query = QString(
"SELECT recordid,search,subtitle,description "
3835 "FROM %1 WHERE search <> %2 AND "
3836 "(recordid = %3 OR %4 = 0) ")
3848 while (result.
next())
3850 QString
prefix = QString(
":NR%1").arg(count);
3851 qphrase = result.
value(3).toString();
3857 LOG(VB_GENERAL, LOG_ERR,
3858 QString(
"Invalid search key in recordid %1")
3859 .arg(result.
value(0).toString()));
3863 QString bindrecid =
prefix +
"RECID";
3864 QString bindphrase =
prefix +
"PHRASE";
3865 QString bindlikephrase1 =
prefix +
"LIKEPHRASE1";
3866 QString bindlikephrase2 =
prefix +
"LIKEPHRASE2";
3867 QString bindlikephrase3 =
prefix +
"LIKEPHRASE3";
3869 bindings[bindrecid] = result.
value(0).toString();
3875 qphrase.remove(
';');
3876 from << result.
value(2).toString();
3877 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
3878 QString(
" AND program.manualid = 0 AND ( %2 )")
3882 bindings[bindlikephrase1] = QString(
"%") + qphrase +
"%";
3884 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
" AND "
3885 "program.manualid = 0 AND "
3886 "program.title LIKE " + bindlikephrase1);
3889 bindings[bindlikephrase1] = QString(
"%") + qphrase +
"%";
3890 bindings[bindlikephrase2] = QString(
"%") + qphrase +
"%";
3891 bindings[bindlikephrase3] = QString(
"%") + qphrase +
"%";
3893 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
3894 " AND program.manualid = 0"
3895 " AND (program.title LIKE " + bindlikephrase1 +
3896 " OR program.subtitle LIKE " + bindlikephrase2 +
3897 " OR program.description LIKE " + bindlikephrase3 +
")");
3900 bindings[bindphrase] = qphrase;
3901 from <<
", people, credits";
3902 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
" AND "
3903 "program.manualid = 0 AND "
3904 "people.name LIKE " + bindphrase +
" AND "
3905 "credits.person = people.person AND "
3906 "program.chanid = credits.chanid AND "
3907 "program.starttime = credits.starttime");
3912 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
3914 QString(
"program.manualid = %1.recordid ")
3918 LOG(VB_GENERAL, LOG_ERR,
3919 QString(
"Unknown RecSearchType (%1) for recordid %2")
3920 .arg(result.
value(1).toInt())
3921 .arg(result.
value(0).toString()));
3922 bindings.remove(bindrecid);
3929 if (recordid == 0 || from.count() == 0)
3931 QString recidmatch =
"";
3933 recidmatch =
"RECTABLE.recordid = :NRRECORDID AND ";
3934 QString s1 = recidmatch +
3935 "RECTABLE.type <> :NRTEMPLATE AND "
3936 "RECTABLE.search = :NRST AND "
3937 "program.manualid = 0 AND "
3938 "program.title = RECTABLE.title ";
3940 QString s2 = recidmatch +
3941 "RECTABLE.type <> :NRTEMPLATE AND "
3942 "RECTABLE.search = :NRST AND "
3943 "program.manualid = 0 AND "
3944 "program.seriesid <> '' AND "
3945 "program.seriesid = RECTABLE.seriesid ";
3955 bindings[
":NRRECORDID"] = recordid;
3961 " WHEN RECTABLE.type IN (%1, %2, %3) THEN 0 "
3962 " WHEN RECTABLE.type IN (%4, %5, %6) THEN -1 "
3963 " ELSE (program.generic - 1) "
3969 "(CASE RECTABLE.type "
3971 " THEN RECTABLE.findid "
3973 " THEN to_days(date_sub(convert_tz(program.starttime, 'UTC', 'SYSTEM'), "
3974 " interval time_format(RECTABLE.findtime, '%H:%i') hour_minute)) "
3976 " THEN floor((to_days(date_sub(convert_tz(program.starttime, 'UTC', "
3977 " 'SYSTEM'), interval time_format(RECTABLE.findtime, '%H:%i') "
3978 " hour_minute)) - RECTABLE.findday)/7) * 7 + RECTABLE.findday "
3980 " THEN RECTABLE.findid "
3989 const QDateTime &maxstarttime)
3993 QString deleteClause;
3994 QString filterClause = QString(
" AND program.endtime > "
3995 "(NOW() - INTERVAL 480 MINUTE)");
3999 deleteClause +=
" AND recordmatch.recordid = :RECORDID";
4000 bindings[
":RECORDID"] = recordid;
4004 deleteClause +=
" AND channel.sourceid = :SOURCEID";
4005 filterClause +=
" AND channel.sourceid = :SOURCEID";
4006 bindings[
":SOURCEID"] = sourceid;
4010 deleteClause +=
" AND channel.mplexid = :MPLEXID";
4011 filterClause +=
" AND channel.mplexid = :MPLEXID";
4012 bindings[
":MPLEXID"] = mplexid;
4014 if (maxstarttime.isValid())
4016 deleteClause +=
" AND recordmatch.starttime <= :MAXSTARTTIME";
4017 filterClause +=
" AND program.starttime <= :MAXSTARTTIME";
4018 bindings[
":MAXSTARTTIME"] = maxstarttime;
4021 query.
prepare(QString(
"DELETE recordmatch FROM recordmatch, channel "
4022 "WHERE recordmatch.chanid = channel.chanid")
4024 MSqlBindings::const_iterator it;
4025 for (it = bindings.cbegin(); it != bindings.cend(); ++it)
4033 bindings.remove(
":RECORDID");
4035 query.
prepare(
"SELECT filterid, clause FROM recordfilter "
4036 "WHERE filterid >= 0 AND filterid < :NUMFILTERS AND "
4037 " TRIM(clause) <> ''");
4044 while (query.
next())
4046 filterClause += QString(
" AND (((RECTABLE.filter & %1) = 0) OR (%2))")
4047 .arg(1 << query.
value(0).toInt()).arg(query.
value(1).toString());
4051 query.
prepare(
"SELECT NULL from record "
4052 "WHERE type = :FINDONE AND findid <= 0;");
4061 QDate epoch(1970, 1, 1);
4064 query.
prepare(
"UPDATE record set findid = :FINDID "
4065 "WHERE type = :FINDONE AND findid <= 0;");
4072 QStringList fromclauses;
4073 QStringList whereclauses;
4079 for (
int clause = 0; clause < fromclauses.count(); ++clause)
4081 LOG(VB_SCHEDULE, LOG_INFO, QString(
"Query %1: %2/%3")
4082 .arg(QString::number(clause), fromclauses[clause],
4083 whereclauses[clause]));
4087 for (
int clause = 0; clause < fromclauses.count(); ++clause)
4089 QString query2 = QString(
4090 "REPLACE INTO recordmatch (recordid, chanid, starttime, manualid, "
4091 " oldrecduplicate, findid) "
4092 "SELECT RECTABLE.recordid, program.chanid, program.starttime, "
4093 " IF(search = %1, RECTABLE.recordid, 0), ").arg(
kManualSearch) +
4095 "FROM (RECTABLE, program INNER JOIN channel "
4096 " ON channel.chanid = program.chanid) ") + fromclauses[clause] + QString(
4097 " WHERE ") + whereclauses[clause] +
4098 QString(
" AND channel.deleted IS NULL "
4099 " AND channel.visible > 0 ") +
4100 filterClause + QString(
" AND "
4103 " (RECTABLE.type = %1 "
4104 " OR RECTABLE.type = %2 "
4105 " OR RECTABLE.type = %3 "
4106 " OR RECTABLE.type = %4) "
4108 " ((RECTABLE.type = %6 "
4109 " OR RECTABLE.type = %7 "
4110 " OR RECTABLE.type = %8)"
4112 " ADDTIME(RECTABLE.startdate, RECTABLE.starttime) = program.starttime "
4114 " RECTABLE.station = channel.callsign) "
4126 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- Start DB Query %1...")
4129 auto dbstart = nowAsDuration<std::chrono::microseconds>();
4133 for (it = bindings.cbegin(); it != bindings.cend(); ++it)
4135 if (query2.contains(it.key()))
4139 bool ok = result.
exec();
4140 auto dbend = nowAsDuration<std::chrono::microseconds>();
4141 auto dbTime = dbend - dbstart;
4149 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- %1 results in %2 sec.")
4151 .arg(duration_cast<std::chrono::seconds>(dbTime).count()));
4155 LOG(VB_SCHEDULE, LOG_INFO,
" +-- Done.");
4164 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_record;");
4170 result.
prepare(
"CREATE TEMPORARY TABLE sched_temp_record "
4177 result.
prepare(
"INSERT sched_temp_record SELECT * from record;");
4185 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_recorded;");
4191 result.
prepare(
"CREATE TEMPORARY TABLE sched_temp_recorded "
4198 result.
prepare(
"INSERT sched_temp_recorded SELECT * from recorded;");
4212 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_record;");
4217 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_recorded;");
4225 if (schedTmpRecord ==
"record")
4226 schedTmpRecord =
"sched_temp_record";
4228 QString rmquery = QString(
4229 "UPDATE recordmatch "
4230 " INNER JOIN RECTABLE ON (recordmatch.recordid = RECTABLE.recordid) "
4231 " INNER JOIN program p ON (recordmatch.chanid = p.chanid AND "
4232 " recordmatch.starttime = p.starttime AND "
4233 " recordmatch.manualid = p.manualid) "
4234 " LEFT JOIN oldrecorded ON "
4236 " RECTABLE.dupmethod > 1 AND "
4237 " oldrecorded.duplicate <> 0 AND "
4238 " p.title = oldrecorded.title AND "
4242 " (p.programid <> '' "
4243 " AND p.programid = oldrecorded.programid) "
4247 " (p.programid = '' OR oldrecorded.programid = '' OR "
4248 " LEFT(p.programid, LOCATE('/', p.programid)) <> "
4249 " LEFT(oldrecorded.programid, LOCATE('/', oldrecorded.programid))) " :
4250 " (p.programid = '' OR oldrecorded.programid = '') " )
4253 " (((RECTABLE.dupmethod & 0x02) = 0) OR (p.subtitle <> '' "
4254 " AND p.subtitle = oldrecorded.subtitle)) "
4256 " (((RECTABLE.dupmethod & 0x04) = 0) OR (p.description <> '' "
4257 " AND p.description = oldrecorded.description)) "
4259 " (((RECTABLE.dupmethod & 0x08) = 0) OR "
4260 " (p.subtitle <> '' AND "
4261 " (p.subtitle = oldrecorded.subtitle OR "
4262 " (oldrecorded.subtitle = '' AND "
4263 " p.subtitle = oldrecorded.description))) OR "
4264 " (p.subtitle = '' AND p.description <> '' AND "
4265 " (p.description = oldrecorded.subtitle OR "
4266 " (oldrecorded.subtitle = '' AND "
4267 " p.description = oldrecorded.description)))) "
4271 " LEFT JOIN sched_temp_recorded recorded ON "
4273 " RECTABLE.dupmethod > 1 AND "
4274 " recorded.duplicate <> 0 AND "
4275 " p.title = recorded.title AND "
4276 " p.generic = 0 AND "
4277 " recorded.recgroup NOT IN ('LiveTV','Deleted') "
4280 " (p.programid <> '' "
4281 " AND p.programid = recorded.programid) "
4285 " (p.programid = '' OR recorded.programid = '' OR "
4286 " LEFT(p.programid, LOCATE('/', p.programid)) <> "
4287 " LEFT(recorded.programid, LOCATE('/', recorded.programid))) " :
4288 " (p.programid = '' OR recorded.programid = '') ")
4291 " (((RECTABLE.dupmethod & 0x02) = 0) OR (p.subtitle <> '' "
4292 " AND p.subtitle = recorded.subtitle)) "
4294 " (((RECTABLE.dupmethod & 0x04) = 0) OR (p.description <> '' "
4295 " AND p.description = recorded.description)) "
4297 " (((RECTABLE.dupmethod & 0x08) = 0) OR "
4298 " (p.subtitle <> '' AND "
4299 " (p.subtitle = recorded.subtitle OR "
4300 " (recorded.subtitle = '' AND "
4301 " p.subtitle = recorded.description))) OR "
4302 " (p.subtitle = '' AND p.description <> '' AND "
4303 " (p.description = recorded.subtitle OR "
4304 " (recorded.subtitle = '' AND "
4305 " p.description = recorded.description)))) "
4309 " LEFT JOIN oldfind ON "
4310 " (oldfind.recordid = recordmatch.recordid AND "
4311 " oldfind.findid = recordmatch.findid) "
4312 " SET oldrecduplicate = (oldrecorded.endtime IS NOT NULL), "
4313 " recduplicate = (recorded.endtime IS NOT NULL), "
4314 " findduplicate = (oldfind.findid IS NOT NULL), "
4315 " oldrecstatus = oldrecorded.recstatus "
4316 " WHERE p.endtime >= (NOW() - INTERVAL 480 MINUTE) "
4317 " AND oldrecduplicate = -1 "
4319 rmquery.replace(
"RECTABLE", schedTmpRecord);
4333 if (schedTmpRecord ==
"record")
4334 schedTmpRecord =
"sched_temp_record";
4338 QMap<int, bool> cardMap;
4339 for (
auto * enc : std::as_const(*
m_tvList))
4341 if (enc->IsConnected() || enc->IsAsleep())
4342 cardMap[enc->GetInputID()] =
true;
4345 QMap<int, bool> tooManyMap;
4346 bool checkTooMany =
false;
4350 rlist.
prepare(QString(
"SELECT recordid, title, maxepisodes, maxnewest "
4351 "FROM %1").arg(schedTmpRecord));
4359 while (rlist.
next())
4361 int recid = rlist.
value(0).toInt();
4363 int maxEpisodes = rlist.
value(2).toInt();
4364 int maxNewest = rlist.
value(3).toInt();
4366 tooManyMap[recid] =
false;
4369 if (maxEpisodes && !maxNewest)
4373 epicnt.
prepare(
"SELECT DISTINCT chanid, progstart, progend "
4375 "WHERE recordid = :RECID AND preserve = 0 "
4376 "AND recgroup NOT IN ('LiveTV','Deleted');");
4381 if (epicnt.
size() >= maxEpisodes - 1)
4384 if (epicnt.
size() >= maxEpisodes)
4386 tooManyMap[recid] =
true;
4387 checkTooMany =
true;
4403 QString pwrpri =
"channel.recpriority + capturecard.recpriority";
4407 pwrpri += QString(
" + "
4408 "IF(capturecard.cardid = RECTABLE.prefinput, 1, 0) * %1")
4414 pwrpri += QString(
" + IF(program.hdtv > 0 OR "
4415 "FIND_IN_SET('HDTV', program.videoprop) > 0, 1, 0) * %1")
4421 pwrpri += QString(
" + "
4422 "IF(FIND_IN_SET('WIDESCREEN', program.videoprop) > 0, 1, 0) * %1")
4428 pwrpri += QString(
" + "
4429 "IF(FIND_IN_SET('SIGNED', program.subtitletypes) > 0, 1, 0) * %1")
4435 pwrpri += QString(
" + "
4436 "IF(FIND_IN_SET('ONSCREEN', program.subtitletypes) > 0, 1, 0) * %1")
4437 .arg(onscrpriority);
4442 pwrpri += QString(
" + "
4443 "IF(FIND_IN_SET('NORMAL', program.subtitletypes) > 0 OR "
4444 "program.closecaptioned > 0 OR program.subtitled > 0, 1, 0) * %1")
4450 pwrpri += QString(
" + "
4451 "IF(FIND_IN_SET('HARDHEAR', program.subtitletypes) > 0 OR "
4452 "FIND_IN_SET('HARDHEAR', program.audioprop) > 0, 1, 0) * %1")
4458 pwrpri += QString(
" + "
4459 "IF(FIND_IN_SET('VISUALIMPAIR', program.audioprop) > 0, 1, 0) * %1")
4465 result.
prepare(QString(
"SELECT recpriority, selectclause FROM %1;")
4474 while (result.
next())
4476 if (result.
value(0).toBool())
4478 QString sclause = result.
value(1).toString();
4480 sclause.remove(
';');
4481 pwrpri += QString(
" + IF(%1, 1, 0) * %2")
4482 .arg(sclause).arg(result.
value(0).toInt());
4485 pwrpri += QString(
" AS powerpriority ");
4487 pwrpri.replace(
"program.",
"p.");
4488 pwrpri.replace(
"channel.",
"c.");
4489 QString query = QString(
4491 " c.chanid, c.sourceid, p.starttime, "
4492 " p.endtime, p.title, p.subtitle, "
4493 " p.description, c.channum, c.callsign, "
4494 " c.name, oldrecduplicate, p.category, "
4495 " RECTABLE.recpriority, RECTABLE.dupin, recduplicate, "
4496 " findduplicate, RECTABLE.type, RECTABLE.recordid, "
4497 " p.starttime - INTERVAL RECTABLE.startoffset "
4498 " minute AS recstartts, "
4499 " p.endtime + INTERVAL RECTABLE.endoffset "
4500 " minute AS recendts, "
4501 " p.previouslyshown, "
4502 " RECTABLE.recgroup, RECTABLE.dupmethod, c.commmethod, "
4503 " capturecard.cardid, 0, p.seriesid, "
4504 " p.programid, RECTABLE.inetref, p.category_type, "
4505 " p.airdate, p.stars, p.originalairdate, "
4506 " RECTABLE.inactive, RECTABLE.parentid, recordmatch.findid, "
4507 " RECTABLE.playgroup, oldrecstatus.recstatus, "
4508 " oldrecstatus.reactivate, p.videoprop+0, "
4509 " p.subtitletypes+0, p.audioprop+0, RECTABLE.storagegroup, "
4510 " capturecard.hostname, recordmatch.oldrecstatus, NULL, "
4511 " oldrecstatus.future, capturecard.schedorder, "
4512 " p.syndicatedepisodenumber, p.partnumber, p.parttotal, "
4513 " c.mplexid, capturecard.displayname, "
4514 " p.season, p.episode, p.totalepisodes, ") +
4517 "INNER JOIN RECTABLE ON (recordmatch.recordid = RECTABLE.recordid) "
4518 "INNER JOIN program AS p "
4519 "ON ( recordmatch.chanid = p.chanid AND "
4520 " recordmatch.starttime = p.starttime AND "
4521 " recordmatch.manualid = p.manualid ) "
4522 "INNER JOIN channel AS c "
4523 "ON ( c.chanid = p.chanid ) "
4524 "INNER JOIN capturecard "
4525 "ON ( c.sourceid = capturecard.sourceid AND "
4526 " ( capturecard.schedorder <> 0 OR "
4527 " capturecard.parentid = 0 ) ) "
4528 "LEFT JOIN oldrecorded as oldrecstatus "
4529 "ON ( oldrecstatus.station = c.callsign AND "
4530 " oldrecstatus.starttime = p.starttime AND "
4531 " oldrecstatus.title = p.title ) "
4532 "WHERE p.endtime > (NOW() - INTERVAL 480 MINUTE) "
4533 "ORDER BY RECTABLE.recordid DESC, p.starttime, p.title, c.callsign, "
4535 query.replace(
"RECTABLE", schedTmpRecord);
4537 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- Start DB Query..."));
4539 auto dbstart = nowAsDuration<std::chrono::microseconds>();
4546 auto dbend = nowAsDuration<std::chrono::microseconds>();
4547 auto dbTime = dbend - dbstart;
4549 LOG(VB_SCHEDULE, LOG_INFO,
4550 QString(
" |-- %1 results in %2 sec. Processing...")
4552 .arg(duration_cast<std::chrono::seconds>(dbTime).count()));
4556 while (result.
next())
4562 uint recordid = result.
value(17).toUInt();
4564 QString title = result.
value(4).toString();
4565 QString callsign = result.
value(8).toString();
4575 uint mplexid = result.
value(51).toUInt();
4576 if (mplexid == 32767)
4579 QString inputname = result.
value(52).toString();
4580 if (inputname.isEmpty())
4581 inputname = QString(
"Input %1").arg(result.
value(24).toUInt());
4586 result.
value(5).toString(),
4588 result.
value(6).toString(),
4589 result.
value(53).toInt(),
4590 result.
value(54).toInt(),
4591 result.
value(55).toInt(),
4592 result.
value(48).toString(),
4593 result.
value(11).toString(),
4595 result.
value(0).toUInt(),
4596 result.
value(7).toString(),
4598 result.
value(9).toString(),
4600 result.
value(21).toString(),
4601 result.
value(36).toString(),
4603 result.
value(43).toString(),
4604 result.
value(42).toString(),
4606 result.
value(30).toUInt(),
4607 result.
value(49).toUInt(),
4608 result.
value(50).toUInt(),
4610 result.
value(26).toString(),
4611 result.
value(27).toString(),
4612 result.
value(28).toString(),
4615 result.
value(12).toInt(),
4622 result.
value(31).toDouble(),
4623 (result.
value(32).isNull()) ? QDate() :
4627 result.
value(20).toBool(),
4630 result.
value(38).toBool(),
4633 result.
value(34).toUInt(),
4638 result.
value(1).toUInt(),
4639 result.
value(24).toUInt(),
4641 result.
value(35).toUInt(),
4644 result.
value(40).toUInt(),
4645 result.
value(39).toUInt(),
4646 result.
value(41).toUInt(),
4647 result.
value(46).toBool(),
4648 result.
value(47).toInt(),
4650 result.
value(24).toUInt(),
4653 if (!
p->m_future && !
p->IsReactivated() &&
4657 p->SetRecordingStatus(
p->m_oldrecstatus);
4660 p->SetRecordingPriority2(result.
value(56).toInt());
4669 if (
p->IsSameTitleStartTimeAndChannel(*r))
4671 if (r->m_sgroupId ==
p->m_sgroupId &&
4672 r->GetRecordingEndTime() !=
p->GetRecordingEndTime() &&
4673 (r->GetRecordingRuleID() ==
p->GetRecordingRuleID() ||
4688 tmpList.push_back(
p);
4695 (!cardMap.contains(
p->GetInputID()) || (
p->m_schedOrder == 0)))
4698 if (
p->m_schedOrder == 0 &&
4702 LOG(VB_GENERAL, LOG_WARNING,
LOC +
4703 QString(
"Channel %1, Title %2 %3 cardinput.schedorder = %4, "
4704 "it must be >0 to record from this input.")
4705 .arg(
p->GetChannelName(),
p->GetTitle(),
4706 p->GetScheduledStartTime().toString(),
4707 QString::number(
p->m_schedOrder)));
4713 if (checkTooMany && tooManyMap[
p->GetRecordingRuleID()] &&
4714 !
p->IsReactivated())
4722 else if (result.
value(15).toBool() && !
p->IsReactivated())
4726 !
p->IsReactivated() &&
4746 bool inactive = result.
value(33).toBool();
4761 p->SetRecordingStatus(newrecstatus);
4763 tmpList.push_back(
p);
4766 LOG(VB_SCHEDULE, LOG_INFO,
" +-- Cleanup...");
4767 for (
auto &
tmp : tmpList)
4775 QString query = QString(
4776 "SELECT RECTABLE.title, RECTABLE.subtitle, "
4777 " RECTABLE.description, RECTABLE.season, "
4778 " RECTABLE.episode, RECTABLE.category, "
4779 " RECTABLE.chanid, channel.channum, "
4780 " RECTABLE.station, channel.name, "
4781 " RECTABLE.recgroup, RECTABLE.playgroup, "
4782 " RECTABLE.seriesid, RECTABLE.programid, "
4783 " RECTABLE.inetref, RECTABLE.recpriority, "
4784 " RECTABLE.startdate, RECTABLE.starttime, "
4785 " RECTABLE.enddate, RECTABLE.endtime, "
4786 " RECTABLE.recordid, RECTABLE.type, "
4787 " RECTABLE.dupin, RECTABLE.dupmethod, "
4788 " RECTABLE.findid, "
4789 " RECTABLE.startoffset, RECTABLE.endoffset, "
4790 " channel.commmethod "
4792 "INNER JOIN channel ON (channel.chanid = RECTABLE.chanid) "
4793 "LEFT JOIN recordmatch on RECTABLE.recordid = recordmatch.recordid "
4794 "WHERE (type = %1 OR type = %2) AND "
4795 " recordmatch.chanid IS NULL")
4801 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- Start DB Query..."));
4803 auto dbstart = nowAsDuration<std::chrono::microseconds>();
4806 bool ok = result.
exec();
4807 auto dbend = nowAsDuration<std::chrono::microseconds>();
4808 auto dbTime = dbend - dbstart;
4816 LOG(VB_SCHEDULE, LOG_INFO,
4817 QString(
" |-- %1 results in %2 sec. Processing...")
4819 .arg(duration_cast<std::chrono::seconds>(dbTime).count()));
4821 while (result.
next())
4825 result.
value(16).toDate(), result.
value(17).toTime(), Qt::UTC);
4827 result.
value(18).toDate(), result.
value(19).toTime(), Qt::UTC);
4829 QDateTime recstartts = startts.addSecs(result.
value(25).toInt() * -60LL);
4830 QDateTime recendts = endts.addSecs( result.
value(26).toInt() * +60LL);
4832 if (recstartts >= recendts)
4835 recstartts = startts;
4846 result.
value(0).toString(),
4848 (sor) ? result.
value(1).toString() : QString(),
4850 (sor) ? result.
value(2).toString() : QString(),
4851 result.
value(3).toUInt(),
4852 result.
value(4).toUInt(),
4855 result.
value(6).toUInt(),
4856 result.
value(7).toString(),
4857 result.
value(8).toString(),
4858 result.
value(9).toString(),
4860 result.
value(10).toString(),
4861 result.
value(11).toString(),
4863 result.
value(12).toString(),
4864 result.
value(13).toString(),
4865 result.
value(14).toString(),
4867 result.
value(15).toInt(),
4870 recstartts, recendts,
4874 result.
value(20).toUInt(),
4880 result.
value(24).toUInt(),
4884 tmpList.push_back(
p);
4887 for (
auto &
tmp : tmpList)
4898 QString sortColumn =
"title";
4906 sortColumn =
"record.title";
4909 sortColumn =
"record.recpriority";
4912 sortColumn =
"record.last_record";
4920 sortColumn =
"record.next_record IS NULL, record.next_record";
4923 sortColumn =
"record.type";
4927 QString order =
"ASC";
4931 QString query = QString(
4932 "SELECT record.title, record.subtitle, "
4933 " record.description, record.season, "
4934 " record.episode, record.category, "
4935 " record.chanid, channel.channum, "
4936 " record.station, channel.name, "
4937 " record.recgroup, record.playgroup, "
4938 " record.seriesid, record.programid, "
4939 " record.inetref, record.recpriority, "
4940 " record.startdate, record.starttime, "
4941 " record.enddate, record.endtime, "
4942 " record.recordid, record.type, "
4943 " record.dupin, record.dupmethod, "
4945 " channel.commmethod "
4947 "LEFT JOIN channel ON channel.callsign = record.station "
4948 " AND deleted IS NULL "
4949 "GROUP BY recordid "
4952 query = query.arg(sortColumn, order);
4963 while (result.
next())
4966 QDateTime startts = QDateTime(result.
value(16).toDate(),
4967 result.
value(17).toTime(), Qt::UTC);
4968 QDateTime endts = QDateTime(result.
value(18).toDate(),
4969 result.
value(19).toTime(), Qt::UTC);
4971 if (!startts.isValid())
4974 if (!endts.isValid())
4978 result.
value(0).toString(), QString(),
4979 result.
value(1).toString(), QString(),
4980 result.
value(2).toString(), result.
value(3).toUInt(),
4981 result.
value(4).toUInt(), result.
value(5).toString(),
4983 result.
value(6).toUInt(), result.
value(7).toString(),
4984 result.
value(8).toString(), result.
value(9).toString(),
4986 result.
value(10).toString(), result.
value(11).toString(),
4988 result.
value(12).toString(), result.
value(13).toString(),
4989 result.
value(14).toString(),
4991 result.
value(15).toInt(),
4998 result.
value(20).toUInt(), rectype,
5002 result.
value(24).toUInt(),
5101 QString recording_dir;
5105 "LiveTV", cur, cur.addSecs(3600), cardid,
5110 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"FindNextLiveTVDir: next dir is '%1'")
5111 .arg(recording_dir));
5118 const QString &title,
5120 const QString &storagegroup,
5121 const QDateTime &recstartts,
5122 const QDateTime &recendts,
5124 QString &recording_dir,
5127 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
"FillRecordingDir: Starting");
5132 if (cnt++ % 20 == 0)
5133 LOG(VB_SCHEDULE, LOG_WARNING,
"Waiting for main server.");
5134 std::this_thread::sleep_for(50ms);
5141 QStringList recsCounted;
5142 std::list<FileSystemInfo *> fsInfoList;
5143 std::list<FileSystemInfo *>::iterator fslistit;
5145 recording_dir.clear();
5147 if (dirlist.size() == 1)
5149 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5150 QString(
"FillRecordingDir: The only directory in the %1 Storage "
5151 "Group is %2, so it will be used by default.")
5152 .arg(storagegroup, dirlist[0]));
5153 recording_dir = dirlist[0];
5154 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
"FillRecordingDir: Finished");
5159 int weightPerRecording =
5161 int weightPerPlayback =
5163 int weightPerCommFlag =
5165 int weightPerTranscode =
5168 QString storageScheduler =
5170 int localStartingWeight =
5172 (storageScheduler !=
"Combination") ? 0
5173 : (
int)(-1.99 * weightPerRecording));
5174 int remoteStartingWeight =
5176 std::chrono::seconds maxOverlap =
5181 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5182 "FillRecordingDir: Calculating initial FS Weights.");
5193 tmpWeight = localStartingWeight;
5194 msg +=
" is local (" + QString::number(tmpWeight) +
")";
5198 tmpWeight = remoteStartingWeight;
5199 msg +=
" is remote (+" + QString::number(tmpWeight) +
")";
5209 msg +=
", has SGweightPerDir offset of "
5210 + QString::number(tmpWeight) +
")";
5212 msg +=
". initial dir weight = " + QString::number(fs->
getWeight());
5213 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, msg);
5215 fsInfoList.push_back(fs);
5218 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5219 "FillRecordingDir: Adjusting FS Weights from inuseprograms.");
5222 saveRecDir.
prepare(
"UPDATE inuseprograms "
5223 "SET recdir = :RECDIR "
5224 "WHERE chanid = :CHANID AND "
5225 " starttime = :STARTTIME");
5228 "SELECT i.chanid, i.starttime, r.endtime, recusage, rechost, recdir "
5229 "FROM inuseprograms i, recorded r "
5230 "WHERE DATE_ADD(lastupdatetime, INTERVAL 16 MINUTE) > NOW() AND "
5231 " i.chanid = r.chanid AND "
5232 " i.starttime = r.starttime");
5240 while (query.
next())
5242 uint recChanid = query.
value(0).toUInt();
5245 QString recUsage( query.
value(3).toString());
5246 QString recHost( query.
value(4).toString());
5247 QString recDir( query.
value(5).toString());
5249 if (recDir.isEmpty())
5253 recDir = recDir.isEmpty() ?
"_UNKNOWN_" : recDir;
5255 saveRecDir.
bindValue(
":RECDIR", recDir);
5256 saveRecDir.
bindValue(
":CHANID", recChanid);
5257 saveRecDir.
bindValue(
":STARTTIME", recStart);
5258 if (!saveRecDir.
exec())
5261 if (recDir ==
"_UNKNOWN_")
5264 for (fslistit = fsInfoList.begin();
5265 fslistit != fsInfoList.end(); ++fslistit)
5271 int weightOffset = 0;
5275 if (recEnd > recstartts.addSecs(maxOverlap.count()))
5277 weightOffset += weightPerRecording;
5278 recsCounted << QString::number(recChanid) +
":" +
5283 weightOffset += weightPerPlayback;
5285 weightOffset += weightPerCommFlag;
5287 weightOffset += weightPerTranscode;
5291 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5292 QString(
" %1 @ %2 in use by '%3' on %4:%5, FSID "
5293 "#%6, FSID weightOffset +%7.")
5294 .arg(QString::number(recChanid),
5296 recUsage, recHost, recDir,
5298 QString::number(weightOffset)));
5306 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5307 QString(
" %1:%2 => old weight %3 plus "
5309 .arg(
fs2->getHostname(),
5311 .arg(
fs2->getWeight())
5313 .arg(
fs2->getWeight() + weightOffset));
5315 fs2->setWeight(
fs2->getWeight() + weightOffset);
5325 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5326 "FillRecordingDir: Adjusting FS Weights from scheduler.");
5328 for (
auto *thispg : reclist)
5330 if ((recendts < thispg->GetRecordingStartTime()) ||
5331 (recstartts > thispg->GetRecordingEndTime()) ||
5334 (thispg->GetInputID() == 0) ||
5335 (recsCounted.contains(QString(
"%1:%2").arg(thispg->GetChanID())
5337 (thispg->GetPathname().isEmpty()))
5340 for (fslistit = fsInfoList.begin();
5341 fslistit != fsInfoList.end(); ++fslistit)
5344 if ((fs->
getHostname() == thispg->GetHostname()) &&
5345 (fs->
getPath() == thispg->GetPathname()))
5347 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5348 QString(
"%1 @ %2 will record on %3:%4, FSID #%5, "
5349 "weightPerRecording +%6.")
5350 .arg(thispg->GetChanID())
5353 .arg(fs->
getFSysID()).arg(weightPerRecording));
5362 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5363 QString(
" %1:%2 => old weight %3 plus %4 = %5")
5364 .arg(
fs2->getHostname(),
fs2->getPath())
5365 .arg(
fs2->getWeight()).arg(weightPerRecording)
5366 .arg(
fs2->getWeight() + weightPerRecording));
5368 fs2->setWeight(
fs2->getWeight() + weightPerRecording);
5376 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5377 QString(
"Using '%1' Storage Scheduler directory sorting algorithm.")
5378 .arg(storageScheduler));
5380 if (storageScheduler ==
"BalancedFreeSpace")
5382 else if (storageScheduler ==
"BalancedPercFreeSpace")
5384 else if (storageScheduler ==
"BalancedDiskIO")
5391 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5392 "--- FillRecordingDir Sorted fsInfoList start ---");
5393 for (fslistit = fsInfoList.begin();fslistit != fsInfoList.end();
5397 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
"%1:%2")
5399 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
" Location : %1")
5400 .arg((fs->
isLocal()) ?
"local" :
"remote"));
5401 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
" weight : %1")
5403 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
" free space : %5")
5406 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5407 "--- FillRecordingDir Sorted fsInfoList end ---");
5416 long long maxSizeKB = (maxByterate + maxByterate/3) *
5417 recstartts.secsTo(recendts) / 1024;
5419 bool simulateAutoExpire =
5422 (fsInfoList.size() > 1));
5436 for (
unsigned int pass = 1; pass <= 3; pass++)
5438 bool foundDir =
false;
5440 if ((pass == 2) && simulateAutoExpire)
5443 QMap <int , long long> remainingSpaceKB;
5444 for (fslistit = fsInfoList.begin();
5445 fslistit != fsInfoList.end(); ++fslistit)
5447 remainingSpaceKB[(*fslistit)->getFSysID()] =
5448 (*fslistit)->getFreeSpace();
5455 for (
auto & expire : expiring)
5459 for (fslistit = fsInfoList.begin();
5460 fslistit != fsInfoList.end(); ++fslistit)
5463 if (expire->GetHostname() != (*fslistit)->getHostname())
5467 if (!dirlist.contains((*fslistit)->getPath()))
5471 (*fslistit)->getPath() +
"/" + expire->GetPathname();
5478 if (checkFile.exists())
5486 QString backuppath = expire->GetPathname();
5488 bool foundSlave =
false;
5490 for (
auto * enc : std::as_const(*
m_tvList))
5492 if (enc->GetHostName() ==
5495 enc->CheckFile(programinfo);
5513 LOG(VB_GENERAL, LOG_ERR,
5514 QString(
"Unable to match '%1' "
5515 "to any file system. Ignoring it.")
5516 .arg(expire->GetBasename()));
5522 expire->GetFilesize() / 1024;
5525 long long desiredSpaceKB =
5529 (desiredSpaceKB + maxSizeKB))
5531 recording_dir = fs->
getPath();
5534 LOG(VB_FILE, LOG_INFO,
5535 QString(
"pass 2: '%1' will record in '%2' "
5536 "although there is only %3 MB free and the "
5537 "AutoExpirer wants at least %4 MB. This "
5538 "directory has the highest priority files "
5539 "to be expired from the AutoExpire list and "
5540 "there are enough that the Expirer should "
5541 "be able to free up space for this recording.")
5542 .arg(title, recording_dir)
5544 .arg(desiredSpaceKB / 1024));
5555 for (fslistit = fsInfoList.begin();
5556 fslistit != fsInfoList.end(); ++fslistit)
5558 long long desiredSpaceKB = 0;
5565 (dirlist.contains(fs->
getPath())) &&
5569 recording_dir = fs->
getPath();
5574 LOG(VB_FILE, LOG_INFO,
5575 QString(
"pass 1: '%1' will record in "
5576 "'%2' which has %3 MB free. This recording "
5577 "could use a max of %4 MB and the "
5578 "AutoExpirer wants to keep %5 MB free.")
5579 .arg(title, recording_dir)
5581 .arg(maxSizeKB / 1024)
5582 .arg(desiredSpaceKB / 1024));
5586 LOG(VB_FILE, LOG_INFO,
5587 QString(
"pass %1: '%2' will record in "
5588 "'%3' although there is only %4 MB free and "
5589 "the AutoExpirer wants at least %5 MB. "
5590 "Something will have to be deleted or expired "
5591 "in order for this recording to complete "
5593 .arg(pass).arg(title, recording_dir)
5595 .arg(desiredSpaceKB / 1024));
5608 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
"FillRecordingDir: Finished");
5614 QList<FileSystemInfo> fsInfos;
5621 QMap <int, bool> fsMap;
5622 QList<FileSystemInfo>::iterator it1;
5623 for (it1 = fsInfos.begin(); it1 != fsInfos.end(); ++it1)
5625 fsMap[it1->getFSysID()] =
true;
5626 m_fsInfoCache[it1->getHostname() +
":" + it1->getPath()] = *it1;
5629 LOG(VB_FILE, LOG_INFO,
LOC +
5630 QString(
"FillDirectoryInfoCache: found %1 unique filesystems")
5631 .arg(fsMap.size()));
5638 auto secsleft = std::chrono::seconds(curtime.secsTo(
m_livetvTime));
5642 if (secsleft - prerollseconds > 120s)
5646 for (
auto * enc : std::as_const(*
m_tvList))
5662 if (
m_schedTime.secsTo(dummy->GetRecordingEndTime()) < 1800)
5663 dummy->SetRecordingEndTime(
m_schedTime.addSecs(1800));
5664 dummy->SetInputID(enc->GetInputID());
5665 dummy->m_mplexId = dummy->QueryMplexID();
5688 bool autoStart =
false;
5690 QDateTime startupTime = QDateTime();
5696 if (startupTime.isValid())
5699 startupSecs = std::max(startupSecs, 15 * 60s);
5706 LOG(VB_GENERAL, LOG_INFO,
5707 "Close to auto-start time, AUTO-Startup assumed");
5713 LOG(VB_GENERAL, LOG_INFO,
5714 "Close to MythFillDB suggested run time, AUTO-Startup to fetch guide data?");
5719 LOG(VB_GENERAL, LOG_DEBUG,
5720 "NOT close to auto-start time, USER-initiated startup assumed");
5722 else if (!s.isEmpty())
5724 LOG(VB_GENERAL, LOG_ERR,
LOC +
5725 QString(
"Invalid MythShutdownWakeupTime specified in database (%1)")
5737 QMap<uint, QSet<uint> > inputSets;
5738 query.
prepare(
"SELECT DISTINCT ci1.cardid, ci2.cardid "
5739 "FROM capturecard ci1, capturecard ci2, "
5740 " inputgroup ig1, inputgroup ig2 "
5741 "WHERE ci1.cardid = ig1.cardinputid AND "
5742 " ci2.cardid = ig2.cardinputid AND"
5743 " ig1.inputgroupid = ig2.inputgroupid AND "
5744 " ci1.cardid <= ci2.cardid "
5745 "ORDER BY ci1.cardid, ci2.cardid");
5751 while (query.
next())
5755 inputSets[id0].insert(id1);
5756 inputSets[id1].insert(id0);
5759 QMap<uint, QSet<uint> >::iterator mit;
5760 for (mit = inputSets.begin(); mit != inputSets.end(); ++mit)
5762 uint inputid = mit.key();
5770 QSet<uint> fullset = mit.value();
5771 QSet<uint> checkset;
5772 QSet<uint>::const_iterator sit;
5773 while (checkset != fullset)
5776 for (
int item : std::as_const(checkset))
5777 fullset += inputSets[item];
5782 auto *conflictlist =
new RecList();
5784 for (
int item : std::as_const(checkset))
5786 LOG(VB_SCHEDULE, LOG_INFO,
5787 QString(
"Assigning input %1 to conflict set %2")
5795 query.
prepare(
"SELECT ci.cardid "
5796 "FROM capturecard ci "
5797 "LEFT JOIN inputgroup ig "
5798 " ON ci.cardid = ig.cardinputid "
5799 "WHERE ig.cardinputid IS NULL");
5805 while (query.
next())
5809 LOG(VB_GENERAL, LOG_ERR,
LOC +
5810 QString(
"Input %1 is not assigned to any input group").arg(
id));
5811 auto *conflictlist =
new RecList();
5813 LOG(VB_SCHEDULE, LOG_INFO,
5814 QString(
"Assigning input %1 to conflict set %2")
5828 query.
prepare(
"SELECT cardid, parentid, schedgroup "
5830 "WHERE sourceid > 0 "
5838 while (query.
next())
5841 uint parentid = query.
value(1).toUInt();
5858 LOG(VB_SCHEDULE, LOG_INFO,
5859 QString(
"Added SchedInputInfo i=%1, g=%2, sg=%3")
5868 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
5869 QString(
"AddChildInput: Handling parent = %1, input = %2")
5870 .arg(parentid).arg(childid));