13#include <QCoreApplication>
20#include "libmythbase/mythconfig.h"
35#define LOC QString("JobQueue: ")
42 m_runningJobsLock(new QRecursiveMutex()),
44 m_queueThread(new
MThread(
"JobQueue", this))
54 LOG(VB_GENERAL, LOG_ERR,
LOC +
55 "The JobQueue has been disabled because "
56 "you compiled with the --enable-valgrind option.");
85 QString message = me->
Message();
87 if (message.startsWith(
"LOCAL_JOB"))
92 message = message.simplified();
93 QStringList tokens = message.split(
" ", Qt::SkipEmptyParts);
94 const QString&
action = tokens[1];
97 if (tokens[2] ==
"ID")
98 jobID = tokens[3].toInt();
110 msg = QString(
"Unable to determine jobID for message: "
111 "%1. Program will not be flagged.")
113 LOG(VB_GENERAL, LOG_ERR,
LOC + msg);
119 msg = QString(
"Received message '%1'").arg(message);
120 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + msg);
131 else if (
action ==
"PAUSE")
133 else if (
action ==
"RESUME")
135 else if (
action ==
"RESTART")
161 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
"ProcessQueue() started");
167 QMap<int, int> jobStatus;
169 QMap<int, JobQueueEntry> jobs;
171 QMap<int, RunningJobInfo>::Iterator rjiter;
178 bool startedJobAlready =
false;
181 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
182 QString(
"Currently set to run up to %1 job(s) max.")
191 if ((*rjiter).pginfo)
192 (*rjiter).pginfo->UpdateInUseMark();
202 for (
const auto & job : std::as_const(jobs))
204 int status = job.status;
207 if (((status == JOB_RUNNING) ||
208 (status == JOB_STARTING) ||
209 (status == JOB_PAUSED)) &&
214 message = QString(
"Currently Running %1 jobs.")
218 message += QString(
" Jobs in Queue, but we are outside of the "
219 "Job Queue time window, no new jobs can be "
221 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
225 message +=
" (At Maximum, no new jobs can be started until "
226 "a running job completes)";
229 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
235 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
243 int jobID = jobs[x].id;
244 int cmds = jobs[x].cmds;
246 int status = jobs[x].status;
250 logInfo = QString(
"jobID #%1").arg(
jobID);
252 logInfo = QString(
"chanid %1 @ %2").arg(jobs[x].chanid)
253 .arg(jobs[x].startts);
256 if ((inTimeWindow) &&
263 jobStatus[
jobID] = status;
265 message = QString(
"Skipping '%1' job for %2, "
266 "should run on '%3' instead")
269 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
278 if (otherJobID && (jobStatus.contains(otherJobID)) &&
279 (!(jobStatus[otherJobID] & JOB_DONE)))
282 QString(
"Skipping '%1' job for %2, "
283 "Job ID %3 is already running for "
284 "this recording with a status of '%4'")
286 QString::number(otherJobID),
288 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
293 jobStatus[
jobID] = status;
298 message = QString(
"Skipping '%1' job for %2, "
299 "not allowed to run on this backend.")
301 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
308 message = QString(
"Skipping '%1' job for %2, this job is "
309 "not scheduled to run until %3.")
313 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
321 if (status != JOB_QUEUED) {
322 message = QString(
"Stopping '%1' job for %2")
324 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
339 message = QString(
"Cancelling '%1' job for %2")
341 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
349 message = QString(
"Unable to claim '%1' job for %2")
351 LOG(VB_JOBQUEUE, LOG_ERR,
LOC + message);
360 if ((cmds &
JOB_PAUSE) && (status != JOB_QUEUED))
362 message = QString(
"Pausing '%1' job for %2")
364 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
375 if ((cmds &
JOB_RESTART) && (status != JOB_QUEUED))
377 message = QString(
"Restart '%1' job for %2")
379 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
390 if (status != JOB_QUEUED)
395 message = QString(
"Resetting '%1' job for %2 to %3 "
396 "status, because no hostname is set.")
400 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
405 else if (inTimeWindow)
407 message = QString(
"Skipping '%1' job for %2, "
408 "current job status is '%3'")
412 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
418 if (startedJobAlready)
421 if ((inTimeWindow) &&
425 message = QString(
"Unable to claim '%1' job for %2")
427 LOG(VB_JOBQUEUE, LOG_ERR,
LOC + message);
433 message = QString(
"Skipping '%1' job for %2, "
434 "current time is outside of the "
435 "Job Queue processing window.")
437 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
441 message = QString(
"Processing '%1' job for %2, "
442 "current status is '%3'")
445 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
449 startedJobAlready =
true;
460 LOG(VB_JOBQUEUE, LOG_INFO, QString(
"%1 jobs running. "
469 LOG(VB_JOBQUEUE, LOG_INFO,
"No jobs running. "
470 "Allowing shutdown.");
479 std::chrono::milliseconds st = (startedJobAlready) ? 5s : sleepTime;
492 jobTypes &= (~JOB_COMMFLAG);
496 QString jobHost = QString(
"");
509 const QString&
args,
const QString& comment, QString host,
510 int flags,
int status, QDateTime schedruntime)
512 int tmpStatus = JOB_UNKNOWN;
513 int tmpCmd = JOB_UNKNOWN;
516 if(!schedruntime.isValid())
525 query.
prepare(
"SELECT status, id, cmds FROM jobqueue "
526 "WHERE chanid = :CHANID AND starttime = :STARTTIME "
527 "AND type = :JOBTYPE;");
529 query.
bindValue(
":STARTTIME", recstartts);
539 tmpStatus = query.
value(0).toInt();
541 tmpCmd = query.
value(2).toInt();
558 if (! (tmpStatus & JOB_DONE) && (tmpCmd &
JOB_STOP))
567 query.
prepare(
"INSERT INTO jobqueue (chanid, starttime, inserttime, type, "
568 "status, statustime, schedruntime, hostname, args, comment, "
570 "VALUES (:CHANID, :STARTTIME, now(), :JOBTYPE, :STATUS, "
571 "now(), :SCHEDRUNTIME, :HOST, :ARGS, :COMMENT, :FLAGS);");
574 query.
bindValue(
":STARTTIME", recstartts);
577 query.
bindValue(
":SCHEDRUNTIME", schedruntime);
593 const QString&
args,
const QString& comment,
const QString& host)
617#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
618 schedruntime = QDateTime(schedruntime.addDays(defer).date(),
619 QTime(0,0,0), Qt::UTC);
621 schedruntime = QDateTime(schedruntime.addDays(defer).date(),
623 QTimeZone(QTimeZone::UTC));
628 0, JOB_QUEUED, schedruntime);
648 query.
prepare(
"SELECT id FROM jobqueue "
649 "WHERE chanid = :CHANID AND starttime = :STARTTIME "
650 "AND type = :JOBTYPE;");
652 query.
bindValue(
":STARTTIME", recstartts);
661 return query.
value(0).toInt();
666 int jobID,
int &jobType,
uint &chanid, QDateTime &recstartts)
670 query.
prepare(
"SELECT type, chanid, starttime FROM jobqueue "
682 jobType = query.
value(0).toInt();
683 chanid = query.
value(1).toUInt();
691 int jobID,
int &jobType,
uint &chanid, QString &recstartts)
693 QDateTime tmpStarttime;
696 jobID, jobType, chanid, tmpStarttime);
708 LOG(VB_GENERAL, LOG_ERR, QString(
"'%1' is an invalid Job Name.")
717 QString message = QString(
"GLOBAL_JOB PAUSE ID %1").arg(
jobID);
726 QString message = QString(
"GLOBAL_JOB RESUME ID %1").arg(
jobID);
735 QString message = QString(
"GLOBAL_JOB RESTART ID %1").arg(
jobID);
744 QString message = QString(
"GLOBAL_JOB STOP ID %1").arg(
jobID);
756 query.
prepare(
"UPDATE jobqueue SET status = :CANCELLED "
757 "WHERE chanid = :CHANID AND starttime = :STARTTIME "
758 "AND status = :QUEUED;");
760 query.
bindValue(
":CANCELLED", JOB_CANCELLED);
762 query.
bindValue(
":STARTTIME", recstartts);
768 query.
prepare(
"UPDATE jobqueue SET cmds = :CMD "
769 "WHERE chanid = :CHANID AND starttime = :STARTTIME "
770 "AND status <> :CANCELLED;");
773 query.
bindValue(
":STARTTIME", recstartts);
774 query.
bindValue(
":CANCELLED", JOB_CANCELLED);
783 bool jobsAreRunning =
true;
784 std::chrono::seconds totalSlept = 0s;
785 std::chrono::seconds maxSleep = 90s;
786 while (jobsAreRunning && totalSlept < maxSleep)
789 query.
prepare(
"SELECT id FROM jobqueue "
790 "WHERE chanid = :CHANID and starttime = :STARTTIME "
792 "(:FINISHED,:ABORTED,:ERRORED,:CANCELLED);");
794 query.
bindValue(
":STARTTIME", recstartts);
795 query.
bindValue(
":FINISHED", JOB_FINISHED);
796 query.
bindValue(
":ABORTED", JOB_ABORTED);
797 query.
bindValue(
":ERRORED", JOB_ERRORED);
798 query.
bindValue(
":CANCELLED", JOB_CANCELLED);
806 if (query.
size() == 0)
808 jobsAreRunning =
false;
811 if ((totalSlept % 5s) == 0s)
813 message = QString(
"Waiting on %1 jobs still running for "
814 "chanid %2 @ %3").arg(query.
size())
815 .arg(chanid).arg(recstartts.toString(
Qt::ISODate));
816 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
823 if (totalSlept <= maxSleep)
825 query.
prepare(
"DELETE FROM jobqueue "
826 "WHERE chanid = :CHANID AND starttime = :STARTTIME;");
828 query.
bindValue(
":STARTTIME", recstartts);
835 query.
prepare(
"SELECT id, type, status, comment FROM jobqueue "
836 "WHERE chanid = :CHANID AND starttime = :STARTTIME "
837 "AND status <> :CANCELLED ORDER BY id;");
840 query.
bindValue(
":STARTTIME", recstartts);
841 query.
bindValue(
":CANCELLED", JOB_CANCELLED);
846 "to query list of Jobs left in Queue.", query);
850 LOG(VB_GENERAL, LOG_ERR,
LOC +
851 QString(
"In DeleteAllJobs: There are Jobs "
852 "left in the JobQueue that are still running for "
853 "chanid %1 @ %2.").arg(chanid)
858 LOG(VB_GENERAL, LOG_ERR,
LOC +
859 QString(
"Job ID %1: '%2' with status '%3' and comment '%4'")
860 .arg(query.
value(0).toString(),
863 query.
value(3).toString()));
878 const QDateTime& recstartts)
886 int thisJob =
GetJobID(jobType, chanid, recstartts);
889 if( thisJob !=
jobID)
891 msg = QString(
"JobType, chanid and starttime don't match jobID %1");
898 msg = QString(
"Can't remove running JobID %1");
906 query.
prepare(
"DELETE FROM jobqueue WHERE id = :ID;");
926 query.
prepare(
"UPDATE jobqueue SET cmds = :CMDS WHERE id = :ID;");
941 const QDateTime &recstartts,
int newCmds)
945 query.
prepare(
"UPDATE jobqueue SET cmds = :CMDS WHERE type = :TYPE "
946 "AND chanid = :CHANID AND starttime = :STARTTIME;");
951 query.
bindValue(
":STARTTIME", recstartts);
969 query.
prepare(
"UPDATE jobqueue SET flags = :FLAGS WHERE id = :ID;");
988 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + QString(
"ChangeJobStatus(%1, %2, '%3')")
993 query.
prepare(
"UPDATE jobqueue SET status = :STATUS, comment = :COMMENT "
994 "WHERE id = :ID AND status <> :NEWSTATUS;");
999 query.
bindValue(
":NEWSTATUS", newStatus);
1015 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + QString(
"ChangeJobComment(%1, '%2')")
1016 .arg(
jobID).arg(comment));
1020 query.
prepare(
"UPDATE jobqueue SET comment = :COMMENT "
1042 query.
prepare(
"UPDATE jobqueue SET args = :ARGS "
1062 if ((jInfo.pginfo->GetChanID() == chanid) &&
1063 (jInfo.pginfo->GetRecordingStartTime() == recstartts))
1077 return (status == JOB_QUEUED);
1082 return ((status != JOB_UNKNOWN) && (status != JOB_QUEUED) &&
1083 ((status & JOB_DONE) == 0));
1087 uint chanid,
const QDateTime &recstartts)
1099 int jobType,
uint chanid,
const QDateTime &recstartts)
1101 int tmpStatus =
GetJobStatus(jobType, chanid, recstartts);
1103 return (tmpStatus != JOB_UNKNOWN) && ((tmpStatus & JOB_DONE) == 0);
1107 int jobType,
uint chanid,
const QDateTime &recstartts)
1119 case JOB_PREVIEW:
return tr(
"Preview Generation");
1124 QString settingName =
1129 return tr(
"Unknown Job");
1133#define JOBSTATUS_STATUSTEXT(A,B,C) case A: return C;
1142 return tr(
"Undefined");
1147 QString queueStartTimeStr;
1148 QString queueEndTimeStr;
1149 QTime queueStartTime;
1151 QTime curTime = QTime::currentTime();
1152 bool inTimeWindow =
false;
1153 orStartsWithinMins = orStartsWithinMins < 0min ? 0min : orStartsWithinMins;
1159 if (!queueStartTime.isValid())
1161 LOG(VB_GENERAL, LOG_ERR,
1162 QString(
"Invalid JobQueueWindowStart time '%1', using 00:00")
1163 .arg(queueStartTimeStr));
1164 queueStartTime = QTime(0, 0);
1168 if (!queueEndTime.isValid())
1170 LOG(VB_GENERAL, LOG_ERR,
1171 QString(
"Invalid JobQueueWindowEnd time '%1', using 23:59")
1172 .arg(queueEndTimeStr));
1173 queueEndTime = QTime(23, 59);
1176 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1177 QString(
"Currently set to run new jobs from %1 to %2")
1178 .arg(queueStartTimeStr, queueEndTimeStr));
1180 if ((queueStartTime <= curTime) && (curTime < queueEndTime))
1182 inTimeWindow =
true;
1184 else if ((queueStartTime > queueEndTime) &&
1185 ((curTime < queueEndTime) || (queueStartTime <= curTime)))
1187 inTimeWindow =
true;
1189 else if (orStartsWithinMins > 0min)
1192 if (curTime <= queueStartTime)
1195 if (queueStartTime.secsTo(curTime) <= duration_cast<std::chrono::seconds>(orStartsWithinMins).count())
1197 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1198 QString(
"Job run window will start within %1 minutes")
1199 .arg(orStartsWithinMins.count()));
1200 inTimeWindow =
true;
1207#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
1208 QDateTime startDateTime = QDateTime(
1209 curDateTime.date(), queueStartTime, Qt::UTC).addDays(1);
1211 QDateTime startDateTime =
1212 QDateTime(curDateTime.date(), queueStartTime,
1213 QTimeZone(QTimeZone::UTC)).addDays(1);
1216 if (curDateTime.secsTo(startDateTime) <= duration_cast<std::chrono::seconds>(orStartsWithinMins).count())
1218 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1219 QString(
"Job run window will start "
1220 "within %1 minutes (tomorrow)")
1221 .arg(orStartsWithinMins.count()));
1222 inTimeWindow =
true;
1227 return inTimeWindow;
1234 QMap<int, JobQueueEntry> jobs;
1235 QMap<int, JobQueueEntry>::Iterator it;
1237 bool checkForQueuedJobs = (startingWithinMins <= 0min
1240 if (checkForQueuedJobs && startingWithinMins > 0min) {
1241 maxSchedRunTime = maxSchedRunTime.addSecs(duration_cast<std::chrono::seconds>(startingWithinMins).count());
1242 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1243 QString(
"HasRunningOrPendingJobs: checking for jobs "
1244 "starting before: %1")
1250 if (!jobs.empty()) {
1251 for (it = jobs.begin(); it != jobs.end(); ++it)
1253 int tmpStatus = (*it).status;
1254 if (tmpStatus == JOB_RUNNING) {
1255 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1256 QString(
"HasRunningOrPendingJobs: found running job"));
1260 if (checkForQueuedJobs) {
1261 if ((tmpStatus != JOB_UNKNOWN) && (!(tmpStatus & JOB_DONE))) {
1262 if (startingWithinMins <= 0min) {
1263 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1264 "HasRunningOrPendingJobs: found pending job");
1267 if ((*it).schedruntime <= maxSchedRunTime) {
1268 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1269 QString(
"HasRunningOrPendingJobs: found pending "
1270 "job scheduled to start at: %1")
1289 bool commflagWhileRecording =
1294 query.
prepare(
"SELECT j.id, j.chanid, j.starttime, j.inserttime, j.type, "
1295 "j.cmds, j.flags, j.status, j.statustime, j.hostname, "
1296 "j.args, j.comment, r.endtime, j.schedruntime "
1298 "LEFT JOIN recorded r "
1299 " ON j.chanid = r.chanid AND j.starttime = r.starttime "
1300 "ORDER BY j.schedruntime, j.id;");
1305 "query list of Jobs in Queue.", query);
1309 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1310 QString(
"GetJobsInQueue: findJobs search bitmask %1, "
1311 "found %2 total jobs")
1312 .arg(findJobs).arg(query.
size()));
1314 while (query.
next())
1316 bool wantThisJob =
false;
1318 thisJob.
id = query.
value(0).toInt();
1328 if (query.
value(1).toInt() == -1)
1331 logInfo = QString(
"jobID #%1").arg(thisJob.
id);
1336 logInfo = QString(
"chanid %1 @ %2").arg(thisJob.
chanid)
1341 ((!commflagWhileRecording) ||
1345 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1346 QString(
"GetJobsInQueue: Ignoring '%1' Job "
1347 "for %2 in %3 state. Endtime in future.")
1355 (thisJob.
status & JOB_DONE)) ||
1357 (!(thisJob.
status & JOB_DONE))) ||
1359 (thisJob.
status == JOB_ERRORED)) ||
1366 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1367 QString(
"GetJobsInQueue: Ignore '%1' Job for %2 in %3 state.")
1373 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1374 QString(
"GetJobsInQueue: Found '%1' Job for %2 in %3 state.")
1382 thisJob.
args = query.
value(10).toString();
1389 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1390 QString(
"GetJobsInQueue: Unknown Job Type: %1")
1391 .arg(thisJob.
type));
1395 jobs[jobCount++] = thisJob;
1405 if (!newHostname.isEmpty())
1407 query.
prepare(
"UPDATE jobqueue SET hostname = :NEWHOSTNAME "
1408 "WHERE hostname = :EMPTY AND id = :ID;");
1409 query.
bindValue(
":NEWHOSTNAME", newHostname);
1415 query.
prepare(
"UPDATE jobqueue SET hostname = :EMPTY "
1424 "Unable to set hostname to '%1' for "
1425 "job %2.").arg(newHostname).arg(
jobID),
1435 QString allowSetting;
1456 case JOB_PREVIEW: allowSetting =
"JobAllowPreview";
1458 default:
return false;
1469 query.
prepare(
"SELECT cmds FROM jobqueue WHERE id = :ID;");
1490 query.
prepare(
"SELECT args FROM jobqueue WHERE id = :ID;");
1497 return query.
value(0).toString();
1511 query.
prepare(
"SELECT flags FROM jobqueue WHERE id = :ID;");
1532 query.
prepare(
"SELECT status FROM jobqueue WHERE id = :ID;");
1549 int jobType,
uint chanid,
const QDateTime &recstartts)
1553 query.
prepare(
"SELECT status FROM jobqueue WHERE type = :TYPE "
1554 "AND chanid = :CHANID AND starttime = :STARTTIME;");
1558 query.
bindValue(
":STARTTIME", recstartts);
1574 QMap<int, JobQueueEntry> jobs;
1578 msg = QString(
"RecoverQueue: Checking for unfinished jobs to "
1580 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + msg);
1586 QMap<int, JobQueueEntry>::Iterator it;
1590 for (it = jobs.begin(); it != jobs.end(); ++it)
1592 int tmpCmds = (*it).cmds;
1593 int tmpStatus = (*it).status;
1596 logInfo = QString(
"jobID #%1").arg((*it).id);
1598 logInfo = QString(
"chanid %1 @ %2").arg((*it).chanid)
1599 .arg((*it).startts);
1601 if (((tmpStatus == JOB_STARTING) ||
1602 (tmpStatus == JOB_RUNNING) ||
1603 (tmpStatus == JOB_PAUSED) ||
1605 (tmpStatus == JOB_STOPPING)) &&
1608 ((*it).statustime < oldDate)))
1610 msg = QString(
"RecoverQueue: Recovering '%1' for %2 "
1614 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + msg);
1624 msg = QString(
"RecoverQueue: Ignoring '%1' for %2 "
1628 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + msg);
1641 delquery.
prepare(
"DELETE FROM jobqueue "
1642 "WHERE (status in (:FINISHED, :ABORTED, :CANCELLED) "
1643 "AND statustime < :DONEPURGEDATE) "
1644 "OR (status in (:ERRORED) "
1645 "AND statustime < :ERRORSPURGEDATE) ");
1646 delquery.
bindValue(
":FINISHED", JOB_FINISHED);
1647 delquery.
bindValue(
":ABORTED", JOB_ABORTED);
1648 delquery.
bindValue(
":CANCELLED", JOB_CANCELLED);
1649 delquery.
bindValue(
":ERRORED", JOB_ERRORED);
1650 delquery.
bindValue(
":DONEPURGEDATE", donePurgeDate);
1651 delquery.
bindValue(
":ERRORSPURGEDATE", errorsPurgeDate);
1653 if (!delquery.
exec())
1656 "old finished jobs.", delquery);
1662 if (!jobstarttsRaw.isValid())
1664 jobstarttsRaw = QDateTime::currentDateTime();
1665 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + QString(
"Invalid date/time passed, "
1667 jobstarttsRaw.toString()));
1673 "JobQueueWindowStart",
hostname,
"00:00")));
1676 "JobQueueWindowEnd",
hostname,
"23:59")));
1680 if (scheduleTime < windowStart || scheduleTime > windowEnd)
1682 LOG(VB_JOBQUEUE, LOG_ERR,
LOC +
"Time not within job queue window, " +
1696 LOG(VB_JOBQUEUE, LOG_ERR,
LOC +
1697 "ProcessJob(): Unable to open database connection");
1710 LOG(VB_JOBQUEUE, LOG_ERR,
LOC +
1711 QString(
"Unable to retrieve program info for chanid %1 @ %2")
1716 tr(
"Unable to retrieve program info from database"));
1746 tr(
"Program has been deleted"));
1771 tr(
"UNKNOWN JobType, unable to process!"));
1784 pthread_t childThread = 0;
1785 pthread_attr_t attr;
1786 pthread_attr_init(&attr);
1787 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
1788 pthread_create(&childThread, &attr, ChildThreadRoutine, jts);
1789 pthread_attr_destroy(&attr);
1797 return "Commercial Detection";
1799 return "Unknown Job";
1801 QString descSetting =
1815 if (command.trimmed().isEmpty())
1816 command =
"mythtranscode";
1818 if (command ==
"mythtranscode")
1824 if (command.trimmed().isEmpty())
1825 command =
"mythcommflag";
1827 if (command ==
"mythcommflag")
1836 if (!command.isEmpty())
1838 command.replace(
"%JOBID%", QString(
"%1").arg(
id));
1841 if (!command.isEmpty() && tmpInfo)
1845 command.replace(
"%VERBOSELEVEL%", QString(
"%1").arg(
verboseMask));
1849 command.replace(
"%TRANSPROFILE%",
1851 "autodetect" : QString::number(transcoder));
1881 const char *m_suffix;
1885 static constexpr std::array<const PpTab_t,9> kPpTab {{
1886 {
"bytes", 9999, 0 },
1896 float fbytes =
bytes;
1898 unsigned int ii = 0;
1899 while (kPpTab[ii].m_max && fbytes > kPpTab[ii].m_max) {
1904 return QString(
"%1 %2")
1905 .arg(fbytes, 0,
'f', kPpTab[ii].m_precision)
1906 .arg(kPpTab[ii].m_suffix);
1929 LOG(VB_JOBQUEUE, LOG_ERR,
LOC +
1930 "The JobQueue cannot currently transcode files that do not "
1931 "have a chanid/starttime in the recorded table.");
1946 bool useCutlist = program_info->
HasCutlist() &&
1950 QString profilearg =
1952 "autodetect" : QString::number(transcoder);
1961 command = QString(
"%1 -j %2 --profile %3")
1962 .arg(path).arg(
jobID).arg(profilearg);
1964 command +=
" --honorcutlist";
1971 QStringList tokens = command.split(
" ", Qt::SkipEmptyParts);
1972 if (!tokens.empty())
1983 QString transcoderName;
1986 transcoderName =
"Autodetect";
1991 query.
prepare(
"SELECT name FROM recordingprofiles WHERE id = :ID;");
1995 transcoderName = query.
value(0).toString();
2000 transcoderName = QString(
"Autodetect(%1)").arg(transcoder);
2015 long long filesize = 0;
2016 long long origfilesize = QFileInfo(
filename).size();
2018 QString msg = QString(
"Transcode %1")
2021 QString details = QString(
"%1: %2 (%3)")
2025 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"%1 for %2")
2026 .arg(msg, details));
2028 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + QString(
"Running command: '%1'")
2031 GetMythDB()->GetDBManager()->CloseDatabases();
2039 tr(
"ERROR: Unable to find mythtranscode, check backend logs."));
2043 details = QString(
"%1: %2 does not exist or is not executable")
2046 LOG(VB_GENERAL, LOG_ERR,
LOC +
2047 QString(
"%1 for %2").arg(msg, details));
2051 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
"Transcode command restarting");
2059 if (status == JOB_FINISHED)
2070 filesize = st.size();
2073 QString comment = tr(
"%1: %2 => %3")
2074 .arg(transcoderName,
2082 details = QString(
"%1: %2 (%3)")
2091 QString(
"could not stat '%1'").arg(
filename);
2095 details = QString(
"%1: %2")
2107 QString comment = tr(
"exit status %1, job status was \"%2\"")
2113 details = QString(
"%1: %2 (%3)")
2121 LOG(VB_GENERAL, LOG_INFO,
LOC + msg +
": " + details);
2125 if (retrylimit == 0)
2127 LOG(VB_JOBQUEUE, LOG_ERR,
LOC +
"Retry limit exceeded for transcoder, "
2128 "setting job status to errored.");
2155 LOG(VB_JOBQUEUE, LOG_ERR,
LOC +
2156 "The JobQueue cannot currently perform lookups for items which do "
2157 "not have a chanid/starttime in the recorded table.");
2167 QString details = QString(
"%1 recorded from channel %3")
2173 QString msg = QString(
"Metadata Lookup failed. Could not open "
2174 "new database connection for %1. "
2175 "Program cannot be looked up.")
2177 LOG(VB_GENERAL, LOG_ERR,
LOC + msg);
2180 tr(
"Could not open new database connection for "
2181 "metadata lookup."));
2183 delete program_info;
2187 LOG(VB_GENERAL, LOG_INFO,
2188 LOC +
"Metadata Lookup Starting for " + details);
2195 command = QString(
"%1 -j %2")
2196 .arg(path).arg(
jobID);
2199 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + QString(
"Running command: '%1'")
2202 GetMythDB()->GetDBManager()->CloseDatabases();
2204 int priority = LOG_NOTICE;
2212 comment = tr(
"Unable to find mythmetadatalookup");
2214 priority = LOG_WARNING;
2218 comment = tr(
"Aborted by user");
2220 priority = LOG_WARNING;
2224 comment = tr(
"Unable to open file or init decoder");
2226 priority = LOG_WARNING;
2230 comment = tr(
"Failed with exit status %1").arg(retVal);
2232 priority = LOG_WARNING;
2236 comment = tr(
"Metadata Lookup Complete.");
2242 QString msg = tr(
"Metadata Lookup %1",
"Job ID")
2245 if (!comment.isEmpty())
2246 details += QString(
" (%1)").arg(comment);
2248 if (priority <= LOG_WARNING)
2249 LOG(VB_GENERAL, LOG_ERR,
LOC + msg +
": " + details);
2275 LOG(VB_JOBQUEUE, LOG_ERR,
LOC +
2276 "The JobQueue cannot currently commflag files that do not "
2277 "have a chanid/starttime in the recorded table.");
2287 QString details = QString(
"%1 recorded from channel %3")
2293 QString msg = QString(
"Commercial Detection failed. Could not open "
2294 "new database connection for %1. "
2295 "Program cannot be flagged.")
2297 LOG(VB_GENERAL, LOG_ERR,
LOC + msg);
2300 tr(
"Could not open new database connection for "
2301 "commercial detector."));
2303 delete program_info;
2307 LOG(VB_GENERAL, LOG_INFO,
2308 LOC +
"Commercial Detection Starting for " + details);
2310 uint breaksFound = 0;
2318 command = QString(
"%1 -j %2 --noprogress")
2319 .arg(path).arg(
jobID);
2325 QStringList tokens = command.split(
" ", Qt::SkipEmptyParts);
2326 if (!tokens.empty())
2331 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + QString(
"Running command: '%1'")
2334 GetMythDB()->GetDBManager()->CloseDatabases();
2336 int priority = LOG_NOTICE;
2344 comment = tr(
"Unable to find mythcommflag");
2346 priority = LOG_WARNING;
2350 comment = tr(
"Aborted by user");
2352 priority = LOG_WARNING;
2356 comment = tr(
"Unable to open file or init decoder");
2358 priority = LOG_WARNING;
2362 comment = tr(
"Failed with exit status %1").arg(breaksFound);
2364 priority = LOG_WARNING;
2368 comment = tr(
"%n commercial break(s)",
"", breaksFound);
2384 QString msg = tr(
"Commercial Detection %1",
"Job ID")
2387 if (!comment.isEmpty())
2388 details += QString(
" (%1)").arg(comment);
2390 if (priority <= LOG_WARNING)
2391 LOG(VB_GENERAL, LOG_ERR,
LOC + msg +
": " + details);
2425 msg = QString(
"Started %1 for %2 recorded from channel %3")
2432 msg = QString(
"Started %1 for jobID %2").arg(jobDesc).arg(
jobID);
2435 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(msg.toLocal8Bit().constData()));
2449 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + QString(
"Running command: '%1'")
2451 GetMythDB()->GetDBManager()->CloseDatabases();
2457 msg = QString(
"User Job '%1' failed, unable to find "
2458 "executable, check your PATH and backend logs.")
2460 LOG(VB_GENERAL, LOG_ERR,
LOC + msg);
2461 LOG(VB_GENERAL, LOG_NOTICE,
LOC + QString(
"Current PATH: '%1'")
2462 .arg(qEnvironmentVariable(
"PATH")));
2465 tr(
"ERROR: Unable to find executable, check backend logs."));
2467 else if (result != 0)
2469 msg = QString(
"User Job '%1' failed.").arg(command);
2470 LOG(VB_GENERAL, LOG_ERR,
LOC + msg);
2473 tr(
"ERROR: User Job returned non-zero, check logs."));
2479 msg = QString(
"Finished %1 for %2 recorded from channel %3")
2486 msg = QString(
"Finished %1 for jobID %2").arg(jobDesc).arg(
jobID);
2489 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(msg.toLocal8Bit().constData()));
2506 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="")
void dispatch(const MythEvent &event)
int GetNumSetting(const QString &key, int defaultval=0)
bool IsBlockingClient(void) const
is this client blocking shutdown
std::enable_if_t< std::chrono::__is_duration< T >::value, T > GetDurSetting(const QString &key, T defaultval=T::zero())
bool GetBoolSetting(const QString &key, bool defaultval=false)
static void DBError(const QString &where, const MSqlQuery &query)
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
#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