12#if defined(Q_OS_NETBSD) || defined(Q_OS_OPENBSD)
13#define PTHREAD_NULL nullptr
20#include <QCoreApplication>
27#include "libmythbase/mythconfig.h"
42#define LOC QString("JobQueue: ")
49 m_runningJobsLock(new QRecursiveMutex()),
51 m_queueThread(new
MThread(
"JobQueue", this))
61 LOG(VB_GENERAL, LOG_ERR,
LOC +
62 "The JobQueue has been disabled because "
63 "you compiled with the --enable-valgrind option.");
92 QString message = me->
Message();
94 if (message.startsWith(
"LOCAL_JOB"))
99 message = message.simplified();
100 QStringList tokens = message.split(
" ", Qt::SkipEmptyParts);
101 const QString&
action = tokens[1];
104 if (tokens[2] ==
"ID")
105 jobID = tokens[3].toInt();
117 msg = QString(
"Unable to determine jobID for message: "
118 "%1. Program will not be flagged.")
120 LOG(VB_GENERAL, LOG_ERR,
LOC + msg);
126 msg = QString(
"Received message '%1'").arg(message);
127 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + msg);
138 else if (
action ==
"PAUSE")
140 else if (
action ==
"RESUME")
142 else if (
action ==
"RESTART")
168 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
"ProcessQueue() started");
174 QMap<int, int> jobStatus;
176 QMap<int, JobQueueEntry> jobs;
178 QMap<int, RunningJobInfo>::Iterator rjiter;
185 bool startedJobAlready =
false;
188 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
189 QString(
"Currently set to run up to %1 job(s) max.")
198 if ((*rjiter).pginfo)
199 (*rjiter).pginfo->UpdateInUseMark();
209 for (
const auto & job : std::as_const(jobs))
211 int status = job.status;
214 if (((status == JOB_RUNNING) ||
215 (status == JOB_STARTING) ||
216 (status == JOB_PAUSED)) &&
221 message = QString(
"Currently Running %1 jobs.")
225 message += QString(
" Jobs in Queue, but we are outside of the "
226 "Job Queue time window, no new jobs can be "
228 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
232 message +=
" (At Maximum, no new jobs can be started until "
233 "a running job completes)";
236 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
242 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
250 int jobID = jobs[x].id;
251 int cmds = jobs[x].cmds;
253 int status = jobs[x].status;
257 logInfo = QString(
"jobID #%1").arg(
jobID);
259 logInfo = QString(
"chanid %1 @ %2").arg(jobs[x].chanid)
260 .arg(jobs[x].startts);
263 if ((inTimeWindow) &&
270 jobStatus[
jobID] = status;
272 message = QString(
"Skipping '%1' job for %2, "
273 "should run on '%3' instead")
276 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
285 if (otherJobID && (jobStatus.contains(otherJobID)) &&
286 (!(jobStatus[otherJobID] & JOB_DONE)))
289 QString(
"Skipping '%1' job for %2, "
290 "Job ID %3 is already running for "
291 "this recording with a status of '%4'")
293 QString::number(otherJobID),
295 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
300 jobStatus[
jobID] = status;
305 message = QString(
"Skipping '%1' job for %2, "
306 "not allowed to run on this backend.")
308 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
315 message = QString(
"Skipping '%1' job for %2, this job is "
316 "not scheduled to run until %3.")
320 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
328 if (status != JOB_QUEUED) {
329 message = QString(
"Stopping '%1' job for %2")
331 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
346 message = QString(
"Cancelling '%1' job for %2")
348 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
356 message = QString(
"Unable to claim '%1' job for %2")
358 LOG(VB_JOBQUEUE, LOG_ERR,
LOC + message);
367 if ((cmds &
JOB_PAUSE) && (status != JOB_QUEUED))
369 message = QString(
"Pausing '%1' job for %2")
371 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
382 if ((cmds &
JOB_RESTART) && (status != JOB_QUEUED))
384 message = QString(
"Restart '%1' job for %2")
386 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
397 if (status != JOB_QUEUED)
402 message = QString(
"Resetting '%1' job for %2 to %3 "
403 "status, because no hostname is set.")
407 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
412 else if (inTimeWindow)
414 message = QString(
"Skipping '%1' job for %2, "
415 "current job status is '%3'")
419 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
425 if (startedJobAlready)
428 if ((inTimeWindow) &&
432 message = QString(
"Unable to claim '%1' job for %2")
434 LOG(VB_JOBQUEUE, LOG_ERR,
LOC + message);
440 message = QString(
"Skipping '%1' job for %2, "
441 "current time is outside of the "
442 "Job Queue processing window.")
444 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
448 message = QString(
"Processing '%1' job for %2, "
449 "current status is '%3'")
452 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
456 startedJobAlready =
true;
467 LOG(VB_JOBQUEUE, LOG_INFO, QString(
"%1 jobs running. "
476 LOG(VB_JOBQUEUE, LOG_INFO,
"No jobs running. "
477 "Allowing shutdown.");
486 std::chrono::milliseconds st = (startedJobAlready) ? 5s : sleepTime;
499 jobTypes &= (~JOB_COMMFLAG);
503 QString jobHost = QString(
"");
516 const QString&
args,
const QString& comment, QString host,
517 int flags,
int status, QDateTime schedruntime)
519 int tmpStatus = JOB_UNKNOWN;
520 int tmpCmd = JOB_UNKNOWN;
523 if(!schedruntime.isValid())
532 query.
prepare(
"SELECT status, id, cmds FROM jobqueue "
533 "WHERE chanid = :CHANID AND starttime = :STARTTIME "
534 "AND type = :JOBTYPE;");
536 query.
bindValue(
":STARTTIME", recstartts);
546 tmpStatus = query.
value(0).toInt();
548 tmpCmd = query.
value(2).toInt();
565 if (! (tmpStatus & JOB_DONE) && (tmpCmd &
JOB_STOP))
574 query.
prepare(
"INSERT INTO jobqueue (chanid, starttime, inserttime, type, "
575 "status, statustime, schedruntime, hostname, args, comment, "
577 "VALUES (:CHANID, :STARTTIME, now(), :JOBTYPE, :STATUS, "
578 "now(), :SCHEDRUNTIME, :HOST, :ARGS, :COMMENT, :FLAGS);");
581 query.
bindValue(
":STARTTIME", recstartts);
584 query.
bindValue(
":SCHEDRUNTIME", schedruntime);
600 const QString&
args,
const QString& comment,
const QString& host)
624#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
625 schedruntime = QDateTime(schedruntime.addDays(defer).date(),
626 QTime(0,0,0), Qt::UTC);
628 schedruntime = QDateTime(schedruntime.addDays(defer).date(),
630 QTimeZone(QTimeZone::UTC));
635 0, JOB_QUEUED, schedruntime);
655 query.
prepare(
"SELECT id FROM jobqueue "
656 "WHERE chanid = :CHANID AND starttime = :STARTTIME "
657 "AND type = :JOBTYPE;");
659 query.
bindValue(
":STARTTIME", recstartts);
668 return query.
value(0).toInt();
673 int jobID,
int &jobType,
uint &chanid, QDateTime &recstartts)
677 query.
prepare(
"SELECT type, chanid, starttime FROM jobqueue "
689 jobType = query.
value(0).toInt();
690 chanid = query.
value(1).toUInt();
698 int jobID,
int &jobType,
uint &chanid, QString &recstartts)
700 QDateTime tmpStarttime;
703 jobID, jobType, chanid, tmpStarttime);
715 LOG(VB_GENERAL, LOG_ERR, QString(
"'%1' is an invalid Job Name.")
724 QString message = QString(
"GLOBAL_JOB PAUSE ID %1").arg(
jobID);
733 QString message = QString(
"GLOBAL_JOB RESUME ID %1").arg(
jobID);
742 QString message = QString(
"GLOBAL_JOB RESTART ID %1").arg(
jobID);
751 QString message = QString(
"GLOBAL_JOB STOP ID %1").arg(
jobID);
763 query.
prepare(
"UPDATE jobqueue SET status = :CANCELLED "
764 "WHERE chanid = :CHANID AND starttime = :STARTTIME "
765 "AND status = :QUEUED;");
767 query.
bindValue(
":CANCELLED", JOB_CANCELLED);
769 query.
bindValue(
":STARTTIME", recstartts);
775 query.
prepare(
"UPDATE jobqueue SET cmds = :CMD "
776 "WHERE chanid = :CHANID AND starttime = :STARTTIME "
777 "AND status <> :CANCELLED;");
780 query.
bindValue(
":STARTTIME", recstartts);
781 query.
bindValue(
":CANCELLED", JOB_CANCELLED);
790 bool jobsAreRunning =
true;
791 std::chrono::seconds totalSlept = 0s;
792 std::chrono::seconds maxSleep = 90s;
793 while (jobsAreRunning && totalSlept < maxSleep)
795 std::this_thread::sleep_for(1ms);
796 query.
prepare(
"SELECT id FROM jobqueue "
797 "WHERE chanid = :CHANID and starttime = :STARTTIME "
799 "(:FINISHED,:ABORTED,:ERRORED,:CANCELLED);");
801 query.
bindValue(
":STARTTIME", recstartts);
802 query.
bindValue(
":FINISHED", JOB_FINISHED);
803 query.
bindValue(
":ABORTED", JOB_ABORTED);
804 query.
bindValue(
":ERRORED", JOB_ERRORED);
805 query.
bindValue(
":CANCELLED", JOB_CANCELLED);
813 if (query.
size() == 0)
815 jobsAreRunning =
false;
818 if ((totalSlept % 5s) == 0s)
820 message = QString(
"Waiting on %1 jobs still running for "
821 "chanid %2 @ %3").arg(query.
size())
822 .arg(chanid).arg(recstartts.toString(
Qt::ISODate));
823 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + message);
826 std::this_thread::sleep_for(1s);
830 if (totalSlept <= maxSleep)
832 query.
prepare(
"DELETE FROM jobqueue "
833 "WHERE chanid = :CHANID AND starttime = :STARTTIME;");
835 query.
bindValue(
":STARTTIME", recstartts);
842 query.
prepare(
"SELECT id, type, status, comment FROM jobqueue "
843 "WHERE chanid = :CHANID AND starttime = :STARTTIME "
844 "AND status <> :CANCELLED ORDER BY id;");
847 query.
bindValue(
":STARTTIME", recstartts);
848 query.
bindValue(
":CANCELLED", JOB_CANCELLED);
853 "to query list of Jobs left in Queue.", query);
857 LOG(VB_GENERAL, LOG_ERR,
LOC +
858 QString(
"In DeleteAllJobs: There are Jobs "
859 "left in the JobQueue that are still running for "
860 "chanid %1 @ %2.").arg(chanid)
865 LOG(VB_GENERAL, LOG_ERR,
LOC +
866 QString(
"Job ID %1: '%2' with status '%3' and comment '%4'")
867 .arg(query.
value(0).toString(),
870 query.
value(3).toString()));
885 const QDateTime& recstartts)
893 int thisJob =
GetJobID(jobType, chanid, recstartts);
896 if( thisJob !=
jobID)
898 msg = QString(
"JobType, chanid and starttime don't match jobID %1");
905 msg = QString(
"Can't remove running JobID %1");
913 query.
prepare(
"DELETE FROM jobqueue WHERE id = :ID;");
933 query.
prepare(
"UPDATE jobqueue SET cmds = :CMDS WHERE id = :ID;");
948 const QDateTime &recstartts,
int newCmds)
952 query.
prepare(
"UPDATE jobqueue SET cmds = :CMDS WHERE type = :TYPE "
953 "AND chanid = :CHANID AND starttime = :STARTTIME;");
958 query.
bindValue(
":STARTTIME", recstartts);
976 query.
prepare(
"UPDATE jobqueue SET flags = :FLAGS WHERE id = :ID;");
995 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + QString(
"ChangeJobStatus(%1, %2, '%3')")
1000 query.
prepare(
"UPDATE jobqueue SET status = :STATUS, comment = :COMMENT "
1001 "WHERE id = :ID AND status <> :NEWSTATUS;");
1006 query.
bindValue(
":NEWSTATUS", newStatus);
1022 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + QString(
"ChangeJobComment(%1, '%2')")
1023 .arg(
jobID).arg(comment));
1027 query.
prepare(
"UPDATE jobqueue SET comment = :COMMENT "
1049 query.
prepare(
"UPDATE jobqueue SET args = :ARGS "
1069 if ((jInfo.pginfo->GetChanID() == chanid) &&
1070 (jInfo.pginfo->GetRecordingStartTime() == recstartts))
1084 return (status == JOB_QUEUED);
1089 return ((status != JOB_UNKNOWN) && (status != JOB_QUEUED) &&
1090 ((status & JOB_DONE) == 0));
1094 uint chanid,
const QDateTime &recstartts)
1106 int jobType,
uint chanid,
const QDateTime &recstartts)
1108 int tmpStatus =
GetJobStatus(jobType, chanid, recstartts);
1110 return (tmpStatus != JOB_UNKNOWN) && ((tmpStatus & JOB_DONE) == 0);
1114 int jobType,
uint chanid,
const QDateTime &recstartts)
1126 case JOB_PREVIEW:
return tr(
"Preview Generation");
1131 QString settingName =
1136 return tr(
"Unknown Job");
1140#define JOBSTATUS_STATUSTEXT(A,B,C) case A: return C;
1149 return tr(
"Undefined");
1154 QString queueStartTimeStr;
1155 QString queueEndTimeStr;
1156 QTime queueStartTime;
1158 QTime curTime = QTime::currentTime();
1159 bool inTimeWindow =
false;
1160 orStartsWithinMins = orStartsWithinMins < 0min ? 0min : orStartsWithinMins;
1166 if (!queueStartTime.isValid())
1168 LOG(VB_GENERAL, LOG_ERR,
1169 QString(
"Invalid JobQueueWindowStart time '%1', using 00:00")
1170 .arg(queueStartTimeStr));
1171 queueStartTime = QTime(0, 0);
1175 if (!queueEndTime.isValid())
1177 LOG(VB_GENERAL, LOG_ERR,
1178 QString(
"Invalid JobQueueWindowEnd time '%1', using 23:59")
1179 .arg(queueEndTimeStr));
1180 queueEndTime = QTime(23, 59);
1183 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1184 QString(
"Currently set to run new jobs from %1 to %2")
1185 .arg(queueStartTimeStr, queueEndTimeStr));
1187 if ((queueStartTime <= curTime) && (curTime < queueEndTime))
1189 inTimeWindow =
true;
1191 else if ((queueStartTime > queueEndTime) &&
1192 ((curTime < queueEndTime) || (queueStartTime <= curTime)))
1194 inTimeWindow =
true;
1196 else if (orStartsWithinMins > 0min)
1199 if (curTime <= queueStartTime)
1202 if (queueStartTime.secsTo(curTime) <= duration_cast<std::chrono::seconds>(orStartsWithinMins).count())
1204 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1205 QString(
"Job run window will start within %1 minutes")
1206 .arg(orStartsWithinMins.count()));
1207 inTimeWindow =
true;
1214#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
1215 QDateTime startDateTime = QDateTime(
1216 curDateTime.date(), queueStartTime, Qt::UTC).addDays(1);
1218 QDateTime startDateTime =
1219 QDateTime(curDateTime.date(), queueStartTime,
1220 QTimeZone(QTimeZone::UTC)).addDays(1);
1223 if (curDateTime.secsTo(startDateTime) <= duration_cast<std::chrono::seconds>(orStartsWithinMins).count())
1225 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1226 QString(
"Job run window will start "
1227 "within %1 minutes (tomorrow)")
1228 .arg(orStartsWithinMins.count()));
1229 inTimeWindow =
true;
1234 return inTimeWindow;
1241 QMap<int, JobQueueEntry> jobs;
1242 QMap<int, JobQueueEntry>::Iterator it;
1244 bool checkForQueuedJobs = (startingWithinMins <= 0min
1247 if (checkForQueuedJobs && startingWithinMins > 0min) {
1248 maxSchedRunTime = maxSchedRunTime.addSecs(duration_cast<std::chrono::seconds>(startingWithinMins).count());
1249 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1250 QString(
"HasRunningOrPendingJobs: checking for jobs "
1251 "starting before: %1")
1257 if (!jobs.empty()) {
1258 for (it = jobs.begin(); it != jobs.end(); ++it)
1260 int tmpStatus = (*it).status;
1261 if (tmpStatus == JOB_RUNNING) {
1262 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1263 QString(
"HasRunningOrPendingJobs: found running job"));
1267 if (checkForQueuedJobs) {
1268 if ((tmpStatus != JOB_UNKNOWN) && (!(tmpStatus & JOB_DONE))) {
1269 if (startingWithinMins <= 0min) {
1270 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1271 "HasRunningOrPendingJobs: found pending job");
1274 if ((*it).schedruntime <= maxSchedRunTime) {
1275 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1276 QString(
"HasRunningOrPendingJobs: found pending "
1277 "job scheduled to start at: %1")
1296 bool commflagWhileRecording =
1301 query.
prepare(
"SELECT j.id, j.chanid, j.starttime, j.inserttime, j.type, "
1302 "j.cmds, j.flags, j.status, j.statustime, j.hostname, "
1303 "j.args, j.comment, r.endtime, j.schedruntime "
1305 "LEFT JOIN recorded r "
1306 " ON j.chanid = r.chanid AND j.starttime = r.starttime "
1307 "ORDER BY j.schedruntime, j.id;");
1312 "query list of Jobs in Queue.", query);
1316 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1317 QString(
"GetJobsInQueue: findJobs search bitmask %1, "
1318 "found %2 total jobs")
1319 .arg(findJobs).arg(query.
size()));
1321 while (query.
next())
1323 bool wantThisJob =
false;
1325 thisJob.
id = query.
value(0).toInt();
1335 if (query.
value(1).toInt() == -1)
1338 logInfo = QString(
"jobID #%1").arg(thisJob.
id);
1343 logInfo = QString(
"chanid %1 @ %2").arg(thisJob.
chanid)
1348 ((!commflagWhileRecording) ||
1352 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1353 QString(
"GetJobsInQueue: Ignoring '%1' Job "
1354 "for %2 in %3 state. Endtime in future.")
1362 (thisJob.
status & JOB_DONE)) ||
1364 (!(thisJob.
status & JOB_DONE))) ||
1366 (thisJob.
status == JOB_ERRORED)) ||
1373 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1374 QString(
"GetJobsInQueue: Ignore '%1' Job for %2 in %3 state.")
1380 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1381 QString(
"GetJobsInQueue: Found '%1' Job for %2 in %3 state.")
1389 thisJob.
args = query.
value(10).toString();
1396 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
1397 QString(
"GetJobsInQueue: Unknown Job Type: %1")
1398 .arg(thisJob.
type));
1402 jobs[jobCount++] = thisJob;
1412 if (!newHostname.isEmpty())
1414 query.
prepare(
"UPDATE jobqueue SET hostname = :NEWHOSTNAME "
1415 "WHERE hostname = :EMPTY AND id = :ID;");
1416 query.
bindValue(
":NEWHOSTNAME", newHostname);
1422 query.
prepare(
"UPDATE jobqueue SET hostname = :EMPTY "
1431 "Unable to set hostname to '%1' for "
1432 "job %2.").arg(newHostname).arg(
jobID),
1442 QString allowSetting;
1463 case JOB_PREVIEW: allowSetting =
"JobAllowPreview";
1465 default:
return false;
1476 query.
prepare(
"SELECT cmds FROM jobqueue WHERE id = :ID;");
1497 query.
prepare(
"SELECT args FROM jobqueue WHERE id = :ID;");
1504 return query.
value(0).toString();
1518 query.
prepare(
"SELECT flags FROM jobqueue WHERE id = :ID;");
1539 query.
prepare(
"SELECT status FROM jobqueue WHERE id = :ID;");
1556 int jobType,
uint chanid,
const QDateTime &recstartts)
1560 query.
prepare(
"SELECT status FROM jobqueue WHERE type = :TYPE "
1561 "AND chanid = :CHANID AND starttime = :STARTTIME;");
1565 query.
bindValue(
":STARTTIME", recstartts);
1581 QMap<int, JobQueueEntry> jobs;
1585 msg = QString(
"RecoverQueue: Checking for unfinished jobs to "
1587 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + msg);
1593 QMap<int, JobQueueEntry>::Iterator it;
1597 for (it = jobs.begin(); it != jobs.end(); ++it)
1599 int tmpCmds = (*it).cmds;
1600 int tmpStatus = (*it).status;
1603 logInfo = QString(
"jobID #%1").arg((*it).id);
1605 logInfo = QString(
"chanid %1 @ %2").arg((*it).chanid)
1606 .arg((*it).startts);
1608 if (((tmpStatus == JOB_STARTING) ||
1609 (tmpStatus == JOB_RUNNING) ||
1610 (tmpStatus == JOB_PAUSED) ||
1612 (tmpStatus == JOB_STOPPING)) &&
1615 ((*it).statustime < oldDate)))
1617 msg = QString(
"RecoverQueue: Recovering '%1' for %2 "
1621 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + msg);
1631 msg = QString(
"RecoverQueue: Ignoring '%1' for %2 "
1635 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + msg);
1648 delquery.
prepare(
"DELETE FROM jobqueue "
1649 "WHERE (status in (:FINISHED, :ABORTED, :CANCELLED) "
1650 "AND statustime < :DONEPURGEDATE) "
1651 "OR (status in (:ERRORED) "
1652 "AND statustime < :ERRORSPURGEDATE) ");
1653 delquery.
bindValue(
":FINISHED", JOB_FINISHED);
1654 delquery.
bindValue(
":ABORTED", JOB_ABORTED);
1655 delquery.
bindValue(
":CANCELLED", JOB_CANCELLED);
1656 delquery.
bindValue(
":ERRORED", JOB_ERRORED);
1657 delquery.
bindValue(
":DONEPURGEDATE", donePurgeDate);
1658 delquery.
bindValue(
":ERRORSPURGEDATE", errorsPurgeDate);
1660 if (!delquery.
exec())
1663 "old finished jobs.", delquery);
1669 if (!jobstarttsRaw.isValid())
1671 jobstarttsRaw = QDateTime::currentDateTime();
1672 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + QString(
"Invalid date/time passed, "
1674 jobstarttsRaw.toString()));
1680 "JobQueueWindowStart",
hostname,
"00:00")));
1683 "JobQueueWindowEnd",
hostname,
"23:59")));
1687 if (scheduleTime < windowStart || scheduleTime > windowEnd)
1689 LOG(VB_JOBQUEUE, LOG_ERR,
LOC +
"Time not within job queue window, " +
1703 LOG(VB_JOBQUEUE, LOG_ERR,
LOC +
1704 "ProcessJob(): Unable to open database connection");
1717 LOG(VB_JOBQUEUE, LOG_ERR,
LOC +
1718 QString(
"Unable to retrieve program info for chanid %1 @ %2")
1723 tr(
"Unable to retrieve program info from database"));
1753 tr(
"Program has been deleted"));
1778 tr(
"UNKNOWN JobType, unable to process!"));
1792 pthread_attr_t attr;
1793 pthread_attr_init(&attr);
1794 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
1795 pthread_create(&childThread, &attr, ChildThreadRoutine, jts);
1796 pthread_attr_destroy(&attr);
1804 return "Commercial Detection";
1806 return "Unknown Job";
1808 QString descSetting =
1822 if (command.trimmed().isEmpty())
1823 command =
"mythtranscode";
1825 if (command ==
"mythtranscode")
1831 if (command.trimmed().isEmpty())
1832 command =
"mythcommflag";
1834 if (command ==
"mythcommflag")
1843 if (!command.isEmpty())
1845 command.replace(
"%JOBID%", QString(
"%1").arg(
id));
1848 if (!command.isEmpty() && tmpInfo)
1852 command.replace(
"%VERBOSELEVEL%", QString(
"%1").arg(
verboseMask));
1856 command.replace(
"%TRANSPROFILE%",
1858 "autodetect" : QString::number(transcoder));
1888 const char *m_suffix;
1892 static constexpr std::array<const PpTab_t,9> kPpTab {{
1893 {
"bytes", 9999, 0 },
1903 float fbytes =
bytes;
1905 unsigned int ii = 0;
1906 while (kPpTab[ii].m_max && fbytes > kPpTab[ii].m_max) {
1911 return QString(
"%1 %2")
1912 .arg(fbytes, 0,
'f', kPpTab[ii].m_precision)
1913 .arg(kPpTab[ii].m_suffix);
1936 LOG(VB_JOBQUEUE, LOG_ERR,
LOC +
1937 "The JobQueue cannot currently transcode files that do not "
1938 "have a chanid/starttime in the recorded table.");
1953 bool useCutlist = program_info->
HasCutlist() &&
1957 QString profilearg =
1959 "autodetect" : QString::number(transcoder);
1968 command = QString(
"%1 -j %2 --profile %3")
1969 .arg(path).arg(
jobID).arg(profilearg);
1971 command +=
" --honorcutlist";
1978 QStringList tokens = command.split(
" ", Qt::SkipEmptyParts);
1979 if (!tokens.empty())
1990 QString transcoderName;
1993 transcoderName =
"Autodetect";
1998 query.
prepare(
"SELECT name FROM recordingprofiles WHERE id = :ID;");
2002 transcoderName = query.
value(0).toString();
2007 transcoderName = QString(
"Autodetect(%1)").arg(transcoder);
2022 long long filesize = 0;
2023 long long origfilesize = QFileInfo(
filename).size();
2025 QString msg = QString(
"Transcode %1")
2028 QString details = QString(
"%1: %2 (%3)")
2032 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"%1 for %2")
2033 .arg(msg, details));
2035 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + QString(
"Running command: '%1'")
2038 GetMythDB()->GetDBManager()->CloseDatabases();
2046 tr(
"ERROR: Unable to find mythtranscode, check backend logs."));
2050 details = QString(
"%1: %2 does not exist or is not executable")
2053 LOG(VB_GENERAL, LOG_ERR,
LOC +
2054 QString(
"%1 for %2").arg(msg, details));
2058 LOG(VB_JOBQUEUE, LOG_INFO,
LOC +
"Transcode command restarting");
2066 if (status == JOB_FINISHED)
2077 filesize = st.size();
2080 QString comment = tr(
"%1: %2 => %3")
2081 .arg(transcoderName,
2089 details = QString(
"%1: %2 (%3)")
2098 QString(
"could not stat '%1'").arg(
filename);
2102 details = QString(
"%1: %2")
2114 QString comment = tr(
"exit status %1, job status was \"%2\"")
2120 details = QString(
"%1: %2 (%3)")
2128 LOG(VB_GENERAL, LOG_INFO,
LOC + msg +
": " + details);
2132 if (retrylimit == 0)
2134 LOG(VB_JOBQUEUE, LOG_ERR,
LOC +
"Retry limit exceeded for transcoder, "
2135 "setting job status to errored.");
2162 LOG(VB_JOBQUEUE, LOG_ERR,
LOC +
2163 "The JobQueue cannot currently perform lookups for items which do "
2164 "not have a chanid/starttime in the recorded table.");
2174 QString details = QString(
"%1 recorded from channel %3")
2180 QString msg = QString(
"Metadata Lookup failed. Could not open "
2181 "new database connection for %1. "
2182 "Program cannot be looked up.")
2184 LOG(VB_GENERAL, LOG_ERR,
LOC + msg);
2187 tr(
"Could not open new database connection for "
2188 "metadata lookup."));
2190 delete program_info;
2194 LOG(VB_GENERAL, LOG_INFO,
2195 LOC +
"Metadata Lookup Starting for " + details);
2202 command = QString(
"%1 -j %2")
2203 .arg(path).arg(
jobID);
2206 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + QString(
"Running command: '%1'")
2209 GetMythDB()->GetDBManager()->CloseDatabases();
2211 int priority = LOG_NOTICE;
2219 comment = tr(
"Unable to find mythmetadatalookup");
2221 priority = LOG_WARNING;
2225 comment = tr(
"Aborted by user");
2227 priority = LOG_WARNING;
2231 comment = tr(
"Unable to open file or init decoder");
2233 priority = LOG_WARNING;
2237 comment = tr(
"Failed with exit status %1").arg(retVal);
2239 priority = LOG_WARNING;
2243 comment = tr(
"Metadata Lookup Complete.");
2249 QString msg = tr(
"Metadata Lookup %1",
"Job ID")
2252 if (!comment.isEmpty())
2253 details += QString(
" (%1)").arg(comment);
2255 if (priority <= LOG_WARNING)
2256 LOG(VB_GENERAL, LOG_ERR,
LOC + msg +
": " + details);
2282 LOG(VB_JOBQUEUE, LOG_ERR,
LOC +
2283 "The JobQueue cannot currently commflag files that do not "
2284 "have a chanid/starttime in the recorded table.");
2294 QString details = QString(
"%1 recorded from channel %3")
2300 QString msg = QString(
"Commercial Detection failed. Could not open "
2301 "new database connection for %1. "
2302 "Program cannot be flagged.")
2304 LOG(VB_GENERAL, LOG_ERR,
LOC + msg);
2307 tr(
"Could not open new database connection for "
2308 "commercial detector."));
2310 delete program_info;
2314 LOG(VB_GENERAL, LOG_INFO,
2315 LOC +
"Commercial Detection Starting for " + details);
2317 uint breaksFound = 0;
2325 command = QString(
"%1 -j %2 --noprogress")
2326 .arg(path).arg(
jobID);
2332 QStringList tokens = command.split(
" ", Qt::SkipEmptyParts);
2333 if (!tokens.empty())
2338 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + QString(
"Running command: '%1'")
2341 GetMythDB()->GetDBManager()->CloseDatabases();
2343 int priority = LOG_NOTICE;
2351 comment = tr(
"Unable to find mythcommflag");
2353 priority = LOG_WARNING;
2357 comment = tr(
"Aborted by user");
2359 priority = LOG_WARNING;
2363 comment = tr(
"Unable to open file or init decoder");
2365 priority = LOG_WARNING;
2369 comment = tr(
"Failed with exit status %1").arg(breaksFound);
2371 priority = LOG_WARNING;
2375 comment = tr(
"%n commercial break(s)",
"", breaksFound);
2391 QString msg = tr(
"Commercial Detection %1",
"Job ID")
2394 if (!comment.isEmpty())
2395 details += QString(
" (%1)").arg(comment);
2397 if (priority <= LOG_WARNING)
2398 LOG(VB_GENERAL, LOG_ERR,
LOC + msg +
": " + details);
2432 msg = QString(
"Started %1 for %2 recorded from channel %3")
2439 msg = QString(
"Started %1 for jobID %2").arg(jobDesc).arg(
jobID);
2442 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(msg.toLocal8Bit().constData()));
2456 LOG(VB_JOBQUEUE, LOG_INFO,
LOC + QString(
"Running command: '%1'")
2458 GetMythDB()->GetDBManager()->CloseDatabases();
2464 msg = QString(
"User Job '%1' failed, unable to find "
2465 "executable, check your PATH and backend logs.")
2467 LOG(VB_GENERAL, LOG_ERR,
LOC + msg);
2468 LOG(VB_GENERAL, LOG_NOTICE,
LOC + QString(
"Current PATH: '%1'")
2469 .arg(qEnvironmentVariable(
"PATH")));
2472 tr(
"ERROR: Unable to find executable, check backend logs."));
2474 else if (result != 0)
2476 msg = QString(
"User Job '%1' failed.").arg(command);
2477 LOG(VB_GENERAL, LOG_ERR,
LOC + msg);
2480 tr(
"ERROR: User Job returned non-zero, check logs."));
2486 msg = QString(
"Finished %1 for %2 recorded from channel %3")
2493 msg = QString(
"Finished %1 for jobID %2").arg(jobDesc).arg(
jobID);
2496 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(msg.toLocal8Bit().constData()));
2513 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