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