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