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