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")
109 jobID = tokens[3].toInt();
122 msg = QString(
"Unable to determine jobID for message: "
123 "%1. Program will not be flagged.")
125 LOG(VB_GENERAL, LOG_ERR,
LOC + msg);
131 msg = QString(
"Received message '%1'").arg(message);
132 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + msg);
143 else if (
action ==
"PAUSE")
145 else if (
action ==
"RESUME")
147 else if (
action ==
"RESTART")
173 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
"ProcessQueue() started");
179 QMap<int, int> jobStatus;
181 QMap<int, JobQueueEntry> jobs;
183 QMap<int, RunningJobInfo>::Iterator rjiter;
190 bool startedJobAlready =
false;
193 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
194 QString(
"Currently set to run up to %1 job(s) max.")
203 if ((*rjiter).pginfo)
204 (*rjiter).pginfo->UpdateInUseMark();
214 for (
const auto & job : std::as_const(jobs))
216 int status = job.status;
219 if (((status == JOB_RUNNING) ||
220 (status == JOB_STARTING) ||
221 (status == JOB_PAUSED)) &&
226 message = QString(
"Currently Running %1 jobs.")
230 message += QString(
" Jobs in Queue, but we are outside of the "
231 "Job Queue time window, no new jobs can be "
233 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
237 message +=
" (At Maximum, no new jobs can be started until "
238 "a running job completes)";
241 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
247 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
255 int jobID = jobs[x].id;
256 int cmds = jobs[x].cmds;
258 int status = jobs[x].status;
262 logInfo = QString(
"jobID #%1").arg(
jobID);
264 logInfo = QString(
"chanid %1 @ %2").arg(jobs[x].chanid)
265 .arg(jobs[x].startts);
275 jobStatus[
jobID] = status;
277 message = QString(
"Skipping '%1' job for %2, "
278 "should run on '%3' instead")
281 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
290 if (otherJobID && (jobStatus.contains(otherJobID)) &&
291 (!(jobStatus[otherJobID] & JOB_DONE)))
294 QString(
"Skipping '%1' job for %2, "
295 "Job ID %3 is already running for "
296 "this recording with a status of '%4'")
298 QString::number(otherJobID),
300 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
305 jobStatus[
jobID] = status;
310 message = QString(
"Skipping '%1' job for %2, "
311 "not allowed to run on this backend.")
313 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
320 message = QString(
"Skipping '%1' job for %2, this job is "
321 "not scheduled to run until %3.")
325 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
333 if (status != JOB_QUEUED) {
334 message = QString(
"Stopping '%1' job for %2")
336 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
351 message = QString(
"Cancelling '%1' job for %2")
353 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
361 message = QString(
"Unable to claim '%1' job for %2")
363 LOG(VB_JOBQUEUE, LOG_ERR,
LOC + message);
372 if ((cmds &
JOB_PAUSE) && (status != JOB_QUEUED))
374 message = QString(
"Pausing '%1' job for %2")
376 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
387 if ((cmds &
JOB_RESTART) && (status != JOB_QUEUED))
389 message = QString(
"Restart '%1' job for %2")
391 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
402 if (status != JOB_QUEUED)
407 message = QString(
"Resetting '%1' job for %2 to %3 "
408 "status, because no hostname is set.")
412 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
417 else if (inTimeWindow)
419 message = QString(
"Skipping '%1' job for %2, "
420 "current job status is '%3'")
424 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
430 if (startedJobAlready)
437 message = QString(
"Unable to claim '%1' job for %2")
439 LOG(VB_JOBQUEUE, LOG_ERR,
LOC + message);
445 message = QString(
"Skipping '%1' job for %2, "
446 "current time is outside of the "
447 "Job Queue processing window.")
449 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
453 message = QString(
"Processing '%1' job for %2, "
454 "current status is '%3'")
457 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
461 startedJobAlready =
true;
472 LOG(VB_JOBQUEUE, LOG_INFO, QString(
"%1 jobs running. "
481 LOG(VB_JOBQUEUE, LOG_INFO,
"No jobs running. "
482 "Allowing shutdown.");
491 std::chrono::milliseconds st = startedJobAlready ? 5s : sleepTime;
504 jobTypes &= (~JOB_COMMFLAG);
508 QString jobHost = QString(
"");
521 const QString&
args,
const QString& comment, QString host,
522 int flags,
int status, QDateTime schedruntime)
524 int tmpStatus = JOB_UNKNOWN;
525 int tmpCmd = JOB_UNKNOWN;
528 if(!schedruntime.isValid())
537 query.
prepare(
"SELECT status, id, cmds FROM jobqueue "
538 "WHERE chanid = :CHANID AND starttime = :STARTTIME "
539 "AND type = :JOBTYPE;");
541 query.
bindValue(
":STARTTIME", recstartts);
551 tmpStatus = query.
value(0).toInt();
553 tmpCmd = query.
value(2).toInt();
570 if (! (tmpStatus & JOB_DONE) && (tmpCmd &
JOB_STOP))
579 query.
prepare(
"INSERT INTO jobqueue (chanid, starttime, inserttime, type, "
580 "status, statustime, schedruntime, hostname, args, comment, "
582 "VALUES (:CHANID, :STARTTIME, now(), :JOBTYPE, :STATUS, "
583 "now(), :SCHEDRUNTIME, :HOST, :ARGS, :COMMENT, :FLAGS);");
586 query.
bindValue(
":STARTTIME", recstartts);
589 query.
bindValue(
":SCHEDRUNTIME", schedruntime);
605 const QString&
args,
const QString& comment,
const QString& host)
629#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
630 schedruntime = QDateTime(schedruntime.addDays(defer).date(),
631 QTime(0,0,0), Qt::UTC);
633 schedruntime = QDateTime(schedruntime.addDays(defer).date(),
635 QTimeZone(QTimeZone::UTC));
640 0, JOB_QUEUED, schedruntime);
660 query.
prepare(
"SELECT id FROM jobqueue "
661 "WHERE chanid = :CHANID AND starttime = :STARTTIME "
662 "AND type = :JOBTYPE;");
664 query.
bindValue(
":STARTTIME", recstartts);
673 return query.
value(0).toInt();
678 int jobID,
int &jobType,
uint &chanid, QDateTime &recstartts)
682 query.
prepare(
"SELECT type, chanid, starttime FROM jobqueue "
694 jobType = query.
value(0).toInt();
695 chanid = query.
value(1).toUInt();
703 int jobID,
int &jobType,
uint &chanid, QString &recstartts)
705 QDateTime tmpStarttime;
708 jobID, jobType, chanid, tmpStarttime);
720 LOG(VB_GENERAL, LOG_ERR, QString(
"'%1' is an invalid Job Name.")
729 QString message = QString(
"GLOBAL_JOB PAUSE ID %1").arg(
jobID);
738 QString message = QString(
"GLOBAL_JOB RESUME ID %1").arg(
jobID);
747 QString message = QString(
"GLOBAL_JOB RESTART ID %1").arg(
jobID);
756 QString message = QString(
"GLOBAL_JOB STOP ID %1").arg(
jobID);
768 query.
prepare(
"UPDATE jobqueue SET status = :CANCELLED "
769 "WHERE chanid = :CHANID AND starttime = :STARTTIME "
770 "AND status = :QUEUED;");
772 query.
bindValue(
":CANCELLED", JOB_CANCELLED);
774 query.
bindValue(
":STARTTIME", recstartts);
780 query.
prepare(
"UPDATE jobqueue SET cmds = :CMD "
781 "WHERE chanid = :CHANID AND starttime = :STARTTIME "
782 "AND status <> :CANCELLED;");
785 query.
bindValue(
":STARTTIME", recstartts);
786 query.
bindValue(
":CANCELLED", JOB_CANCELLED);
795 bool jobsAreRunning =
true;
796 std::chrono::seconds totalSlept = 0s;
797 std::chrono::seconds maxSleep = 90s;
798 while (jobsAreRunning && totalSlept < maxSleep)
800 std::this_thread::sleep_for(1ms);
801 query.
prepare(
"SELECT id FROM jobqueue "
802 "WHERE chanid = :CHANID and starttime = :STARTTIME "
804 "(:FINISHED,:ABORTED,:ERRORED,:CANCELLED);");
806 query.
bindValue(
":STARTTIME", recstartts);
807 query.
bindValue(
":FINISHED", JOB_FINISHED);
808 query.
bindValue(
":ABORTED", JOB_ABORTED);
809 query.
bindValue(
":ERRORED", JOB_ERRORED);
810 query.
bindValue(
":CANCELLED", JOB_CANCELLED);
818 if (query.
size() == 0)
820 jobsAreRunning =
false;
823 if ((totalSlept % 5s) == 0s)
825 message = QString(
"Waiting on %1 jobs still running for "
826 "chanid %2 @ %3").arg(query.
size())
827 .arg(chanid).arg(recstartts.toString(
Qt::ISODate));
828 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
831 std::this_thread::sleep_for(1s);
835 if (totalSlept <= maxSleep)
837 query.
prepare(
"DELETE FROM jobqueue "
838 "WHERE chanid = :CHANID AND starttime = :STARTTIME;");
840 query.
bindValue(
":STARTTIME", recstartts);
847 query.
prepare(
"SELECT id, type, status, comment FROM jobqueue "
848 "WHERE chanid = :CHANID AND starttime = :STARTTIME "
849 "AND status <> :CANCELLED ORDER BY id;");
852 query.
bindValue(
":STARTTIME", recstartts);
853 query.
bindValue(
":CANCELLED", JOB_CANCELLED);
858 "to query list of Jobs left in Queue.", query);
862 LOG(VB_GENERAL, LOG_ERR,
LOC +
863 QString(
"In DeleteAllJobs: There are Jobs "
864 "left in the JobQueue that are still running for "
865 "chanid %1 @ %2.").arg(chanid)
870 LOG(VB_GENERAL, LOG_ERR,
LOC +
871 QString(
"Job ID %1: '%2' with status '%3' and comment '%4'")
872 .arg(query.
value(0).toString(),
875 query.
value(3).toString()));
890 const QDateTime& recstartts)
898 int thisJob =
GetJobID(jobType, chanid, recstartts);
901 if( thisJob !=
jobID)
903 msg = QString(
"JobType, chanid and starttime don't match jobID %1");
910 msg = QString(
"Can't remove running JobID %1");
918 query.
prepare(
"DELETE FROM jobqueue WHERE id = :ID;");
938 query.
prepare(
"UPDATE jobqueue SET cmds = :CMDS WHERE id = :ID;");
953 const QDateTime &recstartts,
int newCmds)
957 query.
prepare(
"UPDATE jobqueue SET cmds = :CMDS WHERE type = :TYPE "
958 "AND chanid = :CHANID AND starttime = :STARTTIME;");
963 query.
bindValue(
":STARTTIME", recstartts);
981 query.
prepare(
"UPDATE jobqueue SET flags = :FLAGS WHERE id = :ID;");
1000 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + QString(
"ChangeJobStatus(%1, %2, '%3')")
1005 query.
prepare(
"UPDATE jobqueue SET status = :STATUS, comment = :COMMENT "
1006 "WHERE id = :ID AND status <> :NEWSTATUS;");
1011 query.
bindValue(
":NEWSTATUS", newStatus);
1027 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + QString(
"ChangeJobComment(%1, '%2')")
1028 .arg(
jobID).arg(comment));
1032 query.
prepare(
"UPDATE jobqueue SET comment = :COMMENT "
1054 query.
prepare(
"UPDATE jobqueue SET args = :ARGS "
1074 if ((jInfo.pginfo->GetChanID() == chanid) &&
1075 (jInfo.pginfo->GetRecordingStartTime() == recstartts))
1089 return (status == JOB_QUEUED);
1094 return ((status != JOB_UNKNOWN) && (status != JOB_QUEUED) &&
1095 ((status & JOB_DONE) == 0));
1099 uint chanid,
const QDateTime &recstartts)
1111 int jobType,
uint chanid,
const QDateTime &recstartts)
1113 int tmpStatus =
GetJobStatus(jobType, chanid, recstartts);
1115 return (tmpStatus != JOB_UNKNOWN) && ((tmpStatus & JOB_DONE) == 0);
1119 int jobType,
uint chanid,
const QDateTime &recstartts)
1131 case JOB_PREVIEW:
return tr(
"Preview Generation");
1136 QString settingName =
1141 return tr(
"Unknown Job");
1145#define JOBSTATUS_STATUSTEXT(A,B,C) case A: return C;
1154 return tr(
"Undefined");
1159 QString queueStartTimeStr;
1160 QString queueEndTimeStr;
1161 QTime queueStartTime;
1163 QTime curTime = QTime::currentTime();
1164 bool inTimeWindow =
false;
1165 orStartsWithinMins = orStartsWithinMins < 0min ? 0min : orStartsWithinMins;
1171 if (!queueStartTime.isValid())
1173 LOG(VB_GENERAL, LOG_ERR,
1174 QString(
"Invalid JobQueueWindowStart time '%1', using 00:00")
1175 .arg(queueStartTimeStr));
1176 queueStartTime = QTime(0, 0);
1180 if (!queueEndTime.isValid())
1182 LOG(VB_GENERAL, LOG_ERR,
1183 QString(
"Invalid JobQueueWindowEnd time '%1', using 23:59")
1184 .arg(queueEndTimeStr));
1185 queueEndTime = QTime(23, 59);
1188 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1189 QString(
"Currently set to run new jobs from %1 to %2")
1190 .arg(queueStartTimeStr, queueEndTimeStr));
1192 if ((queueStartTime <= curTime) && (curTime < queueEndTime))
1194 inTimeWindow =
true;
1196 else if ((queueStartTime > queueEndTime) &&
1197 ((curTime < queueEndTime) || (queueStartTime <= curTime)))
1199 inTimeWindow =
true;
1201 else if (orStartsWithinMins > 0min)
1204 if (curTime <= queueStartTime)
1207 if (queueStartTime.secsTo(curTime) <= duration_cast<std::chrono::seconds>(orStartsWithinMins).count())
1209 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1210 QString(
"Job run window will start within %1 minutes")
1211 .arg(orStartsWithinMins.count()));
1212 inTimeWindow =
true;
1219#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
1220 QDateTime startDateTime = QDateTime(
1221 curDateTime.date(), queueStartTime, Qt::UTC).addDays(1);
1223 QDateTime startDateTime =
1224 QDateTime(curDateTime.date(), queueStartTime,
1225 QTimeZone(QTimeZone::UTC)).addDays(1);
1228 if (curDateTime.secsTo(startDateTime) <= duration_cast<std::chrono::seconds>(orStartsWithinMins).count())
1230 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1231 QString(
"Job run window will start "
1232 "within %1 minutes (tomorrow)")
1233 .arg(orStartsWithinMins.count()));
1234 inTimeWindow =
true;
1239 return inTimeWindow;
1246 QMap<int, JobQueueEntry> jobs;
1247 QMap<int, JobQueueEntry>::Iterator it;
1249 bool checkForQueuedJobs = (startingWithinMins <= 0min
1252 if (checkForQueuedJobs && startingWithinMins > 0min) {
1253 maxSchedRunTime = maxSchedRunTime.addSecs(duration_cast<std::chrono::seconds>(startingWithinMins).count());
1254 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1255 QString(
"HasRunningOrPendingJobs: checking for jobs "
1256 "starting before: %1")
1262 if (!jobs.empty()) {
1263 for (it = jobs.begin(); it != jobs.end(); ++it)
1265 int tmpStatus = (*it).status;
1266 if (tmpStatus == JOB_RUNNING) {
1267 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1268 QString(
"HasRunningOrPendingJobs: found running job"));
1272 if (checkForQueuedJobs) {
1273 if ((tmpStatus != JOB_UNKNOWN) && (!(tmpStatus & JOB_DONE))) {
1274 if (startingWithinMins <= 0min) {
1275 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1276 "HasRunningOrPendingJobs: found pending job");
1279 if ((*it).schedruntime <= maxSchedRunTime) {
1280 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1281 QString(
"HasRunningOrPendingJobs: found pending "
1282 "job scheduled to start at: %1")
1301 bool commflagWhileRecording =
1306 query.
prepare(
"SELECT j.id, j.chanid, j.starttime, j.inserttime, j.type, "
1307 "j.cmds, j.flags, j.status, j.statustime, j.hostname, "
1308 "j.args, j.comment, r.endtime, j.schedruntime "
1310 "LEFT JOIN recorded r "
1311 " ON j.chanid = r.chanid AND j.starttime = r.starttime "
1312 "ORDER BY j.schedruntime, j.id;");
1317 "query list of Jobs in Queue.", query);
1321 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1322 QString(
"GetJobsInQueue: findJobs search bitmask %1, "
1323 "found %2 total jobs")
1324 .arg(findJobs).arg(query.
size()));
1326 while (query.
next())
1328 bool wantThisJob =
false;
1330 thisJob.
id = query.
value(0).toInt();
1340 if (query.
value(1).toInt() == -1)
1343 logInfo = QString(
"jobID #%1").arg(thisJob.
id);
1348 logInfo = QString(
"chanid %1 @ %2").arg(thisJob.
chanid)
1353 ((!commflagWhileRecording) ||
1357 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1358 QString(
"GetJobsInQueue: Ignoring '%1' Job "
1359 "for %2 in %3 state. Endtime in future.")
1367 (thisJob.
status & JOB_DONE)) ||
1369 (!(thisJob.
status & JOB_DONE))) ||
1371 (thisJob.
status == JOB_ERRORED)) ||
1378 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1379 QString(
"GetJobsInQueue: Ignore '%1' Job for %2 in %3 state.")
1385 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1386 QString(
"GetJobsInQueue: Found '%1' Job for %2 in %3 state.")
1394 thisJob.
args = query.
value(10).toString();
1401 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1402 QString(
"GetJobsInQueue: Unknown Job Type: %1")
1403 .arg(thisJob.
type));
1407 jobs[jobCount++] = thisJob;
1417 if (!newHostname.isEmpty())
1419 query.
prepare(
"UPDATE jobqueue SET hostname = :NEWHOSTNAME "
1420 "WHERE hostname = :EMPTY AND id = :ID;");
1421 query.
bindValue(
":NEWHOSTNAME", newHostname);
1427 query.
prepare(
"UPDATE jobqueue SET hostname = :EMPTY "
1436 "Unable to set hostname to '%1' for "
1437 "job %2.").arg(newHostname).arg(
jobID),
1447 QString allowSetting;
1468 case JOB_PREVIEW: allowSetting =
"JobAllowPreview";
1470 default:
return false;
1481 query.
prepare(
"SELECT cmds FROM jobqueue WHERE id = :ID;");
1502 query.
prepare(
"SELECT args FROM jobqueue WHERE id = :ID;");
1509 return query.
value(0).toString();
1523 query.
prepare(
"SELECT flags FROM jobqueue WHERE id = :ID;");
1544 query.
prepare(
"SELECT status FROM jobqueue WHERE id = :ID;");
1561 int jobType,
uint chanid,
const QDateTime &recstartts)
1565 query.
prepare(
"SELECT status FROM jobqueue WHERE type = :TYPE "
1566 "AND chanid = :CHANID AND starttime = :STARTTIME;");
1570 query.
bindValue(
":STARTTIME", recstartts);
1586 QMap<int, JobQueueEntry> jobs;
1590 msg = QString(
"RecoverQueue: Checking for unfinished jobs to "
1592 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + msg);
1598 QMap<int, JobQueueEntry>::Iterator it;
1602 for (it = jobs.begin(); it != jobs.end(); ++it)
1604 int tmpCmds = (*it).cmds;
1605 int tmpStatus = (*it).status;
1608 logInfo = QString(
"jobID #%1").arg((*it).id);
1610 logInfo = QString(
"chanid %1 @ %2").arg((*it).chanid)
1611 .arg((*it).startts);
1613 if (((tmpStatus == JOB_STARTING) ||
1614 (tmpStatus == JOB_RUNNING) ||
1615 (tmpStatus == JOB_PAUSED) ||
1617 (tmpStatus == JOB_STOPPING)) &&
1620 ((*it).statustime < oldDate)))
1622 msg = QString(
"RecoverQueue: Recovering '%1' for %2 "
1626 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + msg);
1636 msg = QString(
"RecoverQueue: Ignoring '%1' for %2 "
1640 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + msg);
1653 delquery.
prepare(
"DELETE FROM jobqueue "
1654 "WHERE (status in (:FINISHED, :ABORTED, :CANCELLED) "
1655 "AND statustime < :DONEPURGEDATE) "
1656 "OR (status in (:ERRORED) "
1657 "AND statustime < :ERRORSPURGEDATE) ");
1658 delquery.
bindValue(
":FINISHED", JOB_FINISHED);
1659 delquery.
bindValue(
":ABORTED", JOB_ABORTED);
1660 delquery.
bindValue(
":CANCELLED", JOB_CANCELLED);
1661 delquery.
bindValue(
":ERRORED", JOB_ERRORED);
1662 delquery.
bindValue(
":DONEPURGEDATE", donePurgeDate);
1663 delquery.
bindValue(
":ERRORSPURGEDATE", errorsPurgeDate);
1665 if (!delquery.
exec())
1668 "old finished jobs.", delquery);
1674 if (!jobstarttsRaw.isValid())
1676 jobstarttsRaw = QDateTime::currentDateTime();
1677 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + QString(
"Invalid date/time passed, "
1679 jobstarttsRaw.toString()));
1685 "JobQueueWindowStart",
hostname,
"00:00")));
1688 "JobQueueWindowEnd",
hostname,
"23:59")));
1692 if (scheduleTime < windowStart || scheduleTime > windowEnd)
1694 LOG(VB_JOBQUEUE, LOG_ERR,
LOC +
"Time not within job queue window, " +
1708 LOG(VB_JOBQUEUE, LOG_ERR,
LOC +
1709 "ProcessJob(): Unable to open database connection");
1722 LOG(VB_JOBQUEUE, LOG_ERR,
LOC +
1723 QString(
"Unable to retrieve program info for chanid %1 @ %2")
1728 tr(
"Unable to retrieve program info from database"));
1758 tr(
"Program has been deleted"));
1783 tr(
"UNKNOWN JobType, unable to process!"));
1797 pthread_attr_t attr;
1798 pthread_attr_init(&attr);
1799 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
1800 pthread_create(&childThread, &attr, ChildThreadRoutine, jts);
1801 pthread_attr_destroy(&attr);
1809 return "Commercial Detection";
1811 return "Unknown Job";
1813 QString descSetting =
1827 if (command.trimmed().isEmpty())
1828 command =
"mythtranscode";
1830 if (command ==
"mythtranscode")
1836 if (command.trimmed().isEmpty())
1837 command =
"mythcommflag";
1839 if (command ==
"mythcommflag")
1848 if (!command.isEmpty())
1850 command.replace(
"%JOBID%", QString(
"%1").arg(
id));
1853 if (!command.isEmpty() && tmpInfo)
1857 command.replace(
"%VERBOSELEVEL%", QString(
"%1").arg(
verboseMask));
1861 command.replace(
"%TRANSPROFILE%",
1863 "autodetect" : QString::number(transcoder));
1893 const char *m_suffix;
1897 static constexpr std::array<const PpTab_t,9> kPpTab {{
1898 { .m_suffix=
"bytes", .m_max=9999, .m_precision=0 },
1899 { .m_suffix=
"kB", .m_max=999, .m_precision=0 },
1900 { .m_suffix=
"MB", .m_max=999, .m_precision=1 },
1901 { .m_suffix=
"GB", .m_max=999, .m_precision=1 },
1902 { .m_suffix=
"TB", .m_max=999, .m_precision=1 },
1903 { .m_suffix=
"PB", .m_max=999, .m_precision=1 },
1904 { .m_suffix=
"EB", .m_max=999, .m_precision=1 },
1905 { .m_suffix=
"ZB", .m_max=999, .m_precision=1 },
1906 { .m_suffix=
"YB", .m_max=0, .m_precision=0 },
1908 float fbytes =
bytes;
1910 unsigned int ii = 0;
1911 while (kPpTab[ii].m_max && fbytes > kPpTab[ii].m_max) {
1916 return QString(
"%1 %2")
1917 .arg(fbytes, 0,
'f', kPpTab[ii].m_precision)
1918 .arg(kPpTab[ii].m_suffix);
1941 LOG(VB_JOBQUEUE, LOG_ERR,
LOC +
1942 "The JobQueue cannot currently transcode files that do not "
1943 "have a chanid/starttime in the recorded table.");
1958 bool useCutlist = program_info->
HasCutlist() &&
1962 QString profilearg =
1964 "autodetect" : QString::number(transcoder);
1973 command = QString(
"%1 -j %2 --profile %3")
1974 .arg(path).arg(
jobID).arg(profilearg);
1976 command +=
" --honorcutlist";
1983 QStringList tokens = command.split(
" ", Qt::SkipEmptyParts);
1984 if (!tokens.empty())
1995 QString transcoderName;
1998 transcoderName =
"Autodetect";
2003 query.
prepare(
"SELECT name FROM recordingprofiles WHERE id = :ID;");
2007 transcoderName = query.
value(0).toString();
2012 transcoderName = QString(
"Autodetect(%1)").arg(transcoder);
2027 long long filesize = 0;
2028 long long origfilesize = QFileInfo(
filename).size();
2030 QString msg = QString(
"Transcode %1")
2033 QString details = QString(
"%1: %2 (%3)")
2037 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"%1 for %2")
2038 .arg(msg, details));
2040 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + QString(
"Running command: '%1'")
2043 GetMythDB()->GetDBManager()->CloseDatabases();
2051 tr(
"ERROR: Unable to find mythtranscode, check backend logs."));
2055 details = QString(
"%1: %2 does not exist or is not executable")
2058 LOG(VB_GENERAL, LOG_ERR,
LOC +
2059 QString(
"%1 for %2").arg(msg, details));
2063 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
"Transcode command restarting");
2071 if (status == JOB_FINISHED)
2082 filesize = st.size();
2085 QString comment = tr(
"%1: %2 => %3")
2086 .arg(transcoderName,
2094 details = QString(
"%1: %2 (%3)")
2103 QString(
"could not stat '%1'").arg(
filename);
2107 details = QString(
"%1: %2")
2119 QString comment = tr(
"exit status %1, job status was \"%2\"")
2125 details = QString(
"%1: %2 (%3)")
2133 LOG(VB_GENERAL, LOG_INFO,
LOC + msg +
": " + details);
2137 if (retrylimit == 0)
2139 LOG(VB_JOBQUEUE, LOG_ERR,
LOC +
"Retry limit exceeded for transcoder, "
2140 "setting job status to errored.");
2167 LOG(VB_JOBQUEUE, LOG_ERR,
LOC +
2168 "The JobQueue cannot currently perform lookups for items which do "
2169 "not have a chanid/starttime in the recorded table.");
2179 QString details = QString(
"%1 recorded from channel %3")
2185 QString msg = QString(
"Metadata Lookup failed. Could not open "
2186 "new database connection for %1. "
2187 "Program cannot be looked up.")
2189 LOG(VB_GENERAL, LOG_ERR,
LOC + msg);
2192 tr(
"Could not open new database connection for "
2193 "metadata lookup."));
2195 delete program_info;
2199 LOG(VB_GENERAL, LOG_INFO,
2200 LOC +
"Metadata Lookup Starting for " + details);
2207 command = QString(
"%1 -j %2")
2208 .arg(path).arg(
jobID);
2211 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + QString(
"Running command: '%1'")
2214 GetMythDB()->GetDBManager()->CloseDatabases();
2216 int priority = LOG_NOTICE;
2224 comment = tr(
"Unable to find mythmetadatalookup");
2226 priority = LOG_WARNING;
2230 comment = tr(
"Aborted by user");
2232 priority = LOG_WARNING;
2236 comment = tr(
"Unable to open file or init decoder");
2238 priority = LOG_WARNING;
2242 comment = tr(
"Failed with exit status %1").arg(retVal);
2244 priority = LOG_WARNING;
2248 comment = tr(
"Metadata Lookup Complete.");
2254 QString msg = tr(
"Metadata Lookup %1",
"Job ID")
2257 if (!comment.isEmpty())
2258 details += QString(
" (%1)").arg(comment);
2260 if (priority <= LOG_WARNING)
2261 LOG(VB_GENERAL, LOG_ERR,
LOC + msg +
": " + details);
2287 LOG(VB_JOBQUEUE, LOG_ERR,
LOC +
2288 "The JobQueue cannot currently commflag files that do not "
2289 "have a chanid/starttime in the recorded table.");
2299 QString details = QString(
"%1 recorded from channel %3")
2305 QString msg = QString(
"Commercial Detection failed. Could not open "
2306 "new database connection for %1. "
2307 "Program cannot be flagged.")
2309 LOG(VB_GENERAL, LOG_ERR,
LOC + msg);
2312 tr(
"Could not open new database connection for "
2313 "commercial detector."));
2315 delete program_info;
2319 LOG(VB_GENERAL, LOG_INFO,
2320 LOC +
"Commercial Detection Starting for " + details);
2322 uint breaksFound = 0;
2330 command = QString(
"%1 -j %2 --noprogress")
2331 .arg(path).arg(
jobID);
2337 QStringList tokens = command.split(
" ", Qt::SkipEmptyParts);
2338 if (!tokens.empty())
2343 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + QString(
"Running command: '%1'")
2346 GetMythDB()->GetDBManager()->CloseDatabases();
2348 int priority = LOG_NOTICE;
2356 comment = tr(
"Unable to find mythcommflag");
2358 priority = LOG_WARNING;
2362 comment = tr(
"Aborted by user");
2364 priority = LOG_WARNING;
2368 comment = tr(
"Unable to open file or init decoder");
2370 priority = LOG_WARNING;
2374 comment = tr(
"Failed with exit status %1").arg(breaksFound);
2376 priority = LOG_WARNING;
2380 comment = tr(
"%n commercial break(s)",
"", breaksFound);
2396 QString msg = tr(
"Commercial Detection %1",
"Job ID")
2399 if (!comment.isEmpty())
2400 details += QString(
" (%1)").arg(comment);
2402 if (priority <= LOG_WARNING)
2403 LOG(VB_GENERAL, LOG_ERR,
LOC + msg +
": " + details);
2437 msg = QString(
"Started %1 for %2 recorded from channel %3")
2444 msg = QString(
"Started %1 for jobID %2").arg(jobDesc).arg(
jobID);
2447 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(msg.toLocal8Bit().constData()));
2461 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + QString(
"Running command: '%1'")
2463 GetMythDB()->GetDBManager()->CloseDatabases();
2469 msg = QString(
"User Job '%1' failed, unable to find "
2470 "executable, check your PATH and backend logs.")
2472 LOG(VB_GENERAL, LOG_ERR,
LOC + msg);
2473 LOG(VB_GENERAL, LOG_NOTICE,
LOC + QString(
"Current PATH: '%1'")
2474 .arg(qEnvironmentVariable(
"PATH")));
2477 tr(
"ERROR: Unable to find executable, check backend logs."));
2479 else if (result != 0)
2481 msg = QString(
"User Job '%1' failed.").arg(command);
2482 LOG(VB_GENERAL, LOG_ERR,
LOC + msg);
2485 tr(
"ERROR: User Job returned non-zero, check logs."));
2491 msg = QString(
"Finished %1 for %2 recorded from channel %3")
2498 msg = QString(
"Finished %1 for jobID %2").arg(jobDesc).arg(
jobID);
2501 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(msg.toLocal8Bit().constData()));
2518 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