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