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