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