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));
2121 QElapsedTimer
t;
t.start();
2124 statuschanged =
true;
2127 auto elapsed = std::chrono::ceil<std::chrono::seconds>(std::chrono::milliseconds(
t.elapsed()));
2128 schedRunTime = std::max(elapsed + elapsed/2 + 2s, schedRunTime);
2149 checkSlaves =
false;
2159 nextWakeTime = nextSleepCheck;
2163 for ( ; startIter !=
m_recList.end(); ++startIter)
2165 if ((*startIter)->GetRecordingStatus() !=
2166 (*startIter)->m_oldrecstatus)
2176 for (
auto it = startIter; it !=
m_recList.end() && !done; ++it)
2179 **it, statuschanged, nextStartTime, nextWakeTime,
2191 for (
auto it = startIter; it !=
m_recList.end(); ++it)
2193 auto secsleft = std::chrono::seconds(curtime.secsTo((*it)->GetRecordingStartTime()));
2194 if ((secsleft - prerollseconds) <= wakeThreshold)
2214 if (idleSince.isValid())
2218 (idleSince.addSecs((
idleTimeoutSecs - 30s).count()) <= curtime) ? 5 : 10);
2222 statuschanged =
false;
2229 const QString &title,
const QString &subtitle,
2230 const QString &descrip,
2231 const QString &programid)
2234 QString filterClause;
2237 if (!title.isEmpty())
2239 filterClause +=
"AND p.title = :TITLE ";
2240 bindings[
":TITLE"] = title;
2244 if (programid !=
"**any**")
2246 filterClause +=
"AND (0 ";
2247 if (!subtitle.isEmpty())
2250 filterClause +=
"OR p.subtitle = :SUBTITLE1 "
2251 "OR p.description = :SUBTITLE2 ";
2252 bindings[
":SUBTITLE1"] = subtitle;
2253 bindings[
":SUBTITLE2"] = subtitle;
2255 if (!descrip.isEmpty())
2258 filterClause +=
"OR p.description = :DESCRIP1 "
2259 "OR p.subtitle = :DESCRIP2 ";
2260 bindings[
":DESCRIP1"] = descrip;
2261 bindings[
":DESCRIP2"] = descrip;
2263 if (!programid.isEmpty())
2265 filterClause +=
"OR p.programid = :PROGRAMID ";
2266 bindings[
":PROGRAMID"] = programid;
2268 filterClause +=
") ";
2271 query.
prepare(QString(
"UPDATE recordmatch rm "
2273 " ON rm.recordid = r.recordid "
2274 "INNER JOIN program p "
2275 " ON rm.chanid = p.chanid "
2276 " AND rm.starttime = p.starttime "
2277 " AND rm.manualid = p.manualid "
2278 "SET oldrecduplicate = -1 "
2279 "WHERE p.generic = 0 "
2280 " AND r.type NOT IN (%2, %3, %4) ")
2286 MSqlBindings::const_iterator it;
2287 for (it = bindings.cbegin(); it != bindings.cend(); ++it)
2292 if (findid && programid !=
"**any**")
2294 query.
prepare(
"UPDATE recordmatch rm "
2295 "SET oldrecduplicate = -1 "
2296 "WHERE rm.recordid = :RECORDID "
2297 " AND rm.findid = :FINDID");
2311 auto fillstart = nowAsDuration<std::chrono::microseconds>();
2313 bool deleteFuture =
false;
2314 bool runCheck =
false;
2320 if (!request.empty())
2322 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
2323 tokens = request[0].split(
' ', QString::SkipEmptyParts);
2325 tokens = request[0].split(
' ', Qt::SkipEmptyParts);
2329 if (request.empty() || tokens.empty())
2331 LOG(VB_GENERAL, LOG_ERR,
"Empty Reschedule request received");
2335 LOG(VB_GENERAL, LOG_INFO, QString(
"Reschedule requested for %1")
2336 .arg(request.join(
" | ")));
2338 if (tokens[0] ==
"MATCH")
2340 if (tokens.size() < 5)
2342 LOG(VB_GENERAL, LOG_ERR,
2343 QString(
"Invalid RescheduleMatch request received (%1)")
2348 uint recordid = tokens[1].toUInt();
2349 uint sourceid = tokens[2].toUInt();
2350 uint mplexid = tokens[3].toUInt();
2352 deleteFuture =
true;
2360 else if (tokens[0] ==
"CHECK")
2362 if (tokens.size() < 4 || request.size() < 5)
2364 LOG(VB_GENERAL, LOG_ERR,
2365 QString(
"Invalid RescheduleCheck request received (%1)")
2370 uint recordid = tokens[2].toUInt();
2371 uint findid = tokens[3].toUInt();
2372 QString title = request[1];
2373 QString subtitle = request[2];
2374 QString descrip = request[3];
2375 QString programid = request[4];
2384 else if (tokens[0] !=
"PLACE")
2386 LOG(VB_GENERAL, LOG_ERR,
2387 QString(
"Unknown Reschedule request received (%1)")
2397 query.
prepare(
"DELETE oldrecorded FROM oldrecorded "
2398 "LEFT JOIN recordmatch ON "
2399 " recordmatch.chanid = oldrecorded.chanid AND "
2400 " recordmatch.starttime = oldrecorded.starttime "
2401 "WHERE oldrecorded.future > 0 AND "
2402 " recordmatch.recordid IS NULL");
2407 auto fillend = nowAsDuration<std::chrono::microseconds>();
2408 auto matchTime = fillend - fillstart;
2410 LOG(VB_SCHEDULE, LOG_INFO,
"CreateTempTables...");
2413 fillstart = nowAsDuration<std::chrono::microseconds>();
2416 LOG(VB_SCHEDULE, LOG_INFO,
"UpdateDuplicates...");
2419 fillend = nowAsDuration<std::chrono::microseconds>();
2420 auto checkTime = fillend - fillstart;
2422 fillstart = nowAsDuration<std::chrono::microseconds>();
2424 fillend = nowAsDuration<std::chrono::microseconds>();
2425 auto placeTime = fillend - fillstart;
2427 LOG(VB_SCHEDULE, LOG_INFO,
"DeleteTempTables...");
2437 LOG(VB_GENERAL, LOG_INFO,
"Reschedule interrupted, will retry");
2442 msg = QString(
"Scheduled %1 items in %2 "
2443 "= %3 match + %4 check + %5 place")
2445 .arg(duration_cast<floatsecs>(matchTime + checkTime + placeTime).count(), 0,
'f', 1)
2446 .arg(duration_cast<floatsecs>(matchTime).count(), 0,
'f', 2)
2447 .arg(duration_cast<floatsecs>(checkTime).count(), 0,
'f', 2)
2448 .arg(duration_cast<floatsecs>(placeTime).count(), 0,
'f', 2);
2449 LOG(VB_GENERAL, LOG_INFO, msg);
2454 if (
p->GetRecordingStatus() !=
p->m_oldrecstatus)
2457 p->AddHistory(
false,
false,
false);
2461 p->AddHistory(
false,
false,
false);
2463 p->AddHistory(
false,
false,
true);
2465 else if (
p->m_future)
2471 p->m_future =
false;
2480 std::chrono::seconds prerollseconds,
2483 bool blockShutdown =
true;
2488 QString startupParam =
"user";
2492 for ( ; firstRunIter !=
m_recList.end(); ++firstRunIter)
2503 ((std::chrono::seconds(curtime.secsTo((*firstRunIter)->GetRecordingStartTime())) -
2506 LOG(VB_GENERAL, LOG_INFO,
LOC +
"AUTO-Startup assumed");
2507 startupParam =
"auto";
2511 blockShutdown =
false;
2515 LOG(VB_GENERAL, LOG_INFO,
LOC +
"Seem to be woken up by USER");
2527 return blockShutdown;
2533 static constexpr std::array<const std::chrono::seconds,4> kSysEventSecs = { 120s, 90s, 60s, 30s };
2537 auto secsleft = std::chrono::seconds(curtime.secsTo(nextrectime));
2547 bool pendingEventSent =
false;
2548 for (
size_t i = 0; i < kSysEventSecs.size(); i++)
2550 if ((secsleft <= kSysEventSecs[i]) &&
2553 if (!pendingEventSent)
2556 QString(
"REC_PENDING SECS %1").arg(secsleft.count()), &ri);
2560 pendingEventSent =
true;
2566 for (
size_t i = 0; i < kSysEventSecs.size(); i++)
2574 keys.insert(rec->MakeUniqueKey());
2575 keys.insert(
"something");
2578 QSet<QString>::iterator sit =
m_sysEvents[i].begin();
2581 if (!keys.contains(*sit))
2592 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
2593 QString(
"Slave Backend %1 is being awakened to record: %2")
2600 ((secsleft - prerollseconds) < 210s) &&
2604 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
2605 QString(
"Slave Backend %1 not available yet, "
2606 "trying to wake it up again.")
2613 ((secsleft - prerollseconds) < 150s) &&
2616 LOG(VB_GENERAL, LOG_WARNING,
LOC +
2617 QString(
"Slave Backend %1 has NOT come "
2618 "back from sleep yet in 150 seconds. Setting "
2619 "slave status to unknown and attempting "
2620 "to reschedule around its tuners.")
2623 for (
auto * enc : qAsConst(*
m_tvList))
2635 QDateTime &nextStartTime, QDateTime &nextWakeTime,
2636 std::chrono::seconds prerollseconds)
2643 std::chrono::seconds origprerollseconds = prerollseconds;
2650 auto nextwake = std::chrono::seconds(nextWakeTime.secsTo(nextrectime));
2651 if (nextwake - prerollseconds > 5min)
2653 nextStartTime = std::min(nextStartTime, nextrectime);
2657 if (curtime < nextrectime)
2658 nextWakeTime = std::min(nextWakeTime, nextrectime);
2664 auto secsleft = std::chrono::seconds(curtime.secsTo(nextrectime));
2669 if (secsleft - prerollseconds > 1min)
2671 nextStartTime = std::min(nextStartTime, nextrectime.addSecs(-30));
2672 nextWakeTime = std::min(nextWakeTime,
2673 nextrectime.addSecs(-prerollseconds.count() - 60));
2686 if (secsleft - prerollseconds > 35s)
2688 nextStartTime = std::min(nextStartTime, nextrectime.addSecs(-30));
2689 nextWakeTime = std::min(nextWakeTime,
2690 nextrectime.addSecs(-prerollseconds.count() - 35));
2699 QString msg = QString(
"Invalid cardid [%1] for %2")
2701 LOG(VB_GENERAL, LOG_ERR,
LOC + msg);
2705 statuschanged =
true;
2713 QString msg = QString(
"SUPPRESSED recording \"%1\" on channel: "
2714 "%2 on cardid: [%3], sourceid %4. Tuner "
2715 "is locked by an external application.")
2720 LOG(VB_GENERAL, LOG_NOTICE, msg);
2724 statuschanged =
true;
2735 if (prerollseconds > 0s)
2743 if (isBusyRecording)
2746 nextWakeTime = std::min(nextWakeTime, curtime.addSecs(5));
2747 prerollseconds = 0s;
2751 if (secsleft - prerollseconds > 30s)
2753 nextStartTime = std::min(nextStartTime, nextrectime.addSecs(-30));
2754 nextWakeTime = std::min(nextWakeTime,
2755 nextrectime.addSecs(-prerollseconds.count() - 30));
2763 LOG(VB_SCHEDULE, LOG_WARNING,
2764 QString(
"WARNING: Slave Backend %1 has NOT come "
2765 "back from sleep yet. Recording can "
2766 "not begin yet for: %2")
2772 LOG(VB_SCHEDULE, LOG_WARNING,
2773 QString(
"WARNING: Slave Backend %1 has NOT come "
2774 "back from sleep yet. Setting slave "
2775 "status to unknown and attempting "
2776 "to reschedule around its tuners.")
2779 for (
auto * enc : qAsConst(*
m_tvList))
2788 nextStartTime = std::min(nextStartTime, nextrectime);
2789 nextWakeTime = std::min(nextWakeTime, curtime.addSecs(1));
2796 QString recording_dir;
2815 MythEvent me(QString(
"ADD_CHILD_INPUT %1")
2818 nextWakeTime = std::min(nextWakeTime, curtime.addSecs(1));
2828 nexttv->
RecordPending(&tempri, std::max(secsleft, 0s),
false);
2834 if (secsleft - prerollseconds > 0s)
2836 nextStartTime = std::min(nextStartTime, nextrectime);
2837 nextWakeTime = std::min(nextWakeTime,
2838 nextrectime.addSecs(-prerollseconds.count()));
2843 recstartts = QDateTime(
2845 QTime(recstartts.time().hour(), recstartts.time().minute()), Qt::UTC);
2849 QString details = QString(
"%1: channel %2 on cardid [%3], sourceid %4")
2876 statuschanged =
true;
2889 bool doSchedAfterStart =
2898 QString(
"Started recording") :
2900 QString(
"Tuning recording") :
2901 QString(
"Canceled recording (%1)")
2904 LOG(VB_GENERAL, LOG_INFO, QString(
"%1: %2").arg(msg, details));
2912 MythEvent me(QString(
"FORCE_DELETE_RECORDING %1 %2")
2920 std::chrono::seconds prerollseconds)
2925 LOG(VB_SCHEDULE, LOG_DEBUG,
2926 QString(
"Assigning input for %1/%2/\"%3\"")
2937 for (
uint i = 0; !bestid && i < inputs.size(); ++i)
2939 uint inputid = inputs[i];
2947 auto recstarttime = std::chrono::seconds(now.secsTo(
p->GetRecordingStartTime()));
2948 if (recstarttime > prerollseconds + 60s)
2950 if (
p->GetInputID() != inputid)
2967 LOG(VB_SCHEDULE, LOG_DEBUG,
2968 QString(
"Input %1 has a pending recording").arg(inputid));
2977 LOG(VB_SCHEDULE, LOG_DEBUG,
2978 QString(
"Input %1 is recording").arg(inputid));
2983 LOG(VB_SCHEDULE, LOG_DEBUG,
2984 QString(
"Input %1 is recording but will be free")
2993 LOG(VB_SCHEDULE, LOG_DEBUG,
2994 QString(
"Input %1 is recording but has to stop")
3000 LOG(VB_SCHEDULE, LOG_DEBUG,
3001 QString(
"Input %1 is recording but could be free")
3013 bool isbusy = rctv->
IsBusy(&busy_info, -1s);
3019 LOG(VB_SCHEDULE, LOG_DEBUG,
3020 QString(
"Input %1 is free").arg(inputid));
3026 LOG(VB_SCHEDULE, LOG_DEBUG,
3027 QString(
"Input %1 is on livetv but has to stop")
3038 LOG(VB_SCHEDULE, LOG_INFO,
3039 QString(
"Assigned input %1 for %2/%3/\"%4\"")
3047 LOG(VB_SCHEDULE, LOG_WARNING,
3048 QString(
"Failed to assign input for %1/%2/\"%3\"")
3054 return bestid != 0U;
3064 bool &blockShutdown, QDateTime &idleSince,
3065 std::chrono::seconds prerollseconds,
3071 uint logmask = VB_IDLE;
3072 int now = QTime::currentTime().msecsSinceStartOfDay();
3073 int tm = std::chrono::milliseconds(now) / 15min;
3076 logmask = VB_GENERAL;
3095 LOG(VB_GENERAL, LOG_NOTICE,
"Client is connected, removing startup block on shutdown");
3096 blockShutdown =
false;
3107 bool recording =
false;
3110 QMap<int, EncoderLink *>::const_iterator it;
3114 if ((*it)->IsBusy())
3128 if (!blocking && !recording && !activeJobs && !delay)
3135 if (idleSince.isValid())
3137 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN -1"));
3140 idleSince = QDateTime();
3145 if (statuschanged || !idleSince.isValid())
3147 bool wasValid = idleSince.isValid();
3149 idleSince = curtime;
3152 for ( ; idleIter !=
m_recList.end(); ++idleIter)
3154 if ((*idleIter)->GetRecordingStatus() ==
3156 (*idleIter)->GetRecordingStatus() ==
3163 auto recstarttime = std::chrono::seconds(curtime.secsTo((*idleIter)->GetRecordingStartTime()));
3166 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3167 "a recording is due to "
3169 idleSince = QDateTime();
3180 if (guideRunTime.isValid() &&
3184 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3185 "mythfilldatabase is due to "
3187 idleSince = QDateTime();
3192 if (idleSince.isValid())
3195 if (wasValid && !idleSince.isValid())
3197 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN -1"));
3202 if (idleSince.isValid())
3214 LOG(VB_GENERAL, LOG_WARNING,
3215 "Waited more than 60"
3216 " seconds for shutdown to complete"
3217 " - resetting idle time");
3218 idleSince = QDateTime();
3224 blockShutdown, logmask))
3230 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN -1"));
3236 auto itime = std::chrono::seconds(idleSince.secsTo(curtime));
3240 msg = QString(
"I\'m idle now... shutdown will "
3241 "occur in %1 seconds.")
3243 LOG(VB_GENERAL, LOG_NOTICE, msg);
3244 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN %1")
3251 msg = QString(
"%1 secs left to system shutdown!").arg(remain);
3252 LOG(logmask, LOG_NOTICE, msg);
3253 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN %1").arg(remain));
3262 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3263 "of an active encoder");
3265 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3266 "of a connected client");
3269 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3273 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3274 "of delay request from external application");
3277 if (idleSince.isValid())
3279 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN -1"));
3282 idleSince = QDateTime();
3289 QDateTime &idleSince,
3290 bool &blockShutdown,
uint logmask)
3292 (void)prerollseconds;
3293 bool retval =
false;
3303 LOG(logmask, LOG_INFO,
3304 "CheckShutdownServer returned - OK to shutdown");
3308 LOG(logmask, LOG_NOTICE,
3309 "CheckShutdownServer returned - Not OK to shutdown");
3311 idleSince = QDateTime();
3314 LOG(logmask, LOG_NOTICE,
3315 "CheckShutdownServer returned - Not OK to shutdown, "
3323 idleSince = QDateTime();
3328 m_noAutoShutdown =
true;
3332 LOG(VB_GENERAL, LOG_NOTICE,
3333 "CheckShutdownServer returned - Not OK");
3336 LOG(VB_GENERAL, LOG_NOTICE, QString(
3337 "CheckShutdownServer returned - Error %1").arg(state));
3348 QDateTime &idleSince)
3353 for ( ; recIter !=
m_recList.end(); ++recIter)
3361 QDateTime restarttime;
3366 .addSecs(-prerollseconds.count());
3374 && guideRefreshTime.isValid()
3376 && (restarttime.isNull() || guideRefreshTime < restarttime))
3377 restarttime = guideRefreshTime;
3379 if (restarttime.isValid())
3383 restarttime = restarttime.addSecs((-1LL) * add);
3386 "hh:mm yyyy-MM-dd");
3388 "echo \'Wakeuptime would "
3389 "be $time if command "
3392 if (setwakeup_cmd.isEmpty())
3394 LOG(VB_GENERAL, LOG_NOTICE,
3395 "SetWakeuptimeCommand is empty, shutdown aborted");
3396 idleSince = QDateTime();
3400 if (wakeup_timeformat ==
"time_t")
3403 setwakeup_cmd.replace(
"$time",
3404 time_ts.setNum(restarttime.toSecsSinceEpoch())
3408 setwakeup_cmd.replace(
3409 "$time", restarttime.toLocalTime().toString(wakeup_timeformat));
3411 LOG(VB_GENERAL, LOG_NOTICE,
3412 QString(
"Running the command to set the next "
3413 "scheduled wakeup time :-\n\t\t\t\t") + setwakeup_cmd);
3418 LOG(VB_GENERAL, LOG_ERR,
3419 "SetWakeuptimeCommand failed, shutdown aborted");
3420 idleSince = QDateTime();
3435 "sudo /sbin/halt -p");
3437 if (!halt_cmd.isEmpty())
3442 LOG(VB_GENERAL, LOG_NOTICE,
3443 QString(
"Running the command to shutdown "
3444 "this computer :-\n\t\t\t\t") + halt_cmd);
3451 LOG(VB_GENERAL, LOG_ERR,
"ServerHaltCommand failed, shutdown aborted");
3456 idleSince = QDateTime();
3462 std::chrono::seconds prerollseconds = 0s;
3463 std::chrono::seconds secsleft = 0s;
3467 bool someSlavesCanSleep =
false;
3468 for (
auto * enc : qAsConst(*
m_tvList))
3470 if (enc->CanSleep())
3471 someSlavesCanSleep =
true;
3474 if (!someSlavesCanSleep)
3477 LOG(VB_SCHEDULE, LOG_INFO,
3478 "Scheduler, Checking for slaves that can be shut down");
3480 auto sleepThreshold =
3483 LOG(VB_SCHEDULE, LOG_DEBUG,
3484 QString(
" Getting list of slaves that will be active in the "
3485 "next %1 minutes.") .arg(duration_cast<std::chrono::minutes>(sleepThreshold).count()));
3487 LOG(VB_SCHEDULE, LOG_DEBUG,
"Checking scheduler's reclist");
3489 QStringList SlavesInUse;
3499 auto recstarttime = std::chrono::seconds(curtime.secsTo(pginfo->GetRecordingStartTime()));
3500 secsleft = recstarttime - prerollseconds;
3501 if (secsleft > sleepThreshold)
3506 EncoderLink *enc = (*m_tvList)[pginfo->GetInputID()];
3513 LOG(VB_SCHEDULE, LOG_DEBUG,
3514 QString(
" Slave %1 will be in use in %2 minutes")
3516 .arg(duration_cast<std::chrono::minutes>(secsleft).count()));
3520 LOG(VB_SCHEDULE, LOG_DEBUG,
3521 QString(
" Slave %1 is in use currently "
3530 LOG(VB_SCHEDULE, LOG_DEBUG,
" Checking inuseprograms table:");
3533 query.
prepare(
"SELECT DISTINCT hostname, recusage FROM inuseprograms "
3534 "WHERE lastupdatetime > :ONEHOURAGO ;");
3535 query.
bindValue(
":ONEHOURAGO", oneHourAgo);
3538 while(query.
next()) {
3539 SlavesInUse << query.
value(0).toString();
3540 LOG(VB_SCHEDULE, LOG_DEBUG,
3541 QString(
" Slave %1 is marked as in use by a %2")
3542 .arg(query.
value(0).toString(),
3543 query.
value(1).toString()));
3547 LOG(VB_SCHEDULE, LOG_DEBUG, QString(
" Shutting down slaves which will "
3548 "be inactive for the next %1 minutes and can be put to sleep.")
3549 .arg(sleepThreshold.count() / 60));
3551 for (
auto * enc : qAsConst(*
m_tvList))
3553 if ((!enc->IsLocal()) &&
3555 (!SlavesInUse.contains(enc->GetHostName())) &&
3556 (!enc->IsFallingAsleep()))
3558 QString sleepCommand =
3560 enc->GetHostName());
3561 QString wakeUpCommand =
3563 enc->GetHostName());
3565 if (!sleepCommand.isEmpty() && !wakeUpCommand.isEmpty())
3567 QString thisHost = enc->GetHostName();
3569 LOG(VB_SCHEDULE, LOG_DEBUG,
3570 QString(
" Commanding %1 to go to sleep.")
3573 if (enc->GoToSleep())
3575 for (
auto * slv : qAsConst(*
m_tvList))
3577 if (slv->GetHostName() == thisHost)
3579 LOG(VB_SCHEDULE, LOG_DEBUG,
3580 QString(
" Marking card %1 on slave %2 "
3581 "as falling asleep.")
3582 .arg(slv->GetInputID())
3583 .arg(slv->GetHostName()));
3590 LOG(VB_GENERAL, LOG_ERR,
LOC +
3591 QString(
"Unable to shutdown %1 slave backend, setting "
3592 "sleep status to undefined.").arg(thisHost));
3593 for (
auto * slv : qAsConst(*
m_tvList))
3595 if (slv->GetHostName() == thisHost)
3608 LOG(VB_GENERAL, LOG_NOTICE,
3609 QString(
"Tried to Wake Up %1, but this is the "
3610 "master backend and it is not asleep.")
3611 .arg(slaveHostname));
3618 if (wakeUpCommand.isEmpty()) {
3619 LOG(VB_GENERAL, LOG_NOTICE,
3620 QString(
"Trying to Wake Up %1, but this slave "
3621 "does not have a WakeUpCommand set.").arg(slaveHostname));
3623 for (
auto * enc : qAsConst(*
m_tvList))
3625 if (enc->GetHostName() == slaveHostname)
3633 for (
auto * enc : qAsConst(*
m_tvList))
3635 if (setWakingStatus && (enc->GetHostName() == slaveHostname))
3637 enc->SetLastWakeTime(curtime);
3642 LOG(VB_SCHEDULE, LOG_NOTICE, QString(
"Executing '%1' to wake up slave.")
3643 .arg(wakeUpCommand));
3655 QStringList SlavesThatCanWake;
3657 for (
auto * enc : qAsConst(*
m_tvList))
3662 thisSlave = enc->GetHostName();
3666 (!SlavesThatCanWake.contains(thisSlave)))
3667 SlavesThatCanWake << thisSlave;
3671 for (; slave < SlavesThatCanWake.count(); slave++)
3673 thisSlave = SlavesThatCanWake[slave];
3674 LOG(VB_SCHEDULE, LOG_NOTICE,
3675 QString(
"Scheduler, Sending wakeup command to slave: %1")
3685 query.
prepare(QString(
"SELECT type,title,subtitle,description,"
3686 "station,startdate,starttime,"
3687 "enddate,endtime,season,episode,inetref,last_record "
3690 if (!query.
exec() || query.
size() != 1)
3700 QString title = query.
value(1).toString();
3701 QString subtitle = query.
value(2).toString();
3702 QString description = query.
value(3).toString();
3703 QString station = query.
value(4).toString();
3704 QDateTime startdt = QDateTime(query.
value(5).toDate(),
3705 query.
value(6).toTime(), Qt::UTC);
3706 int duration = startdt.secsTo(
3707 QDateTime(query.
value(7).toDate(),
3708 query.
value(8).toTime(), Qt::UTC));
3710 int season = query.
value(9).toInt();
3711 int episode = query.
value(10).toInt();
3712 QString inetref = query.
value(11).toString();
3716 QDate originalairdate = QDate(query.
value(12).toDate());
3718 if (description.isEmpty())
3719 description = startdt.toLocalTime().toString();
3721 query.
prepare(
"SELECT chanid from channel "
3722 "WHERE deleted IS NULL AND callsign = :STATION");
3730 std::vector<unsigned int> chanidlist;
3731 while (query.
next())
3732 chanidlist.push_back(query.
value(0).toUInt());
3736 bool weekday =
false;
3738 QDateTime lstartdt = startdt.toLocalTime();
3753 weekday = (lstartdt.date().dayOfWeek() < 6);
3754 daysoff = lstartdt.date().daysTo(
3756 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3757 lstartdt.time(), Qt::LocalTime).toUTC();
3763 daysoff = lstartdt.date().daysTo(
3765 daysoff = (daysoff + 6) / 7 * 7;
3766 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3767 lstartdt.time(), Qt::LocalTime).toUTC();
3770 LOG(VB_GENERAL, LOG_ERR,
3771 QString(
"Invalid rectype for manual recordid %1").arg(recordid));
3777 for (
uint id : chanidlist)
3779 if (weekday && startdt.toLocalTime().date().dayOfWeek() >= 6)
3782 query.
prepare(
"REPLACE INTO program (chanid, starttime, endtime,"
3783 " title, subtitle, description, manualid,"
3784 " season, episode, inetref, originalairdate, generic) "
3785 "VALUES (:CHANID, :STARTTIME, :ENDTIME, :TITLE,"
3786 " :SUBTITLE, :DESCRIPTION, :RECORDID, "
3787 " :SEASON, :EPISODE, :INETREF, :ORIGINALAIRDATE, 1)");
3790 query.
bindValue(
":ENDTIME", startdt.addSecs(duration));
3793 query.
bindValue(
":DESCRIPTION", description);
3797 query.
bindValue(
":ORIGINALAIRDATE", originalairdate);
3806 daysoff += skipdays;
3807 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3808 lstartdt.time(), Qt::LocalTime).toUTC();
3820 query = QString(
"SELECT recordid,search,subtitle,description "
3821 "FROM %1 WHERE search <> %2 AND "
3822 "(recordid = %3 OR %4 = 0) ")
3834 while (result.
next())
3836 QString
prefix = QString(
":NR%1").arg(count);
3837 qphrase = result.
value(3).toString();
3843 LOG(VB_GENERAL, LOG_ERR,
3844 QString(
"Invalid search key in recordid %1")
3845 .arg(result.
value(0).toString()));
3849 QString bindrecid =
prefix +
"RECID";
3850 QString bindphrase =
prefix +
"PHRASE";
3851 QString bindlikephrase1 =
prefix +
"LIKEPHRASE1";
3852 QString bindlikephrase2 =
prefix +
"LIKEPHRASE2";
3853 QString bindlikephrase3 =
prefix +
"LIKEPHRASE3";
3855 bindings[bindrecid] = result.
value(0).toString();
3861 qphrase.remove(
';');
3862 from << result.
value(2).toString();
3863 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
3864 QString(
" AND program.manualid = 0 AND ( %2 )")
3868 bindings[bindlikephrase1] = QString(
"%") + qphrase +
"%";
3870 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
" AND "
3871 "program.manualid = 0 AND "
3872 "program.title LIKE " + bindlikephrase1);
3875 bindings[bindlikephrase1] = QString(
"%") + qphrase +
"%";
3876 bindings[bindlikephrase2] = QString(
"%") + qphrase +
"%";
3877 bindings[bindlikephrase3] = QString(
"%") + qphrase +
"%";
3879 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
3880 " AND program.manualid = 0"
3881 " AND (program.title LIKE " + bindlikephrase1 +
3882 " OR program.subtitle LIKE " + bindlikephrase2 +
3883 " OR program.description LIKE " + bindlikephrase3 +
")");
3886 bindings[bindphrase] = qphrase;
3887 from <<
", people, credits";
3888 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
" AND "
3889 "program.manualid = 0 AND "
3890 "people.name LIKE " + bindphrase +
" AND "
3891 "credits.person = people.person AND "
3892 "program.chanid = credits.chanid AND "
3893 "program.starttime = credits.starttime");
3898 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
3900 QString(
"program.manualid = %1.recordid ")
3904 LOG(VB_GENERAL, LOG_ERR,
3905 QString(
"Unknown RecSearchType (%1) for recordid %2")
3906 .arg(result.
value(1).toInt())
3907 .arg(result.
value(0).toString()));
3908 bindings.remove(bindrecid);
3915 if (recordid == 0 || from.count() == 0)
3917 QString recidmatch =
"";
3919 recidmatch =
"RECTABLE.recordid = :NRRECORDID AND ";
3920 QString s1 = recidmatch +
3921 "RECTABLE.type <> :NRTEMPLATE AND "
3922 "RECTABLE.search = :NRST AND "
3923 "program.manualid = 0 AND "
3924 "program.title = RECTABLE.title ";
3926 QString s2 = recidmatch +
3927 "RECTABLE.type <> :NRTEMPLATE AND "
3928 "RECTABLE.search = :NRST AND "
3929 "program.manualid = 0 AND "
3930 "program.seriesid <> '' AND "
3931 "program.seriesid = RECTABLE.seriesid ";
3941 bindings[
":NRRECORDID"] = recordid;
3947 " WHEN RECTABLE.type IN (%1, %2, %3) THEN 0 "
3948 " WHEN RECTABLE.type IN (%4, %5, %6) THEN -1 "
3949 " ELSE (program.generic - 1) "
3955 "(CASE RECTABLE.type "
3957 " THEN RECTABLE.findid "
3959 " THEN to_days(date_sub(convert_tz(program.starttime, 'UTC', 'SYSTEM'), "
3960 " interval time_format(RECTABLE.findtime, '%H:%i') hour_minute)) "
3962 " THEN floor((to_days(date_sub(convert_tz(program.starttime, 'UTC', "
3963 " 'SYSTEM'), interval time_format(RECTABLE.findtime, '%H:%i') "
3964 " hour_minute)) - RECTABLE.findday)/7) * 7 + RECTABLE.findday "
3966 " THEN RECTABLE.findid "
3975 const QDateTime &maxstarttime)
3979 QString deleteClause;
3980 QString filterClause = QString(
" AND program.endtime > "
3981 "(NOW() - INTERVAL 480 MINUTE)");
3985 deleteClause +=
" AND recordmatch.recordid = :RECORDID";
3986 bindings[
":RECORDID"] = recordid;
3990 deleteClause +=
" AND channel.sourceid = :SOURCEID";
3991 filterClause +=
" AND channel.sourceid = :SOURCEID";
3992 bindings[
":SOURCEID"] = sourceid;
3996 deleteClause +=
" AND channel.mplexid = :MPLEXID";
3997 filterClause +=
" AND channel.mplexid = :MPLEXID";
3998 bindings[
":MPLEXID"] = mplexid;
4000 if (maxstarttime.isValid())
4002 deleteClause +=
" AND recordmatch.starttime <= :MAXSTARTTIME";
4003 filterClause +=
" AND program.starttime <= :MAXSTARTTIME";
4004 bindings[
":MAXSTARTTIME"] = maxstarttime;
4007 query.
prepare(QString(
"DELETE recordmatch FROM recordmatch, channel "
4008 "WHERE recordmatch.chanid = channel.chanid")
4010 MSqlBindings::const_iterator it;
4011 for (it = bindings.cbegin(); it != bindings.cend(); ++it)
4019 bindings.remove(
":RECORDID");
4021 query.
prepare(
"SELECT filterid, clause FROM recordfilter "
4022 "WHERE filterid >= 0 AND filterid < :NUMFILTERS AND "
4023 " TRIM(clause) <> ''");
4030 while (query.
next())
4032 filterClause += QString(
" AND (((RECTABLE.filter & %1) = 0) OR (%2))")
4033 .arg(1 << query.
value(0).toInt()).arg(query.
value(1).toString());
4037 query.
prepare(
"SELECT NULL from record "
4038 "WHERE type = :FINDONE AND findid <= 0;");
4047 QDate epoch(1970, 1, 1);
4050 query.
prepare(
"UPDATE record set findid = :FINDID "
4051 "WHERE type = :FINDONE AND findid <= 0;");
4058 QStringList fromclauses;
4059 QStringList whereclauses;
4065 for (
int clause = 0; clause < fromclauses.count(); ++clause)
4067 LOG(VB_SCHEDULE, LOG_INFO, QString(
"Query %1: %2/%3")
4068 .arg(QString::number(clause), fromclauses[clause],
4069 whereclauses[clause]));
4073 for (
int clause = 0; clause < fromclauses.count(); ++clause)
4075 QString query2 = QString(
4076 "REPLACE INTO recordmatch (recordid, chanid, starttime, manualid, "
4077 " oldrecduplicate, findid) "
4078 "SELECT RECTABLE.recordid, program.chanid, program.starttime, "
4079 " IF(search = %1, RECTABLE.recordid, 0), ").arg(
kManualSearch) +
4081 "FROM (RECTABLE, program INNER JOIN channel "
4082 " ON channel.chanid = program.chanid) ") + fromclauses[clause] + QString(
4083 " WHERE ") + whereclauses[clause] +
4084 QString(
" AND channel.deleted IS NULL "
4085 " AND channel.visible > 0 ") +
4086 filterClause + QString(
" AND "
4089 " (RECTABLE.type = %1 "
4090 " OR RECTABLE.type = %2 "
4091 " OR RECTABLE.type = %3 "
4092 " OR RECTABLE.type = %4) "
4094 " ((RECTABLE.type = %6 "
4095 " OR RECTABLE.type = %7 "
4096 " OR RECTABLE.type = %8)"
4098 " ADDTIME(RECTABLE.startdate, RECTABLE.starttime) = program.starttime "
4100 " RECTABLE.station = channel.callsign) "
4112 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- Start DB Query %1...")
4115 auto dbstart = nowAsDuration<std::chrono::microseconds>();
4119 for (it = bindings.cbegin(); it != bindings.cend(); ++it)
4121 if (query2.contains(it.key()))
4125 bool ok = result.
exec();
4126 auto dbend = nowAsDuration<std::chrono::microseconds>();
4127 auto dbTime = dbend - dbstart;
4135 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- %1 results in %2 sec.")
4137 .arg(duration_cast<std::chrono::seconds>(dbTime).count()));
4141 LOG(VB_SCHEDULE, LOG_INFO,
" +-- Done.");
4150 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_record;");
4156 result.
prepare(
"CREATE TEMPORARY TABLE sched_temp_record "
4163 result.
prepare(
"INSERT sched_temp_record SELECT * from record;");
4171 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_recorded;");
4177 result.
prepare(
"CREATE TEMPORARY TABLE sched_temp_recorded "
4184 result.
prepare(
"INSERT sched_temp_recorded SELECT * from recorded;");
4198 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_record;");
4203 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_recorded;");
4211 if (schedTmpRecord ==
"record")
4212 schedTmpRecord =
"sched_temp_record";
4214 QString rmquery = QString(
4215 "UPDATE recordmatch "
4216 " INNER JOIN RECTABLE ON (recordmatch.recordid = RECTABLE.recordid) "
4217 " INNER JOIN program p ON (recordmatch.chanid = p.chanid AND "
4218 " recordmatch.starttime = p.starttime AND "
4219 " recordmatch.manualid = p.manualid) "
4220 " LEFT JOIN oldrecorded ON "
4222 " RECTABLE.dupmethod > 1 AND "
4223 " oldrecorded.duplicate <> 0 AND "
4224 " p.title = oldrecorded.title AND "
4228 " (p.programid <> '' "
4229 " AND p.programid = oldrecorded.programid) "
4233 " (p.programid = '' OR oldrecorded.programid = '' OR "
4234 " LEFT(p.programid, LOCATE('/', p.programid)) <> "
4235 " LEFT(oldrecorded.programid, LOCATE('/', oldrecorded.programid))) " :
4236 " (p.programid = '' OR oldrecorded.programid = '') " )
4239 " (((RECTABLE.dupmethod & 0x02) = 0) OR (p.subtitle <> '' "
4240 " AND p.subtitle = oldrecorded.subtitle)) "
4242 " (((RECTABLE.dupmethod & 0x04) = 0) OR (p.description <> '' "
4243 " AND p.description = oldrecorded.description)) "
4245 " (((RECTABLE.dupmethod & 0x08) = 0) OR "
4246 " (p.subtitle <> '' AND "
4247 " (p.subtitle = oldrecorded.subtitle OR "
4248 " (oldrecorded.subtitle = '' AND "
4249 " p.subtitle = oldrecorded.description))) OR "
4250 " (p.subtitle = '' AND p.description <> '' AND "
4251 " (p.description = oldrecorded.subtitle OR "
4252 " (oldrecorded.subtitle = '' AND "
4253 " p.description = oldrecorded.description)))) "
4257 " LEFT JOIN sched_temp_recorded recorded ON "
4259 " RECTABLE.dupmethod > 1 AND "
4260 " recorded.duplicate <> 0 AND "
4261 " p.title = recorded.title AND "
4262 " p.generic = 0 AND "
4263 " recorded.recgroup NOT IN ('LiveTV','Deleted') "
4266 " (p.programid <> '' "
4267 " AND p.programid = recorded.programid) "
4271 " (p.programid = '' OR recorded.programid = '' OR "
4272 " LEFT(p.programid, LOCATE('/', p.programid)) <> "
4273 " LEFT(recorded.programid, LOCATE('/', recorded.programid))) " :
4274 " (p.programid = '' OR recorded.programid = '') ")
4277 " (((RECTABLE.dupmethod & 0x02) = 0) OR (p.subtitle <> '' "
4278 " AND p.subtitle = recorded.subtitle)) "
4280 " (((RECTABLE.dupmethod & 0x04) = 0) OR (p.description <> '' "
4281 " AND p.description = recorded.description)) "
4283 " (((RECTABLE.dupmethod & 0x08) = 0) OR "
4284 " (p.subtitle <> '' AND "
4285 " (p.subtitle = recorded.subtitle OR "
4286 " (recorded.subtitle = '' AND "
4287 " p.subtitle = recorded.description))) OR "
4288 " (p.subtitle = '' AND p.description <> '' AND "
4289 " (p.description = recorded.subtitle OR "
4290 " (recorded.subtitle = '' AND "
4291 " p.description = recorded.description)))) "
4295 " LEFT JOIN oldfind ON "
4296 " (oldfind.recordid = recordmatch.recordid AND "
4297 " oldfind.findid = recordmatch.findid) "
4298 " SET oldrecduplicate = (oldrecorded.endtime IS NOT NULL), "
4299 " recduplicate = (recorded.endtime IS NOT NULL), "
4300 " findduplicate = (oldfind.findid IS NOT NULL), "
4301 " oldrecstatus = oldrecorded.recstatus "
4302 " WHERE p.endtime >= (NOW() - INTERVAL 480 MINUTE) "
4303 " AND oldrecduplicate = -1 "
4305 rmquery.replace(
"RECTABLE", schedTmpRecord);
4319 if (schedTmpRecord ==
"record")
4320 schedTmpRecord =
"sched_temp_record";
4324 QMap<int, bool> cardMap;
4325 for (
auto * enc : qAsConst(*
m_tvList))
4327 if (enc->IsConnected() || enc->IsAsleep())
4328 cardMap[enc->GetInputID()] =
true;
4331 QMap<int, bool> tooManyMap;
4332 bool checkTooMany =
false;
4336 rlist.
prepare(QString(
"SELECT recordid, title, maxepisodes, maxnewest "
4337 "FROM %1").arg(schedTmpRecord));
4345 while (rlist.
next())
4347 int recid = rlist.
value(0).toInt();
4349 int maxEpisodes = rlist.
value(2).toInt();
4350 int maxNewest = rlist.
value(3).toInt();
4352 tooManyMap[recid] =
false;
4355 if (maxEpisodes && !maxNewest)
4359 epicnt.
prepare(
"SELECT DISTINCT chanid, progstart, progend "
4361 "WHERE recordid = :RECID AND preserve = 0 "
4362 "AND recgroup NOT IN ('LiveTV','Deleted');");
4367 if (epicnt.
size() >= maxEpisodes - 1)
4370 if (epicnt.
size() >= maxEpisodes)
4372 tooManyMap[recid] =
true;
4373 checkTooMany =
true;
4389 QString pwrpri =
"channel.recpriority + capturecard.recpriority";
4393 pwrpri += QString(
" + "
4394 "IF(capturecard.cardid = RECTABLE.prefinput, 1, 0) * %1")
4400 pwrpri += QString(
" + IF(program.hdtv > 0 OR "
4401 "FIND_IN_SET('HDTV', program.videoprop) > 0, 1, 0) * %1")
4407 pwrpri += QString(
" + "
4408 "IF(FIND_IN_SET('WIDESCREEN', program.videoprop) > 0, 1, 0) * %1")
4414 pwrpri += QString(
" + "
4415 "IF(FIND_IN_SET('SIGNED', program.subtitletypes) > 0, 1, 0) * %1")
4421 pwrpri += QString(
" + "
4422 "IF(FIND_IN_SET('ONSCREEN', program.subtitletypes) > 0, 1, 0) * %1")
4423 .arg(onscrpriority);
4428 pwrpri += QString(
" + "
4429 "IF(FIND_IN_SET('NORMAL', program.subtitletypes) > 0 OR "
4430 "program.closecaptioned > 0 OR program.subtitled > 0, 1, 0) * %1")
4436 pwrpri += QString(
" + "
4437 "IF(FIND_IN_SET('HARDHEAR', program.subtitletypes) > 0 OR "
4438 "FIND_IN_SET('HARDHEAR', program.audioprop) > 0, 1, 0) * %1")
4444 pwrpri += QString(
" + "
4445 "IF(FIND_IN_SET('VISUALIMPAIR', program.audioprop) > 0, 1, 0) * %1")
4451 result.
prepare(QString(
"SELECT recpriority, selectclause FROM %1;")
4460 while (result.
next())
4462 if (result.
value(0).toBool())
4464 QString sclause = result.
value(1).toString();
4466 sclause.remove(
';');
4467 pwrpri += QString(
" + IF(%1, 1, 0) * %2")
4468 .arg(sclause).arg(result.
value(0).toInt());
4471 pwrpri += QString(
" AS powerpriority ");
4473 pwrpri.replace(
"program.",
"p.");
4474 pwrpri.replace(
"channel.",
"c.");
4475 QString query = QString(
4477 " c.chanid, c.sourceid, p.starttime, "
4478 " p.endtime, p.title, p.subtitle, "
4479 " p.description, c.channum, c.callsign, "
4480 " c.name, oldrecduplicate, p.category, "
4481 " RECTABLE.recpriority, RECTABLE.dupin, recduplicate, "
4482 " findduplicate, RECTABLE.type, RECTABLE.recordid, "
4483 " p.starttime - INTERVAL RECTABLE.startoffset "
4484 " minute AS recstartts, "
4485 " p.endtime + INTERVAL RECTABLE.endoffset "
4486 " minute AS recendts, "
4487 " p.previouslyshown, "
4488 " RECTABLE.recgroup, RECTABLE.dupmethod, c.commmethod, "
4489 " capturecard.cardid, 0, p.seriesid, "
4490 " p.programid, RECTABLE.inetref, p.category_type, "
4491 " p.airdate, p.stars, p.originalairdate, "
4492 " RECTABLE.inactive, RECTABLE.parentid, recordmatch.findid, "
4493 " RECTABLE.playgroup, oldrecstatus.recstatus, "
4494 " oldrecstatus.reactivate, p.videoprop+0, "
4495 " p.subtitletypes+0, p.audioprop+0, RECTABLE.storagegroup, "
4496 " capturecard.hostname, recordmatch.oldrecstatus, NULL, "
4497 " oldrecstatus.future, capturecard.schedorder, "
4498 " p.syndicatedepisodenumber, p.partnumber, p.parttotal, "
4499 " c.mplexid, capturecard.displayname, "
4500 " p.season, p.episode, p.totalepisodes, ") +
4503 "INNER JOIN RECTABLE ON (recordmatch.recordid = RECTABLE.recordid) "
4504 "INNER JOIN program AS p "
4505 "ON ( recordmatch.chanid = p.chanid AND "
4506 " recordmatch.starttime = p.starttime AND "
4507 " recordmatch.manualid = p.manualid ) "
4508 "INNER JOIN channel AS c "
4509 "ON ( c.chanid = p.chanid ) "
4510 "INNER JOIN capturecard "
4511 "ON ( c.sourceid = capturecard.sourceid AND "
4512 " ( capturecard.schedorder <> 0 OR "
4513 " capturecard.parentid = 0 ) ) "
4514 "LEFT JOIN oldrecorded as oldrecstatus "
4515 "ON ( oldrecstatus.station = c.callsign AND "
4516 " oldrecstatus.starttime = p.starttime AND "
4517 " oldrecstatus.title = p.title ) "
4518 "WHERE p.endtime > (NOW() - INTERVAL 480 MINUTE) "
4519 "ORDER BY RECTABLE.recordid DESC, p.starttime, p.title, c.callsign, "
4521 query.replace(
"RECTABLE", schedTmpRecord);
4523 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- Start DB Query..."));
4525 auto dbstart = nowAsDuration<std::chrono::microseconds>();
4532 auto dbend = nowAsDuration<std::chrono::microseconds>();
4533 auto dbTime = dbend - dbstart;
4535 LOG(VB_SCHEDULE, LOG_INFO,
4536 QString(
" |-- %1 results in %2 sec. Processing...")
4538 .arg(duration_cast<std::chrono::seconds>(dbTime).count()));
4542 while (result.
next())
4548 uint recordid = result.
value(17).toUInt();
4550 QString title = result.
value(4).toString();
4551 QString callsign = result.
value(8).toString();
4561 uint mplexid = result.
value(51).toUInt();
4562 if (mplexid == 32767)
4565 QString inputname = result.
value(52).toString();
4566 if (inputname.isEmpty())
4567 inputname = QString(
"Input %1").arg(result.
value(24).toUInt());
4572 result.
value(5).toString(),
4574 result.
value(6).toString(),
4575 result.
value(53).toInt(),
4576 result.
value(54).toInt(),
4577 result.
value(55).toInt(),
4578 result.
value(48).toString(),
4579 result.
value(11).toString(),
4581 result.
value(0).toUInt(),
4582 result.
value(7).toString(),
4584 result.
value(9).toString(),
4586 result.
value(21).toString(),
4587 result.
value(36).toString(),
4589 result.
value(43).toString(),
4590 result.
value(42).toString(),
4592 result.
value(30).toUInt(),
4593 result.
value(49).toUInt(),
4594 result.
value(50).toUInt(),
4596 result.
value(26).toString(),
4597 result.
value(27).toString(),
4598 result.
value(28).toString(),
4601 result.
value(12).toInt(),
4608 result.
value(31).toDouble(),
4609 (result.
value(32).isNull()) ? QDate() :
4613 result.
value(20).toBool(),
4616 result.
value(38).toBool(),
4619 result.
value(34).toUInt(),
4624 result.
value(1).toUInt(),
4625 result.
value(24).toUInt(),
4627 result.
value(35).toUInt(),
4630 result.
value(40).toUInt(),
4631 result.
value(39).toUInt(),
4632 result.
value(41).toUInt(),
4633 result.
value(46).toBool(),
4634 result.
value(47).toInt(),
4636 result.
value(24).toUInt(),
4639 if (!
p->m_future && !
p->IsReactivated() &&
4643 p->SetRecordingStatus(
p->m_oldrecstatus);
4646 p->SetRecordingPriority2(result.
value(56).toInt());
4655 if (
p->IsSameTitleStartTimeAndChannel(*r))
4657 if (r->m_sgroupId ==
p->m_sgroupId &&
4658 r->GetRecordingEndTime() !=
p->GetRecordingEndTime() &&
4659 (r->GetRecordingRuleID() ==
p->GetRecordingRuleID() ||
4674 tmpList.push_back(
p);
4681 (!cardMap.contains(
p->GetInputID()) || (
p->m_schedOrder == 0)))
4684 if (
p->m_schedOrder == 0 &&
4688 LOG(VB_GENERAL, LOG_WARNING,
LOC +
4689 QString(
"Channel %1, Title %2 %3 cardinput.schedorder = %4, "
4690 "it must be >0 to record from this input.")
4691 .arg(
p->GetChannelName(),
p->GetTitle(),
4692 p->GetScheduledStartTime().toString(),
4693 QString::number(
p->m_schedOrder)));
4699 if (checkTooMany && tooManyMap[
p->GetRecordingRuleID()] &&
4700 !
p->IsReactivated())
4708 else if (result.
value(15).toBool() && !
p->IsReactivated())
4712 !
p->IsReactivated() &&
4732 bool inactive = result.
value(33).toBool();
4747 p->SetRecordingStatus(newrecstatus);
4749 tmpList.push_back(
p);
4752 LOG(VB_SCHEDULE, LOG_INFO,
" +-- Cleanup...");
4753 for (
auto &
tmp : tmpList)
4761 QString query = QString(
4762 "SELECT RECTABLE.title, RECTABLE.subtitle, "
4763 " RECTABLE.description, RECTABLE.season, "
4764 " RECTABLE.episode, RECTABLE.category, "
4765 " RECTABLE.chanid, channel.channum, "
4766 " RECTABLE.station, channel.name, "
4767 " RECTABLE.recgroup, RECTABLE.playgroup, "
4768 " RECTABLE.seriesid, RECTABLE.programid, "
4769 " RECTABLE.inetref, RECTABLE.recpriority, "
4770 " RECTABLE.startdate, RECTABLE.starttime, "
4771 " RECTABLE.enddate, RECTABLE.endtime, "
4772 " RECTABLE.recordid, RECTABLE.type, "
4773 " RECTABLE.dupin, RECTABLE.dupmethod, "
4774 " RECTABLE.findid, "
4775 " RECTABLE.startoffset, RECTABLE.endoffset, "
4776 " channel.commmethod "
4778 "INNER JOIN channel ON (channel.chanid = RECTABLE.chanid) "
4779 "LEFT JOIN recordmatch on RECTABLE.recordid = recordmatch.recordid "
4780 "WHERE (type = %1 OR type = %2) AND "
4781 " recordmatch.chanid IS NULL")
4787 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- Start DB Query..."));
4789 auto dbstart = nowAsDuration<std::chrono::microseconds>();
4792 bool ok = result.
exec();
4793 auto dbend = nowAsDuration<std::chrono::microseconds>();
4794 auto dbTime = dbend - dbstart;
4802 LOG(VB_SCHEDULE, LOG_INFO,
4803 QString(
" |-- %1 results in %2 sec. Processing...")
4805 .arg(duration_cast<std::chrono::seconds>(dbTime).count()));
4807 while (result.
next())
4811 result.
value(16).toDate(), result.
value(17).toTime(), Qt::UTC);
4813 result.
value(18).toDate(), result.
value(19).toTime(), Qt::UTC);
4815 QDateTime recstartts = startts.addSecs(result.
value(25).toInt() * -60LL);
4816 QDateTime recendts = endts.addSecs( result.
value(26).toInt() * +60LL);
4818 if (recstartts >= recendts)
4821 recstartts = startts;
4832 result.
value(0).toString(),
4834 (sor) ? result.
value(1).toString() : QString(),
4836 (sor) ? result.
value(2).toString() : QString(),
4837 result.
value(3).toUInt(),
4838 result.
value(4).toUInt(),
4841 result.
value(6).toUInt(),
4842 result.
value(7).toString(),
4843 result.
value(8).toString(),
4844 result.
value(9).toString(),
4846 result.
value(10).toString(),
4847 result.
value(11).toString(),
4849 result.
value(12).toString(),
4850 result.
value(13).toString(),
4851 result.
value(14).toString(),
4853 result.
value(15).toInt(),
4856 recstartts, recendts,
4860 result.
value(20).toUInt(),
4866 result.
value(24).toUInt(),
4870 tmpList.push_back(
p);
4873 for (
auto &
tmp : tmpList)
4884 QString sortColumn =
"title";
4892 sortColumn =
"record.title";
4895 sortColumn =
"record.recpriority";
4898 sortColumn =
"record.last_record";
4906 sortColumn =
"record.next_record IS NULL, record.next_record";
4909 sortColumn =
"record.type";
4913 QString order =
"ASC";
4917 QString query = QString(
4918 "SELECT record.title, record.subtitle, "
4919 " record.description, record.season, "
4920 " record.episode, record.category, "
4921 " record.chanid, channel.channum, "
4922 " record.station, channel.name, "
4923 " record.recgroup, record.playgroup, "
4924 " record.seriesid, record.programid, "
4925 " record.inetref, record.recpriority, "
4926 " record.startdate, record.starttime, "
4927 " record.enddate, record.endtime, "
4928 " record.recordid, record.type, "
4929 " record.dupin, record.dupmethod, "
4931 " channel.commmethod "
4933 "LEFT JOIN channel ON channel.callsign = record.station "
4934 " AND deleted IS NULL "
4935 "GROUP BY recordid "
4938 query = query.arg(sortColumn, order);
4949 while (result.
next())
4952 QDateTime startts = QDateTime(result.
value(16).toDate(),
4953 result.
value(17).toTime(), Qt::UTC);
4954 QDateTime endts = QDateTime(result.
value(18).toDate(),
4955 result.
value(19).toTime(), Qt::UTC);
4957 if (!startts.isValid())
4960 if (!endts.isValid())
4964 result.
value(0).toString(), QString(),
4965 result.
value(1).toString(), QString(),
4966 result.
value(2).toString(), result.
value(3).toUInt(),
4967 result.
value(4).toUInt(), result.
value(5).toString(),
4969 result.
value(6).toUInt(), result.
value(7).toString(),
4970 result.
value(8).toString(), result.
value(9).toString(),
4972 result.
value(10).toString(), result.
value(11).toString(),
4974 result.
value(12).toString(), result.
value(13).toString(),
4975 result.
value(14).toString(),
4977 result.
value(15).toInt(),
4984 result.
value(20).toUInt(), rectype,
4988 result.
value(24).toUInt(),
5087 QString recording_dir;
5091 "LiveTV", cur, cur.addSecs(3600), cardid,
5096 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"FindNextLiveTVDir: next dir is '%1'")
5097 .arg(recording_dir));
5104 const QString &title,
5106 const QString &storagegroup,
5107 const QDateTime &recstartts,
5108 const QDateTime &recendts,
5110 QString &recording_dir,
5113 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
"FillRecordingDir: Starting");
5118 if (cnt++ % 20 == 0)
5119 LOG(VB_SCHEDULE, LOG_WARNING,
"Waiting for main server.");
5120 std::this_thread::sleep_for(50ms);
5127 QStringList recsCounted;
5128 std::list<FileSystemInfo *> fsInfoList;
5129 std::list<FileSystemInfo *>::iterator fslistit;
5131 recording_dir.clear();
5133 if (dirlist.size() == 1)
5135 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5136 QString(
"FillRecordingDir: The only directory in the %1 Storage "
5137 "Group is %2, so it will be used by default.")
5138 .arg(storagegroup, dirlist[0]));
5139 recording_dir = dirlist[0];
5140 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
"FillRecordingDir: Finished");
5145 int weightPerRecording =
5147 int weightPerPlayback =
5149 int weightPerCommFlag =
5151 int weightPerTranscode =
5154 QString storageScheduler =
5156 int localStartingWeight =
5158 (storageScheduler !=
"Combination") ? 0
5159 : (
int)(-1.99 * weightPerRecording));
5160 int remoteStartingWeight =
5162 std::chrono::seconds maxOverlap =
5167 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5168 "FillRecordingDir: Calculating initial FS Weights.");
5179 tmpWeight = localStartingWeight;
5180 msg +=
" is local (" + QString::number(tmpWeight) +
")";
5184 tmpWeight = remoteStartingWeight;
5185 msg +=
" is remote (+" + QString::number(tmpWeight) +
")";
5195 msg +=
", has SGweightPerDir offset of "
5196 + QString::number(tmpWeight) +
")";
5198 msg +=
". initial dir weight = " + QString::number(fs->
getWeight());
5199 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, msg);
5201 fsInfoList.push_back(fs);
5204 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5205 "FillRecordingDir: Adjusting FS Weights from inuseprograms.");
5208 saveRecDir.
prepare(
"UPDATE inuseprograms "
5209 "SET recdir = :RECDIR "
5210 "WHERE chanid = :CHANID AND "
5211 " starttime = :STARTTIME");
5214 "SELECT i.chanid, i.starttime, r.endtime, recusage, rechost, recdir "
5215 "FROM inuseprograms i, recorded r "
5216 "WHERE DATE_ADD(lastupdatetime, INTERVAL 16 MINUTE) > NOW() AND "
5217 " i.chanid = r.chanid AND "
5218 " i.starttime = r.starttime");
5226 while (query.
next())
5228 uint recChanid = query.
value(0).toUInt();
5231 QString recUsage( query.
value(3).toString());
5232 QString recHost( query.
value(4).toString());
5233 QString recDir( query.
value(5).toString());
5235 if (recDir.isEmpty())
5239 recDir = recDir.isEmpty() ?
"_UNKNOWN_" : recDir;
5241 saveRecDir.
bindValue(
":RECDIR", recDir);
5242 saveRecDir.
bindValue(
":CHANID", recChanid);
5243 saveRecDir.
bindValue(
":STARTTIME", recStart);
5244 if (!saveRecDir.
exec())
5247 if (recDir ==
"_UNKNOWN_")
5250 for (fslistit = fsInfoList.begin();
5251 fslistit != fsInfoList.end(); ++fslistit)
5257 int weightOffset = 0;
5261 if (recEnd > recstartts.addSecs(maxOverlap.count()))
5263 weightOffset += weightPerRecording;
5264 recsCounted << QString::number(recChanid) +
":" +
5269 weightOffset += weightPerPlayback;
5271 weightOffset += weightPerCommFlag;
5273 weightOffset += weightPerTranscode;
5277 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5278 QString(
" %1 @ %2 in use by '%3' on %4:%5, FSID "
5279 "#%6, FSID weightOffset +%7.")
5280 .arg(QString::number(recChanid),
5282 recUsage, recHost, recDir,
5284 QString::number(weightOffset)));
5292 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5293 QString(
" %1:%2 => old weight %3 plus "
5295 .arg(
fs2->getHostname(),
5297 .arg(
fs2->getWeight())
5299 .arg(
fs2->getWeight() + weightOffset));
5301 fs2->setWeight(
fs2->getWeight() + weightOffset);
5311 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5312 "FillRecordingDir: Adjusting FS Weights from scheduler.");
5314 for (
auto *thispg : reclist)
5316 if ((recendts < thispg->GetRecordingStartTime()) ||
5317 (recstartts > thispg->GetRecordingEndTime()) ||
5320 (thispg->GetInputID() == 0) ||
5321 (recsCounted.contains(QString(
"%1:%2").arg(thispg->GetChanID())
5323 (thispg->GetPathname().isEmpty()))
5326 for (fslistit = fsInfoList.begin();
5327 fslistit != fsInfoList.end(); ++fslistit)
5330 if ((fs->
getHostname() == thispg->GetHostname()) &&
5331 (fs->
getPath() == thispg->GetPathname()))
5333 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5334 QString(
"%1 @ %2 will record on %3:%4, FSID #%5, "
5335 "weightPerRecording +%6.")
5336 .arg(thispg->GetChanID())
5339 .arg(fs->
getFSysID()).arg(weightPerRecording));
5348 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5349 QString(
" %1:%2 => old weight %3 plus %4 = %5")
5350 .arg(
fs2->getHostname(),
fs2->getPath())
5351 .arg(
fs2->getWeight()).arg(weightPerRecording)
5352 .arg(
fs2->getWeight() + weightPerRecording));
5354 fs2->setWeight(
fs2->getWeight() + weightPerRecording);
5362 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5363 QString(
"Using '%1' Storage Scheduler directory sorting algorithm.")
5364 .arg(storageScheduler));
5366 if (storageScheduler ==
"BalancedFreeSpace")
5368 else if (storageScheduler ==
"BalancedPercFreeSpace")
5370 else if (storageScheduler ==
"BalancedDiskIO")
5377 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5378 "--- FillRecordingDir Sorted fsInfoList start ---");
5379 for (fslistit = fsInfoList.begin();fslistit != fsInfoList.end();
5383 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
"%1:%2")
5385 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
" Location : %1")
5386 .arg((fs->
isLocal()) ?
"local" :
"remote"));
5387 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
" weight : %1")
5389 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
" free space : %5")
5392 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5393 "--- FillRecordingDir Sorted fsInfoList end ---");
5402 long long maxSizeKB = (maxByterate + maxByterate/3) *
5403 recstartts.secsTo(recendts) / 1024;
5405 bool simulateAutoExpire =
5408 (fsInfoList.size() > 1));
5422 for (
unsigned int pass = 1; pass <= 3; pass++)
5424 bool foundDir =
false;
5426 if ((pass == 2) && simulateAutoExpire)
5429 QMap <int , long long> remainingSpaceKB;
5430 for (fslistit = fsInfoList.begin();
5431 fslistit != fsInfoList.end(); ++fslistit)
5433 remainingSpaceKB[(*fslistit)->getFSysID()] =
5434 (*fslistit)->getFreeSpace();
5441 for (
auto & expire : expiring)
5445 for (fslistit = fsInfoList.begin();
5446 fslistit != fsInfoList.end(); ++fslistit)
5449 if (expire->GetHostname() != (*fslistit)->getHostname())
5453 if (!dirlist.contains((*fslistit)->getPath()))
5457 (*fslistit)->getPath() +
"/" + expire->GetPathname();
5464 if (checkFile.exists())
5472 QString backuppath = expire->GetPathname();
5474 bool foundSlave =
false;
5476 for (
auto * enc : qAsConst(*
m_tvList))
5478 if (enc->GetHostName() ==
5481 enc->CheckFile(programinfo);
5499 LOG(VB_GENERAL, LOG_ERR,
5500 QString(
"Unable to match '%1' "
5501 "to any file system. Ignoring it.")
5502 .arg(expire->GetBasename()));
5508 expire->GetFilesize() / 1024;
5511 long long desiredSpaceKB =
5515 (desiredSpaceKB + maxSizeKB))
5517 recording_dir = fs->
getPath();
5520 LOG(VB_FILE, LOG_INFO,
5521 QString(
"pass 2: '%1' will record in '%2' "
5522 "although there is only %3 MB free and the "
5523 "AutoExpirer wants at least %4 MB. This "
5524 "directory has the highest priority files "
5525 "to be expired from the AutoExpire list and "
5526 "there are enough that the Expirer should "
5527 "be able to free up space for this recording.")
5528 .arg(title, recording_dir)
5530 .arg(desiredSpaceKB / 1024));
5541 for (fslistit = fsInfoList.begin();
5542 fslistit != fsInfoList.end(); ++fslistit)
5544 long long desiredSpaceKB = 0;
5551 (dirlist.contains(fs->
getPath())) &&
5555 recording_dir = fs->
getPath();
5560 LOG(VB_FILE, LOG_INFO,
5561 QString(
"pass 1: '%1' will record in "
5562 "'%2' which has %3 MB free. This recording "
5563 "could use a max of %4 MB and the "
5564 "AutoExpirer wants to keep %5 MB free.")
5565 .arg(title, recording_dir)
5567 .arg(maxSizeKB / 1024)
5568 .arg(desiredSpaceKB / 1024));
5572 LOG(VB_FILE, LOG_INFO,
5573 QString(
"pass %1: '%2' will record in "
5574 "'%3' although there is only %4 MB free and "
5575 "the AutoExpirer wants at least %5 MB. "
5576 "Something will have to be deleted or expired "
5577 "in order for this recording to complete "
5579 .arg(pass).arg(title, recording_dir)
5581 .arg(desiredSpaceKB / 1024));
5594 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
"FillRecordingDir: Finished");
5600 QList<FileSystemInfo> fsInfos;
5607 QMap <int, bool> fsMap;
5608 QList<FileSystemInfo>::iterator it1;
5609 for (it1 = fsInfos.begin(); it1 != fsInfos.end(); ++it1)
5611 fsMap[it1->getFSysID()] =
true;
5612 m_fsInfoCache[it1->getHostname() +
":" + it1->getPath()] = *it1;
5615 LOG(VB_FILE, LOG_INFO,
LOC +
5616 QString(
"FillDirectoryInfoCache: found %1 unique filesystems")
5617 .arg(fsMap.size()));
5624 auto secsleft = std::chrono::seconds(curtime.secsTo(
m_livetvTime));
5628 if (secsleft - prerollseconds > 120s)
5632 for (
auto * enc : qAsConst(*
m_tvList))
5648 if (
m_schedTime.secsTo(dummy->GetRecordingEndTime()) < 1800)
5649 dummy->SetRecordingEndTime(
m_schedTime.addSecs(1800));
5650 dummy->SetInputID(enc->GetInputID());
5651 dummy->m_mplexId = dummy->QueryMplexID();
5674 bool autoStart =
false;
5676 QDateTime startupTime = QDateTime();
5682 if (startupTime.isValid())
5685 startupSecs = std::max(startupSecs, 15 * 60s);
5692 LOG(VB_GENERAL, LOG_INFO,
5693 "Close to auto-start time, AUTO-Startup assumed");
5699 LOG(VB_GENERAL, LOG_INFO,
5700 "Close to MythFillDB suggested run time, AUTO-Startup to fetch guide data?");
5705 LOG(VB_GENERAL, LOG_DEBUG,
5706 "NOT close to auto-start time, USER-initiated startup assumed");