12#if QT_VERSION >= QT_VERSION_CHECK(6,5,0)
13#include <QtSystemDetection>
23#include <QCoreApplication>
30#include "libmythbase/mythconfig.h"
45#define LOC QString("JobQueue: ")
52 m_runningJobsLock(new QRecursiveMutex()),
54 m_queueThread(new
MThread(
"JobQueue", this))
64 LOG(VB_GENERAL, LOG_ERR,
LOC +
65 "The JobQueue has been disabled because "
66 "you compiled with the --enable-valgrind option.");
95 QString message = me->
Message();
97 if (message.startsWith(
"LOCAL_JOB"))
102 message = message.simplified();
103 QStringList tokens = message.split(
" ", Qt::SkipEmptyParts);
104 const QString&
action = tokens[1];
107 if (tokens[2] ==
"ID")
108 jobID = tokens[3].toInt();
120 msg = QString(
"Unable to determine jobID for message: "
121 "%1. Program will not be flagged.")
123 LOG(VB_GENERAL, LOG_ERR,
LOC + msg);
129 msg = QString(
"Received message '%1'").arg(message);
130 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + msg);
141 else if (
action ==
"PAUSE")
143 else if (
action ==
"RESUME")
145 else if (
action ==
"RESTART")
171 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
"ProcessQueue() started");
177 QMap<int, int> jobStatus;
179 QMap<int, JobQueueEntry> jobs;
181 QMap<int, RunningJobInfo>::Iterator rjiter;
188 bool startedJobAlready =
false;
191 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
192 QString(
"Currently set to run up to %1 job(s) max.")
201 if ((*rjiter).pginfo)
202 (*rjiter).pginfo->UpdateInUseMark();
212 for (
const auto & job : std::as_const(jobs))
214 int status = job.status;
217 if (((status == JOB_RUNNING) ||
218 (status == JOB_STARTING) ||
219 (status == JOB_PAUSED)) &&
224 message = QString(
"Currently Running %1 jobs.")
228 message += QString(
" Jobs in Queue, but we are outside of the "
229 "Job Queue time window, no new jobs can be "
231 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
235 message +=
" (At Maximum, no new jobs can be started until "
236 "a running job completes)";
239 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
245 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
253 int jobID = jobs[x].id;
254 int cmds = jobs[x].cmds;
256 int status = jobs[x].status;
260 logInfo = QString(
"jobID #%1").arg(
jobID);
262 logInfo = QString(
"chanid %1 @ %2").arg(jobs[x].chanid)
263 .arg(jobs[x].startts);
266 if ((inTimeWindow) &&
273 jobStatus[
jobID] = status;
275 message = QString(
"Skipping '%1' job for %2, "
276 "should run on '%3' instead")
279 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
288 if (otherJobID && (jobStatus.contains(otherJobID)) &&
289 (!(jobStatus[otherJobID] & JOB_DONE)))
292 QString(
"Skipping '%1' job for %2, "
293 "Job ID %3 is already running for "
294 "this recording with a status of '%4'")
296 QString::number(otherJobID),
298 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
303 jobStatus[
jobID] = status;
308 message = QString(
"Skipping '%1' job for %2, "
309 "not allowed to run on this backend.")
311 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
318 message = QString(
"Skipping '%1' job for %2, this job is "
319 "not scheduled to run until %3.")
323 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
331 if (status != JOB_QUEUED) {
332 message = QString(
"Stopping '%1' job for %2")
334 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
349 message = QString(
"Cancelling '%1' job for %2")
351 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
359 message = QString(
"Unable to claim '%1' job for %2")
361 LOG(VB_JOBQUEUE, LOG_ERR,
LOC + message);
370 if ((cmds &
JOB_PAUSE) && (status != JOB_QUEUED))
372 message = QString(
"Pausing '%1' job for %2")
374 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
385 if ((cmds &
JOB_RESTART) && (status != JOB_QUEUED))
387 message = QString(
"Restart '%1' job for %2")
389 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
400 if (status != JOB_QUEUED)
405 message = QString(
"Resetting '%1' job for %2 to %3 "
406 "status, because no hostname is set.")
410 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
415 else if (inTimeWindow)
417 message = QString(
"Skipping '%1' job for %2, "
418 "current job status is '%3'")
422 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
428 if (startedJobAlready)
431 if ((inTimeWindow) &&
435 message = QString(
"Unable to claim '%1' job for %2")
437 LOG(VB_JOBQUEUE, LOG_ERR,
LOC + message);
443 message = QString(
"Skipping '%1' job for %2, "
444 "current time is outside of the "
445 "Job Queue processing window.")
447 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
451 message = QString(
"Processing '%1' job for %2, "
452 "current status is '%3'")
455 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
459 startedJobAlready =
true;
470 LOG(VB_JOBQUEUE, LOG_INFO, QString(
"%1 jobs running. "
479 LOG(VB_JOBQUEUE, LOG_INFO,
"No jobs running. "
480 "Allowing shutdown.");
489 std::chrono::milliseconds st = (startedJobAlready) ? 5s : sleepTime;
502 jobTypes &= (~JOB_COMMFLAG);
506 QString jobHost = QString(
"");
519 const QString&
args,
const QString& comment, QString host,
520 int flags,
int status, QDateTime schedruntime)
522 int tmpStatus = JOB_UNKNOWN;
523 int tmpCmd = JOB_UNKNOWN;
526 if(!schedruntime.isValid())
535 query.
prepare(
"SELECT status, id, cmds FROM jobqueue "
536 "WHERE chanid = :CHANID AND starttime = :STARTTIME "
537 "AND type = :JOBTYPE;");
539 query.
bindValue(
":STARTTIME", recstartts);
549 tmpStatus = query.
value(0).toInt();
551 tmpCmd = query.
value(2).toInt();
568 if (! (tmpStatus & JOB_DONE) && (tmpCmd &
JOB_STOP))
577 query.
prepare(
"INSERT INTO jobqueue (chanid, starttime, inserttime, type, "
578 "status, statustime, schedruntime, hostname, args, comment, "
580 "VALUES (:CHANID, :STARTTIME, now(), :JOBTYPE, :STATUS, "
581 "now(), :SCHEDRUNTIME, :HOST, :ARGS, :COMMENT, :FLAGS);");
584 query.
bindValue(
":STARTTIME", recstartts);
587 query.
bindValue(
":SCHEDRUNTIME", schedruntime);
603 const QString&
args,
const QString& comment,
const QString& host)
627#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
628 schedruntime = QDateTime(schedruntime.addDays(defer).date(),
629 QTime(0,0,0), Qt::UTC);
631 schedruntime = QDateTime(schedruntime.addDays(defer).date(),
633 QTimeZone(QTimeZone::UTC));
638 0, JOB_QUEUED, schedruntime);
658 query.
prepare(
"SELECT id FROM jobqueue "
659 "WHERE chanid = :CHANID AND starttime = :STARTTIME "
660 "AND type = :JOBTYPE;");
662 query.
bindValue(
":STARTTIME", recstartts);
671 return query.
value(0).toInt();
676 int jobID,
int &jobType,
uint &chanid, QDateTime &recstartts)
680 query.
prepare(
"SELECT type, chanid, starttime FROM jobqueue "
692 jobType = query.
value(0).toInt();
693 chanid = query.
value(1).toUInt();
701 int jobID,
int &jobType,
uint &chanid, QString &recstartts)
703 QDateTime tmpStarttime;
706 jobID, jobType, chanid, tmpStarttime);
718 LOG(VB_GENERAL, LOG_ERR, QString(
"'%1' is an invalid Job Name.")
727 QString message = QString(
"GLOBAL_JOB PAUSE ID %1").arg(
jobID);
736 QString message = QString(
"GLOBAL_JOB RESUME ID %1").arg(
jobID);
745 QString message = QString(
"GLOBAL_JOB RESTART ID %1").arg(
jobID);
754 QString message = QString(
"GLOBAL_JOB STOP ID %1").arg(
jobID);
766 query.
prepare(
"UPDATE jobqueue SET status = :CANCELLED "
767 "WHERE chanid = :CHANID AND starttime = :STARTTIME "
768 "AND status = :QUEUED;");
770 query.
bindValue(
":CANCELLED", JOB_CANCELLED);
772 query.
bindValue(
":STARTTIME", recstartts);
778 query.
prepare(
"UPDATE jobqueue SET cmds = :CMD "
779 "WHERE chanid = :CHANID AND starttime = :STARTTIME "
780 "AND status <> :CANCELLED;");
783 query.
bindValue(
":STARTTIME", recstartts);
784 query.
bindValue(
":CANCELLED", JOB_CANCELLED);
793 bool jobsAreRunning =
true;
794 std::chrono::seconds totalSlept = 0s;
795 std::chrono::seconds maxSleep = 90s;
796 while (jobsAreRunning && totalSlept < maxSleep)
798 std::this_thread::sleep_for(1ms);
799 query.
prepare(
"SELECT id FROM jobqueue "
800 "WHERE chanid = :CHANID and starttime = :STARTTIME "
802 "(:FINISHED,:ABORTED,:ERRORED,:CANCELLED);");
804 query.
bindValue(
":STARTTIME", recstartts);
805 query.
bindValue(
":FINISHED", JOB_FINISHED);
806 query.
bindValue(
":ABORTED", JOB_ABORTED);
807 query.
bindValue(
":ERRORED", JOB_ERRORED);
808 query.
bindValue(
":CANCELLED", JOB_CANCELLED);
816 if (query.
size() == 0)
818 jobsAreRunning =
false;
821 if ((totalSlept % 5s) == 0s)
823 message = QString(
"Waiting on %1 jobs still running for "
824 "chanid %2 @ %3").arg(query.
size())
825 .arg(chanid).arg(recstartts.toString(
Qt::ISODate));
826 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
829 std::this_thread::sleep_for(1s);
833 if (totalSlept <= maxSleep)
835 query.
prepare(
"DELETE FROM jobqueue "
836 "WHERE chanid = :CHANID AND starttime = :STARTTIME;");
838 query.
bindValue(
":STARTTIME", recstartts);
845 query.
prepare(
"SELECT id, type, status, comment FROM jobqueue "
846 "WHERE chanid = :CHANID AND starttime = :STARTTIME "
847 "AND status <> :CANCELLED ORDER BY id;");
850 query.
bindValue(
":STARTTIME", recstartts);
851 query.
bindValue(
":CANCELLED", JOB_CANCELLED);
856 "to query list of Jobs left in Queue.", query);
860 LOG(VB_GENERAL, LOG_ERR,
LOC +
861 QString(
"In DeleteAllJobs: There are Jobs "
862 "left in the JobQueue that are still running for "
863 "chanid %1 @ %2.").arg(chanid)
868 LOG(VB_GENERAL, LOG_ERR,
LOC +
869 QString(
"Job ID %1: '%2' with status '%3' and comment '%4'")
870 .arg(query.
value(0).toString(),
873 query.
value(3).toString()));
888 const QDateTime& recstartts)
896 int thisJob =
GetJobID(jobType, chanid, recstartts);
899 if( thisJob !=
jobID)
901 msg = QString(
"JobType, chanid and starttime don't match jobID %1");
908 msg = QString(
"Can't remove running JobID %1");
916 query.
prepare(
"DELETE FROM jobqueue WHERE id = :ID;");
936 query.
prepare(
"UPDATE jobqueue SET cmds = :CMDS WHERE id = :ID;");
951 const QDateTime &recstartts,
int newCmds)
955 query.
prepare(
"UPDATE jobqueue SET cmds = :CMDS WHERE type = :TYPE "
956 "AND chanid = :CHANID AND starttime = :STARTTIME;");
961 query.
bindValue(
":STARTTIME", recstartts);
979 query.
prepare(
"UPDATE jobqueue SET flags = :FLAGS WHERE id = :ID;");
998 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + QString(
"ChangeJobStatus(%1, %2, '%3')")
1003 query.
prepare(
"UPDATE jobqueue SET status = :STATUS, comment = :COMMENT "
1004 "WHERE id = :ID AND status <> :NEWSTATUS;");
1009 query.
bindValue(
":NEWSTATUS", newStatus);
1025 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + QString(
"ChangeJobComment(%1, '%2')")
1026 .arg(
jobID).arg(comment));
1030 query.
prepare(
"UPDATE jobqueue SET comment = :COMMENT "
1052 query.
prepare(
"UPDATE jobqueue SET args = :ARGS "
1072 if ((jInfo.pginfo->GetChanID() == chanid) &&
1073 (jInfo.pginfo->GetRecordingStartTime() == recstartts))
1087 return (status == JOB_QUEUED);
1092 return ((status != JOB_UNKNOWN) && (status != JOB_QUEUED) &&
1093 ((status & JOB_DONE) == 0));
1097 uint chanid,
const QDateTime &recstartts)
1109 int jobType,
uint chanid,
const QDateTime &recstartts)
1111 int tmpStatus =
GetJobStatus(jobType, chanid, recstartts);
1113 return (tmpStatus != JOB_UNKNOWN) && ((tmpStatus & JOB_DONE) == 0);
1117 int jobType,
uint chanid,
const QDateTime &recstartts)
1129 case JOB_PREVIEW:
return tr(
"Preview Generation");
1134 QString settingName =
1139 return tr(
"Unknown Job");
1143#define JOBSTATUS_STATUSTEXT(A,B,C) case A: return C;
1152 return tr(
"Undefined");
1157 QString queueStartTimeStr;
1158 QString queueEndTimeStr;
1159 QTime queueStartTime;
1161 QTime curTime = QTime::currentTime();
1162 bool inTimeWindow =
false;
1163 orStartsWithinMins = orStartsWithinMins < 0min ? 0min : orStartsWithinMins;
1169 if (!queueStartTime.isValid())
1171 LOG(VB_GENERAL, LOG_ERR,
1172 QString(
"Invalid JobQueueWindowStart time '%1', using 00:00")
1173 .arg(queueStartTimeStr));
1174 queueStartTime = QTime(0, 0);
1178 if (!queueEndTime.isValid())
1180 LOG(VB_GENERAL, LOG_ERR,
1181 QString(
"Invalid JobQueueWindowEnd time '%1', using 23:59")
1182 .arg(queueEndTimeStr));
1183 queueEndTime = QTime(23, 59);
1186 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1187 QString(
"Currently set to run new jobs from %1 to %2")
1188 .arg(queueStartTimeStr, queueEndTimeStr));
1190 if ((queueStartTime <= curTime) && (curTime < queueEndTime))
1192 inTimeWindow =
true;
1194 else if ((queueStartTime > queueEndTime) &&
1195 ((curTime < queueEndTime) || (queueStartTime <= curTime)))
1197 inTimeWindow =
true;
1199 else if (orStartsWithinMins > 0min)
1202 if (curTime <= queueStartTime)
1205 if (queueStartTime.secsTo(curTime) <= duration_cast<std::chrono::seconds>(orStartsWithinMins).count())
1207 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1208 QString(
"Job run window will start within %1 minutes")
1209 .arg(orStartsWithinMins.count()));
1210 inTimeWindow =
true;
1217#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
1218 QDateTime startDateTime = QDateTime(
1219 curDateTime.date(), queueStartTime, Qt::UTC).addDays(1);
1221 QDateTime startDateTime =
1222 QDateTime(curDateTime.date(), queueStartTime,
1223 QTimeZone(QTimeZone::UTC)).addDays(1);
1226 if (curDateTime.secsTo(startDateTime) <= duration_cast<std::chrono::seconds>(orStartsWithinMins).count())
1228 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1229 QString(
"Job run window will start "
1230 "within %1 minutes (tomorrow)")
1231 .arg(orStartsWithinMins.count()));
1232 inTimeWindow =
true;
1237 return inTimeWindow;
1244 QMap<int, JobQueueEntry> jobs;
1245 QMap<int, JobQueueEntry>::Iterator it;
1247 bool checkForQueuedJobs = (startingWithinMins <= 0min
1250 if (checkForQueuedJobs && startingWithinMins > 0min) {
1251 maxSchedRunTime = maxSchedRunTime.addSecs(duration_cast<std::chrono::seconds>(startingWithinMins).count());
1252 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1253 QString(
"HasRunningOrPendingJobs: checking for jobs "
1254 "starting before: %1")
1260 if (!jobs.empty()) {
1261 for (it = jobs.begin(); it != jobs.end(); ++it)
1263 int tmpStatus = (*it).status;
1264 if (tmpStatus == JOB_RUNNING) {
1265 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1266 QString(
"HasRunningOrPendingJobs: found running job"));
1270 if (checkForQueuedJobs) {
1271 if ((tmpStatus != JOB_UNKNOWN) && (!(tmpStatus & JOB_DONE))) {
1272 if (startingWithinMins <= 0min) {
1273 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1274 "HasRunningOrPendingJobs: found pending job");
1277 if ((*it).schedruntime <= maxSchedRunTime) {
1278 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1279 QString(
"HasRunningOrPendingJobs: found pending "
1280 "job scheduled to start at: %1")
1299 bool commflagWhileRecording =
1304 query.
prepare(
"SELECT j.id, j.chanid, j.starttime, j.inserttime, j.type, "
1305 "j.cmds, j.flags, j.status, j.statustime, j.hostname, "
1306 "j.args, j.comment, r.endtime, j.schedruntime "
1308 "LEFT JOIN recorded r "
1309 " ON j.chanid = r.chanid AND j.starttime = r.starttime "
1310 "ORDER BY j.schedruntime, j.id;");
1315 "query list of Jobs in Queue.", query);
1319 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1320 QString(
"GetJobsInQueue: findJobs search bitmask %1, "
1321 "found %2 total jobs")
1322 .arg(findJobs).arg(query.
size()));
1324 while (query.
next())
1326 bool wantThisJob =
false;
1328 thisJob.
id = query.
value(0).toInt();
1338 if (query.
value(1).toInt() == -1)
1341 logInfo = QString(
"jobID #%1").arg(thisJob.
id);
1346 logInfo = QString(
"chanid %1 @ %2").arg(thisJob.
chanid)
1351 ((!commflagWhileRecording) ||
1355 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1356 QString(
"GetJobsInQueue: Ignoring '%1' Job "
1357 "for %2 in %3 state. Endtime in future.")
1365 (thisJob.
status & JOB_DONE)) ||
1367 (!(thisJob.
status & JOB_DONE))) ||
1369 (thisJob.
status == JOB_ERRORED)) ||
1376 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1377 QString(
"GetJobsInQueue: Ignore '%1' Job for %2 in %3 state.")
1383 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1384 QString(
"GetJobsInQueue: Found '%1' Job for %2 in %3 state.")
1392 thisJob.
args = query.
value(10).toString();
1399 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1400 QString(
"GetJobsInQueue: Unknown Job Type: %1")
1401 .arg(thisJob.
type));
1405 jobs[jobCount++] = thisJob;
1415 if (!newHostname.isEmpty())
1417 query.
prepare(
"UPDATE jobqueue SET hostname = :NEWHOSTNAME "
1418 "WHERE hostname = :EMPTY AND id = :ID;");
1419 query.
bindValue(
":NEWHOSTNAME", newHostname);
1425 query.
prepare(
"UPDATE jobqueue SET hostname = :EMPTY "
1434 "Unable to set hostname to '%1' for "
1435 "job %2.").arg(newHostname).arg(
jobID),
1445 QString allowSetting;
1466 case JOB_PREVIEW: allowSetting =
"JobAllowPreview";
1468 default:
return false;
1479 query.
prepare(
"SELECT cmds FROM jobqueue WHERE id = :ID;");
1500 query.
prepare(
"SELECT args FROM jobqueue WHERE id = :ID;");
1507 return query.
value(0).toString();
1521 query.
prepare(
"SELECT flags FROM jobqueue WHERE id = :ID;");
1542 query.
prepare(
"SELECT status FROM jobqueue WHERE id = :ID;");
1559 int jobType,
uint chanid,
const QDateTime &recstartts)
1563 query.
prepare(
"SELECT status FROM jobqueue WHERE type = :TYPE "
1564 "AND chanid = :CHANID AND starttime = :STARTTIME;");
1568 query.
bindValue(
":STARTTIME", recstartts);
1584 QMap<int, JobQueueEntry> jobs;
1588 msg = QString(
"RecoverQueue: Checking for unfinished jobs to "
1590 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + msg);
1596 QMap<int, JobQueueEntry>::Iterator it;
1600 for (it = jobs.begin(); it != jobs.end(); ++it)
1602 int tmpCmds = (*it).cmds;
1603 int tmpStatus = (*it).status;
1606 logInfo = QString(
"jobID #%1").arg((*it).id);
1608 logInfo = QString(
"chanid %1 @ %2").arg((*it).chanid)
1609 .arg((*it).startts);
1611 if (((tmpStatus == JOB_STARTING) ||
1612 (tmpStatus == JOB_RUNNING) ||
1613 (tmpStatus == JOB_PAUSED) ||
1615 (tmpStatus == JOB_STOPPING)) &&
1618 ((*it).statustime < oldDate)))
1620 msg = QString(
"RecoverQueue: Recovering '%1' for %2 "
1624 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + msg);
1634 msg = QString(
"RecoverQueue: Ignoring '%1' for %2 "
1638 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + msg);
1651 delquery.
prepare(
"DELETE FROM jobqueue "
1652 "WHERE (status in (:FINISHED, :ABORTED, :CANCELLED) "
1653 "AND statustime < :DONEPURGEDATE) "
1654 "OR (status in (:ERRORED) "
1655 "AND statustime < :ERRORSPURGEDATE) ");
1656 delquery.
bindValue(
":FINISHED", JOB_FINISHED);
1657 delquery.
bindValue(
":ABORTED", JOB_ABORTED);
1658 delquery.
bindValue(
":CANCELLED", JOB_CANCELLED);
1659 delquery.
bindValue(
":ERRORED", JOB_ERRORED);
1660 delquery.
bindValue(
":DONEPURGEDATE", donePurgeDate);
1661 delquery.
bindValue(
":ERRORSPURGEDATE", errorsPurgeDate);
1663 if (!delquery.
exec())
1666 "old finished jobs.", delquery);
1672 if (!jobstarttsRaw.isValid())
1674 jobstarttsRaw = QDateTime::currentDateTime();
1675 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + QString(
"Invalid date/time passed, "
1677 jobstarttsRaw.toString()));
1683 "JobQueueWindowStart",
hostname,
"00:00")));
1686 "JobQueueWindowEnd",
hostname,
"23:59")));
1690 if (scheduleTime < windowStart || scheduleTime > windowEnd)
1692 LOG(VB_JOBQUEUE, LOG_ERR,
LOC +
"Time not within job queue window, " +
1706 LOG(VB_JOBQUEUE, LOG_ERR,
LOC +
1707 "ProcessJob(): Unable to open database connection");
1720 LOG(VB_JOBQUEUE, LOG_ERR,
LOC +
1721 QString(
"Unable to retrieve program info for chanid %1 @ %2")
1726 tr(
"Unable to retrieve program info from database"));
1756 tr(
"Program has been deleted"));
1781 tr(
"UNKNOWN JobType, unable to process!"));
1795 pthread_attr_t attr;
1796 pthread_attr_init(&attr);
1797 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
1798 pthread_create(&childThread, &attr, ChildThreadRoutine, jts);
1799 pthread_attr_destroy(&attr);
1807 return "Commercial Detection";
1809 return "Unknown Job";
1811 QString descSetting =
1825 if (command.trimmed().isEmpty())
1826 command =
"mythtranscode";
1828 if (command ==
"mythtranscode")
1834 if (command.trimmed().isEmpty())
1835 command =
"mythcommflag";
1837 if (command ==
"mythcommflag")
1846 if (!command.isEmpty())
1848 command.replace(
"%JOBID%", QString(
"%1").arg(
id));
1851 if (!command.isEmpty() && tmpInfo)
1855 command.replace(
"%VERBOSELEVEL%", QString(
"%1").arg(
verboseMask));
1859 command.replace(
"%TRANSPROFILE%",
1861 "autodetect" : QString::number(transcoder));
1891 const char *m_suffix;
1895 static constexpr std::array<const PpTab_t,9> kPpTab {{
1896 { .m_suffix=
"bytes", .m_max=9999, .m_precision=0 },
1897 { .m_suffix=
"kB", .m_max=999, .m_precision=0 },
1898 { .m_suffix=
"MB", .m_max=999, .m_precision=1 },
1899 { .m_suffix=
"GB", .m_max=999, .m_precision=1 },
1900 { .m_suffix=
"TB", .m_max=999, .m_precision=1 },
1901 { .m_suffix=
"PB", .m_max=999, .m_precision=1 },
1902 { .m_suffix=
"EB", .m_max=999, .m_precision=1 },
1903 { .m_suffix=
"ZB", .m_max=999, .m_precision=1 },
1904 { .m_suffix=
"YB", .m_max=0, .m_precision=0 },
1906 float fbytes =
bytes;
1908 unsigned int ii = 0;
1909 while (kPpTab[ii].m_max && fbytes > kPpTab[ii].m_max) {
1914 return QString(
"%1 %2")
1915 .arg(fbytes, 0,
'f', kPpTab[ii].m_precision)
1916 .arg(kPpTab[ii].m_suffix);
1939 LOG(VB_JOBQUEUE, LOG_ERR,
LOC +
1940 "The JobQueue cannot currently transcode files that do not "
1941 "have a chanid/starttime in the recorded table.");
1956 bool useCutlist = program_info->
HasCutlist() &&
1960 QString profilearg =
1962 "autodetect" : QString::number(transcoder);
1971 command = QString(
"%1 -j %2 --profile %3")
1972 .arg(path).arg(
jobID).arg(profilearg);
1974 command +=
" --honorcutlist";
1981 QStringList tokens = command.split(
" ", Qt::SkipEmptyParts);
1982 if (!tokens.empty())
1993 QString transcoderName;
1996 transcoderName =
"Autodetect";
2001 query.
prepare(
"SELECT name FROM recordingprofiles WHERE id = :ID;");
2005 transcoderName = query.
value(0).toString();
2010 transcoderName = QString(
"Autodetect(%1)").arg(transcoder);
2025 long long filesize = 0;
2026 long long origfilesize = QFileInfo(
filename).size();
2028 QString msg = QString(
"Transcode %1")
2031 QString details = QString(
"%1: %2 (%3)")
2035 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"%1 for %2")
2036 .arg(msg, details));
2038 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + QString(
"Running command: '%1'")
2041 GetMythDB()->GetDBManager()->CloseDatabases();
2049 tr(
"ERROR: Unable to find mythtranscode, check backend logs."));
2053 details = QString(
"%1: %2 does not exist or is not executable")
2056 LOG(VB_GENERAL, LOG_ERR,
LOC +
2057 QString(
"%1 for %2").arg(msg, details));
2061 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
"Transcode command restarting");
2069 if (status == JOB_FINISHED)
2080 filesize = st.size();
2083 QString comment = tr(
"%1: %2 => %3")
2084 .arg(transcoderName,
2092 details = QString(
"%1: %2 (%3)")
2101 QString(
"could not stat '%1'").arg(
filename);
2105 details = QString(
"%1: %2")
2117 QString comment = tr(
"exit status %1, job status was \"%2\"")
2123 details = QString(
"%1: %2 (%3)")
2131 LOG(VB_GENERAL, LOG_INFO,
LOC + msg +
": " + details);
2135 if (retrylimit == 0)
2137 LOG(VB_JOBQUEUE, LOG_ERR,
LOC +
"Retry limit exceeded for transcoder, "
2138 "setting job status to errored.");
2165 LOG(VB_JOBQUEUE, LOG_ERR,
LOC +
2166 "The JobQueue cannot currently perform lookups for items which do "
2167 "not have a chanid/starttime in the recorded table.");
2177 QString details = QString(
"%1 recorded from channel %3")
2183 QString msg = QString(
"Metadata Lookup failed. Could not open "
2184 "new database connection for %1. "
2185 "Program cannot be looked up.")
2187 LOG(VB_GENERAL, LOG_ERR,
LOC + msg);
2190 tr(
"Could not open new database connection for "
2191 "metadata lookup."));
2193 delete program_info;
2197 LOG(VB_GENERAL, LOG_INFO,
2198 LOC +
"Metadata Lookup Starting for " + details);
2205 command = QString(
"%1 -j %2")
2206 .arg(path).arg(
jobID);
2209 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + QString(
"Running command: '%1'")
2212 GetMythDB()->GetDBManager()->CloseDatabases();
2214 int priority = LOG_NOTICE;
2222 comment = tr(
"Unable to find mythmetadatalookup");
2224 priority = LOG_WARNING;
2228 comment = tr(
"Aborted by user");
2230 priority = LOG_WARNING;
2234 comment = tr(
"Unable to open file or init decoder");
2236 priority = LOG_WARNING;
2240 comment = tr(
"Failed with exit status %1").arg(retVal);
2242 priority = LOG_WARNING;
2246 comment = tr(
"Metadata Lookup Complete.");
2252 QString msg = tr(
"Metadata Lookup %1",
"Job ID")
2255 if (!comment.isEmpty())
2256 details += QString(
" (%1)").arg(comment);
2258 if (priority <= LOG_WARNING)
2259 LOG(VB_GENERAL, LOG_ERR,
LOC + msg +
": " + details);
2285 LOG(VB_JOBQUEUE, LOG_ERR,
LOC +
2286 "The JobQueue cannot currently commflag files that do not "
2287 "have a chanid/starttime in the recorded table.");
2297 QString details = QString(
"%1 recorded from channel %3")
2303 QString msg = QString(
"Commercial Detection failed. Could not open "
2304 "new database connection for %1. "
2305 "Program cannot be flagged.")
2307 LOG(VB_GENERAL, LOG_ERR,
LOC + msg);
2310 tr(
"Could not open new database connection for "
2311 "commercial detector."));
2313 delete program_info;
2317 LOG(VB_GENERAL, LOG_INFO,
2318 LOC +
"Commercial Detection Starting for " + details);
2320 uint breaksFound = 0;
2328 command = QString(
"%1 -j %2 --noprogress")
2329 .arg(path).arg(
jobID);
2335 QStringList tokens = command.split(
" ", Qt::SkipEmptyParts);
2336 if (!tokens.empty())
2341 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + QString(
"Running command: '%1'")
2344 GetMythDB()->GetDBManager()->CloseDatabases();
2346 int priority = LOG_NOTICE;
2354 comment = tr(
"Unable to find mythcommflag");
2356 priority = LOG_WARNING;
2360 comment = tr(
"Aborted by user");
2362 priority = LOG_WARNING;
2366 comment = tr(
"Unable to open file or init decoder");
2368 priority = LOG_WARNING;
2372 comment = tr(
"Failed with exit status %1").arg(breaksFound);
2374 priority = LOG_WARNING;
2378 comment = tr(
"%n commercial break(s)",
"", breaksFound);
2394 QString msg = tr(
"Commercial Detection %1",
"Job ID")
2397 if (!comment.isEmpty())
2398 details += QString(
" (%1)").arg(comment);
2400 if (priority <= LOG_WARNING)
2401 LOG(VB_GENERAL, LOG_ERR,
LOC + msg +
": " + details);
2435 msg = QString(
"Started %1 for %2 recorded from channel %3")
2442 msg = QString(
"Started %1 for jobID %2").arg(jobDesc).arg(
jobID);
2445 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(msg.toLocal8Bit().constData()));
2459 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + QString(
"Running command: '%1'")
2461 GetMythDB()->GetDBManager()->CloseDatabases();
2467 msg = QString(
"User Job '%1' failed, unable to find "
2468 "executable, check your PATH and backend logs.")
2470 LOG(VB_GENERAL, LOG_ERR,
LOC + msg);
2471 LOG(VB_GENERAL, LOG_NOTICE,
LOC + QString(
"Current PATH: '%1'")
2472 .arg(qEnvironmentVariable(
"PATH")));
2475 tr(
"ERROR: Unable to find executable, check backend logs."));
2477 else if (result != 0)
2479 msg = QString(
"User Job '%1' failed.").arg(command);
2480 LOG(VB_GENERAL, LOG_ERR,
LOC + msg);
2483 tr(
"ERROR: User Job returned non-zero, check logs."));
2489 msg = QString(
"Finished %1 for %2 recorded from channel %3")
2496 msg = QString(
"Finished %1 for jobID %2").arg(jobDesc).arg(
jobID);
2499 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(msg.toLocal8Bit().constData()));
2516 while ((x != 0) && ((x & 0x01) == 0))
static bool QueueRecordingJobs(const RecordingInfo &recinfo, int jobTypes=JOB_NONE)
QMap< int, RunningJobInfo > m_runningJobs
static bool ChangeJobHost(int jobID, const QString &newHostname)
static bool ChangeJobFlags(int jobID, int newFlags)
static void RecoverQueue(bool justOld=false)
static QString GetJobCommand(int id, int jobType, ProgramInfo *tmpInfo)
static bool RestartJob(int jobID)
static bool SafeDeleteJob(int jobID, int jobType, int chanid, const QDateTime &recstartts)
static bool ChangeJobCmds(int jobID, int newCmds)
static QString GetJobArgs(int jobID)
QWaitCondition m_queueThreadCond
static void CleanupOldJobsInQueue()
void DoUserJobThread(int jobID)
bool AllowedToRun(const JobQueueEntry &job)
static bool GetJobInfoFromID(int jobID, int &jobType, uint &chanid, QDateTime &recstartts)
static void * TranscodeThread(void *param)
static void * UserJobThread(void *param)
static bool InJobRunWindow(QDateTime jobstarttsRaw)
static int GetJobsInQueue(QMap< int, JobQueueEntry > &jobs, int findJobs=JOB_LIST_NOT_DONE)
void DoFlagCommercialsThread(int jobID)
static enum JobFlags GetJobFlags(int jobID)
static QString JobText(int jobType)
QRecursiveMutex * m_runningJobsLock
static bool ChangeJobArgs(int jobID, const QString &args="")
static enum JobCmds GetJobCmd(int jobID)
static bool DeleteAllJobs(uint chanid, const QDateTime &recstartts)
static bool DeleteJob(int jobID)
static bool QueueJob(int jobType, uint chanid, const QDateTime &recstartts, const QString &args="", const QString &comment="", QString host="", int flags=0, int status=JOB_QUEUED, QDateTime schedruntime=QDateTime())
static bool IsJobStatusQueued(int status)
static int GetJobID(int jobType, uint chanid, const QDateTime &recstartts)
void StartChildJob(void *(*ChildThreadRoutine)(void *), int jobID)
static bool StopJob(int jobID)
QMutex m_queueThreadCondLock
static bool ResumeJob(int jobID)
static bool ChangeJobComment(int jobID, const QString &comment="")
void ProcessJob(const JobQueueEntry &job)
static bool IsJobQueuedOrRunning(int jobType, uint chanid, const QDateTime &recstartts)
static void * FlagCommercialsThread(void *param)
static bool IsJobRunning(int jobType, uint chanid, const QDateTime &recstartts)
static QString GetJobDescription(int jobType)
static bool ChangeJobStatus(int jobID, int newStatus, const QString &comment="")
static void * MetadataLookupThread(void *param)
static QString PrettyPrint(off_t bytes)
static enum JobStatus GetJobStatus(int jobID)
void RemoveRunningJob(int id)
int GetRunningJobID(uint chanid, const QDateTime &recstartts)
static bool PauseJob(int jobID)
static bool IsJobQueued(int jobType, uint chanid, const QDateTime &recstartts)
void DoMetadataLookupThread(int jobID)
void DoTranscodeThread(int jobID)
static bool QueueJobs(int jobTypes, uint chanid, const QDateTime &recstartts, const QString &args="", const QString &comment="", const QString &host="")
static QString StatusText(int status)
static int GetJobTypeFromName(const QString &name)
static bool HasRunningOrPendingJobs(std::chrono::minutes startingWithinMins=0min)
static int UserJobTypeToIndex(int JobType)
static bool IsJobStatusRunning(int status)
void customEvent(QEvent *e) override
QSqlQuery wrapper that fetches a DB connection from the connection pool.
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
QVariant value(int i) const
static bool testDBConnection()
Checks DB connection + login (login info via Mythcontext)
int numRowsAffected() const
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
This is a wrapper around QThread that does several additional things.
static void ThreadCleanup(void)
This is to be called on exit in those few threads that haven't been ported to MThread.
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
static void ThreadSetup(const QString &name)
This is to be called on startup in those few threads that haven't been ported to MThread.
QString GetHostName(void)
QString GetSetting(const QString &key, const QString &defaultval="")
QString GetSettingOnHost(const QString &key, const QString &host, const QString &defaultval="")
T GetDurSetting(const QString &key, T defaultval=T::zero())
void dispatch(const MythEvent &event)
int GetNumSetting(const QString &key, int defaultval=0)
bool IsBlockingClient(void) const
is this client blocking shutdown
bool GetBoolSetting(const QString &key, bool defaultval=false)
static void DBError(const QString &where, const MSqlQuery &query)
This class is used as a container for messages.
const QString & Message() const
static const Type kMythEventMessage
void addListener(QObject *listener)
Add a listener to the observable.
void removeListener(QObject *listener)
Remove a listener to the observable.
This class creates a preview image of a recording.
Holds information on recordings and videos.
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
void SaveTranscodeStatus(TranscodingStatus trans)
Set "transcoded" field in "recorded" table to "trans".
QString toString(Verbosity v=kLongDescription, const QString &sep=":", const QString &grp="\"") const
virtual void SaveFilesize(uint64_t fsize)
Sets recording file size in database, and sets "filesize" field.
QString GetRecordingGroup(void) const
uint QueryTranscoderID(void) const
bool HasCutlist(void) const
QString GetHostname(void) const
void MarkAsInUse(bool inuse, const QString &usedFor="")
Tracks a recording's in use status, to prevent deletion and to allow the storage scheduler to perform...
QDateTime GetRecordingStartTime(void) const
Approximate time the recording started.
bool IsCommercialFree(void) const
virtual void SubstituteMatches(QString &str)
Subsitute MATCH% type variable names in the given string.
QString GetPlaybackURL(bool checkMaster=false, bool forceCheckLocal=false)
Returns filename or URL to be used to play back this recording.
void SendUpdateEvent(void) const
Sends event out that the ProgramInfo should be reloaded.
void SetPathname(const QString &pn)
Holds information on a TV Program one might wish to record.
int GetAutoRunJobs(void) const
Returns a bitmap of which jobs are attached to this RecordingInfo.
static const uint kTranscoderAutodetect
sentinel value
@ GENERIC_EXIT_RESTART
Need to restart transcoding.
@ GENERIC_EXIT_CMD_NOT_FOUND
Command not found.
@ GENERIC_EXIT_DAEMONIZING_ERROR
Error daemonizing or execl.
@ GENERIC_EXIT_NO_RECORDING_DATA
No program/recording data.
@ GENERIC_EXIT_NOT_OK
Exited with error.
static constexpr int64_t kRecentInterval
static constexpr int PTHREAD_NULL
#define JOBSTATUS_STATUSTEXT(A, B, C)
static QMap< QString, int > JobNameToType
static constexpr const char * MYTH_APPNAME_MYTHJOBQUEUE
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
QString GetAppBinDir(void)
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
bool myth_ioprio(int)
Allows setting the I/O priority of the current process/thread.
@ kMSLowExitVal
allow exit values 0-127 only
uint myth_system(const QString &command, uint flags, std::chrono::seconds timeout)
QDateTime as_utc(const QDateTime &old_dt)
Returns copy of QDateTime with TimeSpec set to UTC.
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
@ kFilename
Default UTC, "yyyyMMddhhmmss".
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
const QString kJobQueueInUseID
@ TRANSCODING_NOT_TRANSCODED