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