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