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