MythTV  master
jobqueue.cpp
Go to the documentation of this file.
1 
2 #include <unistd.h>
3 #include <sys/types.h>
4 #include <sys/stat.h>
5 #include <iostream>
6 #include <cstdlib>
7 #include <fcntl.h>
8 #include <pthread.h>
9 
10 #include <QDateTime>
11 #include <QFileInfo>
12 #include <QEvent>
13 #include <QCoreApplication>
14 
15 #include "libmythbase/compat.h"
16 #include "libmythbase/exitcodes.h"
17 #include "libmythbase/mthread.h"
18 #include "libmythbase/mythconfig.h"
20 #include "libmythbase/mythdate.h"
21 #include "libmythbase/mythdb.h"
22 #include "libmythbase/mythdirs.h"
27 
28 #include "jobqueue.h"
29 #include "previewgenerator.h"
30 #include "recordinginfo.h"
31 #include "recordingprofile.h"
32 
33 #define LOC QString("JobQueue: ")
34 
35 // Consider anything less than 4 hours as a "recent" job.
36 static constexpr int64_t kRecentInterval {4LL * 60 * 60};
37 
38 JobQueue::JobQueue(bool master) :
39  m_hostname(gCoreContext->GetHostName()),
40 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
41  m_runningJobsLock(new QMutex(QMutex::Recursive)),
42 #else
43  m_runningJobsLock(new QRecursiveMutex()),
44 #endif
45  m_isMaster(master),
46  m_queueThread(new MThread("JobQueue", this))
47 {
48  m_jobQueueCPU = gCoreContext->GetNumSetting("JobQueueCPU", 0);
49 
50 #ifndef USING_VALGRIND
51  QMutexLocker locker(&m_queueThreadCondLock);
52  m_processQueue = true;
54 #else
55  LOG(VB_GENERAL, LOG_ERR, LOC +
56  "The JobQueue has been disabled because "
57  "you compiled with the --enable-valgrind option.");
58 #endif // USING_VALGRIND
59 
61 }
62 
64 {
65  m_queueThreadCondLock.lock();
66  m_processQueue = false;
67  m_queueThreadCond.wakeAll();
68  m_queueThreadCondLock.unlock();
69 
71  delete m_queueThread;
72  m_queueThread = nullptr;
73 
75 
76  delete m_runningJobsLock;
77 }
78 
79 void JobQueue::customEvent(QEvent *e)
80 {
81  if (e->type() == MythEvent::kMythEventMessage)
82  {
83  auto *me = dynamic_cast<MythEvent *>(e);
84  if (me == nullptr)
85  return;
86  QString message = me->Message();
87 
88  if (message.startsWith("LOCAL_JOB"))
89  {
90  // LOCAL_JOB action ID jobID
91  // LOCAL_JOB action type chanid recstartts hostname
92  QString msg;
93  message = message.simplified();
94 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
95  QStringList tokens = message.split(" ", QString::SkipEmptyParts);
96 #else
97  QStringList tokens = message.split(" ", Qt::SkipEmptyParts);
98 #endif
99  QString action = tokens[1];
100  int jobID = -1;
101 
102  if (tokens[2] == "ID")
103  jobID = tokens[3].toInt();
104  else
105  {
106  jobID = GetJobID(
107  tokens[2].toInt(),
108  tokens[3].toUInt(),
109  MythDate::fromString(tokens[4]));
110  }
111 
112  m_runningJobsLock->lock();
113  if (!m_runningJobs.contains(jobID))
114  {
115  msg = QString("Unable to determine jobID for message: "
116  "%1. Program will not be flagged.")
117  .arg(message);
118  LOG(VB_GENERAL, LOG_ERR, LOC + msg);
119  m_runningJobsLock->unlock();
120  return;
121  }
122  m_runningJobsLock->unlock();
123 
124  msg = QString("Received message '%1'").arg(message);
125  LOG(VB_JOBQUEUE, LOG_INFO, LOC + msg);
126 
127  if ((action == "STOP") ||
128  (action == "PAUSE") ||
129  (action == "RESTART") ||
130  (action == "RESUME" ))
131  {
132  m_runningJobsLock->lock();
133 
134  if (action == "STOP")
135  m_runningJobs[jobID].flag = JOB_STOP;
136  else if (action == "PAUSE")
137  m_runningJobs[jobID].flag = JOB_PAUSE;
138  else if (action == "RESUME")
139  m_runningJobs[jobID].flag = JOB_RUN;
140  else if (action == "RESTART")
142 
143  m_runningJobsLock->unlock();
144  }
145  }
146  }
147 }
148 
149 void JobQueue::run(void)
150 {
151  m_queueThreadCondLock.lock();
152  m_queueThreadCond.wakeAll();
153  m_queueThreadCondLock.unlock();
154 
155  RecoverQueue();
156 
157  m_queueThreadCondLock.lock();
158  m_queueThreadCond.wait(&m_queueThreadCondLock, 10 * 1000UL);
159  m_queueThreadCondLock.unlock();
160 
161  ProcessQueue();
162 }
163 
165 {
166  LOG(VB_JOBQUEUE, LOG_INFO, LOC + "ProcessQueue() started");
167 
168  QString logInfo;
169  //int flags;
170  QString hostname;
171 
172  QMap<int, int> jobStatus;
173  QString message;
174  QMap<int, JobQueueEntry> jobs;
175  bool atMax = false;
176  QMap<int, RunningJobInfo>::Iterator rjiter;
177 
178  QMutexLocker locker(&m_queueThreadCondLock);
179  while (m_processQueue)
180  {
181  locker.unlock();
182 
183  bool startedJobAlready = false;
184  auto sleepTime = gCoreContext->GetDurSetting<std::chrono::seconds>("JobQueueCheckFrequency", 30s);
185  int maxJobs = gCoreContext->GetNumSetting("JobQueueMaxSimultaneousJobs", 3);
186  LOG(VB_JOBQUEUE, LOG_INFO, LOC +
187  QString("Currently set to run up to %1 job(s) max.")
188  .arg(maxJobs));
189 
190  jobStatus.clear();
191 
192  m_runningJobsLock->lock();
193  for (rjiter = m_runningJobs.begin(); rjiter != m_runningJobs.end();
194  ++rjiter)
195  {
196  if ((*rjiter).pginfo)
197  (*rjiter).pginfo->UpdateInUseMark();
198  }
199  m_runningJobsLock->unlock();
200 
201  m_jobsRunning = 0;
202  GetJobsInQueue(jobs);
203 
204  if (!jobs.empty())
205  {
206  bool inTimeWindow = InJobRunWindow();
207  for (const auto & job : qAsConst(jobs))
208  {
209  int status = job.status;
210  hostname = job.hostname;
211 
212  if (((status == JOB_RUNNING) ||
213  (status == JOB_STARTING) ||
214  (status == JOB_PAUSED)) &&
215  (hostname == m_hostname))
216  m_jobsRunning++;
217  }
218 
219  message = QString("Currently Running %1 jobs.")
220  .arg(m_jobsRunning);
221  if (!inTimeWindow)
222  {
223  message += QString(" Jobs in Queue, but we are outside of the "
224  "Job Queue time window, no new jobs can be "
225  "started.");
226  LOG(VB_JOBQUEUE, LOG_INFO, LOC + message);
227  }
228  else if (m_jobsRunning >= maxJobs)
229  {
230  message += " (At Maximum, no new jobs can be started until "
231  "a running job completes)";
232 
233  if (!atMax)
234  LOG(VB_JOBQUEUE, LOG_INFO, LOC + message);
235 
236  atMax = true;
237  }
238  else
239  {
240  LOG(VB_JOBQUEUE, LOG_INFO, LOC + message);
241  atMax = false;
242  }
243 
244 
245  for ( int x = 0;
246  (x < jobs.size()) && (m_jobsRunning < maxJobs); x++)
247  {
248  int jobID = jobs[x].id;
249  int cmds = jobs[x].cmds;
250  //flags = jobs[x].flags;
251  int status = jobs[x].status;
252  hostname = jobs[x].hostname;
253 
254  if (!jobs[x].chanid)
255  logInfo = QString("jobID #%1").arg(jobID);
256  else
257  logInfo = QString("chanid %1 @ %2").arg(jobs[x].chanid)
258  .arg(jobs[x].startts);
259 
260  // Should we even be looking at this job?
261  if ((inTimeWindow) &&
262  (!hostname.isEmpty()) &&
263  (hostname != m_hostname))
264  {
265  // Setting the status here will prevent us from processing
266  // any other jobs for this recording until this one is
267  // completed on the remote host.
268  jobStatus[jobID] = status;
269 
270  message = QString("Skipping '%1' job for %2, "
271  "should run on '%3' instead")
272  .arg(JobText(jobs[x].type), logInfo,
273  hostname);
274  LOG(VB_JOBQUEUE, LOG_INFO, LOC + message);
275  continue;
276  }
277 
278  // Check to see if there was a previous job that is not done
279  if (inTimeWindow)
280  {
281  int otherJobID = GetRunningJobID(jobs[x].chanid,
282  jobs[x].recstartts);
283  if (otherJobID && (jobStatus.contains(otherJobID)) &&
284  (!(jobStatus[otherJobID] & JOB_DONE)))
285  {
286  message =
287  QString("Skipping '%1' job for %2, "
288  "Job ID %3 is already running for "
289  "this recording with a status of '%4'")
290  .arg(JobText(jobs[x].type), logInfo,
291  QString::number(otherJobID),
292  StatusText(jobStatus[otherJobID]));
293  LOG(VB_JOBQUEUE, LOG_INFO, LOC + message);
294  continue;
295  }
296  }
297 
298  jobStatus[jobID] = status;
299 
300  // Are we allowed to run this job?
301  if ((inTimeWindow) && (!AllowedToRun(jobs[x])))
302  {
303  message = QString("Skipping '%1' job for %2, "
304  "not allowed to run on this backend.")
305  .arg(JobText(jobs[x].type), logInfo);
306  LOG(VB_JOBQUEUE, LOG_INFO, LOC + message);
307  continue;
308  }
309 
310  // Is this job scheduled for the future
311  if (jobs[x].schedruntime > MythDate::current())
312  {
313  message = QString("Skipping '%1' job for %2, this job is "
314  "not scheduled to run until %3.")
315  .arg(JobText(jobs[x].type), logInfo,
316  jobs[x].schedruntime
318  LOG(VB_JOBQUEUE, LOG_INFO, LOC + message);
319  continue;
320  }
321 
322  if (cmds & JOB_STOP)
323  {
324  // if we're trying to stop a job and it's not queued
325  // then lets send a STOP command
326  if (status != JOB_QUEUED) {
327  message = QString("Stopping '%1' job for %2")
328  .arg(JobText(jobs[x].type), logInfo);
329  LOG(VB_JOBQUEUE, LOG_INFO, LOC + message);
330 
331  m_runningJobsLock->lock();
332  if (m_runningJobs.contains(jobID))
333  m_runningJobs[jobID].flag = JOB_STOP;
334  m_runningJobsLock->unlock();
335 
336  // ChangeJobCmds(m_db, jobID, JOB_RUN);
337  continue;
338 
339  // if we're trying to stop a job and it's still queued
340  // then let's just change the status to cancelled so
341  // we don't try to run it from the queue
342  }
343 
344  message = QString("Cancelling '%1' job for %2")
345  .arg(JobText(jobs[x].type), logInfo);
346  LOG(VB_JOBQUEUE, LOG_INFO, LOC + message);
347 
348  // at the bottom of this loop we requeue any jobs that
349  // are not currently queued and also not associated
350  // with a hostname so we must claim this job before we
351  // can cancel it
353  {
354  message = QString("Unable to claim '%1' job for %2")
355  .arg(JobText(jobs[x].type), logInfo);
356  LOG(VB_JOBQUEUE, LOG_ERR, LOC + message);
357  continue;
358  }
359 
360  ChangeJobStatus(jobID, JOB_CANCELLED, "");
362  continue;
363  }
364 
365  if ((cmds & JOB_PAUSE) && (status != JOB_QUEUED))
366  {
367  message = QString("Pausing '%1' job for %2")
368  .arg(JobText(jobs[x].type), logInfo);
369  LOG(VB_JOBQUEUE, LOG_INFO, LOC + message);
370 
371  m_runningJobsLock->lock();
372  if (m_runningJobs.contains(jobID))
373  m_runningJobs[jobID].flag = JOB_PAUSE;
374  m_runningJobsLock->unlock();
375 
377  continue;
378  }
379 
380  if ((cmds & JOB_RESTART) && (status != JOB_QUEUED))
381  {
382  message = QString("Restart '%1' job for %2")
383  .arg(JobText(jobs[x].type), logInfo);
384  LOG(VB_JOBQUEUE, LOG_INFO, LOC + message);
385 
386  m_runningJobsLock->lock();
387  if (m_runningJobs.contains(jobID))
388  m_runningJobs[jobID].flag = JOB_RUN;
389  m_runningJobsLock->unlock();
390 
392  continue;
393  }
394 
395  if (status != JOB_QUEUED)
396  {
397 
398  if (hostname.isEmpty())
399  {
400  message = QString("Resetting '%1' job for %2 to %3 "
401  "status, because no hostname is set.")
402  .arg(JobText(jobs[x].type),
403  logInfo,
404  StatusText(JOB_QUEUED));
405  LOG(VB_JOBQUEUE, LOG_INFO, LOC + message);
406 
407  ChangeJobStatus(jobID, JOB_QUEUED, "");
409  }
410  else if (inTimeWindow)
411  {
412  message = QString("Skipping '%1' job for %2, "
413  "current job status is '%3'")
414  .arg(JobText(jobs[x].type),
415  logInfo,
416  StatusText(status));
417  LOG(VB_JOBQUEUE, LOG_INFO, LOC + message);
418  }
419  continue;
420  }
421 
422  // never start or claim more than one job in a single run
423  if (startedJobAlready)
424  continue;
425 
426  if ((inTimeWindow) &&
427  (hostname.isEmpty()) &&
429  {
430  message = QString("Unable to claim '%1' job for %2")
431  .arg(JobText(jobs[x].type), logInfo);
432  LOG(VB_JOBQUEUE, LOG_ERR, LOC + message);
433  continue;
434  }
435 
436  if (!inTimeWindow)
437  {
438  message = QString("Skipping '%1' job for %2, "
439  "current time is outside of the "
440  "Job Queue processing window.")
441  .arg(JobText(jobs[x].type), logInfo);
442  LOG(VB_JOBQUEUE, LOG_INFO, LOC + message);
443  continue;
444  }
445 
446  message = QString("Processing '%1' job for %2, "
447  "current status is '%3'")
448  .arg(JobText(jobs[x].type), logInfo,
449  StatusText(status));
450  LOG(VB_JOBQUEUE, LOG_INFO, LOC + message);
451 
452  ProcessJob(jobs[x]);
453 
454  startedJobAlready = true;
455  }
456  }
457 
458  if (QCoreApplication::applicationName() == MYTH_APPNAME_MYTHJOBQUEUE)
459  {
460  if (m_jobsRunning > 0)
461  {
462  if (!(gCoreContext->IsBlockingClient()))
463  {
465  LOG(VB_JOBQUEUE, LOG_INFO, QString("%1 jobs running. "
466  "Blocking shutdown.").arg(m_jobsRunning));
467  }
468  }
469  else
470  {
472  {
474  LOG(VB_JOBQUEUE, LOG_INFO, "No jobs running. "
475  "Allowing shutdown.");
476  }
477  }
478  }
479 
480 
481  locker.relock();
482  if (m_processQueue)
483  {
484  std::chrono::milliseconds st = (startedJobAlready) ? 5s : sleepTime;
485  if (st > 0ms)
486  m_queueThreadCond.wait(locker.mutex(), st.count());
487  }
488  }
489 }
490 
491 bool JobQueue::QueueRecordingJobs(const RecordingInfo &recinfo, int jobTypes)
492 {
493  if (jobTypes == JOB_NONE)
494  jobTypes = recinfo.GetAutoRunJobs();
495 
496  if (recinfo.IsCommercialFree())
497  jobTypes &= (~JOB_COMMFLAG);
498 
499  if (jobTypes != JOB_NONE)
500  {
501  QString jobHost = QString("");
502 
503  if (gCoreContext->GetBoolSetting("JobsRunOnRecordHost", false))
504  jobHost = recinfo.GetHostname();
505 
506  return JobQueue::QueueJobs(
507  jobTypes, recinfo.GetChanID(), recinfo.GetRecordingStartTime(),
508  "", "", jobHost);
509  }
510  return false;
511 }
512 
513 bool JobQueue::QueueJob(int jobType, uint chanid, const QDateTime &recstartts,
514  const QString& args, const QString& comment, QString host,
515  int flags, int status, QDateTime schedruntime)
516 {
517  int tmpStatus = JOB_UNKNOWN;
518  int tmpCmd = JOB_UNKNOWN;
519  int chanidInt = -1;
520 
521  if(!schedruntime.isValid())
522  schedruntime = MythDate::current();
523 
524  MSqlQuery query(MSqlQuery::InitCon());
525 
526  // In order to replace a job, we must have a chanid/recstartts combo
527  if (chanid)
528  {
529  int jobID = -1;
530  query.prepare("SELECT status, id, cmds FROM jobqueue "
531  "WHERE chanid = :CHANID AND starttime = :STARTTIME "
532  "AND type = :JOBTYPE;");
533  query.bindValue(":CHANID", chanid);
534  query.bindValue(":STARTTIME", recstartts);
535  query.bindValue(":JOBTYPE", jobType);
536 
537  if (!query.exec())
538  {
539  MythDB::DBError("Error in JobQueue::QueueJob()", query);
540  return false;
541  }
542  if (query.next())
543  {
544  tmpStatus = query.value(0).toInt();
545  jobID = query.value(1).toInt();
546  tmpCmd = query.value(2).toInt();
547  }
548  switch (tmpStatus)
549  {
550  case JOB_UNKNOWN:
551  break;
552  case JOB_STARTING:
553  case JOB_RUNNING:
554  case JOB_PAUSED:
555  case JOB_STOPPING:
556  case JOB_ERRORING:
557  case JOB_ABORTING:
558  return false;
559  default:
560  DeleteJob(jobID);
561  break;
562  }
563  if (! (tmpStatus & JOB_DONE) && (tmpCmd & JOB_STOP))
564  return false;
565 
566  chanidInt = chanid;
567  }
568 
569  if (host.isNull())
570  host = QString("");
571 
572  query.prepare("INSERT INTO jobqueue (chanid, starttime, inserttime, type, "
573  "status, statustime, schedruntime, hostname, args, comment, "
574  "flags) "
575  "VALUES (:CHANID, :STARTTIME, now(), :JOBTYPE, :STATUS, "
576  "now(), :SCHEDRUNTIME, :HOST, :ARGS, :COMMENT, :FLAGS);");
577 
578  query.bindValue(":CHANID", chanidInt);
579  query.bindValue(":STARTTIME", recstartts);
580  query.bindValue(":JOBTYPE", jobType);
581  query.bindValue(":STATUS", status);
582  query.bindValue(":SCHEDRUNTIME", schedruntime);
583  query.bindValue(":HOST", host);
584  query.bindValue(":ARGS", args);
585  query.bindValue(":COMMENT", comment);
586  query.bindValue(":FLAGS", flags);
587 
588  if (!query.exec())
589  {
590  MythDB::DBError("Error in JobQueue::StartJob()", query);
591  return false;
592  }
593 
594  return true;
595 }
596 
597 bool JobQueue::QueueJobs(int jobTypes, uint chanid, const QDateTime &recstartts,
598  const QString& args, const QString& comment, const QString& host)
599 {
600  if (gCoreContext->GetBoolSetting("AutoTranscodeBeforeAutoCommflag", false))
601  {
602  if (jobTypes & JOB_METADATA)
603  QueueJob(JOB_METADATA, chanid, recstartts, args, comment, host);
604  if (jobTypes & JOB_TRANSCODE)
605  QueueJob(JOB_TRANSCODE, chanid, recstartts, args, comment, host);
606  if (jobTypes & JOB_COMMFLAG)
607  QueueJob(JOB_COMMFLAG, chanid, recstartts, args, comment, host);
608  }
609  else
610  {
611  if (jobTypes & JOB_METADATA)
612  QueueJob(JOB_METADATA, chanid, recstartts, args, comment, host);
613  if (jobTypes & JOB_COMMFLAG)
614  QueueJob(JOB_COMMFLAG, chanid, recstartts, args, comment, host);
615  if (jobTypes & JOB_TRANSCODE)
616  {
617  QDateTime schedruntime = MythDate::current();
618 
619  int defer = gCoreContext->GetNumSetting("DeferAutoTranscodeDays", 0);
620  if (defer)
621  {
622  schedruntime = QDateTime(schedruntime.addDays(defer).date(),
623  QTime(0,0,0), Qt::UTC);
624  }
625 
626  QueueJob(JOB_TRANSCODE, chanid, recstartts, args, comment, host,
627  0, JOB_QUEUED, schedruntime);
628  }
629  }
630 
631  if (jobTypes & JOB_USERJOB1)
632  QueueJob(JOB_USERJOB1, chanid, recstartts, args, comment, host);
633  if (jobTypes & JOB_USERJOB2)
634  QueueJob(JOB_USERJOB2, chanid, recstartts, args, comment, host);
635  if (jobTypes & JOB_USERJOB3)
636  QueueJob(JOB_USERJOB3, chanid, recstartts, args, comment, host);
637  if (jobTypes & JOB_USERJOB4)
638  QueueJob(JOB_USERJOB4, chanid, recstartts, args, comment, host);
639 
640  return true;
641 }
642 
643 int JobQueue::GetJobID(int jobType, uint chanid, const QDateTime &recstartts)
644 {
645  MSqlQuery query(MSqlQuery::InitCon());
646 
647  query.prepare("SELECT id FROM jobqueue "
648  "WHERE chanid = :CHANID AND starttime = :STARTTIME "
649  "AND type = :JOBTYPE;");
650  query.bindValue(":CHANID", chanid);
651  query.bindValue(":STARTTIME", recstartts);
652  query.bindValue(":JOBTYPE", jobType);
653 
654  if (!query.exec())
655  {
656  MythDB::DBError("Error in JobQueue::GetJobID()", query);
657  return -1;
658  }
659  if (query.next())
660  return query.value(0).toInt();
661  return -1;
662 }
663 
665  int jobID, int &jobType, uint &chanid, QDateTime &recstartts)
666 {
667  MSqlQuery query(MSqlQuery::InitCon());
668 
669  query.prepare("SELECT type, chanid, starttime FROM jobqueue "
670  "WHERE id = :ID;");
671 
672  query.bindValue(":ID", jobID);
673 
674  if (!query.exec())
675  {
676  MythDB::DBError("Error in JobQueue::GetJobInfoFromID()", query);
677  return false;
678  }
679  if (query.next())
680  {
681  jobType = query.value(0).toInt();
682  chanid = query.value(1).toUInt();
683  recstartts = MythDate::as_utc(query.value(2).toDateTime());
684  return true;
685  }
686  return false;
687 }
688 
690  int jobID, int &jobType, uint &chanid, QString &recstartts)
691 {
692  QDateTime tmpStarttime;
693 
694  bool result = JobQueue::GetJobInfoFromID(
695  jobID, jobType, chanid, tmpStarttime);
696 
697  if (result)
698  recstartts = MythDate::toString(tmpStarttime, MythDate::kFilename);
699 
700  return result;
701 }
702 
703 int JobQueue::GetJobTypeFromName(const QString &name)
704 {
705  if (!JobNameToType.contains(name))
706  {
707  LOG(VB_GENERAL, LOG_ERR, QString("'%1' is an invalid Job Name.")
708  .arg(name));
709  return JOB_NONE;
710  }
711  return JobNameToType[name];
712 }
713 
715 {
716  QString message = QString("GLOBAL_JOB PAUSE ID %1").arg(jobID);
717  MythEvent me(message);
718  gCoreContext->dispatch(me);
719 
720  return ChangeJobCmds(jobID, JOB_PAUSE);
721 }
722 
724 {
725  QString message = QString("GLOBAL_JOB RESUME ID %1").arg(jobID);
726  MythEvent me(message);
727  gCoreContext->dispatch(me);
728 
729  return ChangeJobCmds(jobID, JOB_RESUME);
730 }
731 
733 {
734  QString message = QString("GLOBAL_JOB RESTART ID %1").arg(jobID);
735  MythEvent me(message);
736  gCoreContext->dispatch(me);
737 
739 }
740 
742 {
743  QString message = QString("GLOBAL_JOB STOP ID %1").arg(jobID);
744  MythEvent me(message);
745  gCoreContext->dispatch(me);
746 
747  return ChangeJobCmds(jobID, JOB_STOP);
748 }
749 
750 bool JobQueue::DeleteAllJobs(uint chanid, const QDateTime &recstartts)
751 {
752  MSqlQuery query(MSqlQuery::InitCon());
753  QString message;
754 
755  query.prepare("UPDATE jobqueue SET status = :CANCELLED "
756  "WHERE chanid = :CHANID AND starttime = :STARTTIME "
757  "AND status = :QUEUED;");
758 
759  query.bindValue(":CANCELLED", JOB_CANCELLED);
760  query.bindValue(":CHANID", chanid);
761  query.bindValue(":STARTTIME", recstartts);
762  query.bindValue(":QUEUED", JOB_QUEUED);
763 
764  if (!query.exec())
765  MythDB::DBError("Cancel Pending Jobs", query);
766 
767  query.prepare("UPDATE jobqueue SET cmds = :CMD "
768  "WHERE chanid = :CHANID AND starttime = :STARTTIME "
769  "AND status <> :CANCELLED;");
770  query.bindValue(":CMD", JOB_STOP);
771  query.bindValue(":CHANID", chanid);
772  query.bindValue(":STARTTIME", recstartts);
773  query.bindValue(":CANCELLED", JOB_CANCELLED);
774 
775  if (!query.exec())
776  {
777  MythDB::DBError("Stop Unfinished Jobs", query);
778  return false;
779  }
780 
781  // wait until running job(s) are done
782  bool jobsAreRunning = true;
783  std::chrono::seconds totalSlept = 0s;
784  std::chrono::seconds maxSleep = 90s;
785  while (jobsAreRunning && totalSlept < maxSleep)
786  {
787  usleep(1000);
788  query.prepare("SELECT id FROM jobqueue "
789  "WHERE chanid = :CHANID and starttime = :STARTTIME "
790  "AND status NOT IN "
791  "(:FINISHED,:ABORTED,:ERRORED,:CANCELLED);");
792  query.bindValue(":CHANID", chanid);
793  query.bindValue(":STARTTIME", recstartts);
794  query.bindValue(":FINISHED", JOB_FINISHED);
795  query.bindValue(":ABORTED", JOB_ABORTED);
796  query.bindValue(":ERRORED", JOB_ERRORED);
797  query.bindValue(":CANCELLED", JOB_CANCELLED);
798 
799  if (!query.exec())
800  {
801  MythDB::DBError("Stop Unfinished Jobs", query);
802  return false;
803  }
804 
805  if (query.size() == 0)
806  {
807  jobsAreRunning = false;
808  continue;
809  }
810  if ((totalSlept % 5s) == 0s)
811  {
812  message = QString("Waiting on %1 jobs still running for "
813  "chanid %2 @ %3").arg(query.size())
814  .arg(chanid).arg(recstartts.toString(Qt::ISODate));
815  LOG(VB_JOBQUEUE, LOG_INFO, LOC + message);
816  }
817 
818  sleep(1);
819  totalSlept++;
820  }
821 
822  if (totalSlept <= maxSleep)
823  {
824  query.prepare("DELETE FROM jobqueue "
825  "WHERE chanid = :CHANID AND starttime = :STARTTIME;");
826  query.bindValue(":CHANID", chanid);
827  query.bindValue(":STARTTIME", recstartts);
828 
829  if (!query.exec())
830  MythDB::DBError("Delete All Jobs", query);
831  }
832  else
833  {
834  query.prepare("SELECT id, type, status, comment FROM jobqueue "
835  "WHERE chanid = :CHANID AND starttime = :STARTTIME "
836  "AND status <> :CANCELLED ORDER BY id;");
837 
838  query.bindValue(":CHANID", chanid);
839  query.bindValue(":STARTTIME", recstartts);
840  query.bindValue(":CANCELLED", JOB_CANCELLED);
841 
842  if (!query.exec())
843  {
844  MythDB::DBError("Error in JobQueue::DeleteAllJobs(), Unable "
845  "to query list of Jobs left in Queue.", query);
846  return false;
847  }
848 
849  LOG(VB_GENERAL, LOG_ERR, LOC +
850  QString( "In DeleteAllJobs: There are Jobs "
851  "left in the JobQueue that are still running for "
852  "chanid %1 @ %2.").arg(chanid)
853  .arg(recstartts.toString(Qt::ISODate)));
854 
855  while (query.next())
856  {
857  LOG(VB_GENERAL, LOG_ERR, LOC +
858  QString("Job ID %1: '%2' with status '%3' and comment '%4'")
859  .arg(query.value(0).toString(),
860  JobText(query.value(1).toInt()),
861  StatusText(query.value(2).toInt()),
862  query.value(3).toString()));
863  }
864 
865  return false;
866  }
867 
868  return true;
869 }
870 
872 {
873  return JobQueue::SafeDeleteJob(jobID, 0, 0, QDateTime());
874 }
875 
876 bool JobQueue::SafeDeleteJob(int jobID, int jobType, int chanid,
877  const QDateTime& recstartts)
878 {
879  if (jobID < 0)
880  return false;
881 
882  if (chanid)
883  {
884 
885  int thisJob = GetJobID(jobType, chanid, recstartts);
886  QString msg;
887 
888  if( thisJob != jobID)
889  {
890  msg = QString("JobType, chanid and starttime don't match jobID %1");
891  LOG(VB_JOBQUEUE, LOG_ERR, LOC + msg.arg(jobID));
892  return false;
893  }
894 
895  if (JobQueue::IsJobRunning(jobType, chanid, recstartts))
896  {
897  msg = QString("Can't remove running JobID %1");
898  LOG(VB_GENERAL, LOG_ERR, LOC + msg.arg(jobID));
899  return false;
900  }
901  }
902 
903  MSqlQuery query(MSqlQuery::InitCon());
904 
905  query.prepare("DELETE FROM jobqueue WHERE id = :ID;");
906 
907  query.bindValue(":ID", jobID);
908 
909  if (!query.exec())
910  {
911  MythDB::DBError("Error in JobQueue::SafeDeleteJob()", query);
912  return false;
913  }
914 
915  return true;
916 }
917 
918 bool JobQueue::ChangeJobCmds(int jobID, int newCmds)
919 {
920  if (jobID < 0)
921  return false;
922 
923  MSqlQuery query(MSqlQuery::InitCon());
924 
925  query.prepare("UPDATE jobqueue SET cmds = :CMDS WHERE id = :ID;");
926 
927  query.bindValue(":CMDS", newCmds);
928  query.bindValue(":ID", jobID);
929 
930  if (!query.exec())
931  {
932  MythDB::DBError("Error in JobQueue::ChangeJobCmds()", query);
933  return false;
934  }
935 
936  return true;
937 }
938 
939 bool JobQueue::ChangeJobCmds(int jobType, uint chanid,
940  const QDateTime &recstartts, int newCmds)
941 {
942  MSqlQuery query(MSqlQuery::InitCon());
943 
944  query.prepare("UPDATE jobqueue SET cmds = :CMDS WHERE type = :TYPE "
945  "AND chanid = :CHANID AND starttime = :STARTTIME;");
946 
947  query.bindValue(":CMDS", newCmds);
948  query.bindValue(":TYPE", jobType);
949  query.bindValue(":CHANID", chanid);
950  query.bindValue(":STARTTIME", recstartts);
951 
952  if (!query.exec())
953  {
954  MythDB::DBError("Error in JobQueue::ChangeJobCmds()", query);
955  return false;
956  }
957 
958  return true;
959 }
960 
961 bool JobQueue::ChangeJobFlags(int jobID, int newFlags)
962 {
963  if (jobID < 0)
964  return false;
965 
966  MSqlQuery query(MSqlQuery::InitCon());
967 
968  query.prepare("UPDATE jobqueue SET flags = :FLAGS WHERE id = :ID;");
969 
970  query.bindValue(":FLAGS", newFlags);
971  query.bindValue(":ID", jobID);
972 
973  if (!query.exec())
974  {
975  MythDB::DBError("Error in JobQueue::ChangeJobFlags()", query);
976  return false;
977  }
978 
979  return true;
980 }
981 
982 bool JobQueue::ChangeJobStatus(int jobID, int newStatus, const QString& comment)
983 {
984  if (jobID < 0)
985  return false;
986 
987  LOG(VB_JOBQUEUE, LOG_INFO, LOC + QString("ChangeJobStatus(%1, %2, '%3')")
988  .arg(jobID).arg(StatusText(newStatus), comment));
989 
990  MSqlQuery query(MSqlQuery::InitCon());
991 
992  query.prepare("UPDATE jobqueue SET status = :STATUS, comment = :COMMENT "
993  "WHERE id = :ID AND status <> :NEWSTATUS;");
994 
995  query.bindValue(":STATUS", newStatus);
996  query.bindValue(":COMMENT", comment);
997  query.bindValue(":ID", jobID);
998  query.bindValue(":NEWSTATUS", newStatus);
999 
1000  if (!query.exec())
1001  {
1002  MythDB::DBError("Error in JobQueue::ChangeJobStatus()", query);
1003  return false;
1004  }
1005 
1006  return true;
1007 }
1008 
1009 bool JobQueue::ChangeJobComment(int jobID, const QString& comment)
1010 {
1011  if (jobID < 0)
1012  return false;
1013 
1014  LOG(VB_JOBQUEUE, LOG_INFO, LOC + QString("ChangeJobComment(%1, '%2')")
1015  .arg(jobID).arg(comment));
1016 
1017  MSqlQuery query(MSqlQuery::InitCon());
1018 
1019  query.prepare("UPDATE jobqueue SET comment = :COMMENT "
1020  "WHERE id = :ID;");
1021 
1022  query.bindValue(":COMMENT", comment);
1023  query.bindValue(":ID", jobID);
1024 
1025  if (!query.exec())
1026  {
1027  MythDB::DBError("Error in JobQueue::ChangeJobComment()", query);
1028  return false;
1029  }
1030 
1031  return true;
1032 }
1033 
1034 bool JobQueue::ChangeJobArgs(int jobID, const QString& args)
1035 {
1036  if (jobID < 0)
1037  return false;
1038 
1039  MSqlQuery query(MSqlQuery::InitCon());
1040 
1041  query.prepare("UPDATE jobqueue SET args = :ARGS "
1042  "WHERE id = :ID;");
1043 
1044  query.bindValue(":ARGS", args);
1045  query.bindValue(":ID", jobID);
1046 
1047  if (!query.exec())
1048  {
1049  MythDB::DBError("Error in JobQueue::ChangeJobArgs()", query);
1050  return false;
1051  }
1052 
1053  return true;
1054 }
1055 
1056 int JobQueue::GetRunningJobID(uint chanid, const QDateTime &recstartts)
1057 {
1058  m_runningJobsLock->lock();
1059  for (const auto& jInfo : qAsConst(m_runningJobs))
1060  {
1061  if ((jInfo.pginfo->GetChanID() == chanid) &&
1062  (jInfo.pginfo->GetRecordingStartTime() == recstartts))
1063  {
1064  m_runningJobsLock->unlock();
1065 
1066  return jInfo.id;
1067  }
1068  }
1069  m_runningJobsLock->unlock();
1070 
1071  return 0;
1072 }
1073 
1075 {
1076  return (status == JOB_QUEUED);
1077 }
1078 
1080 {
1081  return ((status != JOB_UNKNOWN) && (status != JOB_QUEUED) &&
1082  ((status & JOB_DONE) == 0));
1083 }
1084 
1085 bool JobQueue::IsJobRunning(int jobType,
1086  uint chanid, const QDateTime &recstartts)
1087 {
1088  return IsJobStatusRunning(GetJobStatus(jobType, chanid, recstartts));
1089 }
1090 
1091 bool JobQueue::IsJobRunning(int jobType, const ProgramInfo &pginfo)
1092 {
1093  return JobQueue::IsJobRunning(
1094  jobType, pginfo.GetChanID(), pginfo.GetRecordingStartTime());
1095 }
1096 
1098  int jobType, uint chanid, const QDateTime &recstartts)
1099 {
1100  int tmpStatus = GetJobStatus(jobType, chanid, recstartts);
1101 
1102  return (tmpStatus != JOB_UNKNOWN) && ((tmpStatus & JOB_DONE) == 0);
1103 }
1104 
1106  int jobType, uint chanid, const QDateTime &recstartts)
1107 {
1108  return IsJobStatusQueued(GetJobStatus(jobType, chanid, recstartts));
1109 }
1110 
1111 QString JobQueue::JobText(int jobType)
1112 {
1113  switch (jobType)
1114  {
1115  case JOB_TRANSCODE: return tr("Transcode");
1116  case JOB_COMMFLAG: return tr("Flag Commercials");
1117  case JOB_METADATA: return tr("Look up Metadata");
1118  case JOB_PREVIEW: return tr("Preview Generation");
1119  }
1120 
1121  if (jobType & JOB_USERJOB)
1122  {
1123  QString settingName =
1124  QString("UserJobDesc%1").arg(UserJobTypeToIndex(jobType));
1125  return gCoreContext->GetSetting(settingName, settingName);
1126  }
1127 
1128  return tr("Unknown Job");
1129 }
1130 
1131 // NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
1132 #define JOBSTATUS_STATUSTEXT(A,B,C) case A: return C;
1133 
1134 QString JobQueue::StatusText(int status)
1135 {
1136  switch (status)
1137  {
1139  default: break;
1140  }
1141  return tr("Undefined");
1142 }
1143 
1144 bool JobQueue::InJobRunWindow(std::chrono::minutes orStartsWithinMins)
1145 {
1146  QString queueStartTimeStr;
1147  QString queueEndTimeStr;
1148  QTime queueStartTime;
1149  QTime queueEndTime;
1150  QTime curTime = QTime::currentTime();
1151  bool inTimeWindow = false;
1152  orStartsWithinMins = orStartsWithinMins < 0min ? 0min : orStartsWithinMins;
1153 
1154  queueStartTimeStr = gCoreContext->GetSetting("JobQueueWindowStart", "00:00");
1155  queueEndTimeStr = gCoreContext->GetSetting("JobQueueWindowEnd", "23:59");
1156 
1157  queueStartTime = QTime::fromString(queueStartTimeStr, "hh:mm");
1158  if (!queueStartTime.isValid())
1159  {
1160  LOG(VB_GENERAL, LOG_ERR,
1161  QString("Invalid JobQueueWindowStart time '%1', using 00:00")
1162  .arg(queueStartTimeStr));
1163  queueStartTime = QTime(0, 0);
1164  }
1165 
1166  queueEndTime = QTime::fromString(queueEndTimeStr, "hh:mm");
1167  if (!queueEndTime.isValid())
1168  {
1169  LOG(VB_GENERAL, LOG_ERR,
1170  QString("Invalid JobQueueWindowEnd time '%1', using 23:59")
1171  .arg(queueEndTimeStr));
1172  queueEndTime = QTime(23, 59);
1173  }
1174 
1175  LOG(VB_JOBQUEUE, LOG_INFO, LOC +
1176  QString("Currently set to run new jobs from %1 to %2")
1177  .arg(queueStartTimeStr, queueEndTimeStr));
1178 
1179  if ((queueStartTime <= curTime) && (curTime < queueEndTime))
1180  { // NOLINT(bugprone-branch-clone)
1181  inTimeWindow = true;
1182  }
1183  else if ((queueStartTime > queueEndTime) &&
1184  ((curTime < queueEndTime) || (queueStartTime <= curTime)))
1185  {
1186  inTimeWindow = true;
1187  }
1188  else if (orStartsWithinMins > 0min)
1189  {
1190  // Check if the window starts soon
1191  if (curTime <= queueStartTime)
1192  {
1193  // Start time hasn't passed yet today
1194  if (queueStartTime.secsTo(curTime) <= duration_cast<std::chrono::seconds>(orStartsWithinMins).count())
1195  {
1196  LOG(VB_JOBQUEUE, LOG_INFO, LOC +
1197  QString("Job run window will start within %1 minutes")
1198  .arg(orStartsWithinMins.count()));
1199  inTimeWindow = true;
1200  }
1201  }
1202  else
1203  {
1204  // We passed the start time for today, try tomorrow
1205  QDateTime curDateTime = MythDate::current();
1206  QDateTime startDateTime = QDateTime(
1207  curDateTime.date(), queueStartTime, Qt::UTC).addDays(1);
1208 
1209  if (curDateTime.secsTo(startDateTime) <= duration_cast<std::chrono::seconds>(orStartsWithinMins).count())
1210  {
1211  LOG(VB_JOBQUEUE, LOG_INFO, LOC +
1212  QString("Job run window will start "
1213  "within %1 minutes (tomorrow)")
1214  .arg(orStartsWithinMins.count()));
1215  inTimeWindow = true;
1216  }
1217  }
1218  }
1219 
1220  return inTimeWindow;
1221 }
1222 
1223 bool JobQueue::HasRunningOrPendingJobs(std::chrono::minutes startingWithinMins)
1224 {
1225  /* startingWithinMins <= 0 - look for any pending jobs
1226  > 0 - only consider pending starting within this time */
1227  QMap<int, JobQueueEntry> jobs;
1228  QMap<int, JobQueueEntry>::Iterator it;
1229  QDateTime maxSchedRunTime = MythDate::current();
1230  bool checkForQueuedJobs = (startingWithinMins <= 0min
1231  || InJobRunWindow(startingWithinMins));
1232 
1233  if (checkForQueuedJobs && startingWithinMins > 0min) {
1234  maxSchedRunTime = maxSchedRunTime.addSecs(duration_cast<std::chrono::seconds>(startingWithinMins).count());
1235  LOG(VB_JOBQUEUE, LOG_INFO, LOC +
1236  QString("HasRunningOrPendingJobs: checking for jobs "
1237  "starting before: %1")
1238  .arg(maxSchedRunTime.toString(Qt::ISODate)));
1239  }
1240 
1242 
1243  if (!jobs.empty()) {
1244  for (it = jobs.begin(); it != jobs.end(); ++it)
1245  {
1246  int tmpStatus = (*it).status;
1247  if (tmpStatus == JOB_RUNNING) {
1248  LOG(VB_JOBQUEUE, LOG_INFO, LOC +
1249  QString("HasRunningOrPendingJobs: found running job"));
1250  return true;
1251  }
1252 
1253  if (checkForQueuedJobs) {
1254  if ((tmpStatus != JOB_UNKNOWN) && (!(tmpStatus & JOB_DONE))) {
1255  if (startingWithinMins <= 0min) {
1256  LOG(VB_JOBQUEUE, LOG_INFO, LOC +
1257  "HasRunningOrPendingJobs: found pending job");
1258  return true;
1259  }
1260  if ((*it).schedruntime <= maxSchedRunTime) {
1261  LOG(VB_JOBQUEUE, LOG_INFO, LOC +
1262  QString("HasRunningOrPendingJobs: found pending "
1263  "job scheduled to start at: %1")
1264  .arg((*it).schedruntime.toString(Qt::ISODate)));
1265  return true;
1266  }
1267  }
1268  }
1269  }
1270  }
1271  return false;
1272 }
1273 
1274 
1275 int JobQueue::GetJobsInQueue(QMap<int, JobQueueEntry> &jobs, int findJobs)
1276 {
1277  JobQueueEntry thisJob;
1278  MSqlQuery query(MSqlQuery::InitCon());
1279  QDateTime recentDate = MythDate::current().addSecs(-kRecentInterval);
1280  QString logInfo;
1281  int jobCount = 0;
1282  bool commflagWhileRecording =
1283  gCoreContext->GetBoolSetting("AutoCommflagWhileRecording", false);
1284 
1285  jobs.clear();
1286 
1287  query.prepare("SELECT j.id, j.chanid, j.starttime, j.inserttime, j.type, "
1288  "j.cmds, j.flags, j.status, j.statustime, j.hostname, "
1289  "j.args, j.comment, r.endtime, j.schedruntime "
1290  "FROM jobqueue j "
1291  "LEFT JOIN recorded r "
1292  " ON j.chanid = r.chanid AND j.starttime = r.starttime "
1293  "ORDER BY j.schedruntime, j.id;");
1294 
1295  if (!query.exec())
1296  {
1297  MythDB::DBError("Error in JobQueue::GetJobs(), Unable to "
1298  "query list of Jobs in Queue.", query);
1299  return 0;
1300  }
1301 
1302  LOG(VB_JOBQUEUE, LOG_INFO, LOC +
1303  QString("GetJobsInQueue: findJobs search bitmask %1, "
1304  "found %2 total jobs")
1305  .arg(findJobs).arg(query.size()));
1306 
1307  while (query.next())
1308  {
1309  bool wantThisJob = false;
1310 
1311  thisJob.id = query.value(0).toInt();
1312  thisJob.recstartts = MythDate::as_utc(query.value(2).toDateTime());
1313  thisJob.schedruntime = MythDate::as_utc(query.value(13).toDateTime());
1314  thisJob.type = query.value(4).toInt();
1315  thisJob.status = query.value(7).toInt();
1316  thisJob.statustime = MythDate::as_utc(query.value(8).toDateTime());
1317  thisJob.startts = MythDate::toString(
1318  thisJob.recstartts, MythDate::kFilename);
1319 
1320  // -1 indicates the chanid is empty
1321  if (query.value(1).toInt() == -1)
1322  {
1323  thisJob.chanid = 0;
1324  logInfo = QString("jobID #%1").arg(thisJob.id);
1325  }
1326  else
1327  {
1328  thisJob.chanid = query.value(1).toUInt();
1329  logInfo = QString("chanid %1 @ %2").arg(thisJob.chanid)
1330  .arg(thisJob.startts);
1331  }
1332 
1333  if ((MythDate::as_utc(query.value(12).toDateTime()) > MythDate::current()) &&
1334  ((!commflagWhileRecording) ||
1335  ((thisJob.type != JOB_COMMFLAG) &&
1336  (thisJob.type != JOB_METADATA))))
1337  {
1338  LOG(VB_JOBQUEUE, LOG_INFO, LOC +
1339  QString("GetJobsInQueue: Ignoring '%1' Job "
1340  "for %2 in %3 state. Endtime in future.")
1341  .arg(JobText(thisJob.type),
1342  logInfo, StatusText(thisJob.status)));
1343  continue;
1344  }
1345 
1346  if ((findJobs & JOB_LIST_ALL) ||
1347  ((findJobs & JOB_LIST_DONE) &&
1348  (thisJob.status & JOB_DONE)) ||
1349  ((findJobs & JOB_LIST_NOT_DONE) &&
1350  (!(thisJob.status & JOB_DONE))) ||
1351  ((findJobs & JOB_LIST_ERROR) &&
1352  (thisJob.status == JOB_ERRORED)) ||
1353  ((findJobs & JOB_LIST_RECENT) &&
1354  (thisJob.statustime > recentDate)))
1355  wantThisJob = true;
1356 
1357  if (!wantThisJob)
1358  {
1359  LOG(VB_JOBQUEUE, LOG_INFO, LOC +
1360  QString("GetJobsInQueue: Ignore '%1' Job for %2 in %3 state.")
1361  .arg(JobText(thisJob.type),
1362  logInfo, StatusText(thisJob.status)));
1363  continue;
1364  }
1365 
1366  LOG(VB_JOBQUEUE, LOG_INFO, LOC +
1367  QString("GetJobsInQueue: Found '%1' Job for %2 in %3 state.")
1368  .arg(JobText(thisJob.type),
1369  logInfo, StatusText(thisJob.status)));
1370 
1371  thisJob.inserttime = MythDate::as_utc(query.value(3).toDateTime());
1372  thisJob.cmds = query.value(5).toInt();
1373  thisJob.flags = query.value(6).toInt();
1374  thisJob.hostname = query.value(9).toString();
1375  thisJob.args = query.value(10).toString();
1376  thisJob.comment = query.value(11).toString();
1377 
1378  if ((thisJob.type & JOB_USERJOB) &&
1379  (UserJobTypeToIndex(thisJob.type) == 0))
1380  {
1381  thisJob.type = JOB_NONE;
1382  LOG(VB_JOBQUEUE, LOG_INFO, LOC +
1383  QString("GetJobsInQueue: Unknown Job Type: %1")
1384  .arg(thisJob.type));
1385  }
1386 
1387  if (thisJob.type != JOB_NONE)
1388  jobs[jobCount++] = thisJob;
1389  }
1390 
1391  return jobCount;
1392 }
1393 
1394 bool JobQueue::ChangeJobHost(int jobID, const QString& newHostname)
1395 {
1396  MSqlQuery query(MSqlQuery::InitCon());
1397 
1398  if (!newHostname.isEmpty())
1399  {
1400  query.prepare("UPDATE jobqueue SET hostname = :NEWHOSTNAME "
1401  "WHERE hostname = :EMPTY AND id = :ID;");
1402  query.bindValue(":NEWHOSTNAME", newHostname);
1403  query.bindValue(":EMPTY", "");
1404  query.bindValue(":ID", jobID);
1405  }
1406  else
1407  {
1408  query.prepare("UPDATE jobqueue SET hostname = :EMPTY "
1409  "WHERE id = :ID;");
1410  query.bindValue(":EMPTY", "");
1411  query.bindValue(":ID", jobID);
1412  }
1413 
1414  if (!query.exec())
1415  {
1416  MythDB::DBError(QString("Error in JobQueue::ChangeJobHost(), "
1417  "Unable to set hostname to '%1' for "
1418  "job %2.").arg(newHostname).arg(jobID),
1419  query);
1420  return false;
1421  }
1422 
1423  return query.numRowsAffected() > 0;
1424 }
1425 
1427 {
1428  QString allowSetting;
1429 
1430  if ((!job.hostname.isEmpty()) &&
1431  (job.hostname != m_hostname))
1432  return false;
1433 
1434  if (job.type & JOB_USERJOB)
1435  {
1436  allowSetting =
1437  QString("JobAllowUserJob%1").arg(UserJobTypeToIndex(job.type));
1438  }
1439  else
1440  {
1441  switch (job.type)
1442  {
1443  case JOB_TRANSCODE: allowSetting = "JobAllowTranscode";
1444  break;
1445  case JOB_COMMFLAG: allowSetting = "JobAllowCommFlag";
1446  break;
1447  case JOB_METADATA: allowSetting = "JobAllowMetadata";
1448  break;
1449  case JOB_PREVIEW: allowSetting = "JobAllowPreview";
1450  break;
1451  default: return false;
1452  }
1453  }
1454 
1455  return gCoreContext->GetBoolSetting(allowSetting, true);
1456 }
1457 
1459 {
1460  MSqlQuery query(MSqlQuery::InitCon());
1461 
1462  query.prepare("SELECT cmds FROM jobqueue WHERE id = :ID;");
1463 
1464  query.bindValue(":ID", jobID);
1465 
1466  if (query.exec())
1467  {
1468  if (query.next())
1469  return (enum JobCmds)query.value(0).toInt();
1470  }
1471  else
1472  {
1473  MythDB::DBError("Error in JobQueue::GetJobCmd()", query);
1474  }
1475 
1476  return JOB_RUN;
1477 }
1478 
1480 {
1481  MSqlQuery query(MSqlQuery::InitCon());
1482 
1483  query.prepare("SELECT args FROM jobqueue WHERE id = :ID;");
1484 
1485  query.bindValue(":ID", jobID);
1486 
1487  if (query.exec())
1488  {
1489  if (query.next())
1490  return query.value(0).toString();
1491  }
1492  else
1493  {
1494  MythDB::DBError("Error in JobQueue::GetJobArgs()", query);
1495  }
1496 
1497  return {""};
1498 }
1499 
1501 {
1502  MSqlQuery query(MSqlQuery::InitCon());
1503 
1504  query.prepare("SELECT flags FROM jobqueue WHERE id = :ID;");
1505 
1506  query.bindValue(":ID", jobID);
1507 
1508  if (query.exec())
1509  {
1510  if (query.next())
1511  return (enum JobFlags)query.value(0).toInt();
1512  }
1513  else
1514  {
1515  MythDB::DBError("Error in JobQueue::GetJobFlags()", query);
1516  }
1517 
1518  return JOB_NO_FLAGS;
1519 }
1520 
1522 {
1523  MSqlQuery query(MSqlQuery::InitCon());
1524 
1525  query.prepare("SELECT status FROM jobqueue WHERE id = :ID;");
1526 
1527  query.bindValue(":ID", jobID);
1528 
1529  if (query.exec())
1530  {
1531  if (query.next())
1532  return (enum JobStatus)query.value(0).toInt();
1533  }
1534  else
1535  {
1536  MythDB::DBError("Error in JobQueue::GetJobStatus()", query);
1537  }
1538  return JOB_UNKNOWN;
1539 }
1540 
1542  int jobType, uint chanid, const QDateTime &recstartts)
1543 {
1544  MSqlQuery query(MSqlQuery::InitCon());
1545 
1546  query.prepare("SELECT status FROM jobqueue WHERE type = :TYPE "
1547  "AND chanid = :CHANID AND starttime = :STARTTIME;");
1548 
1549  query.bindValue(":TYPE", jobType);
1550  query.bindValue(":CHANID", chanid);
1551  query.bindValue(":STARTTIME", recstartts);
1552 
1553  if (query.exec())
1554  {
1555  if (query.next())
1556  return (enum JobStatus)query.value(0).toInt();
1557  }
1558  else
1559  {
1560  MythDB::DBError("Error in JobQueue::GetJobStatus()", query);
1561  }
1562  return JOB_UNKNOWN;
1563 }
1564 
1565 void JobQueue::RecoverQueue(bool justOld)
1566 {
1567  QMap<int, JobQueueEntry> jobs;
1568  QString msg;
1569  QString logInfo;
1570 
1571  msg = QString("RecoverQueue: Checking for unfinished jobs to "
1572  "recover.");
1573  LOG(VB_JOBQUEUE, LOG_INFO, LOC + msg);
1574 
1575  GetJobsInQueue(jobs);
1576 
1577  if (!jobs.empty())
1578  {
1579  QMap<int, JobQueueEntry>::Iterator it;
1580  QDateTime oldDate = MythDate::current().addDays(-1);
1581  QString hostname = gCoreContext->GetHostName();
1582 
1583  for (it = jobs.begin(); it != jobs.end(); ++it)
1584  {
1585  int tmpCmds = (*it).cmds;
1586  int tmpStatus = (*it).status;
1587 
1588  if (!(*it).chanid)
1589  logInfo = QString("jobID #%1").arg((*it).id);
1590  else
1591  logInfo = QString("chanid %1 @ %2").arg((*it).chanid)
1592  .arg((*it).startts);
1593 
1594  if (((tmpStatus == JOB_STARTING) ||
1595  (tmpStatus == JOB_RUNNING) ||
1596  (tmpStatus == JOB_PAUSED) ||
1597  (tmpCmds & JOB_STOP) ||
1598  (tmpStatus == JOB_STOPPING)) &&
1599  (((!justOld) &&
1600  ((*it).hostname == hostname)) ||
1601  ((*it).statustime < oldDate)))
1602  {
1603  msg = QString("RecoverQueue: Recovering '%1' for %2 "
1604  "from '%3' state.")
1605  .arg(JobText((*it).type),
1606  logInfo, StatusText((*it).status));
1607  LOG(VB_JOBQUEUE, LOG_INFO, LOC + msg);
1608 
1609  ChangeJobStatus((*it).id, JOB_QUEUED, "");
1610  ChangeJobCmds((*it).id, JOB_RUN);
1611  if (!gCoreContext->GetBoolSetting("JobsRunOnRecordHost", false))
1612  ChangeJobHost((*it).id, "");
1613  }
1614  else
1615  {
1616 #if 0
1617  msg = QString("RecoverQueue: Ignoring '%1' for %2 "
1618  "in '%3' state.")
1619  .arg(JobText((*it).type))
1620  .arg(logInfo).arg(StatusText((*it).status));
1621  LOG(VB_JOBQUEUE, LOG_INFO, LOC + msg);
1622 #endif
1623  }
1624  }
1625  }
1626 }
1627 
1629 {
1630  MSqlQuery delquery(MSqlQuery::InitCon());
1631  QDateTime donePurgeDate = MythDate::current().addDays(-2);
1632  QDateTime errorsPurgeDate = MythDate::current().addDays(-4);
1633 
1634  delquery.prepare("DELETE FROM jobqueue "
1635  "WHERE (status in (:FINISHED, :ABORTED, :CANCELLED) "
1636  "AND statustime < :DONEPURGEDATE) "
1637  "OR (status in (:ERRORED) "
1638  "AND statustime < :ERRORSPURGEDATE) ");
1639  delquery.bindValue(":FINISHED", JOB_FINISHED);
1640  delquery.bindValue(":ABORTED", JOB_ABORTED);
1641  delquery.bindValue(":CANCELLED", JOB_CANCELLED);
1642  delquery.bindValue(":ERRORED", JOB_ERRORED);
1643  delquery.bindValue(":DONEPURGEDATE", donePurgeDate);
1644  delquery.bindValue(":ERRORSPURGEDATE", errorsPurgeDate);
1645 
1646  if (!delquery.exec())
1647  {
1648  MythDB::DBError("JobQueue::CleanupOldJobsInQueue: Error deleting "
1649  "old finished jobs.", delquery);
1650  }
1651 }
1652 
1653 bool JobQueue::InJobRunWindow(QDateTime jobstarttsRaw)
1654 {
1655  if (!jobstarttsRaw.isValid())
1656  {
1657  jobstarttsRaw = QDateTime::currentDateTime();
1658  LOG(VB_JOBQUEUE, LOG_INFO, LOC + QString("Invalid date/time passed, "
1659  "using %1").arg(
1660  jobstarttsRaw.toString()));
1661  }
1662 
1663  QString hostname(gCoreContext->GetHostName());
1664 
1665  QTime windowStart(QTime::fromString(gCoreContext->GetSettingOnHost(
1666  "JobQueueWindowStart", hostname, "00:00")));
1667 
1669  "JobQueueWindowEnd", hostname, "23:59")));
1670 
1671  QTime scheduleTime(QTime::fromString(jobstarttsRaw.toString("hh:mm")));
1672 
1673  if (scheduleTime < windowStart || scheduleTime > windowEnd)
1674  {
1675  LOG(VB_JOBQUEUE, LOG_ERR, LOC + "Time not within job queue window, " +
1676  "job not queued");
1677  return false;
1678  }
1679 
1680  return true;
1681 }
1682 
1684 {
1685  int jobID = job.id;
1686 
1688  {
1689  LOG(VB_JOBQUEUE, LOG_ERR, LOC +
1690  "ProcessJob(): Unable to open database connection");
1691  return;
1692  }
1693 
1694  ChangeJobStatus(jobID, JOB_PENDING);
1695  ProgramInfo *pginfo = nullptr;
1696 
1697  if (job.chanid)
1698  {
1699  pginfo = new ProgramInfo(job.chanid, job.recstartts);
1700 
1701  if (!pginfo->GetChanID())
1702  {
1703  LOG(VB_JOBQUEUE, LOG_ERR, LOC +
1704  QString("Unable to retrieve program info for chanid %1 @ %2")
1705  .arg(job.chanid)
1706  .arg(job.recstartts.toString(Qt::ISODate)));
1707 
1708  ChangeJobStatus(jobID, JOB_ERRORED,
1709  tr("Unable to retrieve program info from database"));
1710 
1711  delete pginfo;
1712 
1713  return;
1714  }
1715 
1716  pginfo->SetPathname(pginfo->GetPlaybackURL());
1717  }
1718 
1719 
1720  m_runningJobsLock->lock();
1721 
1722  ChangeJobStatus(jobID, JOB_STARTING);
1723  RunningJobInfo jInfo;
1724  jInfo.type = job.type;
1725  jInfo.id = jobID;
1726  jInfo.flag = JOB_RUN;
1727  jInfo.desc = GetJobDescription(job.type);
1728  jInfo.command = GetJobCommand(jobID, job.type, pginfo);
1729  jInfo.pginfo = pginfo;
1730 
1731  m_runningJobs[jobID] = jInfo;
1732 
1733  if (pginfo)
1734  pginfo->MarkAsInUse(true, kJobQueueInUseID);
1735 
1736  if (pginfo && pginfo->GetRecordingGroup() == "Deleted")
1737  {
1738  ChangeJobStatus(jobID, JOB_CANCELLED,
1739  tr("Program has been deleted"));
1741  }
1742  else if ((job.type == JOB_TRANSCODE) ||
1743  (m_runningJobs[jobID].command == "mythtranscode"))
1744  {
1746  }
1747  else if ((job.type == JOB_COMMFLAG) ||
1748  (m_runningJobs[jobID].command == "mythcommflag"))
1749  {
1751  }
1752  else if ((job.type == JOB_METADATA) ||
1753  (m_runningJobs[jobID].command == "mythmetadatalookup"))
1754  {
1756  }
1757  else if (job.type & JOB_USERJOB)
1758  {
1760  }
1761  else
1762  {
1763  ChangeJobStatus(jobID, JOB_ERRORED,
1764  tr("UNKNOWN JobType, unable to process!"));
1766  }
1767 
1768  m_runningJobsLock->unlock();
1769 }
1770 
1771 void JobQueue::StartChildJob(void *(*ChildThreadRoutine)(void *), int jobID)
1772 {
1773  auto *jts = new JobThreadStruct;
1774  jts->jq = this;
1775  jts->jobID = jobID;
1776 
1777  pthread_t childThread = 0;
1778  pthread_attr_t attr;
1779  pthread_attr_init(&attr);
1780  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
1781  pthread_create(&childThread, &attr, ChildThreadRoutine, jts);
1782  pthread_attr_destroy(&attr);
1783 }
1784 
1785 QString JobQueue::GetJobDescription(int jobType)
1786 {
1787  if (jobType == JOB_TRANSCODE)
1788  return "Transcode";
1789  if (jobType == JOB_COMMFLAG)
1790  return "Commercial Detection";
1791  if (!(jobType & JOB_USERJOB))
1792  return "Unknown Job";
1793 
1794  QString descSetting =
1795  QString("UserJobDesc%1").arg(UserJobTypeToIndex(jobType));
1796 
1797  return gCoreContext->GetSetting(descSetting, "Unknown Job");
1798 }
1799 
1800 QString JobQueue::GetJobCommand(int id, int jobType, ProgramInfo *tmpInfo)
1801 {
1802  QString command;
1803  MSqlQuery query(MSqlQuery::InitCon());
1804 
1805  if (jobType == JOB_TRANSCODE)
1806  {
1807  command = gCoreContext->GetSetting("JobQueueTranscodeCommand");
1808  if (command.trimmed().isEmpty())
1809  command = "mythtranscode";
1810 
1811  if (command == "mythtranscode")
1812  return command;
1813  }
1814  else if (jobType == JOB_COMMFLAG)
1815  {
1816  command = gCoreContext->GetSetting("JobQueueCommFlagCommand");
1817  if (command.trimmed().isEmpty())
1818  command = "mythcommflag";
1819 
1820  if (command == "mythcommflag")
1821  return command;
1822  }
1823  else if (jobType & JOB_USERJOB)
1824  {
1825  command = gCoreContext->GetSetting(
1826  QString("UserJob%1").arg(UserJobTypeToIndex(jobType)), "");
1827  }
1828 
1829  if (!command.isEmpty())
1830  {
1831  command.replace("%JOBID%", QString("%1").arg(id));
1832  }
1833 
1834  if (!command.isEmpty() && tmpInfo)
1835  {
1836  tmpInfo->SubstituteMatches(command);
1837 
1838  command.replace("%VERBOSELEVEL%", QString("%1").arg(verboseMask));
1839  command.replace("%VERBOSEMODE%", QString("%1").arg(logPropagateArgs));
1840 
1841  uint transcoder = tmpInfo->QueryTranscoderID();
1842  command.replace("%TRANSPROFILE%",
1843  (RecordingProfile::kTranscoderAutodetect == transcoder) ?
1844  "autodetect" : QString::number(transcoder));
1845  }
1846 
1847  return command;
1848 }
1849 
1851 {
1852  m_runningJobsLock->lock();
1853 
1854  if (m_runningJobs.contains(id))
1855  {
1856  ProgramInfo *pginfo = m_runningJobs[id].pginfo;
1857  if (pginfo)
1858  {
1859  pginfo->MarkAsInUse(false, kJobQueueInUseID);
1860  delete pginfo;
1861  }
1862 
1863  m_runningJobs.remove(id);
1864  }
1865 
1866  m_runningJobsLock->unlock();
1867 }
1868 
1870 {
1871  // Pretty print "bytes" as KB, MB, GB, TB, etc., subject to the desired
1872  // number of units
1873  struct PpTab_t {
1874  const char *m_suffix;
1875  unsigned int m_max;
1876  int m_precision;
1877  };
1878  static constexpr std::array<const PpTab_t,9> kPpTab {{
1879  { "bytes", 9999, 0 },
1880  { "kB", 999, 0 },
1881  { "MB", 999, 1 },
1882  { "GB", 999, 1 },
1883  { "TB", 999, 1 },
1884  { "PB", 999, 1 },
1885  { "EB", 999, 1 },
1886  { "ZB", 999, 1 },
1887  { "YB", 0, 0 },
1888  }};
1889  float fbytes = bytes;
1890 
1891  unsigned int ii = 0;
1892  while (kPpTab[ii].m_max && fbytes > kPpTab[ii].m_max) {
1893  fbytes /= 1024;
1894  ii++;
1895  }
1896 
1897  return QString("%1 %2")
1898  .arg(fbytes, 0, 'f', kPpTab[ii].m_precision)
1899  .arg(kPpTab[ii].m_suffix);
1900 }
1901 
1902 void *JobQueue::TranscodeThread(void *param)
1903 {
1904  auto *jts = (JobThreadStruct *)param;
1905  JobQueue *jq = jts->jq;
1906 
1907  MThread::ThreadSetup(QString("Transcode_%1").arg(jts->jobID));
1908  jq->DoTranscodeThread(jts->jobID);
1910 
1911  delete jts;
1912 
1913  return nullptr;
1914 }
1915 
1917 {
1918  // We can't currently transcode non-recording files w/o a ProgramInfo
1919  m_runningJobsLock->lock();
1920  if (!m_runningJobs[jobID].pginfo)
1921  {
1922  LOG(VB_JOBQUEUE, LOG_ERR, LOC +
1923  "The JobQueue cannot currently transcode files that do not "
1924  "have a chanid/starttime in the recorded table.");
1925  ChangeJobStatus(jobID, JOB_ERRORED, "ProgramInfo data not found");
1927  m_runningJobsLock->unlock();
1928  return;
1929  }
1930 
1931  ProgramInfo *program_info = m_runningJobs[jobID].pginfo;
1932  m_runningJobsLock->unlock();
1933 
1934  ChangeJobStatus(jobID, JOB_RUNNING);
1935 
1936  // make sure flags are up to date
1937  program_info->Reload();
1938 
1939  bool useCutlist = program_info->HasCutlist() &&
1940  ((GetJobFlags(jobID) & JOB_USE_CUTLIST) != 0);
1941 
1942  uint transcoder = program_info->QueryTranscoderID();
1943  QString profilearg =
1944  (RecordingProfile::kTranscoderAutodetect == transcoder) ?
1945  "autodetect" : QString::number(transcoder);
1946 
1947  QString path;
1948  QString command;
1949 
1950  m_runningJobsLock->lock();
1951  if (m_runningJobs[jobID].command == "mythtranscode")
1952  {
1953  path = GetAppBinDir() + "mythtranscode";
1954  command = QString("%1 -j %2 --profile %3")
1955  .arg(path).arg(jobID).arg(profilearg);
1956  if (useCutlist)
1957  command += " --honorcutlist";
1958  command += logPropagateArgs;
1959  }
1960  else
1961  {
1962  command = m_runningJobs[jobID].command;
1963 
1964 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1965  QStringList tokens = command.split(" ", QString::SkipEmptyParts);
1966 #else
1967  QStringList tokens = command.split(" ", Qt::SkipEmptyParts);
1968 #endif
1969  if (!tokens.empty())
1970  path = tokens[0];
1971  }
1972  m_runningJobsLock->unlock();
1973 
1974  if (m_jobQueueCPU < 2)
1975  {
1976  myth_nice(17);
1977  myth_ioprio((0 == m_jobQueueCPU) ? 8 : 7);
1978  }
1979 
1980  QString transcoderName;
1981  if (transcoder == RecordingProfile::kTranscoderAutodetect)
1982  {
1983  transcoderName = "Autodetect";
1984  }
1985  else
1986  {
1987  MSqlQuery query(MSqlQuery::InitCon());
1988  query.prepare("SELECT name FROM recordingprofiles WHERE id = :ID;");
1989  query.bindValue(":ID", transcoder);
1990  if (query.exec() && query.next())
1991  {
1992  transcoderName = query.value(0).toString();
1993  }
1994  else
1995  {
1996  /* Unexpected value; log it. */
1997  transcoderName = QString("Autodetect(%1)").arg(transcoder);
1998  }
1999  }
2000 
2001  bool retry = true;
2002  int retrylimit = 3;
2003  while (retry)
2004  {
2005  retry = false;
2006 
2007  ChangeJobStatus(jobID, JOB_STARTING);
2009 
2010  QString filename = program_info->GetPlaybackURL(false, true);
2011 
2012  long long filesize = 0;
2013  long long origfilesize = QFileInfo(filename).size();
2014 
2015  QString msg = QString("Transcode %1")
2016  .arg(StatusText(GetJobStatus(jobID)));
2017 
2018  QString details = QString("%1: %2 (%3)")
2019  .arg(program_info->toString(ProgramInfo::kTitleSubtitle),
2020  transcoderName, PrettyPrint(origfilesize));
2021 
2022  LOG(VB_GENERAL, LOG_INFO, LOC + QString("%1 for %2")
2023  .arg(msg, details));
2024 
2025  LOG(VB_JOBQUEUE, LOG_INFO, LOC + QString("Running command: '%1'")
2026  .arg(command));
2027 
2028  GetMythDB()->GetDBManager()->CloseDatabases();
2029  uint result = myth_system(command);
2030  int status = GetJobStatus(jobID);
2031 
2032  if ((result == GENERIC_EXIT_DAEMONIZING_ERROR) ||
2033  (result == GENERIC_EXIT_CMD_NOT_FOUND))
2034  {
2035  ChangeJobStatus(jobID, JOB_ERRORED,
2036  tr("ERROR: Unable to find mythtranscode, check backend logs."));
2038 
2039  msg = QString("Transcode %1").arg(StatusText(GetJobStatus(jobID)));
2040  details = QString("%1: %2 does not exist or is not executable")
2041  .arg(program_info->toString(ProgramInfo::kTitleSubtitle),path);
2042 
2043  LOG(VB_GENERAL, LOG_ERR, LOC +
2044  QString("%1 for %2").arg(msg, details));
2045  }
2046  else if (result == GENERIC_EXIT_RESTART && retrylimit > 0)
2047  {
2048  LOG(VB_JOBQUEUE, LOG_INFO, LOC + "Transcode command restarting");
2049  retry = true;
2050  retrylimit--;
2051 
2053  }
2054  else
2055  {
2056  if (status == JOB_FINISHED)
2057  {
2058  ChangeJobStatus(jobID, JOB_FINISHED, tr("Finished."));
2059  retry = false;
2060 
2061  program_info->Reload(); // Refresh, the basename may have changed
2062  filename = program_info->GetPlaybackURL(false, true);
2063  QFileInfo st(filename);
2064 
2065  if (st.exists())
2066  {
2067  filesize = st.size();
2068  /*: %1 is transcoder name, %2 is the original file size
2069  and %3 is the current file size */
2070  QString comment = tr("%1: %2 => %3")
2071  .arg(transcoderName,
2072  PrettyPrint(origfilesize),
2073  PrettyPrint(filesize));
2074  ChangeJobComment(jobID, comment);
2075 
2076  if (filesize > 0)
2077  program_info->SaveFilesize(filesize);
2078 
2079  details = QString("%1: %2 (%3)")
2080  .arg(program_info->toString(
2082  transcoderName,
2083  PrettyPrint(filesize));
2084  }
2085  else
2086  {
2087  QString comment =
2088  QString("could not stat '%1'").arg(filename);
2089 
2090  ChangeJobStatus(jobID, JOB_FINISHED, comment);
2091 
2092  details = QString("%1: %2")
2093  .arg(program_info->toString(
2095  comment);
2096  }
2097 
2099  }
2100  else
2101  {
2103 
2104  QString comment = tr("exit status %1, job status was \"%2\"")
2105  .arg(result)
2106  .arg(StatusText(status));
2107 
2108  ChangeJobStatus(jobID, JOB_ERRORED, comment);
2109 
2110  details = QString("%1: %2 (%3)")
2111  .arg(program_info->toString(
2113  transcoderName,
2114  comment);
2115  }
2116 
2117  msg = QString("Transcode %1").arg(StatusText(GetJobStatus(jobID)));
2118  LOG(VB_GENERAL, LOG_INFO, LOC + msg + ": " + details);
2119  }
2120  }
2121 
2122  if (retrylimit == 0)
2123  {
2124  LOG(VB_JOBQUEUE, LOG_ERR, LOC + "Retry limit exceeded for transcoder, "
2125  "setting job status to errored.");
2126  ChangeJobStatus(jobID, JOB_ERRORED, tr("Retry limit exceeded"));
2127  }
2128 
2130 }
2131 
2133 {
2134  auto *jts = (JobThreadStruct *)param;
2135  JobQueue *jq = jts->jq;
2136 
2137  MThread::ThreadSetup(QString("Metadata_%1").arg(jts->jobID));
2138  jq->DoMetadataLookupThread(jts->jobID);
2140 
2141  delete jts;
2142 
2143  return nullptr;
2144 }
2145 
2147 {
2148  // We can't currently lookup non-recording files w/o a ProgramInfo
2149  m_runningJobsLock->lock();
2150  if (!m_runningJobs[jobID].pginfo)
2151  {
2152  LOG(VB_JOBQUEUE, LOG_ERR, LOC +
2153  "The JobQueue cannot currently perform lookups for items which do "
2154  "not have a chanid/starttime in the recorded table.");
2155  ChangeJobStatus(jobID, JOB_ERRORED, "ProgramInfo data not found");
2157  m_runningJobsLock->unlock();
2158  return;
2159  }
2160 
2161  ProgramInfo *program_info = m_runningJobs[jobID].pginfo;
2162  m_runningJobsLock->unlock();
2163 
2164  QString details = QString("%1 recorded from channel %3")
2165  .arg(program_info->toString(ProgramInfo::kTitleSubtitle),
2166  program_info->toString(ProgramInfo::kRecordingKey));
2167 
2169  {
2170  QString msg = QString("Metadata Lookup failed. Could not open "
2171  "new database connection for %1. "
2172  "Program cannot be looked up.")
2173  .arg(details);
2174  LOG(VB_GENERAL, LOG_ERR, LOC + msg);
2175 
2176  ChangeJobStatus(jobID, JOB_ERRORED,
2177  tr("Could not open new database connection for "
2178  "metadata lookup."));
2179 
2180  delete program_info;
2181  return;
2182  }
2183 
2184  LOG(VB_GENERAL, LOG_INFO,
2185  LOC + "Metadata Lookup Starting for " + details);
2186 
2187  uint retVal = 0;
2188  QString path;
2189  QString command;
2190 
2191  path = GetAppBinDir() + "mythmetadatalookup";
2192  command = QString("%1 -j %2")
2193  .arg(path).arg(jobID);
2194  command += logPropagateArgs;
2195 
2196  LOG(VB_JOBQUEUE, LOG_INFO, LOC + QString("Running command: '%1'")
2197  .arg(command));
2198 
2199  GetMythDB()->GetDBManager()->CloseDatabases();
2200  retVal = myth_system(command);
2201  int priority = LOG_NOTICE;
2202  QString comment;
2203 
2204  m_runningJobsLock->lock();
2205 
2206  if ((retVal == GENERIC_EXIT_DAEMONIZING_ERROR) ||
2207  (retVal == GENERIC_EXIT_CMD_NOT_FOUND))
2208  {
2209  comment = tr("Unable to find mythmetadatalookup");
2210  ChangeJobStatus(jobID, JOB_ERRORED, comment);
2211  priority = LOG_WARNING;
2212  }
2213  else if (m_runningJobs[jobID].flag == JOB_STOP)
2214  {
2215  comment = tr("Aborted by user");
2216  ChangeJobStatus(jobID, JOB_ABORTED, comment);
2217  priority = LOG_WARNING;
2218  }
2219  else if (retVal == GENERIC_EXIT_NO_RECORDING_DATA)
2220  {
2221  comment = tr("Unable to open file or init decoder");
2222  ChangeJobStatus(jobID, JOB_ERRORED, comment);
2223  priority = LOG_WARNING;
2224  }
2225  else if (retVal >= GENERIC_EXIT_NOT_OK) // 256 or above - error
2226  {
2227  comment = tr("Failed with exit status %1").arg(retVal);
2228  ChangeJobStatus(jobID, JOB_ERRORED, comment);
2229  priority = LOG_WARNING;
2230  }
2231  else
2232  {
2233  comment = tr("Metadata Lookup Complete.");
2234  ChangeJobStatus(jobID, JOB_FINISHED, comment);
2235 
2236  program_info->SendUpdateEvent();
2237  }
2238 
2239  QString msg = tr("Metadata Lookup %1", "Job ID")
2240  .arg(StatusText(GetJobStatus(jobID)));
2241 
2242  if (!comment.isEmpty())
2243  details += QString(" (%1)").arg(comment);
2244 
2245  if (priority <= LOG_WARNING)
2246  LOG(VB_GENERAL, LOG_ERR, LOC + msg + ": " + details);
2247 
2249  m_runningJobsLock->unlock();
2250 }
2251 
2253 {
2254  auto *jts = (JobThreadStruct *)param;
2255  JobQueue *jq = jts->jq;
2256 
2257  MThread::ThreadSetup(QString("Commflag_%1").arg(jts->jobID));
2258  jq->DoFlagCommercialsThread(jts->jobID);
2260 
2261  delete jts;
2262 
2263  return nullptr;
2264 }
2265 
2267 {
2268  // We can't currently commflag non-recording files w/o a ProgramInfo
2269  m_runningJobsLock->lock();
2270  if (!m_runningJobs[jobID].pginfo)
2271  {
2272  LOG(VB_JOBQUEUE, LOG_ERR, LOC +
2273  "The JobQueue cannot currently commflag files that do not "
2274  "have a chanid/starttime in the recorded table.");
2275  ChangeJobStatus(jobID, JOB_ERRORED, "ProgramInfo data not found");
2277  m_runningJobsLock->unlock();
2278  return;
2279  }
2280 
2281  ProgramInfo *program_info = m_runningJobs[jobID].pginfo;
2282  m_runningJobsLock->unlock();
2283 
2284  QString details = QString("%1 recorded from channel %3")
2285  .arg(program_info->toString(ProgramInfo::kTitleSubtitle),
2286  program_info->toString(ProgramInfo::kRecordingKey));
2287 
2289  {
2290  QString msg = QString("Commercial Detection failed. Could not open "
2291  "new database connection for %1. "
2292  "Program cannot be flagged.")
2293  .arg(details);
2294  LOG(VB_GENERAL, LOG_ERR, LOC + msg);
2295 
2296  ChangeJobStatus(jobID, JOB_ERRORED,
2297  tr("Could not open new database connection for "
2298  "commercial detector."));
2299 
2300  delete program_info;
2301  return;
2302  }
2303 
2304  LOG(VB_GENERAL, LOG_INFO,
2305  LOC + "Commercial Detection Starting for " + details);
2306 
2307  uint breaksFound = 0;
2308  QString path;
2309  QString command;
2310 
2311  m_runningJobsLock->lock();
2312  if (m_runningJobs[jobID].command == "mythcommflag")
2313  {
2314  path = GetAppBinDir() + "mythcommflag";
2315  command = QString("%1 -j %2 --noprogress")
2316  .arg(path).arg(jobID);
2317  command += logPropagateArgs;
2318  }
2319  else
2320  {
2321  command = m_runningJobs[jobID].command;
2322 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
2323  QStringList tokens = command.split(" ", QString::SkipEmptyParts);
2324 #else
2325  QStringList tokens = command.split(" ", Qt::SkipEmptyParts);
2326 #endif
2327  if (!tokens.empty())
2328  path = tokens[0];
2329  }
2330  m_runningJobsLock->unlock();
2331 
2332  LOG(VB_JOBQUEUE, LOG_INFO, LOC + QString("Running command: '%1'")
2333  .arg(command));
2334 
2335  GetMythDB()->GetDBManager()->CloseDatabases();
2336  breaksFound = myth_system(command, kMSLowExitVal);
2337  int priority = LOG_NOTICE;
2338  QString comment;
2339 
2340  m_runningJobsLock->lock();
2341 
2342  if ((breaksFound == GENERIC_EXIT_DAEMONIZING_ERROR) ||
2343  (breaksFound == GENERIC_EXIT_CMD_NOT_FOUND))
2344  {
2345  comment = tr("Unable to find mythcommflag");
2346  ChangeJobStatus(jobID, JOB_ERRORED, comment);
2347  priority = LOG_WARNING;
2348  }
2349  else if (m_runningJobs[jobID].flag == JOB_STOP)
2350  {
2351  comment = tr("Aborted by user");
2352  ChangeJobStatus(jobID, JOB_ABORTED, comment);
2353  priority = LOG_WARNING;
2354  }
2355  else if (breaksFound == GENERIC_EXIT_NO_RECORDING_DATA)
2356  {
2357  comment = tr("Unable to open file or init decoder");
2358  ChangeJobStatus(jobID, JOB_ERRORED, comment);
2359  priority = LOG_WARNING;
2360  }
2361  else if (breaksFound >= GENERIC_EXIT_NOT_OK) // 256 or above - error
2362  {
2363  comment = tr("Failed with exit status %1").arg(breaksFound);
2364  ChangeJobStatus(jobID, JOB_ERRORED, comment);
2365  priority = LOG_WARNING;
2366  }
2367  else
2368  {
2369  comment = tr("%n commercial break(s)", "", breaksFound);
2370  ChangeJobStatus(jobID, JOB_FINISHED, comment);
2371 
2372  program_info->SendUpdateEvent();
2373 
2374  if (!program_info->IsLocal())
2375  program_info->SetPathname(program_info->GetPlaybackURL(false,true));
2376  if (program_info->IsLocal())
2377  {
2378  auto *pg = new PreviewGenerator(program_info, QString(),
2380  pg->Run();
2381  pg->deleteLater();
2382  }
2383  }
2384 
2385  QString msg = tr("Commercial Detection %1", "Job ID")
2386  .arg(StatusText(GetJobStatus(jobID)));
2387 
2388  if (!comment.isEmpty())
2389  details += QString(" (%1)").arg(comment);
2390 
2391  if (priority <= LOG_WARNING)
2392  LOG(VB_GENERAL, LOG_ERR, LOC + msg + ": " + details);
2393 
2395  m_runningJobsLock->unlock();
2396 }
2397 
2398 void *JobQueue::UserJobThread(void *param)
2399 {
2400  auto *jts = (JobThreadStruct *)param;
2401  JobQueue *jq = jts->jq;
2402 
2403  MThread::ThreadSetup(QString("UserJob_%1").arg(jts->jobID));
2404  jq->DoUserJobThread(jts->jobID);
2406 
2407  delete jts;
2408 
2409  return nullptr;
2410 }
2411 
2413 {
2414  m_runningJobsLock->lock();
2415  ProgramInfo *pginfo = m_runningJobs[jobID].pginfo;
2416  QString jobDesc = m_runningJobs[jobID].desc;
2417  QString command = m_runningJobs[jobID].command;
2418  m_runningJobsLock->unlock();
2419 
2420  ChangeJobStatus(jobID, JOB_RUNNING);
2421 
2422  QString msg;
2423 
2424  if (pginfo)
2425  {
2426  msg = QString("Started %1 for %2 recorded from channel %3")
2427  .arg(jobDesc,
2430  }
2431  else
2432  msg = QString("Started %1 for jobID %2").arg(jobDesc).arg(jobID);
2433 
2434  LOG(VB_GENERAL, LOG_INFO, LOC + QString(msg.toLocal8Bit().constData()));
2435 
2436  switch (m_jobQueueCPU)
2437  {
2438  case 0: myth_nice(17);
2439  myth_ioprio(8);
2440  break;
2441  case 1: myth_nice(10);
2442  myth_ioprio(7);
2443  break;
2444  case 2:
2445  default: break;
2446  }
2447 
2448  LOG(VB_JOBQUEUE, LOG_INFO, LOC + QString("Running command: '%1'")
2449  .arg(command));
2450  GetMythDB()->GetDBManager()->CloseDatabases();
2451  uint result = myth_system(command);
2452 
2453  if ((result == GENERIC_EXIT_DAEMONIZING_ERROR) ||
2454  (result == GENERIC_EXIT_CMD_NOT_FOUND))
2455  {
2456  msg = QString("User Job '%1' failed, unable to find "
2457  "executable, check your PATH and backend logs.")
2458  .arg(command);
2459  LOG(VB_GENERAL, LOG_ERR, LOC + msg);
2460  LOG(VB_GENERAL, LOG_NOTICE, LOC + QString("Current PATH: '%1'")
2461  .arg(qEnvironmentVariable("PATH")));
2462 
2463  ChangeJobStatus(jobID, JOB_ERRORED,
2464  tr("ERROR: Unable to find executable, check backend logs."));
2465  }
2466  else if (result != 0)
2467  {
2468  msg = QString("User Job '%1' failed.").arg(command);
2469  LOG(VB_GENERAL, LOG_ERR, LOC + msg);
2470 
2471  ChangeJobStatus(jobID, JOB_ERRORED,
2472  tr("ERROR: User Job returned non-zero, check logs."));
2473  }
2474  else
2475  {
2476  if (pginfo)
2477  {
2478  msg = QString("Finished %1 for %2 recorded from channel %3")
2479  .arg(jobDesc,
2482  }
2483  else
2484  msg = QString("Finished %1 for jobID %2").arg(jobDesc).arg(jobID);
2485 
2486  LOG(VB_GENERAL, LOG_INFO, LOC + QString(msg.toLocal8Bit().constData()));
2487 
2488  ChangeJobStatus(jobID, JOB_FINISHED, tr("Successfully Completed."));
2489 
2490  if (pginfo)
2491  pginfo->SendUpdateEvent();
2492  }
2493 
2495 }
2496 
2498 {
2499  if (jobType & JOB_USERJOB)
2500  {
2501  int x = ((jobType & JOB_USERJOB)>> 8);
2502  int bits = 1;
2503  while ((x != 0) && ((x & 0x01) == 0))
2504  {
2505  bits++;
2506  x = x >> 1;
2507  }
2508  if ( bits > 4 )
2509  return JOB_NONE;
2510 
2511  return bits;
2512  }
2513  return JOB_NONE;
2514 }
2515 
2516 /* vim: set expandtab tabstop=4 shiftwidth=4: */
JOB_USERJOB3
@ JOB_USERJOB3
Definition: jobqueue.h:88
JobQueue::GetJobCmd
static enum JobCmds GetJobCmd(int jobID)
Definition: jobqueue.cpp:1458
JobQueue::PrettyPrint
static QString PrettyPrint(off_t bytes)
Definition: jobqueue.cpp:1869
JOB_USERJOB4
@ JOB_USERJOB4
Definition: jobqueue.h:89
ProgramInfo::SaveTranscodeStatus
void SaveTranscodeStatus(TranscodingStatus trans)
Set "transcoded" field in "recorded" table to "trans".
Definition: programinfo.cpp:3318
MSqlQuery::next
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:811
MSqlQuery
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:128
build_compdb.args
args
Definition: build_compdb.py:11
ProgramInfo::SaveFilesize
virtual void SaveFilesize(uint64_t fsize)
Sets recording file size in database, and sets "filesize" field.
Definition: programinfo.cpp:6314
kJobQueueInUseID
const QString kJobQueueInUseID
Definition: programtypes.cpp:26
JobQueueEntry::comment
QString comment
Definition: jobqueue.h:116
MythDate::toString
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
Definition: mythdate.cpp:84
MSqlQuery::size
int size(void) const
Definition: mythdbcon.h:215
MThread::start
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:283
JobQueueEntry::hostname
QString hostname
Definition: jobqueue.h:114
JobQueue::m_processQueue
bool m_processQueue
Definition: jobqueue.h:279
RunningJobInfo::id
int id
Definition: jobqueue.h:120
JobQueue::JobQueue
JobQueue(bool master)
Definition: jobqueue.cpp:38
JobQueueEntry::inserttime
QDateTime inserttime
Definition: jobqueue.h:108
MythEvent::kMythEventMessage
static const Type kMythEventMessage
Definition: mythevent.h:79
JobNameToType
static QMap< QString, int > JobNameToType
Definition: jobqueue.h:92
GENERIC_EXIT_CMD_NOT_FOUND
@ GENERIC_EXIT_CMD_NOT_FOUND
Command not found.
Definition: exitcodes.h:13
JobQueueEntry::type
int type
Definition: jobqueue.h:109
ProgramInfo::GetHostname
QString GetHostname(void) const
Definition: programinfo.h:421
mythdb.h
JobQueueEntry
Definition: jobqueue.h:102
JobCmds
JobCmds
Definition: jobqueue.h:52
RunningJobInfo
Definition: jobqueue.h:119
MythCoreContext::IsBlockingClient
bool IsBlockingClient(void) const
is this client blocking shutdown
Definition: mythcorecontext.cpp:623
JobQueue::QueueJob
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())
Definition: jobqueue.cpp:513
MythDate::as_utc
QDateTime as_utc(const QDateTime &old_dt)
Returns copy of QDateTime with TimeSpec set to UTC.
Definition: mythdate.cpp:27
MythCoreContext::AllowShutdown
void AllowShutdown(void)
Definition: mythcorecontext.cpp:609
JobQueue::customEvent
void customEvent(QEvent *e) override
Definition: jobqueue.cpp:79
myth_ioprio
bool myth_ioprio(int)
Allows setting the I/O priority of the current process/thread.
Definition: mythmiscutil.cpp:752
JobQueue::ChangeJobCmds
static bool ChangeJobCmds(int jobID, int newCmds)
Definition: jobqueue.cpp:918
MThread::wait
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:300
JOB_RUN
@ JOB_RUN
Definition: jobqueue.h:53
RecordingInfo
Holds information on a TV Program one might wish to record.
Definition: recordinginfo.h:35
RunningJobInfo::desc
QString desc
Definition: jobqueue.h:123
PreviewGenerator
This class creates a preview image of a recording.
Definition: previewgenerator.h:27
JobQueue::StartChildJob
void StartChildJob(void *(*ChildThreadRoutine)(void *), int jobID)
Definition: jobqueue.cpp:1771
MythEvent
This class is used as a container for messages.
Definition: mythevent.h:16
JOB_COMMFLAG
@ JOB_COMMFLAG
Definition: jobqueue.h:81
ProgramInfo::kTitleSubtitle
@ kTitleSubtitle
Definition: programinfo.h:509
JOB_USE_CUTLIST
@ JOB_USE_CUTLIST
Definition: jobqueue.h:62
JOB_PAUSE
@ JOB_PAUSE
Definition: jobqueue.h:54
JobQueue::QueueRecordingJobs
static bool QueueRecordingJobs(const RecordingInfo &recinfo, int jobTypes=JOB_NONE)
Definition: jobqueue.cpp:491
MSqlQuery::value
QVariant value(int i) const
Definition: mythdbcon.h:205
JOB_RESUME
@ JOB_RESUME
Definition: jobqueue.h:55
JOB_NONE
@ JOB_NONE
Definition: jobqueue.h:77
JobQueue::SafeDeleteJob
static bool SafeDeleteJob(int jobID, int jobType, int chanid, const QDateTime &recstartts)
Definition: jobqueue.cpp:876
JobQueue::GetJobInfoFromID
static bool GetJobInfoFromID(int jobID, int &jobType, uint &chanid, QDateTime &recstartts)
Definition: jobqueue.cpp:664
TRANSCODING_RUNNING
@ TRANSCODING_RUNNING
Definition: programtypes.h:161
MSqlQuery::exec
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:617
JobQueue::RemoveRunningJob
void RemoveRunningJob(int id)
Definition: jobqueue.cpp:1850
JOB_LIST_NOT_DONE
@ JOB_LIST_NOT_DONE
Definition: jobqueue.h:71
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
JOB_STOP
@ JOB_STOP
Definition: jobqueue.h:56
GetMythDB
MythDB * GetMythDB(void)
Definition: mythdb.cpp:50
mythdirs.h
JobQueue::StopJob
static bool StopJob(int jobID)
Definition: jobqueue.cpp:741
myth_system
uint myth_system(const QString &command, uint flags, std::chrono::seconds timeout)
Definition: mythsystemlegacy.cpp:506
ProgramInfo::GetRecordingGroup
QString GetRecordingGroup(void) const
Definition: programinfo.h:419
JobQueue::m_queueThread
MThread * m_queueThread
Definition: jobqueue.h:276
JobQueue::m_jobsRunning
int m_jobsRunning
Definition: jobqueue.h:259
JOBSTATUS_STATUSTEXT
#define JOBSTATUS_STATUSTEXT(A, B, C)
Definition: jobqueue.cpp:1132
JobQueue::~JobQueue
~JobQueue(void) override
Definition: jobqueue.cpp:63
TRANSCODING_NOT_TRANSCODED
@ TRANSCODING_NOT_TRANSCODED
Definition: programtypes.h:159
LOC
#define LOC
Definition: jobqueue.cpp:33
jobID
int jobID
Definition: mythcommflag.cpp:80
MythDate::current
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:14
MythEvent::Message
const QString & Message() const
Definition: mythevent.h:65
JobQueue::IsJobRunning
static bool IsJobRunning(int jobType, uint chanid, const QDateTime &recstartts)
Definition: jobqueue.cpp:1085
ProgramInfo::GetRecordingStartTime
QDateTime GetRecordingStartTime(void) const
Approximate time the recording started.
Definition: programinfo.h:404
JobQueue::GetRunningJobID
int GetRunningJobID(uint chanid, const QDateTime &recstartts)
Definition: jobqueue.cpp:1056
JOB_USERJOB2
@ JOB_USERJOB2
Definition: jobqueue.h:87
mythsystemlegacy.h
JobQueue::InJobRunWindow
static bool InJobRunWindow(QDateTime jobstarttsRaw)
Definition: jobqueue.cpp:1653
MythObservable::addListener
void addListener(QObject *listener)
Add a listener to the observable.
Definition: mythobservable.cpp:38
ProgramInfo::Reload
bool Reload(void)
Definition: programinfo.cpp:1971
JobQueue::JobText
static QString JobText(int jobType)
Definition: jobqueue.cpp:1111
JobQueue::PauseJob
static bool PauseJob(int jobID)
Definition: jobqueue.cpp:714
JOB_LIST_ALL
@ JOB_LIST_ALL
Definition: jobqueue.h:69
JOB_PREVIEW
@ JOB_PREVIEW
Definition: jobqueue.h:83
TRANSCODING_COMPLETE
@ TRANSCODING_COMPLETE
Definition: programtypes.h:160
JobQueueEntry::schedruntime
QDateTime schedruntime
Definition: jobqueue.h:106
RunningJobInfo::command
QString command
Definition: jobqueue.h:124
ProgramInfo::MarkAsInUse
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...
Definition: programinfo.cpp:5073
RunningJobInfo::pginfo
ProgramInfo * pginfo
Definition: jobqueue.h:125
JobQueue::GetJobsInQueue
static int GetJobsInQueue(QMap< int, JobQueueEntry > &jobs, int findJobs=JOB_LIST_NOT_DONE)
Definition: jobqueue.cpp:1275
JOBSTATUS_MAP
#define JOBSTATUS_MAP(F)
Definition: jobqueue.h:27
mythdate.h
ProgramInfo::SetPathname
void SetPathname(const QString &pn)
Definition: programinfo.cpp:2438
JobQueue::m_hostname
QString m_hostname
Definition: jobqueue.h:257
programinfo.h
JobQueue::IsJobQueuedOrRunning
static bool IsJobQueuedOrRunning(int jobType, uint chanid, const QDateTime &recstartts)
Definition: jobqueue.cpp:1097
mythlogging.h
MythDate::kFilename
@ kFilename
Default UTC, "yyyyMMddhhmmss".
Definition: mythdate.h:18
JobQueue::GetJobCommand
static QString GetJobCommand(int id, int jobType, ProgramInfo *tmpInfo)
Definition: jobqueue.cpp:1800
JobQueue::ChangeJobComment
static bool ChangeJobComment(int jobID, const QString &comment="")
Definition: jobqueue.cpp:1009
JOB_USERJOB1
@ JOB_USERJOB1
Definition: jobqueue.h:86
verboseMask
uint64_t verboseMask
Definition: logging.cpp:97
JobQueue::ProcessQueue
void ProcessQueue(void)
Definition: jobqueue.cpp:164
JobQueue::GetJobArgs
static QString GetJobArgs(int jobID)
Definition: jobqueue.cpp:1479
MSqlQuery::InitCon
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:549
compat.h
JobFlags
JobFlags
Definition: jobqueue.h:60
JobQueue::GetJobStatus
static enum JobStatus GetJobStatus(int jobID)
Definition: jobqueue.cpp:1521
MythDB::DBError
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:226
JobQueue::DeleteAllJobs
static bool DeleteAllJobs(uint chanid, const QDateTime &recstartts)
Definition: jobqueue.cpp:750
JobQueue::CleanupOldJobsInQueue
static void CleanupOldJobsInQueue()
Definition: jobqueue.cpp:1628
MythCoreContext::GetDurSetting
std::enable_if_t< std::chrono::__is_duration< T >::value, T > GetDurSetting(const QString &key, T defaultval=T::zero())
Definition: mythcorecontext.h:169
MSqlQuery::testDBConnection
static bool testDBConnection()
Checks DB connection + login (login info via Mythcontext)
Definition: mythdbcon.cpp:875
JOB_RESTART
@ JOB_RESTART
Definition: jobqueue.h:57
JobQueueEntry::startts
QString startts
Definition: jobqueue.h:107
JobQueueEntry::args
QString args
Definition: jobqueue.h:115
JobQueueEntry::statustime
QDateTime statustime
Definition: jobqueue.h:113
RecordingInfo::GetAutoRunJobs
int GetAutoRunJobs(void) const
Returns a bitmap of which jobs are attached to this RecordingInfo.
Definition: recordinginfo.cpp:506
JobQueue::IsJobQueued
static bool IsJobQueued(int jobType, uint chanid, const QDateTime &recstartts)
Definition: jobqueue.cpp:1105
ProgramInfo::HasCutlist
bool HasCutlist(void) const
Definition: programinfo.h:479
kRecentInterval
static constexpr int64_t kRecentInterval
Definition: jobqueue.cpp:36
ProgramInfo::toString
QString toString(Verbosity v=kLongDescription, const QString &sep=":", const QString &grp="\"") const
Definition: programinfo.cpp:1934
GENERIC_EXIT_NO_RECORDING_DATA
@ GENERIC_EXIT_NO_RECORDING_DATA
No program/recording data.
Definition: exitcodes.h:30
JobQueueEntry::status
int status
Definition: jobqueue.h:112
JobQueue::m_queueThreadCondLock
QMutex m_queueThreadCondLock
Definition: jobqueue.h:278
JobQueue::ChangeJobHost
static bool ChangeJobHost(int jobID, const QString &newHostname)
Definition: jobqueue.cpp:1394
JobStatus
JobStatus
Definition: jobqueue.h:46
RecordingProfile::kTranscoderAutodetect
static const uint kTranscoderAutodetect
sentinel value
Definition: recordingprofile.h:132
jobqueue.h
JobQueueEntry::chanid
uint chanid
Definition: jobqueue.h:104
JobQueueEntry::flags
int flags
Definition: jobqueue.h:111
GENERIC_EXIT_NOT_OK
@ GENERIC_EXIT_NOT_OK
Exited with error.
Definition: exitcodes.h:12
JobQueue::HasRunningOrPendingJobs
static bool HasRunningOrPendingJobs(std::chrono::minutes startingWithinMins=0min)
Definition: jobqueue.cpp:1223
ProgramInfo::QueryTranscoderID
uint QueryTranscoderID(void) const
Definition: programinfo.cpp:4998
JobQueue::m_runningJobs
QMap< int, RunningJobInfo > m_runningJobs
Definition: jobqueue.h:272
uint
unsigned int uint
Definition: compat.h:81
RunningJobInfo::flag
int flag
Definition: jobqueue.h:122
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:55
GENERIC_EXIT_DAEMONIZING_ERROR
@ GENERIC_EXIT_DAEMONIZING_ERROR
Error daemonizing or execl.
Definition: exitcodes.h:29
MythCoreContext::GetNumSetting
int GetNumSetting(const QString &key, int defaultval=0)
Definition: mythcorecontext.cpp:911
RunningJobInfo::type
int type
Definition: jobqueue.h:121
JobQueue::DeleteJob
static bool DeleteJob(int jobID)
Definition: jobqueue.cpp:871
MythDate::fromString
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
Definition: mythdate.cpp:34
off_t
#define off_t
Definition: mythiowrapper.cpp:241
MythCoreContext::GetBoolSetting
bool GetBoolSetting(const QString &key, bool defaultval=false)
Definition: mythcorecontext.cpp:905
JobQueue::FlagCommercialsThread
static void * FlagCommercialsThread(void *param)
Definition: jobqueue.cpp:2252
JobQueue::AllowedToRun
bool AllowedToRun(const JobQueueEntry &job)
Definition: jobqueue.cpp:1426
recordinginfo.h
MYTH_APPNAME_MYTHJOBQUEUE
static constexpr const char * MYTH_APPNAME_MYTHJOBQUEUE
Definition: mythcorecontext.h:21
JobQueue::ChangeJobFlags
static bool ChangeJobFlags(int jobID, int newFlags)
Definition: jobqueue.cpp:961
ProgramInfo::GetChanID
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
Definition: programinfo.h:372
JobQueue::m_jobQueueCPU
int m_jobQueueCPU
Definition: jobqueue.h:260
JobQueue::m_runningJobsLock
QRecursiveMutex * m_runningJobsLock
Definition: jobqueue.h:270
JobQueue::TranscodeThread
static void * TranscodeThread(void *param)
Definition: jobqueue.cpp:1902
ProgramInfo
Holds information on recordings and videos.
Definition: programinfo.h:67
mythmiscutil.h
JobQueueEntry::cmds
int cmds
Definition: jobqueue.h:110
musicbrainzngs.compat.bytes
bytes
Definition: compat.py:49
mythcorecontext.h
JobQueue::GetJobTypeFromName
static int GetJobTypeFromName(const QString &name)
Definition: jobqueue.cpp:703
JobQueue::IsJobStatusRunning
static bool IsJobStatusRunning(int status)
Definition: jobqueue.cpp:1079
ProgramInfo::IsLocal
bool IsLocal(void) const
Definition: programinfo.h:351
MythCoreContext::GetSettingOnHost
QString GetSettingOnHost(const QString &key, const QString &host, const QString &defaultval="")
Definition: mythcorecontext.cpp:925
JobQueue::GetJobID
static int GetJobID(int jobType, uint chanid, const QDateTime &recstartts)
Definition: jobqueue.cpp:643
ProgramInfo::GetPlaybackURL
QString GetPlaybackURL(bool checkMaster=false, bool forceCheckLocal=false)
Returns filename or URL to be used to play back this recording.
Definition: programinfo.cpp:2546
MSqlQuery::bindValue
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:887
JOB_METADATA
@ JOB_METADATA
Definition: jobqueue.h:82
MythDate::ISODate
@ ISODate
Default UTC.
Definition: mythdate.h:17
JobQueue::GetJobFlags
static enum JobFlags GetJobFlags(int jobID)
Definition: jobqueue.cpp:1500
JobQueue::RestartJob
static bool RestartJob(int jobID)
Definition: jobqueue.cpp:732
ProgramInfo::SubstituteMatches
virtual void SubstituteMatches(QString &str)
Subsitute MATCH% type variable names in the given string.
Definition: programinfo.cpp:5390
JobQueue::DoFlagCommercialsThread
void DoFlagCommercialsThread(int jobID)
Definition: jobqueue.cpp:2266
kMSLowExitVal
@ kMSLowExitVal
allow exit values 0-127 only
Definition: mythsystem.h:47
GENERIC_EXIT_RESTART
@ GENERIC_EXIT_RESTART
Need to restart transcoding.
Definition: exitcodes.h:32
MThread
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:48
mthread.h
ProgramInfo::SendUpdateEvent
void SendUpdateEvent(void) const
Sends event out that the ProgramInfo should be reloaded.
Definition: programinfo.cpp:2755
GetAppBinDir
QString GetAppBinDir(void)
Definition: mythdirs.cpp:253
JOB_LIST_DONE
@ JOB_LIST_DONE
Definition: jobqueue.h:70
build_compdb.action
action
Definition: build_compdb.py:9
JobQueue::ResumeJob
static bool ResumeJob(int jobID)
Definition: jobqueue.cpp:723
JobQueue
Definition: jobqueue.h:130
JobQueue::DoMetadataLookupThread
void DoMetadataLookupThread(int jobID)
Definition: jobqueue.cpp:2146
JOB_NO_FLAGS
@ JOB_NO_FLAGS
Definition: jobqueue.h:61
MThread::ThreadCleanup
static void ThreadCleanup(void)
This is to be called on exit in those few threads that haven't been ported to MThread.
Definition: mthread.cpp:226
JobQueue::IsJobStatusQueued
static bool IsJobStatusQueued(int status)
Definition: jobqueue.cpp:1074
JobQueue::RecoverQueue
static void RecoverQueue(bool justOld=false)
Definition: jobqueue.cpp:1565
MSqlQuery::numRowsAffected
int numRowsAffected() const
Definition: mythdbcon.h:218
JobQueue::m_queueThreadCond
QWaitCondition m_queueThreadCond
Definition: jobqueue.h:277
MythCoreContext::GetHostName
QString GetHostName(void)
Definition: mythcorecontext.cpp:837
recordingprofile.h
MThread::ThreadSetup
static void ThreadSetup(const QString &name)
This is to be called on startup in those few threads that haven't been ported to MThread.
Definition: mthread.cpp:221
JOB_LIST_RECENT
@ JOB_LIST_RECENT
Definition: jobqueue.h:73
logPropagateArgs
QString logPropagateArgs
Definition: logging.cpp:82
JobQueue::DoTranscodeThread
void DoTranscodeThread(int jobID)
Definition: jobqueue.cpp:1916
JobQueue::MetadataLookupThread
static void * MetadataLookupThread(void *param)
Definition: jobqueue.cpp:2132
JobQueue::ChangeJobArgs
static bool ChangeJobArgs(int jobID, const QString &args="")
Definition: jobqueue.cpp:1034
PreviewGenerator::kLocal
@ kLocal
Definition: previewgenerator.h:43
myth_nice
bool myth_nice(int val)
Definition: mythmiscutil.cpp:656
musicbrainzngs.caa.hostname
string hostname
Definition: caa.py:17
JobQueueEntry::recstartts
QDateTime recstartts
Definition: jobqueue.h:105
MythCoreContext::BlockShutdown
void BlockShutdown(void)
Definition: mythcorecontext.cpp:595
JobQueue::ProcessJob
void ProcessJob(const JobQueueEntry &job)
Definition: jobqueue.cpp:1683
previewgenerator.h
exitcodes.h
JOB_LIST_ERROR
@ JOB_LIST_ERROR
Definition: jobqueue.h:72
JobQueue::ChangeJobStatus
static bool ChangeJobStatus(int jobID, int newStatus, const QString &comment="")
Definition: jobqueue.cpp:982
JobQueue::UserJobThread
static void * UserJobThread(void *param)
Definition: jobqueue.cpp:2398
build_compdb.filename
filename
Definition: build_compdb.py:21
JobQueueEntry::id
int id
Definition: jobqueue.h:103
JobQueue::GetJobDescription
static QString GetJobDescription(int jobType)
Definition: jobqueue.cpp:1785
JOB_TRANSCODE
@ JOB_TRANSCODE
Definition: jobqueue.h:80
MythCoreContext::dispatch
void dispatch(const MythEvent &event)
Definition: mythcorecontext.cpp:1722
JobQueue::run
void run(void) override
Definition: jobqueue.cpp:149
MythObservable::removeListener
void removeListener(QObject *listener)
Remove a listener to the observable.
Definition: mythobservable.cpp:55
ProgramInfo::IsCommercialFree
bool IsCommercialFree(void) const
Definition: programinfo.h:477
JobQueue::QueueJobs
static bool QueueJobs(int jobTypes, uint chanid, const QDateTime &recstartts, const QString &args="", const QString &comment="", const QString &host="")
Definition: jobqueue.cpp:597
JobQueue::UserJobTypeToIndex
static int UserJobTypeToIndex(int JobType)
Definition: jobqueue.cpp:2497
JobQueue::DoUserJobThread
void DoUserJobThread(int jobID)
Definition: jobqueue.cpp:2412
JobQueue::StatusText
static QString StatusText(int status)
Definition: jobqueue.cpp:1134
JobQueue::JobThreadStruct::jq
JobQueue * jq
Definition: jobqueue.h:224
MythCoreContext::GetSetting
QString GetSetting(const QString &key, const QString &defaultval="")
Definition: mythcorecontext.cpp:897
ProgramInfo::kRecordingKey
@ kRecordingKey
Definition: programinfo.h:510
MSqlQuery::prepare
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:836
JOB_USERJOB
@ JOB_USERJOB
Definition: jobqueue.h:85
JobQueue::JobThreadStruct
Definition: jobqueue.h:222