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 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
2341 tokens = request[0].split(
' ', QString::SkipEmptyParts);
2343 tokens = request[0].split(
' ', Qt::SkipEmptyParts);
2347 if (request.empty() || tokens.empty())
2349 LOG(VB_GENERAL, LOG_ERR,
"Empty Reschedule request received");
2353 LOG(VB_GENERAL, LOG_INFO, QString(
"Reschedule requested for %1")
2354 .arg(request.join(
" | ")));
2356 if (tokens[0] ==
"MATCH")
2358 if (tokens.size() < 5)
2360 LOG(VB_GENERAL, LOG_ERR,
2361 QString(
"Invalid RescheduleMatch request received (%1)")
2366 uint recordid = tokens[1].toUInt();
2367 uint sourceid = tokens[2].toUInt();
2368 uint mplexid = tokens[3].toUInt();
2370 deleteFuture =
true;
2378 else if (tokens[0] ==
"CHECK")
2380 if (tokens.size() < 4 || request.size() < 5)
2382 LOG(VB_GENERAL, LOG_ERR,
2383 QString(
"Invalid RescheduleCheck request received (%1)")
2388 uint recordid = tokens[2].toUInt();
2389 uint findid = tokens[3].toUInt();
2390 QString title = request[1];
2391 QString subtitle = request[2];
2392 QString descrip = request[3];
2393 QString programid = request[4];
2402 else if (tokens[0] !=
"PLACE")
2404 LOG(VB_GENERAL, LOG_ERR,
2405 QString(
"Unknown Reschedule request received (%1)")
2415 query.
prepare(
"DELETE oldrecorded FROM oldrecorded "
2416 "LEFT JOIN recordmatch ON "
2417 " recordmatch.chanid = oldrecorded.chanid AND "
2418 " recordmatch.starttime = oldrecorded.starttime "
2419 "WHERE oldrecorded.future > 0 AND "
2420 " recordmatch.recordid IS NULL");
2425 auto fillend = nowAsDuration<std::chrono::microseconds>();
2426 auto matchTime = fillend - fillstart;
2428 LOG(VB_SCHEDULE, LOG_INFO,
"CreateTempTables...");
2431 fillstart = nowAsDuration<std::chrono::microseconds>();
2434 LOG(VB_SCHEDULE, LOG_INFO,
"UpdateDuplicates...");
2437 fillend = nowAsDuration<std::chrono::microseconds>();
2438 auto checkTime = fillend - fillstart;
2440 fillstart = nowAsDuration<std::chrono::microseconds>();
2442 fillend = nowAsDuration<std::chrono::microseconds>();
2443 auto placeTime = fillend - fillstart;
2445 LOG(VB_SCHEDULE, LOG_INFO,
"DeleteTempTables...");
2455 LOG(VB_GENERAL, LOG_INFO,
"Reschedule interrupted, will retry");
2460 msg = QString(
"Scheduled %1 items in %2 "
2461 "= %3 match + %4 check + %5 place")
2463 .arg(duration_cast<floatsecs>(matchTime + checkTime + placeTime).count(), 0,
'f', 1)
2464 .arg(duration_cast<floatsecs>(matchTime).count(), 0,
'f', 2)
2465 .arg(duration_cast<floatsecs>(checkTime).count(), 0,
'f', 2)
2466 .arg(duration_cast<floatsecs>(placeTime).count(), 0,
'f', 2);
2467 LOG(VB_GENERAL, LOG_INFO, msg);
2472 if (
p->GetRecordingStatus() !=
p->m_oldrecstatus)
2475 p->AddHistory(
false,
false,
false);
2479 p->AddHistory(
false,
false,
false);
2481 p->AddHistory(
false,
false,
true);
2483 else if (
p->m_future)
2489 p->m_future =
false;
2498 std::chrono::seconds prerollseconds,
2501 bool blockShutdown =
true;
2506 QString startupParam =
"user";
2510 for ( ; firstRunIter !=
m_recList.end(); ++firstRunIter)
2521 ((std::chrono::seconds(curtime.secsTo((*firstRunIter)->GetRecordingStartTime())) -
2524 LOG(VB_GENERAL, LOG_INFO,
LOC +
"AUTO-Startup assumed");
2525 startupParam =
"auto";
2529 blockShutdown =
false;
2533 LOG(VB_GENERAL, LOG_INFO,
LOC +
"Seem to be woken up by USER");
2545 return blockShutdown;
2551 static constexpr std::array<const std::chrono::seconds,4> kSysEventSecs = { 120s, 90s, 60s, 30s };
2555 auto secsleft = std::chrono::seconds(curtime.secsTo(nextrectime));
2565 bool pendingEventSent =
false;
2566 for (
size_t i = 0; i < kSysEventSecs.size(); i++)
2568 auto pending_secs = std::max((secsleft - prerollseconds), 0s);
2569 if ((pending_secs <= kSysEventSecs[i]) &&
2572 if (!pendingEventSent)
2575 QString(
"REC_PENDING SECS %1").arg(pending_secs.count()), &ri);
2579 pendingEventSent =
true;
2585 for (
size_t i = 0; i < kSysEventSecs.size(); i++)
2593 keys.insert(rec->MakeUniqueKey());
2594 keys.insert(
"something");
2597 QSet<QString>::iterator sit =
m_sysEvents[i].begin();
2600 if (!keys.contains(*sit))
2611 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
2612 QString(
"Slave Backend %1 is being awakened to record: %2")
2619 ((secsleft - prerollseconds) < 210s) &&
2623 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
2624 QString(
"Slave Backend %1 not available yet, "
2625 "trying to wake it up again.")
2632 ((secsleft - prerollseconds) < 150s) &&
2635 LOG(VB_GENERAL, LOG_WARNING,
LOC +
2636 QString(
"Slave Backend %1 has NOT come "
2637 "back from sleep yet in 150 seconds. Setting "
2638 "slave status to unknown and attempting "
2639 "to reschedule around its tuners.")
2642 for (
auto * enc : qAsConst(*
m_tvList))
2654 QDateTime &nextStartTime, QDateTime &nextWakeTime,
2655 std::chrono::seconds prerollseconds)
2662 std::chrono::seconds origprerollseconds = prerollseconds;
2669 auto nextwake = std::chrono::seconds(nextWakeTime.secsTo(nextrectime));
2670 if (nextwake - prerollseconds > 5min)
2672 nextStartTime = std::min(nextStartTime, nextrectime);
2676 if (curtime < nextrectime)
2677 nextWakeTime = std::min(nextWakeTime, nextrectime);
2683 auto secsleft = std::chrono::seconds(curtime.secsTo(nextrectime));
2688 if (secsleft - prerollseconds > 1min)
2690 nextStartTime = std::min(nextStartTime, nextrectime.addSecs(-30));
2691 nextWakeTime = std::min(nextWakeTime,
2692 nextrectime.addSecs(-prerollseconds.count() - 60));
2705 if (secsleft - prerollseconds > 35s)
2707 nextStartTime = std::min(nextStartTime, nextrectime.addSecs(-30));
2708 nextWakeTime = std::min(nextWakeTime,
2709 nextrectime.addSecs(-prerollseconds.count() - 35));
2718 QString msg = QString(
"Invalid cardid [%1] for %2")
2720 LOG(VB_GENERAL, LOG_ERR,
LOC + msg);
2724 statuschanged =
true;
2732 QString msg = QString(
"SUPPRESSED recording \"%1\" on channel: "
2733 "%2 on cardid: [%3], sourceid %4. Tuner "
2734 "is locked by an external application.")
2739 LOG(VB_GENERAL, LOG_NOTICE, msg);
2743 statuschanged =
true;
2754 if (prerollseconds > 0s)
2762 if (isBusyRecording)
2765 nextWakeTime = std::min(nextWakeTime, curtime.addSecs(5));
2766 prerollseconds = 0s;
2770 if (secsleft - prerollseconds > 30s)
2772 nextStartTime = std::min(nextStartTime, nextrectime.addSecs(-30));
2773 nextWakeTime = std::min(nextWakeTime,
2774 nextrectime.addSecs(-prerollseconds.count() - 30));
2782 LOG(VB_SCHEDULE, LOG_WARNING,
2783 QString(
"WARNING: Slave Backend %1 has NOT come "
2784 "back from sleep yet. Recording can "
2785 "not begin yet for: %2")
2791 LOG(VB_SCHEDULE, LOG_WARNING,
2792 QString(
"WARNING: Slave Backend %1 has NOT come "
2793 "back from sleep yet. Setting slave "
2794 "status to unknown and attempting "
2795 "to reschedule around its tuners.")
2798 for (
auto * enc : qAsConst(*
m_tvList))
2807 nextStartTime = std::min(nextStartTime, nextrectime);
2808 nextWakeTime = std::min(nextWakeTime, curtime.addSecs(1));
2815 QString recording_dir;
2834 MythEvent me(QString(
"ADD_CHILD_INPUT %1")
2837 nextWakeTime = std::min(nextWakeTime, curtime.addSecs(1));
2847 nexttv->
RecordPending(&tempri, std::max(secsleft, 0s),
false);
2853 if (secsleft - prerollseconds > 0s)
2855 nextStartTime = std::min(nextStartTime, nextrectime);
2856 nextWakeTime = std::min(nextWakeTime,
2857 nextrectime.addSecs(-prerollseconds.count()));
2862 recstartts = QDateTime(
2864 QTime(recstartts.time().hour(), recstartts.time().minute()), Qt::UTC);
2868 QString details = QString(
"%1: channel %2 on cardid [%3], sourceid %4")
2895 statuschanged =
true;
2908 bool doSchedAfterStart =
2917 QString(
"Started recording") :
2919 QString(
"Tuning recording") :
2920 QString(
"Canceled recording (%1)")
2923 LOG(VB_GENERAL, LOG_INFO, QString(
"%1: %2").arg(msg, details));
2931 MythEvent me(QString(
"FORCE_DELETE_RECORDING %1 %2")
2939 std::chrono::seconds prerollseconds)
2944 LOG(VB_SCHEDULE, LOG_DEBUG,
2945 QString(
"Assigning input for %1/%2/\"%3\"")
2956 for (
uint i = 0; !bestid && i < inputs.size(); ++i)
2958 uint inputid = inputs[i];
2966 auto recstarttime = std::chrono::seconds(now.secsTo(
p->GetRecordingStartTime()));
2967 if (recstarttime > prerollseconds + 60s)
2969 if (
p->GetInputID() != inputid)
2986 LOG(VB_SCHEDULE, LOG_DEBUG,
2987 QString(
"Input %1 has a pending recording").arg(inputid));
2996 LOG(VB_SCHEDULE, LOG_DEBUG,
2997 QString(
"Input %1 is recording").arg(inputid));
3002 LOG(VB_SCHEDULE, LOG_DEBUG,
3003 QString(
"Input %1 is recording but will be free")
3012 LOG(VB_SCHEDULE, LOG_DEBUG,
3013 QString(
"Input %1 is recording but has to stop")
3019 LOG(VB_SCHEDULE, LOG_DEBUG,
3020 QString(
"Input %1 is recording but could be free")
3032 bool isbusy = rctv->
IsBusy(&busy_info, -1s);
3038 LOG(VB_SCHEDULE, LOG_DEBUG,
3039 QString(
"Input %1 is free").arg(inputid));
3045 LOG(VB_SCHEDULE, LOG_DEBUG,
3046 QString(
"Input %1 is on livetv but has to stop")
3057 LOG(VB_SCHEDULE, LOG_INFO,
3058 QString(
"Assigned input %1 for %2/%3/\"%4\"")
3066 LOG(VB_SCHEDULE, LOG_WARNING,
3067 QString(
"Failed to assign input for %1/%2/\"%3\"")
3073 return bestid != 0U;
3083 bool &blockShutdown, QDateTime &idleSince,
3084 std::chrono::seconds prerollseconds,
3090 uint logmask = VB_IDLE;
3091 int now = QTime::currentTime().msecsSinceStartOfDay();
3092 int tm = std::chrono::milliseconds(now) / 15min;
3095 logmask = VB_GENERAL;
3114 LOG(VB_GENERAL, LOG_NOTICE,
"Client is connected, removing startup block on shutdown");
3115 blockShutdown =
false;
3126 bool recording =
false;
3129 QMap<int, EncoderLink *>::const_iterator it;
3133 if ((*it)->IsBusy())
3147 if (!blocking && !recording && !activeJobs && !delay)
3154 if (idleSince.isValid())
3156 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN -1"));
3159 idleSince = QDateTime();
3164 if (statuschanged || !idleSince.isValid())
3166 bool wasValid = idleSince.isValid();
3168 idleSince = curtime;
3171 for ( ; idleIter !=
m_recList.end(); ++idleIter)
3173 if ((*idleIter)->GetRecordingStatus() ==
3175 (*idleIter)->GetRecordingStatus() ==
3182 auto recstarttime = std::chrono::seconds(curtime.secsTo((*idleIter)->GetRecordingStartTime()));
3185 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3186 "a recording is due to "
3188 idleSince = QDateTime();
3199 if (guideRunTime.isValid() &&
3203 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3204 "mythfilldatabase is due to "
3206 idleSince = QDateTime();
3211 if (idleSince.isValid())
3214 if (wasValid && !idleSince.isValid())
3216 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN -1"));
3221 if (idleSince.isValid())
3233 LOG(VB_GENERAL, LOG_WARNING,
3234 "Waited more than 60"
3235 " seconds for shutdown to complete"
3236 " - resetting idle time");
3237 idleSince = QDateTime();
3243 blockShutdown, logmask))
3249 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN -1"));
3255 auto itime = std::chrono::seconds(idleSince.secsTo(curtime));
3259 msg = QString(
"I\'m idle now... shutdown will "
3260 "occur in %1 seconds.")
3262 LOG(VB_GENERAL, LOG_NOTICE, msg);
3263 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN %1")
3270 msg = QString(
"%1 secs left to system shutdown!").arg(remain);
3271 LOG(logmask, LOG_NOTICE, msg);
3272 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN %1").arg(remain));
3281 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3282 "of an active encoder");
3284 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3285 "of a connected client");
3288 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3292 LOG(logmask, LOG_NOTICE,
"Blocking shutdown because "
3293 "of delay request from external application");
3296 if (idleSince.isValid())
3298 MythEvent me(QString(
"SHUTDOWN_COUNTDOWN -1"));
3301 idleSince = QDateTime();
3308 QDateTime &idleSince,
3309 bool &blockShutdown,
uint logmask)
3311 bool retval =
false;
3321 LOG(logmask, LOG_INFO,
3322 "CheckShutdownServer returned - OK to shutdown");
3326 LOG(logmask, LOG_NOTICE,
3327 "CheckShutdownServer returned - Not OK to shutdown");
3329 idleSince = QDateTime();
3332 LOG(logmask, LOG_NOTICE,
3333 "CheckShutdownServer returned - Not OK to shutdown, "
3341 idleSince = QDateTime();
3346 m_noAutoShutdown =
true;
3350 LOG(VB_GENERAL, LOG_NOTICE,
3351 "CheckShutdownServer returned - Not OK");
3354 LOG(VB_GENERAL, LOG_NOTICE, QString(
3355 "CheckShutdownServer returned - Error %1").arg(state));
3366 QDateTime &idleSince)
3371 for ( ; recIter !=
m_recList.end(); ++recIter)
3379 QDateTime restarttime;
3384 .addSecs(-prerollseconds.count());
3392 && guideRefreshTime.isValid()
3394 && (restarttime.isNull() || guideRefreshTime < restarttime))
3395 restarttime = guideRefreshTime;
3397 if (restarttime.isValid())
3401 restarttime = restarttime.addSecs((-1LL) * add);
3404 "hh:mm yyyy-MM-dd");
3406 "echo \'Wakeuptime would "
3407 "be $time if command "
3410 if (setwakeup_cmd.isEmpty())
3412 LOG(VB_GENERAL, LOG_NOTICE,
3413 "SetWakeuptimeCommand is empty, shutdown aborted");
3414 idleSince = QDateTime();
3418 if (wakeup_timeformat ==
"time_t")
3421 setwakeup_cmd.replace(
"$time",
3422 time_ts.setNum(restarttime.toSecsSinceEpoch())
3426 setwakeup_cmd.replace(
3427 "$time", restarttime.toLocalTime().toString(wakeup_timeformat));
3429 LOG(VB_GENERAL, LOG_NOTICE,
3430 QString(
"Running the command to set the next "
3431 "scheduled wakeup time :-\n\t\t\t\t") + setwakeup_cmd);
3436 LOG(VB_GENERAL, LOG_ERR,
3437 "SetWakeuptimeCommand failed, shutdown aborted");
3438 idleSince = QDateTime();
3453 "sudo /sbin/halt -p");
3455 if (!halt_cmd.isEmpty())
3460 LOG(VB_GENERAL, LOG_NOTICE,
3461 QString(
"Running the command to shutdown "
3462 "this computer :-\n\t\t\t\t") + halt_cmd);
3469 LOG(VB_GENERAL, LOG_ERR,
"ServerHaltCommand failed, shutdown aborted");
3474 idleSince = QDateTime();
3480 std::chrono::seconds prerollseconds = 0s;
3481 std::chrono::seconds secsleft = 0s;
3485 bool someSlavesCanSleep =
false;
3486 for (
auto * enc : qAsConst(*
m_tvList))
3488 if (enc->CanSleep())
3489 someSlavesCanSleep =
true;
3492 if (!someSlavesCanSleep)
3495 LOG(VB_SCHEDULE, LOG_INFO,
3496 "Scheduler, Checking for slaves that can be shut down");
3498 auto sleepThreshold =
3501 LOG(VB_SCHEDULE, LOG_DEBUG,
3502 QString(
" Getting list of slaves that will be active in the "
3503 "next %1 minutes.") .arg(duration_cast<std::chrono::minutes>(sleepThreshold).count()));
3505 LOG(VB_SCHEDULE, LOG_DEBUG,
"Checking scheduler's reclist");
3507 QStringList SlavesInUse;
3517 auto recstarttime = std::chrono::seconds(curtime.secsTo(pginfo->GetRecordingStartTime()));
3518 secsleft = recstarttime - prerollseconds;
3519 if (secsleft > sleepThreshold)
3524 EncoderLink *enc = (*m_tvList)[pginfo->GetInputID()];
3531 LOG(VB_SCHEDULE, LOG_DEBUG,
3532 QString(
" Slave %1 will be in use in %2 minutes")
3534 .arg(duration_cast<std::chrono::minutes>(secsleft).count()));
3538 LOG(VB_SCHEDULE, LOG_DEBUG,
3539 QString(
" Slave %1 is in use currently "
3548 LOG(VB_SCHEDULE, LOG_DEBUG,
" Checking inuseprograms table:");
3551 query.
prepare(
"SELECT DISTINCT hostname, recusage FROM inuseprograms "
3552 "WHERE lastupdatetime > :ONEHOURAGO ;");
3553 query.
bindValue(
":ONEHOURAGO", oneHourAgo);
3556 while(query.
next()) {
3557 SlavesInUse << query.
value(0).toString();
3558 LOG(VB_SCHEDULE, LOG_DEBUG,
3559 QString(
" Slave %1 is marked as in use by a %2")
3560 .arg(query.
value(0).toString(),
3561 query.
value(1).toString()));
3565 LOG(VB_SCHEDULE, LOG_DEBUG, QString(
" Shutting down slaves which will "
3566 "be inactive for the next %1 minutes and can be put to sleep.")
3567 .arg(sleepThreshold.count() / 60));
3569 for (
auto * enc : qAsConst(*
m_tvList))
3571 if ((!enc->IsLocal()) &&
3573 (!SlavesInUse.contains(enc->GetHostName())) &&
3574 (!enc->IsFallingAsleep()))
3576 QString sleepCommand =
3578 enc->GetHostName());
3579 QString wakeUpCommand =
3581 enc->GetHostName());
3583 if (!sleepCommand.isEmpty() && !wakeUpCommand.isEmpty())
3585 QString thisHost = enc->GetHostName();
3587 LOG(VB_SCHEDULE, LOG_DEBUG,
3588 QString(
" Commanding %1 to go to sleep.")
3591 if (enc->GoToSleep())
3593 for (
auto * slv : qAsConst(*
m_tvList))
3595 if (slv->GetHostName() == thisHost)
3597 LOG(VB_SCHEDULE, LOG_DEBUG,
3598 QString(
" Marking card %1 on slave %2 "
3599 "as falling asleep.")
3600 .arg(slv->GetInputID())
3601 .arg(slv->GetHostName()));
3608 LOG(VB_GENERAL, LOG_ERR,
LOC +
3609 QString(
"Unable to shutdown %1 slave backend, setting "
3610 "sleep status to undefined.").arg(thisHost));
3611 for (
auto * slv : qAsConst(*
m_tvList))
3613 if (slv->GetHostName() == thisHost)
3626 LOG(VB_GENERAL, LOG_NOTICE,
3627 QString(
"Tried to Wake Up %1, but this is the "
3628 "master backend and it is not asleep.")
3629 .arg(slaveHostname));
3636 if (wakeUpCommand.isEmpty()) {
3637 LOG(VB_GENERAL, LOG_NOTICE,
3638 QString(
"Trying to Wake Up %1, but this slave "
3639 "does not have a WakeUpCommand set.").arg(slaveHostname));
3641 for (
auto * enc : qAsConst(*
m_tvList))
3643 if (enc->GetHostName() == slaveHostname)
3651 for (
auto * enc : qAsConst(*
m_tvList))
3653 if (setWakingStatus && (enc->GetHostName() == slaveHostname))
3655 enc->SetLastWakeTime(curtime);
3660 LOG(VB_SCHEDULE, LOG_NOTICE, QString(
"Executing '%1' to wake up slave.")
3661 .arg(wakeUpCommand));
3673 QStringList SlavesThatCanWake;
3675 for (
auto * enc : qAsConst(*
m_tvList))
3680 thisSlave = enc->GetHostName();
3684 (!SlavesThatCanWake.contains(thisSlave)))
3685 SlavesThatCanWake << thisSlave;
3689 for (; slave < SlavesThatCanWake.count(); slave++)
3691 thisSlave = SlavesThatCanWake[slave];
3692 LOG(VB_SCHEDULE, LOG_NOTICE,
3693 QString(
"Scheduler, Sending wakeup command to slave: %1")
3703 query.
prepare(QString(
"SELECT type,title,subtitle,description,"
3704 "station,startdate,starttime,"
3705 "enddate,endtime,season,episode,inetref,last_record "
3708 if (!query.
exec() || query.
size() != 1)
3718 QString title = query.
value(1).toString();
3719 QString subtitle = query.
value(2).toString();
3720 QString description = query.
value(3).toString();
3721 QString station = query.
value(4).toString();
3722 QDateTime startdt = QDateTime(query.
value(5).toDate(),
3723 query.
value(6).toTime(), Qt::UTC);
3724 int duration = startdt.secsTo(
3725 QDateTime(query.
value(7).toDate(),
3726 query.
value(8).toTime(), Qt::UTC));
3728 int season = query.
value(9).toInt();
3729 int episode = query.
value(10).toInt();
3730 QString inetref = query.
value(11).toString();
3734 QDate originalairdate = QDate(query.
value(12).toDate());
3736 if (description.isEmpty())
3737 description = startdt.toLocalTime().toString();
3739 query.
prepare(
"SELECT chanid from channel "
3740 "WHERE deleted IS NULL AND callsign = :STATION");
3748 std::vector<unsigned int> chanidlist;
3749 while (query.
next())
3750 chanidlist.push_back(query.
value(0).toUInt());
3754 bool weekday =
false;
3756 QDateTime lstartdt = startdt.toLocalTime();
3771 weekday = (lstartdt.date().dayOfWeek() < 6);
3772 daysoff = lstartdt.date().daysTo(
3774 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3775 lstartdt.time(), Qt::LocalTime).toUTC();
3781 daysoff = lstartdt.date().daysTo(
3783 daysoff = (daysoff + 6) / 7 * 7;
3784 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3785 lstartdt.time(), Qt::LocalTime).toUTC();
3788 LOG(VB_GENERAL, LOG_ERR,
3789 QString(
"Invalid rectype for manual recordid %1").arg(recordid));
3795 for (
uint id : chanidlist)
3797 if (weekday && startdt.toLocalTime().date().dayOfWeek() >= 6)
3800 query.
prepare(
"REPLACE INTO program (chanid, starttime, endtime,"
3801 " title, subtitle, description, manualid,"
3802 " season, episode, inetref, originalairdate, generic) "
3803 "VALUES (:CHANID, :STARTTIME, :ENDTIME, :TITLE,"
3804 " :SUBTITLE, :DESCRIPTION, :RECORDID, "
3805 " :SEASON, :EPISODE, :INETREF, :ORIGINALAIRDATE, 1)");
3808 query.
bindValue(
":ENDTIME", startdt.addSecs(duration));
3811 query.
bindValue(
":DESCRIPTION", description);
3815 query.
bindValue(
":ORIGINALAIRDATE", originalairdate);
3824 daysoff += skipdays;
3825 startdt = QDateTime(lstartdt.date().addDays(daysoff),
3826 lstartdt.time(), Qt::LocalTime).toUTC();
3838 query = QString(
"SELECT recordid,search,subtitle,description "
3839 "FROM %1 WHERE search <> %2 AND "
3840 "(recordid = %3 OR %4 = 0) ")
3852 while (result.
next())
3854 QString
prefix = QString(
":NR%1").arg(count);
3855 qphrase = result.
value(3).toString();
3861 LOG(VB_GENERAL, LOG_ERR,
3862 QString(
"Invalid search key in recordid %1")
3863 .arg(result.
value(0).toString()));
3867 QString bindrecid =
prefix +
"RECID";
3868 QString bindphrase =
prefix +
"PHRASE";
3869 QString bindlikephrase1 =
prefix +
"LIKEPHRASE1";
3870 QString bindlikephrase2 =
prefix +
"LIKEPHRASE2";
3871 QString bindlikephrase3 =
prefix +
"LIKEPHRASE3";
3873 bindings[bindrecid] = result.
value(0).toString();
3879 qphrase.remove(
';');
3880 from << result.
value(2).toString();
3881 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
3882 QString(
" AND program.manualid = 0 AND ( %2 )")
3886 bindings[bindlikephrase1] = QString(
"%") + qphrase +
"%";
3888 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
" AND "
3889 "program.manualid = 0 AND "
3890 "program.title LIKE " + bindlikephrase1);
3893 bindings[bindlikephrase1] = QString(
"%") + qphrase +
"%";
3894 bindings[bindlikephrase2] = QString(
"%") + qphrase +
"%";
3895 bindings[bindlikephrase3] = QString(
"%") + qphrase +
"%";
3897 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
3898 " AND program.manualid = 0"
3899 " AND (program.title LIKE " + bindlikephrase1 +
3900 " OR program.subtitle LIKE " + bindlikephrase2 +
3901 " OR program.description LIKE " + bindlikephrase3 +
")");
3904 bindings[bindphrase] = qphrase;
3905 from <<
", people, credits";
3906 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
" AND "
3907 "program.manualid = 0 AND "
3908 "people.name LIKE " + bindphrase +
" AND "
3909 "credits.person = people.person AND "
3910 "program.chanid = credits.chanid AND "
3911 "program.starttime = credits.starttime");
3916 where << (QString(
"%1.recordid = ").arg(
m_recordTable) + bindrecid +
3918 QString(
"program.manualid = %1.recordid ")
3922 LOG(VB_GENERAL, LOG_ERR,
3923 QString(
"Unknown RecSearchType (%1) for recordid %2")
3924 .arg(result.
value(1).toInt())
3925 .arg(result.
value(0).toString()));
3926 bindings.remove(bindrecid);
3933 if (recordid == 0 || from.count() == 0)
3935 QString recidmatch =
"";
3937 recidmatch =
"RECTABLE.recordid = :NRRECORDID AND ";
3938 QString s1 = recidmatch +
3939 "RECTABLE.type <> :NRTEMPLATE AND "
3940 "RECTABLE.search = :NRST AND "
3941 "program.manualid = 0 AND "
3942 "program.title = RECTABLE.title ";
3944 QString s2 = recidmatch +
3945 "RECTABLE.type <> :NRTEMPLATE AND "
3946 "RECTABLE.search = :NRST AND "
3947 "program.manualid = 0 AND "
3948 "program.seriesid <> '' AND "
3949 "program.seriesid = RECTABLE.seriesid ";
3959 bindings[
":NRRECORDID"] = recordid;
3965 " WHEN RECTABLE.type IN (%1, %2, %3) THEN 0 "
3966 " WHEN RECTABLE.type IN (%4, %5, %6) THEN -1 "
3967 " ELSE (program.generic - 1) "
3973 "(CASE RECTABLE.type "
3975 " THEN RECTABLE.findid "
3977 " THEN to_days(date_sub(convert_tz(program.starttime, 'UTC', 'SYSTEM'), "
3978 " interval time_format(RECTABLE.findtime, '%H:%i') hour_minute)) "
3980 " THEN floor((to_days(date_sub(convert_tz(program.starttime, 'UTC', "
3981 " 'SYSTEM'), interval time_format(RECTABLE.findtime, '%H:%i') "
3982 " hour_minute)) - RECTABLE.findday)/7) * 7 + RECTABLE.findday "
3984 " THEN RECTABLE.findid "
3993 const QDateTime &maxstarttime)
3997 QString deleteClause;
3998 QString filterClause = QString(
" AND program.endtime > "
3999 "(NOW() - INTERVAL 480 MINUTE)");
4003 deleteClause +=
" AND recordmatch.recordid = :RECORDID";
4004 bindings[
":RECORDID"] = recordid;
4008 deleteClause +=
" AND channel.sourceid = :SOURCEID";
4009 filterClause +=
" AND channel.sourceid = :SOURCEID";
4010 bindings[
":SOURCEID"] = sourceid;
4014 deleteClause +=
" AND channel.mplexid = :MPLEXID";
4015 filterClause +=
" AND channel.mplexid = :MPLEXID";
4016 bindings[
":MPLEXID"] = mplexid;
4018 if (maxstarttime.isValid())
4020 deleteClause +=
" AND recordmatch.starttime <= :MAXSTARTTIME";
4021 filterClause +=
" AND program.starttime <= :MAXSTARTTIME";
4022 bindings[
":MAXSTARTTIME"] = maxstarttime;
4025 query.
prepare(QString(
"DELETE recordmatch FROM recordmatch, channel "
4026 "WHERE recordmatch.chanid = channel.chanid")
4028 MSqlBindings::const_iterator it;
4029 for (it = bindings.cbegin(); it != bindings.cend(); ++it)
4037 bindings.remove(
":RECORDID");
4039 query.
prepare(
"SELECT filterid, clause FROM recordfilter "
4040 "WHERE filterid >= 0 AND filterid < :NUMFILTERS AND "
4041 " TRIM(clause) <> ''");
4048 while (query.
next())
4050 filterClause += QString(
" AND (((RECTABLE.filter & %1) = 0) OR (%2))")
4051 .arg(1 << query.
value(0).toInt()).arg(query.
value(1).toString());
4055 query.
prepare(
"SELECT NULL from record "
4056 "WHERE type = :FINDONE AND findid <= 0;");
4065 QDate epoch(1970, 1, 1);
4068 query.
prepare(
"UPDATE record set findid = :FINDID "
4069 "WHERE type = :FINDONE AND findid <= 0;");
4076 QStringList fromclauses;
4077 QStringList whereclauses;
4083 for (
int clause = 0; clause < fromclauses.count(); ++clause)
4085 LOG(VB_SCHEDULE, LOG_INFO, QString(
"Query %1: %2/%3")
4086 .arg(QString::number(clause), fromclauses[clause],
4087 whereclauses[clause]));
4091 for (
int clause = 0; clause < fromclauses.count(); ++clause)
4093 QString query2 = QString(
4094 "REPLACE INTO recordmatch (recordid, chanid, starttime, manualid, "
4095 " oldrecduplicate, findid) "
4096 "SELECT RECTABLE.recordid, program.chanid, program.starttime, "
4097 " IF(search = %1, RECTABLE.recordid, 0), ").arg(
kManualSearch) +
4099 "FROM (RECTABLE, program INNER JOIN channel "
4100 " ON channel.chanid = program.chanid) ") + fromclauses[clause] + QString(
4101 " WHERE ") + whereclauses[clause] +
4102 QString(
" AND channel.deleted IS NULL "
4103 " AND channel.visible > 0 ") +
4104 filterClause + QString(
" AND "
4107 " (RECTABLE.type = %1 "
4108 " OR RECTABLE.type = %2 "
4109 " OR RECTABLE.type = %3 "
4110 " OR RECTABLE.type = %4) "
4112 " ((RECTABLE.type = %6 "
4113 " OR RECTABLE.type = %7 "
4114 " OR RECTABLE.type = %8)"
4116 " ADDTIME(RECTABLE.startdate, RECTABLE.starttime) = program.starttime "
4118 " RECTABLE.station = channel.callsign) "
4130 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- Start DB Query %1...")
4133 auto dbstart = nowAsDuration<std::chrono::microseconds>();
4137 for (it = bindings.cbegin(); it != bindings.cend(); ++it)
4139 if (query2.contains(it.key()))
4143 bool ok = result.
exec();
4144 auto dbend = nowAsDuration<std::chrono::microseconds>();
4145 auto dbTime = dbend - dbstart;
4153 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- %1 results in %2 sec.")
4155 .arg(duration_cast<std::chrono::seconds>(dbTime).count()));
4159 LOG(VB_SCHEDULE, LOG_INFO,
" +-- Done.");
4168 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_record;");
4174 result.
prepare(
"CREATE TEMPORARY TABLE sched_temp_record "
4181 result.
prepare(
"INSERT sched_temp_record SELECT * from record;");
4189 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_recorded;");
4195 result.
prepare(
"CREATE TEMPORARY TABLE sched_temp_recorded "
4202 result.
prepare(
"INSERT sched_temp_recorded SELECT * from recorded;");
4216 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_record;");
4221 result.
prepare(
"DROP TABLE IF EXISTS sched_temp_recorded;");
4229 if (schedTmpRecord ==
"record")
4230 schedTmpRecord =
"sched_temp_record";
4232 QString rmquery = QString(
4233 "UPDATE recordmatch "
4234 " INNER JOIN RECTABLE ON (recordmatch.recordid = RECTABLE.recordid) "
4235 " INNER JOIN program p ON (recordmatch.chanid = p.chanid AND "
4236 " recordmatch.starttime = p.starttime AND "
4237 " recordmatch.manualid = p.manualid) "
4238 " LEFT JOIN oldrecorded ON "
4240 " RECTABLE.dupmethod > 1 AND "
4241 " oldrecorded.duplicate <> 0 AND "
4242 " p.title = oldrecorded.title AND "
4246 " (p.programid <> '' "
4247 " AND p.programid = oldrecorded.programid) "
4251 " (p.programid = '' OR oldrecorded.programid = '' OR "
4252 " LEFT(p.programid, LOCATE('/', p.programid)) <> "
4253 " LEFT(oldrecorded.programid, LOCATE('/', oldrecorded.programid))) " :
4254 " (p.programid = '' OR oldrecorded.programid = '') " )
4257 " (((RECTABLE.dupmethod & 0x02) = 0) OR (p.subtitle <> '' "
4258 " AND p.subtitle = oldrecorded.subtitle)) "
4260 " (((RECTABLE.dupmethod & 0x04) = 0) OR (p.description <> '' "
4261 " AND p.description = oldrecorded.description)) "
4263 " (((RECTABLE.dupmethod & 0x08) = 0) OR "
4264 " (p.subtitle <> '' AND "
4265 " (p.subtitle = oldrecorded.subtitle OR "
4266 " (oldrecorded.subtitle = '' AND "
4267 " p.subtitle = oldrecorded.description))) OR "
4268 " (p.subtitle = '' AND p.description <> '' AND "
4269 " (p.description = oldrecorded.subtitle OR "
4270 " (oldrecorded.subtitle = '' AND "
4271 " p.description = oldrecorded.description)))) "
4275 " LEFT JOIN sched_temp_recorded recorded ON "
4277 " RECTABLE.dupmethod > 1 AND "
4278 " recorded.duplicate <> 0 AND "
4279 " p.title = recorded.title AND "
4280 " p.generic = 0 AND "
4281 " recorded.recgroup NOT IN ('LiveTV','Deleted') "
4284 " (p.programid <> '' "
4285 " AND p.programid = recorded.programid) "
4289 " (p.programid = '' OR recorded.programid = '' OR "
4290 " LEFT(p.programid, LOCATE('/', p.programid)) <> "
4291 " LEFT(recorded.programid, LOCATE('/', recorded.programid))) " :
4292 " (p.programid = '' OR recorded.programid = '') ")
4295 " (((RECTABLE.dupmethod & 0x02) = 0) OR (p.subtitle <> '' "
4296 " AND p.subtitle = recorded.subtitle)) "
4298 " (((RECTABLE.dupmethod & 0x04) = 0) OR (p.description <> '' "
4299 " AND p.description = recorded.description)) "
4301 " (((RECTABLE.dupmethod & 0x08) = 0) OR "
4302 " (p.subtitle <> '' AND "
4303 " (p.subtitle = recorded.subtitle OR "
4304 " (recorded.subtitle = '' AND "
4305 " p.subtitle = recorded.description))) OR "
4306 " (p.subtitle = '' AND p.description <> '' AND "
4307 " (p.description = recorded.subtitle OR "
4308 " (recorded.subtitle = '' AND "
4309 " p.description = recorded.description)))) "
4313 " LEFT JOIN oldfind ON "
4314 " (oldfind.recordid = recordmatch.recordid AND "
4315 " oldfind.findid = recordmatch.findid) "
4316 " SET oldrecduplicate = (oldrecorded.endtime IS NOT NULL), "
4317 " recduplicate = (recorded.endtime IS NOT NULL), "
4318 " findduplicate = (oldfind.findid IS NOT NULL), "
4319 " oldrecstatus = oldrecorded.recstatus "
4320 " WHERE p.endtime >= (NOW() - INTERVAL 480 MINUTE) "
4321 " AND oldrecduplicate = -1 "
4323 rmquery.replace(
"RECTABLE", schedTmpRecord);
4337 if (schedTmpRecord ==
"record")
4338 schedTmpRecord =
"sched_temp_record";
4342 QMap<int, bool> cardMap;
4343 for (
auto * enc : qAsConst(*
m_tvList))
4345 if (enc->IsConnected() || enc->IsAsleep())
4346 cardMap[enc->GetInputID()] =
true;
4349 QMap<int, bool> tooManyMap;
4350 bool checkTooMany =
false;
4354 rlist.
prepare(QString(
"SELECT recordid, title, maxepisodes, maxnewest "
4355 "FROM %1").arg(schedTmpRecord));
4363 while (rlist.
next())
4365 int recid = rlist.
value(0).toInt();
4367 int maxEpisodes = rlist.
value(2).toInt();
4368 int maxNewest = rlist.
value(3).toInt();
4370 tooManyMap[recid] =
false;
4373 if (maxEpisodes && !maxNewest)
4377 epicnt.
prepare(
"SELECT DISTINCT chanid, progstart, progend "
4379 "WHERE recordid = :RECID AND preserve = 0 "
4380 "AND recgroup NOT IN ('LiveTV','Deleted');");
4385 if (epicnt.
size() >= maxEpisodes - 1)
4388 if (epicnt.
size() >= maxEpisodes)
4390 tooManyMap[recid] =
true;
4391 checkTooMany =
true;
4407 QString pwrpri =
"channel.recpriority + capturecard.recpriority";
4411 pwrpri += QString(
" + "
4412 "IF(capturecard.cardid = RECTABLE.prefinput, 1, 0) * %1")
4418 pwrpri += QString(
" + IF(program.hdtv > 0 OR "
4419 "FIND_IN_SET('HDTV', program.videoprop) > 0, 1, 0) * %1")
4425 pwrpri += QString(
" + "
4426 "IF(FIND_IN_SET('WIDESCREEN', program.videoprop) > 0, 1, 0) * %1")
4432 pwrpri += QString(
" + "
4433 "IF(FIND_IN_SET('SIGNED', program.subtitletypes) > 0, 1, 0) * %1")
4439 pwrpri += QString(
" + "
4440 "IF(FIND_IN_SET('ONSCREEN', program.subtitletypes) > 0, 1, 0) * %1")
4441 .arg(onscrpriority);
4446 pwrpri += QString(
" + "
4447 "IF(FIND_IN_SET('NORMAL', program.subtitletypes) > 0 OR "
4448 "program.closecaptioned > 0 OR program.subtitled > 0, 1, 0) * %1")
4454 pwrpri += QString(
" + "
4455 "IF(FIND_IN_SET('HARDHEAR', program.subtitletypes) > 0 OR "
4456 "FIND_IN_SET('HARDHEAR', program.audioprop) > 0, 1, 0) * %1")
4462 pwrpri += QString(
" + "
4463 "IF(FIND_IN_SET('VISUALIMPAIR', program.audioprop) > 0, 1, 0) * %1")
4469 result.
prepare(QString(
"SELECT recpriority, selectclause FROM %1;")
4478 while (result.
next())
4480 if (result.
value(0).toBool())
4482 QString sclause = result.
value(1).toString();
4484 sclause.remove(
';');
4485 pwrpri += QString(
" + IF(%1, 1, 0) * %2")
4486 .arg(sclause).arg(result.
value(0).toInt());
4489 pwrpri += QString(
" AS powerpriority ");
4491 pwrpri.replace(
"program.",
"p.");
4492 pwrpri.replace(
"channel.",
"c.");
4493 QString query = QString(
4495 " c.chanid, c.sourceid, p.starttime, "
4496 " p.endtime, p.title, p.subtitle, "
4497 " p.description, c.channum, c.callsign, "
4498 " c.name, oldrecduplicate, p.category, "
4499 " RECTABLE.recpriority, RECTABLE.dupin, recduplicate, "
4500 " findduplicate, RECTABLE.type, RECTABLE.recordid, "
4501 " p.starttime - INTERVAL RECTABLE.startoffset "
4502 " minute AS recstartts, "
4503 " p.endtime + INTERVAL RECTABLE.endoffset "
4504 " minute AS recendts, "
4505 " p.previouslyshown, "
4506 " RECTABLE.recgroup, RECTABLE.dupmethod, c.commmethod, "
4507 " capturecard.cardid, 0, p.seriesid, "
4508 " p.programid, RECTABLE.inetref, p.category_type, "
4509 " p.airdate, p.stars, p.originalairdate, "
4510 " RECTABLE.inactive, RECTABLE.parentid, recordmatch.findid, "
4511 " RECTABLE.playgroup, oldrecstatus.recstatus, "
4512 " oldrecstatus.reactivate, p.videoprop+0, "
4513 " p.subtitletypes+0, p.audioprop+0, RECTABLE.storagegroup, "
4514 " capturecard.hostname, recordmatch.oldrecstatus, NULL, "
4515 " oldrecstatus.future, capturecard.schedorder, "
4516 " p.syndicatedepisodenumber, p.partnumber, p.parttotal, "
4517 " c.mplexid, capturecard.displayname, "
4518 " p.season, p.episode, p.totalepisodes, ") +
4521 "INNER JOIN RECTABLE ON (recordmatch.recordid = RECTABLE.recordid) "
4522 "INNER JOIN program AS p "
4523 "ON ( recordmatch.chanid = p.chanid AND "
4524 " recordmatch.starttime = p.starttime AND "
4525 " recordmatch.manualid = p.manualid ) "
4526 "INNER JOIN channel AS c "
4527 "ON ( c.chanid = p.chanid ) "
4528 "INNER JOIN capturecard "
4529 "ON ( c.sourceid = capturecard.sourceid AND "
4530 " ( capturecard.schedorder <> 0 OR "
4531 " capturecard.parentid = 0 ) ) "
4532 "LEFT JOIN oldrecorded as oldrecstatus "
4533 "ON ( oldrecstatus.station = c.callsign AND "
4534 " oldrecstatus.starttime = p.starttime AND "
4535 " oldrecstatus.title = p.title ) "
4536 "WHERE p.endtime > (NOW() - INTERVAL 480 MINUTE) "
4537 "ORDER BY RECTABLE.recordid DESC, p.starttime, p.title, c.callsign, "
4539 query.replace(
"RECTABLE", schedTmpRecord);
4541 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- Start DB Query..."));
4543 auto dbstart = nowAsDuration<std::chrono::microseconds>();
4550 auto dbend = nowAsDuration<std::chrono::microseconds>();
4551 auto dbTime = dbend - dbstart;
4553 LOG(VB_SCHEDULE, LOG_INFO,
4554 QString(
" |-- %1 results in %2 sec. Processing...")
4556 .arg(duration_cast<std::chrono::seconds>(dbTime).count()));
4560 while (result.
next())
4566 uint recordid = result.
value(17).toUInt();
4568 QString title = result.
value(4).toString();
4569 QString callsign = result.
value(8).toString();
4579 uint mplexid = result.
value(51).toUInt();
4580 if (mplexid == 32767)
4583 QString inputname = result.
value(52).toString();
4584 if (inputname.isEmpty())
4585 inputname = QString(
"Input %1").arg(result.
value(24).toUInt());
4590 result.
value(5).toString(),
4592 result.
value(6).toString(),
4593 result.
value(53).toInt(),
4594 result.
value(54).toInt(),
4595 result.
value(55).toInt(),
4596 result.
value(48).toString(),
4597 result.
value(11).toString(),
4599 result.
value(0).toUInt(),
4600 result.
value(7).toString(),
4602 result.
value(9).toString(),
4604 result.
value(21).toString(),
4605 result.
value(36).toString(),
4607 result.
value(43).toString(),
4608 result.
value(42).toString(),
4610 result.
value(30).toUInt(),
4611 result.
value(49).toUInt(),
4612 result.
value(50).toUInt(),
4614 result.
value(26).toString(),
4615 result.
value(27).toString(),
4616 result.
value(28).toString(),
4619 result.
value(12).toInt(),
4626 result.
value(31).toDouble(),
4627 (result.
value(32).isNull()) ? QDate() :
4631 result.
value(20).toBool(),
4634 result.
value(38).toBool(),
4637 result.
value(34).toUInt(),
4642 result.
value(1).toUInt(),
4643 result.
value(24).toUInt(),
4645 result.
value(35).toUInt(),
4648 result.
value(40).toUInt(),
4649 result.
value(39).toUInt(),
4650 result.
value(41).toUInt(),
4651 result.
value(46).toBool(),
4652 result.
value(47).toInt(),
4654 result.
value(24).toUInt(),
4657 if (!
p->m_future && !
p->IsReactivated() &&
4661 p->SetRecordingStatus(
p->m_oldrecstatus);
4664 p->SetRecordingPriority2(result.
value(56).toInt());
4673 if (
p->IsSameTitleStartTimeAndChannel(*r))
4675 if (r->m_sgroupId ==
p->m_sgroupId &&
4676 r->GetRecordingEndTime() !=
p->GetRecordingEndTime() &&
4677 (r->GetRecordingRuleID() ==
p->GetRecordingRuleID() ||
4692 tmpList.push_back(
p);
4699 (!cardMap.contains(
p->GetInputID()) || (
p->m_schedOrder == 0)))
4702 if (
p->m_schedOrder == 0 &&
4706 LOG(VB_GENERAL, LOG_WARNING,
LOC +
4707 QString(
"Channel %1, Title %2 %3 cardinput.schedorder = %4, "
4708 "it must be >0 to record from this input.")
4709 .arg(
p->GetChannelName(),
p->GetTitle(),
4710 p->GetScheduledStartTime().toString(),
4711 QString::number(
p->m_schedOrder)));
4717 if (checkTooMany && tooManyMap[
p->GetRecordingRuleID()] &&
4718 !
p->IsReactivated())
4726 else if (result.
value(15).toBool() && !
p->IsReactivated())
4730 !
p->IsReactivated() &&
4750 bool inactive = result.
value(33).toBool();
4765 p->SetRecordingStatus(newrecstatus);
4767 tmpList.push_back(
p);
4770 LOG(VB_SCHEDULE, LOG_INFO,
" +-- Cleanup...");
4771 for (
auto &
tmp : tmpList)
4779 QString query = QString(
4780 "SELECT RECTABLE.title, RECTABLE.subtitle, "
4781 " RECTABLE.description, RECTABLE.season, "
4782 " RECTABLE.episode, RECTABLE.category, "
4783 " RECTABLE.chanid, channel.channum, "
4784 " RECTABLE.station, channel.name, "
4785 " RECTABLE.recgroup, RECTABLE.playgroup, "
4786 " RECTABLE.seriesid, RECTABLE.programid, "
4787 " RECTABLE.inetref, RECTABLE.recpriority, "
4788 " RECTABLE.startdate, RECTABLE.starttime, "
4789 " RECTABLE.enddate, RECTABLE.endtime, "
4790 " RECTABLE.recordid, RECTABLE.type, "
4791 " RECTABLE.dupin, RECTABLE.dupmethod, "
4792 " RECTABLE.findid, "
4793 " RECTABLE.startoffset, RECTABLE.endoffset, "
4794 " channel.commmethod "
4796 "INNER JOIN channel ON (channel.chanid = RECTABLE.chanid) "
4797 "LEFT JOIN recordmatch on RECTABLE.recordid = recordmatch.recordid "
4798 "WHERE (type = %1 OR type = %2) AND "
4799 " recordmatch.chanid IS NULL")
4805 LOG(VB_SCHEDULE, LOG_INFO, QString(
" |-- Start DB Query..."));
4807 auto dbstart = nowAsDuration<std::chrono::microseconds>();
4810 bool ok = result.
exec();
4811 auto dbend = nowAsDuration<std::chrono::microseconds>();
4812 auto dbTime = dbend - dbstart;
4820 LOG(VB_SCHEDULE, LOG_INFO,
4821 QString(
" |-- %1 results in %2 sec. Processing...")
4823 .arg(duration_cast<std::chrono::seconds>(dbTime).count()));
4825 while (result.
next())
4829 result.
value(16).toDate(), result.
value(17).toTime(), Qt::UTC);
4831 result.
value(18).toDate(), result.
value(19).toTime(), Qt::UTC);
4833 QDateTime recstartts = startts.addSecs(result.
value(25).toInt() * -60LL);
4834 QDateTime recendts = endts.addSecs( result.
value(26).toInt() * +60LL);
4836 if (recstartts >= recendts)
4839 recstartts = startts;
4850 result.
value(0).toString(),
4852 (sor) ? result.
value(1).toString() : QString(),
4854 (sor) ? result.
value(2).toString() : QString(),
4855 result.
value(3).toUInt(),
4856 result.
value(4).toUInt(),
4859 result.
value(6).toUInt(),
4860 result.
value(7).toString(),
4861 result.
value(8).toString(),
4862 result.
value(9).toString(),
4864 result.
value(10).toString(),
4865 result.
value(11).toString(),
4867 result.
value(12).toString(),
4868 result.
value(13).toString(),
4869 result.
value(14).toString(),
4871 result.
value(15).toInt(),
4874 recstartts, recendts,
4878 result.
value(20).toUInt(),
4884 result.
value(24).toUInt(),
4888 tmpList.push_back(
p);
4891 for (
auto &
tmp : tmpList)
4902 QString sortColumn =
"title";
4910 sortColumn =
"record.title";
4913 sortColumn =
"record.recpriority";
4916 sortColumn =
"record.last_record";
4924 sortColumn =
"record.next_record IS NULL, record.next_record";
4927 sortColumn =
"record.type";
4931 QString order =
"ASC";
4935 QString query = QString(
4936 "SELECT record.title, record.subtitle, "
4937 " record.description, record.season, "
4938 " record.episode, record.category, "
4939 " record.chanid, channel.channum, "
4940 " record.station, channel.name, "
4941 " record.recgroup, record.playgroup, "
4942 " record.seriesid, record.programid, "
4943 " record.inetref, record.recpriority, "
4944 " record.startdate, record.starttime, "
4945 " record.enddate, record.endtime, "
4946 " record.recordid, record.type, "
4947 " record.dupin, record.dupmethod, "
4949 " channel.commmethod "
4951 "LEFT JOIN channel ON channel.callsign = record.station "
4952 " AND deleted IS NULL "
4953 "GROUP BY recordid "
4956 query = query.arg(sortColumn, order);
4967 while (result.
next())
4970 QDateTime startts = QDateTime(result.
value(16).toDate(),
4971 result.
value(17).toTime(), Qt::UTC);
4972 QDateTime endts = QDateTime(result.
value(18).toDate(),
4973 result.
value(19).toTime(), Qt::UTC);
4975 if (!startts.isValid())
4978 if (!endts.isValid())
4982 result.
value(0).toString(), QString(),
4983 result.
value(1).toString(), QString(),
4984 result.
value(2).toString(), result.
value(3).toUInt(),
4985 result.
value(4).toUInt(), result.
value(5).toString(),
4987 result.
value(6).toUInt(), result.
value(7).toString(),
4988 result.
value(8).toString(), result.
value(9).toString(),
4990 result.
value(10).toString(), result.
value(11).toString(),
4992 result.
value(12).toString(), result.
value(13).toString(),
4993 result.
value(14).toString(),
4995 result.
value(15).toInt(),
5002 result.
value(20).toUInt(), rectype,
5006 result.
value(24).toUInt(),
5105 QString recording_dir;
5109 "LiveTV", cur, cur.addSecs(3600), cardid,
5114 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"FindNextLiveTVDir: next dir is '%1'")
5115 .arg(recording_dir));
5122 const QString &title,
5124 const QString &storagegroup,
5125 const QDateTime &recstartts,
5126 const QDateTime &recendts,
5128 QString &recording_dir,
5131 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
"FillRecordingDir: Starting");
5136 if (cnt++ % 20 == 0)
5137 LOG(VB_SCHEDULE, LOG_WARNING,
"Waiting for main server.");
5138 std::this_thread::sleep_for(50ms);
5145 QStringList recsCounted;
5146 std::list<FileSystemInfo *> fsInfoList;
5147 std::list<FileSystemInfo *>::iterator fslistit;
5149 recording_dir.clear();
5151 if (dirlist.size() == 1)
5153 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5154 QString(
"FillRecordingDir: The only directory in the %1 Storage "
5155 "Group is %2, so it will be used by default.")
5156 .arg(storagegroup, dirlist[0]));
5157 recording_dir = dirlist[0];
5158 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
"FillRecordingDir: Finished");
5163 int weightPerRecording =
5165 int weightPerPlayback =
5167 int weightPerCommFlag =
5169 int weightPerTranscode =
5172 QString storageScheduler =
5174 int localStartingWeight =
5176 (storageScheduler !=
"Combination") ? 0
5177 : (
int)(-1.99 * weightPerRecording));
5178 int remoteStartingWeight =
5180 std::chrono::seconds maxOverlap =
5185 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5186 "FillRecordingDir: Calculating initial FS Weights.");
5197 tmpWeight = localStartingWeight;
5198 msg +=
" is local (" + QString::number(tmpWeight) +
")";
5202 tmpWeight = remoteStartingWeight;
5203 msg +=
" is remote (+" + QString::number(tmpWeight) +
")";
5213 msg +=
", has SGweightPerDir offset of "
5214 + QString::number(tmpWeight) +
")";
5216 msg +=
". initial dir weight = " + QString::number(fs->
getWeight());
5217 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, msg);
5219 fsInfoList.push_back(fs);
5222 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5223 "FillRecordingDir: Adjusting FS Weights from inuseprograms.");
5226 saveRecDir.
prepare(
"UPDATE inuseprograms "
5227 "SET recdir = :RECDIR "
5228 "WHERE chanid = :CHANID AND "
5229 " starttime = :STARTTIME");
5232 "SELECT i.chanid, i.starttime, r.endtime, recusage, rechost, recdir "
5233 "FROM inuseprograms i, recorded r "
5234 "WHERE DATE_ADD(lastupdatetime, INTERVAL 16 MINUTE) > NOW() AND "
5235 " i.chanid = r.chanid AND "
5236 " i.starttime = r.starttime");
5244 while (query.
next())
5246 uint recChanid = query.
value(0).toUInt();
5249 QString recUsage( query.
value(3).toString());
5250 QString recHost( query.
value(4).toString());
5251 QString recDir( query.
value(5).toString());
5253 if (recDir.isEmpty())
5257 recDir = recDir.isEmpty() ?
"_UNKNOWN_" : recDir;
5259 saveRecDir.
bindValue(
":RECDIR", recDir);
5260 saveRecDir.
bindValue(
":CHANID", recChanid);
5261 saveRecDir.
bindValue(
":STARTTIME", recStart);
5262 if (!saveRecDir.
exec())
5265 if (recDir ==
"_UNKNOWN_")
5268 for (fslistit = fsInfoList.begin();
5269 fslistit != fsInfoList.end(); ++fslistit)
5275 int weightOffset = 0;
5279 if (recEnd > recstartts.addSecs(maxOverlap.count()))
5281 weightOffset += weightPerRecording;
5282 recsCounted << QString::number(recChanid) +
":" +
5287 weightOffset += weightPerPlayback;
5289 weightOffset += weightPerCommFlag;
5291 weightOffset += weightPerTranscode;
5295 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5296 QString(
" %1 @ %2 in use by '%3' on %4:%5, FSID "
5297 "#%6, FSID weightOffset +%7.")
5298 .arg(QString::number(recChanid),
5300 recUsage, recHost, recDir,
5302 QString::number(weightOffset)));
5310 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5311 QString(
" %1:%2 => old weight %3 plus "
5313 .arg(
fs2->getHostname(),
5315 .arg(
fs2->getWeight())
5317 .arg(
fs2->getWeight() + weightOffset));
5319 fs2->setWeight(
fs2->getWeight() + weightOffset);
5329 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
LOC +
5330 "FillRecordingDir: Adjusting FS Weights from scheduler.");
5332 for (
auto *thispg : reclist)
5334 if ((recendts < thispg->GetRecordingStartTime()) ||
5335 (recstartts > thispg->GetRecordingEndTime()) ||
5338 (thispg->GetInputID() == 0) ||
5339 (recsCounted.contains(QString(
"%1:%2").arg(thispg->GetChanID())
5341 (thispg->GetPathname().isEmpty()))
5344 for (fslistit = fsInfoList.begin();
5345 fslistit != fsInfoList.end(); ++fslistit)
5348 if ((fs->
getHostname() == thispg->GetHostname()) &&
5349 (fs->
getPath() == thispg->GetPathname()))
5351 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5352 QString(
"%1 @ %2 will record on %3:%4, FSID #%5, "
5353 "weightPerRecording +%6.")
5354 .arg(thispg->GetChanID())
5357 .arg(fs->
getFSysID()).arg(weightPerRecording));
5366 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5367 QString(
" %1:%2 => old weight %3 plus %4 = %5")
5368 .arg(
fs2->getHostname(),
fs2->getPath())
5369 .arg(
fs2->getWeight()).arg(weightPerRecording)
5370 .arg(
fs2->getWeight() + weightPerRecording));
5372 fs2->setWeight(
fs2->getWeight() + weightPerRecording);
5380 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5381 QString(
"Using '%1' Storage Scheduler directory sorting algorithm.")
5382 .arg(storageScheduler));
5384 if (storageScheduler ==
"BalancedFreeSpace")
5386 else if (storageScheduler ==
"BalancedPercFreeSpace")
5388 else if (storageScheduler ==
"BalancedDiskIO")
5395 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5396 "--- FillRecordingDir Sorted fsInfoList start ---");
5397 for (fslistit = fsInfoList.begin();fslistit != fsInfoList.end();
5401 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
"%1:%2")
5403 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
" Location : %1")
5404 .arg((fs->
isLocal()) ?
"local" :
"remote"));
5405 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
" weight : %1")
5407 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(
" free space : %5")
5410 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
5411 "--- FillRecordingDir Sorted fsInfoList end ---");
5420 long long maxSizeKB = (maxByterate + maxByterate/3) *
5421 recstartts.secsTo(recendts) / 1024;
5423 bool simulateAutoExpire =
5426 (fsInfoList.size() > 1));
5440 for (
unsigned int pass = 1; pass <= 3; pass++)
5442 bool foundDir =
false;
5444 if ((pass == 2) && simulateAutoExpire)
5447 QMap <int , long long> remainingSpaceKB;
5448 for (fslistit = fsInfoList.begin();
5449 fslistit != fsInfoList.end(); ++fslistit)
5451 remainingSpaceKB[(*fslistit)->getFSysID()] =
5452 (*fslistit)->getFreeSpace();
5459 for (
auto & expire : expiring)
5463 for (fslistit = fsInfoList.begin();
5464 fslistit != fsInfoList.end(); ++fslistit)
5467 if (expire->GetHostname() != (*fslistit)->getHostname())
5471 if (!dirlist.contains((*fslistit)->getPath()))
5475 (*fslistit)->getPath() +
"/" + expire->GetPathname();
5482 if (checkFile.exists())
5490 QString backuppath = expire->GetPathname();
5492 bool foundSlave =
false;
5494 for (
auto * enc : qAsConst(*
m_tvList))
5496 if (enc->GetHostName() ==
5499 enc->CheckFile(programinfo);
5517 LOG(VB_GENERAL, LOG_ERR,
5518 QString(
"Unable to match '%1' "
5519 "to any file system. Ignoring it.")
5520 .arg(expire->GetBasename()));
5526 expire->GetFilesize() / 1024;
5529 long long desiredSpaceKB =
5533 (desiredSpaceKB + maxSizeKB))
5535 recording_dir = fs->
getPath();
5538 LOG(VB_FILE, LOG_INFO,
5539 QString(
"pass 2: '%1' will record in '%2' "
5540 "although there is only %3 MB free and the "
5541 "AutoExpirer wants at least %4 MB. This "
5542 "directory has the highest priority files "
5543 "to be expired from the AutoExpire list and "
5544 "there are enough that the Expirer should "
5545 "be able to free up space for this recording.")
5546 .arg(title, recording_dir)
5548 .arg(desiredSpaceKB / 1024));
5559 for (fslistit = fsInfoList.begin();
5560 fslistit != fsInfoList.end(); ++fslistit)
5562 long long desiredSpaceKB = 0;
5569 (dirlist.contains(fs->
getPath())) &&
5573 recording_dir = fs->
getPath();
5578 LOG(VB_FILE, LOG_INFO,
5579 QString(
"pass 1: '%1' will record in "
5580 "'%2' which has %3 MB free. This recording "
5581 "could use a max of %4 MB and the "
5582 "AutoExpirer wants to keep %5 MB free.")
5583 .arg(title, recording_dir)
5585 .arg(maxSizeKB / 1024)
5586 .arg(desiredSpaceKB / 1024));
5590 LOG(VB_FILE, LOG_INFO,
5591 QString(
"pass %1: '%2' will record in "
5592 "'%3' although there is only %4 MB free and "
5593 "the AutoExpirer wants at least %5 MB. "
5594 "Something will have to be deleted or expired "
5595 "in order for this recording to complete "
5597 .arg(pass).arg(title, recording_dir)
5599 .arg(desiredSpaceKB / 1024));
5612 LOG(VB_SCHEDULE, LOG_INFO,
LOC +
"FillRecordingDir: Finished");
5618 QList<FileSystemInfo> fsInfos;
5625 QMap <int, bool> fsMap;
5626 QList<FileSystemInfo>::iterator it1;
5627 for (it1 = fsInfos.begin(); it1 != fsInfos.end(); ++it1)
5629 fsMap[it1->getFSysID()] =
true;
5630 m_fsInfoCache[it1->getHostname() +
":" + it1->getPath()] = *it1;
5633 LOG(VB_FILE, LOG_INFO,
LOC +
5634 QString(
"FillDirectoryInfoCache: found %1 unique filesystems")
5635 .arg(fsMap.size()));
5642 auto secsleft = std::chrono::seconds(curtime.secsTo(
m_livetvTime));
5646 if (secsleft - prerollseconds > 120s)
5650 for (
auto * enc : qAsConst(*
m_tvList))
5666 if (
m_schedTime.secsTo(dummy->GetRecordingEndTime()) < 1800)
5667 dummy->SetRecordingEndTime(
m_schedTime.addSecs(1800));
5668 dummy->SetInputID(enc->GetInputID());
5669 dummy->m_mplexId = dummy->QueryMplexID();
5692 bool autoStart =
false;
5694 QDateTime startupTime = QDateTime();
5700 if (startupTime.isValid())
5703 startupSecs = std::max(startupSecs, 15 * 60s);