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