MythTV  master
ExternalStreamHandler.cpp
Go to the documentation of this file.
1 // -*- Mode: c++ -*-
2 
3 // POSIX headers
4 #include <thread>
5 #include <iostream>
6 #include <fcntl.h>
7 #include <unistd.h>
8 #include <algorithm>
9 #if !defined( USING_MINGW ) && !defined( _MSC_VER )
10 #include <poll.h>
11 #include <sys/ioctl.h>
12 #endif
13 #ifdef Q_OS_ANDROID
14 #include <sys/wait.h>
15 #endif
16 
17 // Qt headers
18 #include <QString>
19 #include <QFile>
20 
21 // MythTV headers
22 #include "ExternalStreamHandler.h"
23 #include "ExternalChannel.h"
24 //#include "ThreadedFileWriter.h"
25 #include "dtvsignalmonitor.h"
26 #include "streamlisteners.h"
27 #include "mpegstreamdata.h"
28 #include "cardutil.h"
29 #include "exitcodes.h"
30 
31 #define LOC QString("ExternSH[%1](%2): ").arg(m_inputId).arg(m_loc)
32 
33 ExternIO::ExternIO(const QString & app,
34  const QStringList & args)
35  : m_status(&m_statusBuf, QIODevice::ReadWrite)
36 {
37  m_app = (app);
38 
39  if (!m_app.exists())
40  {
41  m_error = QString("ExternIO: '%1' does not exist.").arg(app);
42  return;
43  }
44  if (!m_app.isReadable() || !m_app.isFile())
45  {
46  m_error = QString("ExternIO: '%1' is not readable.")
47  .arg(m_app.canonicalFilePath());
48  return;
49  }
50  if (!m_app.isExecutable())
51  {
52  m_error = QString("ExternIO: '%1' is not executable.")
53  .arg(m_app.canonicalFilePath());
54  return;
55  }
56 
57  m_args = args;
58  m_args.prepend(m_app.baseName());
59 
60  m_status.setString(&m_statusBuf);
61 }
62 
64 {
65  close(m_appIn);
66  close(m_appOut);
67  close(m_appErr);
68 
69  // waitpid(m_pid, &status, 0);
70  delete[] m_buffer;
71 }
72 
73 bool ExternIO::Ready(int fd, int timeout, const QString & what)
74 {
75 #if !defined( USING_MINGW ) && !defined( _MSC_VER )
76  std::array<struct pollfd,2> m_poll {};
77 
78  m_poll[0].fd = fd;
79  m_poll[0].events = POLLIN | POLLPRI;
80  int ret = poll(m_poll.data(), 1, timeout);
81 
82  if (m_poll[0].revents & POLLHUP)
83  {
84  m_error = what + " poll eof (POLLHUP)";
85  return false;
86  }
87  if (m_poll[0].revents & POLLNVAL)
88  {
89  LOG(VB_GENERAL, LOG_ERR, "poll error");
90  return false;
91  }
92  if (m_poll[0].revents & POLLIN)
93  {
94  if (ret > 0)
95  return true;
96 
97  if ((EOVERFLOW == errno))
98  m_error = "poll overflow";
99  return false;
100  }
101 #endif // !defined( USING_MINGW ) && !defined( _MSC_VER )
102  return false;
103 }
104 
105 int ExternIO::Read(QByteArray & buffer, int maxlen, int timeout)
106 {
107  if (Error())
108  {
109  LOG(VB_RECORD, LOG_ERR,
110  QString("ExternIO::Read: already in error state: '%1'")
111  .arg(m_error));
112  return 0;
113  }
114 
115  if (!Ready(m_appOut, timeout, "data"))
116  return 0;
117 
118  if (m_bufSize < maxlen)
119  {
120  m_bufSize = maxlen;
121  delete [] m_buffer;
122  m_buffer = new char[m_bufSize];
123  }
124 
125  int len = read(m_appOut, m_buffer, maxlen);
126 
127  if (len < 0)
128  {
129  if (errno == EAGAIN)
130  {
131  if (++m_errCnt > kMaxErrorCnt)
132  {
133  m_error = "Failed to read from External Recorder: " + ENO;
134  LOG(VB_RECORD, LOG_WARNING,
135  "External Recorder not ready. Giving up.");
136  }
137  else
138  {
139  LOG(VB_RECORD, LOG_WARNING,
140  QString("External Recorder not ready. Will retry (%1/%2).")
142  std::this_thread::sleep_for(std::chrono::milliseconds(100));
143  }
144  }
145  else
146  {
147  m_error = "Failed to read from External Recorder: " + ENO;
148  LOG(VB_RECORD, LOG_ERR, m_error);
149  }
150  }
151  else
152  m_errCnt = 0;
153 
154  if (len == 0)
155  return 0;
156 
157  buffer.append(m_buffer, len);
158 
159  LOG(VB_RECORD, LOG_DEBUG,
160  QString("ExternIO::Read '%1' bytes, buffer size %2")
161  .arg(len).arg(buffer.size()));
162 
163  return len;
164 }
165 
167 {
168  if (Error())
169  {
170  LOG(VB_RECORD, LOG_ERR,
171  QString("ExternIO::GetStatus: already in error state: '%1'")
172  .arg(m_error));
173  return QByteArray();
174  }
175 
176  int waitfor = m_status.atEnd() ? timeout : 0;
177  if (Ready(m_appErr, waitfor, "status"))
178  {
179  std::array<char,2048> buffer {};
180  int len = read(m_appErr, buffer.data(), buffer.size());
181  m_status << QString::fromLatin1(buffer.data(), len);
182  }
183 
184  if (m_status.atEnd())
185  return QByteArray();
186 
187  QString msg = m_status.readLine();
188 
189  LOG(VB_RECORD, LOG_DEBUG, QString("ExternIO::GetStatus '%1'")
190  .arg(msg));
191 
192  return msg;
193 }
194 
195 int ExternIO::Write(const QByteArray & buffer)
196 {
197  if (Error())
198  {
199  LOG(VB_RECORD, LOG_ERR,
200  QString("ExternIO::Write: already in error state: '%1'")
201  .arg(m_error));
202  return -1;
203  }
204 
205  LOG(VB_RECORD, LOG_DEBUG, QString("ExternIO::Write('%1')")
206  .arg(QString(buffer).simplified()));
207 
208  int len = write(m_appIn, buffer.constData(), buffer.size());
209  if (len != buffer.size())
210  {
211  if (len > 0)
212  {
213  LOG(VB_RECORD, LOG_WARNING,
214  QString("ExternIO::Write: only wrote %1 of %2 bytes '%3'")
215  .arg(len).arg(buffer.size()).arg(QString(buffer)));
216  }
217  else
218  {
219  m_error = QString("ExternIO: Failed to write '%1' to app's stdin: ")
220  .arg(QString(buffer)) + ENO;
221  return -1;
222  }
223  }
224 
225  return len;
226 }
227 
228 bool ExternIO::Run(void)
229 {
230  LOG(VB_RECORD, LOG_INFO, QString("ExternIO::Run()"));
231 
232  Fork();
233  GetStatus(10);
234 
235  return true;
236 }
237 
238 /* Return true if the process is not, or is no longer running */
239 bool ExternIO::KillIfRunning(const QString & cmd)
240 {
241 #if CONFIG_DARWIN || (__FreeBSD__) || defined(__OpenBSD__)
242  Q_UNUSED(cmd);
243  return false;
244 #elif defined USING_MINGW
245  Q_UNUSED(cmd);
246  return false;
247 #elif defined( _MSC_VER )
248  Q_UNUSED(cmd);
249  return false;
250 #else
251  QString grp = QString("pgrep -x -f -- \"%1\" 2>&1 > /dev/null").arg(cmd);
252  QString kil = QString("pkill --signal 15 -x -f -- \"%1\" 2>&1 > /dev/null")
253  .arg(cmd);
254 
255  int res_grp = system(grp.toUtf8().constData());
256  if (WEXITSTATUS(res_grp) == 1)
257  {
258  LOG(VB_RECORD, LOG_DEBUG, QString("'%1' not running.").arg(cmd));
259  return true;
260  }
261 
262  LOG(VB_RECORD, LOG_WARNING, QString("'%1' already running, killing...")
263  .arg(cmd));
264  int res_kil = system(kil.toUtf8().constData());
265  if (WEXITSTATUS(res_kil) == 1)
266  LOG(VB_GENERAL, LOG_WARNING, QString("'%1' failed: %2")
267  .arg(kil).arg(ENO));
268 
269  res_grp = system(grp.toUtf8().constData());
270  if (WEXITSTATUS(res_grp) == 1)
271  {
272  LOG(WEXITSTATUS(res_kil) == 0 ? VB_RECORD : VB_GENERAL, LOG_WARNING,
273  QString("'%1' terminated.").arg(cmd));
274  return true;
275  }
276 
277  std::this_thread::sleep_for(std::chrono::milliseconds(50));
278 
279  kil = QString("pkill --signal 9 -x -f \"%1\" 2>&1 > /dev/null").arg(cmd);
280  res_kil = system(kil.toUtf8().constData());
281  if (WEXITSTATUS(res_kil) > 0)
282  LOG(VB_GENERAL, LOG_WARNING, QString("'%1' failed: %2")
283  .arg(kil).arg(ENO));
284 
285  res_grp = system(grp.toUtf8().constData());
286  LOG(WEXITSTATUS(res_kil) == 0 ? VB_RECORD : VB_GENERAL, LOG_WARNING,
287  QString("'%1' %2.").arg(cmd)
288  .arg(WEXITSTATUS(res_grp) == 0 ? "sill running" : "terminated"));
289 
290  return (WEXITSTATUS(res_grp) != 0);
291 #endif
292 }
293 
294 void ExternIO::Fork(void)
295 {
296 #if !defined( USING_MINGW ) && !defined( _MSC_VER )
297  if (Error())
298  {
299  LOG(VB_RECORD, LOG_INFO, QString("ExternIO in bad state: '%1'")
300  .arg(m_error));
301  return;
302  }
303 
304  QString full_command = QString("%1").arg(m_args.join(" "));
305 
306  if (!KillIfRunning(full_command))
307  {
308  // Give it one more chance.
309  std::this_thread::sleep_for(std::chrono::milliseconds(50));
310  if (!KillIfRunning(full_command))
311  {
312  m_error = QString("Unable to kill existing '%1'.")
313  .arg(full_command);
314  LOG(VB_GENERAL, LOG_ERR, m_error);
315  return;
316  }
317  }
318 
319 
320  LOG(VB_RECORD, LOG_INFO, QString("ExternIO::Fork '%1'").arg(full_command));
321 
322  std::array<int,2> in = {-1, -1};
323  std::array<int,2> out = {-1, -1};
324  std::array<int,2> err = {-1, -1};
325 
326  if (pipe(in.data()) < 0)
327  {
328  m_error = "pipe(in) failed: " + ENO;
329  return;
330  }
331  if (pipe(out.data()) < 0)
332  {
333  m_error = "pipe(out) failed: " + ENO;
334  close(in[0]);
335  close(in[1]);
336  return;
337  }
338  if (pipe(err.data()) < 0)
339  {
340  m_error = "pipe(err) failed: " + ENO;
341  close(in[0]);
342  close(in[1]);
343  close(out[0]);
344  close(out[1]);
345  return;
346  }
347 
348  m_pid = fork();
349  if (m_pid < 0)
350  {
351  // Failed
352  m_error = "fork() failed: " + ENO;
353  return;
354  }
355  if (m_pid > 0)
356  {
357  // Parent
358  close(in[0]);
359  close(out[1]);
360  close(err[1]);
361  m_appIn = in[1];
362  m_appOut = out[0];
363  m_appErr = err[0];
364 
365  bool error = false;
366  error = (fcntl(m_appIn, F_SETFL, O_NONBLOCK) == -1);
367  error |= (fcntl(m_appOut, F_SETFL, O_NONBLOCK) == -1);
368  error |= (fcntl(m_appErr, F_SETFL, O_NONBLOCK) == -1);
369 
370  if (error)
371  {
372  LOG(VB_GENERAL, LOG_WARNING,
373  "ExternIO::Fork(): Failed to set O_NONBLOCK for FD: " + ENO);
374  std::this_thread::sleep_for(std::chrono::seconds(2));
376  }
377 
378  LOG(VB_RECORD, LOG_INFO, "Spawned");
379  return;
380  }
381 
382  // Child
383  close(in[1]);
384  close(out[0]);
385  close(err[0]);
386  if (dup2( in[0], 0) < 0)
387  {
388  std::cerr << "dup2(stdin) failed: " << strerror(errno);
390  }
391  else if (dup2(out[1], 1) < 0)
392  {
393  std::cerr << "dup2(stdout) failed: " << strerror(errno);
395  }
396  else if (dup2(err[1], 2) < 0)
397  {
398  std::cerr << "dup2(stderr) failed: " << strerror(errno);
400  }
401 
402  /* Close all open file descriptors except stdin/stdout/stderr */
403  for (int i = sysconf(_SC_OPEN_MAX) - 1; i > 2; --i)
404  close(i);
405 
406  /* Set the process group id to be the same as the pid of this
407  * child process. This ensures that any subprocesses launched by this
408  * process can be killed along with the process itself. */
409  if (setpgid(0,0) < 0)
410  {
411  std::cerr << "ExternIO: "
412  << "setpgid() failed: "
413  << strerror(errno) << std::endl;
414  }
415 
416  /* run command */
417  char *command = strdup(m_app.canonicalFilePath()
418  .toUtf8().constData());
419  // Copy QStringList to char**
420  char **arguments = new char*[m_args.size() + 1];
421  for (int i = 0; i < m_args.size(); ++i)
422  {
423  int len = m_args[i].size() + 1;
424  arguments[i] = new char[len];
425  memcpy(arguments[i], m_args[i].toStdString().c_str(), len);
426  }
427  arguments[m_args.size()] = nullptr;
428 
429  if (execv(command, arguments) < 0)
430  {
431  // Can't use LOG due to locking fun.
432  std::cerr << "ExternIO: "
433  << "execv() failed: "
434  << strerror(errno) << std::endl;
435  }
436  else
437  {
438  std::cerr << "ExternIO: "
439  << "execv() should not be here?: "
440  << strerror(errno) << std::endl;
441  }
442 
443 #endif // !defined( USING_MINGW ) && !defined( _MSC_VER )
444 
445  /* Failed to exec */
446  _exit(GENERIC_EXIT_DAEMONIZING_ERROR); // this exit is ok
447 }
448 
449 
450 QMap<int, ExternalStreamHandler*> ExternalStreamHandler::s_handlers;
453 
455  int inputid, int majorid)
456 {
457  QMutexLocker locker(&s_handlersLock);
458 
459  QMap<int, ExternalStreamHandler*>::iterator it = s_handlers.find(majorid);
460 
461  if (it == s_handlers.end())
462  {
463  auto *newhandler = new ExternalStreamHandler(devname, inputid, majorid);
464  s_handlers[majorid] = newhandler;
465  s_handlersRefCnt[majorid] = 1;
466 
467  LOG(VB_RECORD, LOG_INFO,
468  QString("ExternSH[%1]: Creating new stream handler %2 for %3")
469  .arg(inputid).arg(majorid).arg(devname));
470  }
471  else
472  {
473  s_handlersRefCnt[majorid]++;
474  uint rcount = s_handlersRefCnt[majorid];
475  LOG(VB_RECORD, LOG_INFO,
476  QString("ExternSH[%1]: Using existing stream handler for %2")
477  .arg(inputid).arg(majorid) + QString(" (%1 in use)").arg(rcount));
478  }
479 
480  return s_handlers[majorid];
481 }
482 
484  int inputid)
485 {
486  QMutexLocker locker(&s_handlersLock);
487 
488  int majorid = ref->m_majorId;
489 
490  QMap<int, uint>::iterator rit = s_handlersRefCnt.find(majorid);
491  if (rit == s_handlersRefCnt.end())
492  return;
493 
494  QMap<int, ExternalStreamHandler*>::iterator it =
495  s_handlers.find(majorid);
496 
497  LOG(VB_RECORD, LOG_INFO, QString("ExternSH[%1]: Return %2 in use %3")
498  .arg(inputid).arg(majorid).arg(*rit));
499 
500  if (*rit > 1)
501  {
502  ref = nullptr;
503  --(*rit);
504  return;
505  }
506 
507  if ((it != s_handlers.end()) && (*it == ref))
508  {
509  LOG(VB_RECORD, LOG_INFO, QString("ExternSH[%1]: Closing handler for %2")
510  .arg(inputid).arg(majorid));
511  delete *it;
512  s_handlers.erase(it);
513  }
514  else
515  {
516  LOG(VB_GENERAL, LOG_ERR,
517  QString("ExternSH[%1]: Error: Couldn't find handler for %2")
518  .arg(inputid).arg(majorid));
519  }
520 
521  s_handlersRefCnt.erase(rit);
522  ref = nullptr;
523 }
524 
525 /*
526  ExternalStreamHandler
527  */
528 
530  int inputid,
531  int majorid)
532  : StreamHandler(path, inputid)
533  , m_loc(m_device)
534  , m_majorId(majorid)
535 {
536  setObjectName("ExternSH");
537 
538 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
539  m_args = path.split(' ',QString::SkipEmptyParts) +
540  logPropagateArgs.split(' ', QString::SkipEmptyParts);
541 #else
542  m_args = path.split(' ',Qt::SkipEmptyParts) +
543  logPropagateArgs.split(' ', Qt::SkipEmptyParts);
544 #endif
545  m_app = m_args.first();
546  m_args.removeFirst();
547 
548  // Pass one (and only one) 'quiet'
549  if (!m_args.contains("--quiet") && !m_args.contains("-q"))
550  m_args << "--quiet";
551 
552  m_args << "--inputid" << QString::number(majorid);
553  LOG(VB_RECORD, LOG_INFO, LOC + QString("args \"%1\"")
554  .arg(m_args.join(" ")));
555 
556  if (!OpenApp())
557  {
558  LOG(VB_GENERAL, LOG_ERR, LOC +
559  QString("Failed to start %1").arg(m_device));
560  }
561 }
562 
564 {
565  return m_streamingCnt.loadAcquire();
566 }
567 
569 {
570  QString result;
571  QString ready_cmd;
572  QByteArray buffer;
573  int sz = 0;
574  uint len = 0;
575  uint read_len = 0;
576  uint restart_cnt = 0;
577  MythTimer status_timer;
578  MythTimer nodata_timer;
579 
580  bool good_data = false;
581  uint data_proc_err = 0;
582  uint data_short_err = 0;
583 
584  if (!m_io)
585  {
586  LOG(VB_GENERAL, LOG_ERR, LOC +
587  QString("%1 is not running.").arg(m_device));
588  }
589 
590  status_timer.start();
591 
592  RunProlog();
593 
594  LOG(VB_RECORD, LOG_INFO, LOC + "run(): begin");
595 
596  SetRunning(true, true, false);
597 
598  if (m_pollMode)
599  ready_cmd = "SendBytes";
600  else
601  ready_cmd = "XON";
602 
603  uint remainder = 0;
604  while (m_runningDesired && !m_bError)
605  {
606  if (!IsTSOpen())
607  {
608  LOG(VB_RECORD, LOG_WARNING, LOC + "TS not open yet.");
609  std::this_thread::sleep_for(std::chrono::milliseconds(10));
610  continue;
611  }
612 
613  if (StreamingCount() == 0)
614  {
615  std::this_thread::sleep_for(std::chrono::milliseconds(10));
616  continue;
617  }
618 
620 
621  if (!m_xon || m_pollMode)
622  {
623  if (buffer.size() > TOO_FAST_SIZE)
624  {
625  LOG(VB_RECORD, LOG_WARNING, LOC +
626  "Internal buffer too full to accept more data from "
627  "external application.");
628  }
629  else
630  {
631  if (!ProcessCommand(ready_cmd, result))
632  {
633  if (result.startsWith("ERR"))
634  {
635  LOG(VB_GENERAL, LOG_ERR, LOC +
636  QString("Aborting: %1 -> %2")
637  .arg(ready_cmd).arg(result));
638  m_bError = true;
639  continue;
640  }
641 
642  if (restart_cnt++)
643  std::this_thread::sleep_for(std::chrono::seconds(20));
644  if (!RestartStream())
645  {
646  LOG(VB_RECORD, LOG_ERR, LOC +
647  "Failed to restart stream.");
648  m_bError = true;
649  }
650  continue;
651  }
652  m_xon = true;
653  }
654  }
655 
656  if (m_xon)
657  {
658  if (status_timer.elapsed() >= 2000)
659  {
660  // Since we may never need to send the XOFF
661  // command, occationally check to see if the
662  // External recorder needs to report an issue.
663  if (CheckForError())
664  {
665  if (restart_cnt++)
666  std::this_thread::sleep_for(std::chrono::seconds(20));
667  if (!RestartStream())
668  {
669  LOG(VB_RECORD, LOG_ERR, LOC + "Failed to restart stream.");
670  m_bError = true;
671  }
672  continue;
673  }
674 
675  status_timer.restart();
676  }
677 
678  if (buffer.size() > TOO_FAST_SIZE)
679  {
680  if (!m_pollMode)
681  {
682  // Data is comming a little too fast, so XOFF
683  // to give us time to process it.
684  if (!ProcessCommand(QString("XOFF"), result))
685  {
686  if (result.startsWith("ERR"))
687  {
688  LOG(VB_GENERAL, LOG_ERR, LOC +
689  QString("Aborting: XOFF -> %2")
690  .arg(result));
691  m_bError = true;
692  }
693  }
694  m_xon = false;
695  }
696  }
697 
698  if (m_io && (sz = PACKET_SIZE - remainder) > 0)
699  read_len = m_io->Read(buffer, sz, 100);
700  else
701  read_len = 0;
702  }
703  else
704  read_len = 0;
705 
706  if (read_len == 0)
707  {
708  if (!nodata_timer.isRunning())
709  nodata_timer.start();
710  else
711  {
712  if (nodata_timer.elapsed() >= 50000)
713  {
714  LOG(VB_GENERAL, LOG_WARNING, LOC +
715  "No data for 50 seconds, Restarting stream.");
716  if (!RestartStream())
717  {
718  LOG(VB_RECORD, LOG_ERR, LOC +
719  "Failed to restart stream.");
720  m_bError = true;
721  }
722  nodata_timer.stop();
723  continue;
724  }
725  }
726 
727  std::this_thread::sleep_for(std::chrono::milliseconds(50));
728 
729  // HLS type streams may only produce data every ~10 seconds
730  if (nodata_timer.elapsed() < 12000 && buffer.size() < TS_PACKET_SIZE)
731  continue;
732  }
733  else
734  {
735  nodata_timer.stop();
736  restart_cnt = 0;
737  }
738 
739  if (m_io == nullptr)
740  {
741  LOG(VB_GENERAL, LOG_ERR, LOC + "I/O thread has disappeared!");
742  m_bError = true;
743  break;
744  }
745  if (m_io->Error())
746  {
747  LOG(VB_GENERAL, LOG_ERR, LOC +
748  QString("Fatal Error from External Recorder: %1")
749  .arg(m_io->ErrorString()));
750  CloseApp();
751  m_bError = true;
752  break;
753  }
754 
755  len = remainder = buffer.size();
756 
757  if (len == 0)
758  continue;
759 
760  if (len < TS_PACKET_SIZE)
761  {
762  if (m_xon && data_short_err++ == 0)
763  LOG(VB_RECORD, LOG_INFO, LOC + "Waiting for a full TS packet.");
764  std::this_thread::sleep_for(std::chrono::microseconds(50));
765  continue;
766  }
767  if (data_short_err)
768  {
769  if (data_short_err > 1)
770  {
771  LOG(VB_RECORD, LOG_INFO, LOC +
772  QString("Waited for a full TS packet %1 times.")
773  .arg(data_short_err));
774  }
775  data_short_err = 0;
776  }
777 
778  if (!m_streamLock.tryLock())
779  continue;
780 
781  if (!m_listenerLock.tryLock())
782  continue;
783 
784  for (auto sit = m_streamDataList.cbegin();
785  sit != m_streamDataList.cend(); ++sit)
786  {
787  remainder = sit.key()->ProcessData
788  (reinterpret_cast<const uint8_t *>
789  (buffer.constData()), buffer.size());
790  }
791 
792  m_listenerLock.unlock();
793 
794  if (m_replay)
795  {
796  m_replayBuffer += buffer.left(len - remainder);
797  if (m_replayBuffer.size() > (50 * PACKET_SIZE))
798  {
799  m_replayBuffer.remove(0, len - remainder);
800  LOG(VB_RECORD, LOG_WARNING, LOC +
801  QString("Replay size truncated to %1 bytes")
802  .arg(m_replayBuffer.size()));
803  }
804  }
805 
806  m_streamLock.unlock();
807 
808  if (remainder == 0)
809  {
810  buffer.clear();
811  good_data = (len != 0U);
812  }
813  else if (len > remainder) // leftover bytes
814  {
815  buffer.remove(0, len - remainder);
816  good_data = (len != 0U);
817  }
818  else if (len == remainder)
819  good_data = false;
820 
821  if (good_data)
822  {
823  if (data_proc_err)
824  {
825  if (data_proc_err > 1)
826  {
827  LOG(VB_RECORD, LOG_WARNING, LOC +
828  QString("Failed to process the data received %1 times.")
829  .arg(data_proc_err));
830  }
831  data_proc_err = 0;
832  }
833  }
834  else
835  {
836  if (data_proc_err++ == 0)
837  {
838  LOG(VB_RECORD, LOG_WARNING, LOC +
839  "Failed to process the data received");
840  }
841  }
842  }
843 
844  LOG(VB_RECORD, LOG_INFO, LOC + "run(): " +
845  QString("%1 shutdown").arg(m_bError ? "Error" : "Normal"));
846 
848  SetRunning(false, true, false);
849 
850  LOG(VB_RECORD, LOG_INFO, LOC + "run(): " + "end");
851 
852  RunEpilog();
853 }
854 
856 {
857  QString result;
858 
859  if (ProcessCommand("APIVersion?", result, 10000))
860  {
861 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
862  QStringList tokens = result.split(':', QString::SkipEmptyParts);
863 #else
864  QStringList tokens = result.split(':', Qt::SkipEmptyParts);
865 #endif
866 
867  if (tokens.size() > 1)
868  m_apiVersion = tokens[1].toUInt();
869  m_apiVersion = std::min(m_apiVersion, static_cast<int>(MAX_API_VERSION));
870  if (m_apiVersion < 1)
871  {
872  LOG(VB_RECORD, LOG_ERR, LOC +
873  QString("Bad response to 'APIVersion?' - '%1'. "
874  "Expecting 1 or 2").arg(result));
875  m_apiVersion = 1;
876  }
877 
878  ProcessCommand(QString("APIVersion:%1").arg(m_apiVersion), result);
879  return true;
880  }
881 
882  return false;
883 }
884 
886 {
887  if (m_apiVersion > 1)
888  {
889  QString result;
890 
891  if (ProcessCommand("Description?", result))
892  m_loc = result.mid(3);
893  else
894  m_loc = m_device;
895  }
896 
897  return m_loc;
898 }
899 
901 {
902  {
903  QMutexLocker locker(&m_ioLock);
904 
905  if (m_io)
906  {
907  LOG(VB_RECORD, LOG_WARNING, LOC + "OpenApp: already open!");
908  return true;
909  }
910 
911  m_io = new ExternIO(m_app, m_args);
912 
913  if (m_io == nullptr)
914  {
915  LOG(VB_GENERAL, LOG_ERR, LOC + "ExternIO failed: " + ENO);
916  m_bError = true;
917  }
918  else
919  {
920  LOG(VB_RECORD, LOG_INFO, LOC + QString("Spawn '%1'").arg(m_device));
921  m_io->Run();
922  if (m_io->Error())
923  {
924  LOG(VB_GENERAL, LOG_ERR,
925  "Failed to start External Recorder: " + m_io->ErrorString());
926  delete m_io;
927  m_io = nullptr;
928  m_bError = true;
929  return false;
930  }
931  }
932  }
933 
934  QString result;
935 
936  if (!SetAPIVersion())
937  {
938  // Try again using API version 2
939  m_apiVersion = 2;
940  if (!SetAPIVersion())
941  m_apiVersion = 1;
942  }
943 
944  if (!IsAppOpen())
945  {
946  LOG(VB_RECORD, LOG_ERR, LOC + "Application is not responding.");
947  m_bError = true;
948  return false;
949  }
950 
952 
953  // Gather capabilities
954  if (!ProcessCommand("HasTuner?", result))
955  {
956  LOG(VB_RECORD, LOG_ERR, LOC +
957  QString("Bad response to 'HasTuner?' - '%1'").arg(result));
958  m_bError = true;
959  return false;
960  }
961  m_hasTuner = result.startsWith("OK:Yes");
962 
963  if (!ProcessCommand("HasPictureAttributes?", result))
964  {
965  LOG(VB_RECORD, LOG_ERR, LOC +
966  QString("Bad response to 'HasPictureAttributes?' - '%1'")
967  .arg(result));
968  m_bError = true;
969  return false;
970  }
971  m_hasPictureAttributes = result.startsWith("OK:Yes");
972 
973  /* Operate in "poll" or "xon/xoff" mode */
974  m_pollMode = ProcessCommand("FlowControl?", result) &&
975  result.startsWith("OK:Poll");
976 
977  LOG(VB_RECORD, LOG_INFO, LOC + "App opened successfully");
978  LOG(VB_RECORD, LOG_INFO, LOC +
979  QString("Capabilities: tuner(%1) "
980  "Picture attributes(%2) "
981  "Flow control(%3)")
982  .arg(m_hasTuner ? "yes" : "no")
983  .arg(m_hasPictureAttributes ? "yes" : "no")
984  .arg(m_pollMode ? "Polling" : "XON/XOFF")
985  );
986 
987  /* Let the external app know how many bytes will read without blocking */
988  ProcessCommand(QString("BlockSize:%1").arg(PACKET_SIZE), result);
989 
990  return true;
991 }
992 
994 {
995  if (m_io == nullptr)
996  {
997  LOG(VB_RECORD, LOG_WARNING, LOC +
998  "WARNING: Unable to communicate with external app.");
999  return false;
1000  }
1001 
1002  QString result;
1003  return ProcessCommand("Version?", result, 10000);
1004 }
1005 
1007 {
1008  if (m_tsOpen)
1009  return true;
1010 
1011  QString result;
1012 
1013  if (!ProcessCommand("IsOpen?", result))
1014  return false;
1015 
1016  m_tsOpen = true;
1017  return m_tsOpen;
1018 }
1019 
1021 {
1022  m_ioLock.lock();
1023  if (m_io)
1024  {
1025  QString result;
1026 
1027  LOG(VB_RECORD, LOG_INFO, LOC + "CloseRecorder");
1028  m_ioLock.unlock();
1029  ProcessCommand("CloseRecorder", result, 10000);
1030  m_ioLock.lock();
1031 
1032  if (!result.startsWith("OK"))
1033  {
1034  LOG(VB_RECORD, LOG_INFO, LOC +
1035  "CloseRecorder failed, sending kill.");
1036 
1037  QString full_command = QString("%1").arg(m_args.join(" "));
1038 
1039  if (!m_io->KillIfRunning(full_command))
1040  {
1041  // Give it one more chance.
1042  std::this_thread::sleep_for(std::chrono::milliseconds(50));
1043  if (!m_io->KillIfRunning(full_command))
1044  {
1045  LOG(VB_GENERAL, LOG_ERR,
1046  QString("Unable to kill existing '%1'.")
1047  .arg(full_command));
1048  return;
1049  }
1050  }
1051  }
1052  delete m_io;
1053  m_io = nullptr;
1054  }
1055  m_ioLock.unlock();
1056 }
1057 
1059 {
1060  bool streaming = (StreamingCount() > 0);
1061 
1062  LOG(VB_RECORD, LOG_INFO, LOC + "Restarting stream.");
1063 
1064  if (streaming)
1065  StopStreaming();
1066 
1067  std::this_thread::sleep_for(std::chrono::seconds(1));
1068 
1069  if (streaming)
1070  return StartStreaming();
1071 
1072  return true;
1073 }
1074 
1076 {
1077  if (m_replay)
1078  {
1079  QString result;
1080 
1081  // Let the external app know that we could be busy for a little while
1082  if (!m_pollMode)
1083  {
1084  ProcessCommand(QString("XOFF"), result);
1085  m_xon = false;
1086  }
1087 
1088  /* If the input is not a 'broadcast' it may only have one
1089  * copy of the SPS right at the beginning of the stream,
1090  * so make sure we don't miss it!
1091  */
1092  QMutexLocker listen_lock(&m_listenerLock);
1093 
1094  if (!m_streamDataList.empty())
1095  {
1096  for (auto sit = m_streamDataList.cbegin();
1097  sit != m_streamDataList.cend(); ++sit)
1098  {
1099  sit.key()->ProcessData(reinterpret_cast<const uint8_t *>
1100  (m_replayBuffer.constData()),
1101  m_replayBuffer.size());
1102  }
1103  }
1104  LOG(VB_RECORD, LOG_INFO, LOC + QString("Replayed %1 bytes")
1105  .arg(m_replayBuffer.size()));
1106  m_replayBuffer.clear();
1107  m_replay = false;
1108 
1109  // Let the external app know that we are ready
1110  if (!m_pollMode)
1111  {
1112  if (ProcessCommand(QString("XON"), result))
1113  m_xon = true;
1114  }
1115  }
1116 }
1117 
1119 {
1120  QString result;
1121 
1122  QMutexLocker locker(&m_streamLock);
1123 
1125 
1126  LOG(VB_RECORD, LOG_INFO, LOC +
1127  QString("StartStreaming with %1 current listeners")
1128  .arg(StreamingCount()));
1129 
1130  if (!IsAppOpen())
1131  {
1132  LOG(VB_GENERAL, LOG_ERR, LOC + "External Recorder not started.");
1133  return false;
1134  }
1135 
1136  if (StreamingCount() == 0)
1137  {
1138  if (!ProcessCommand("StartStreaming", result, 15000))
1139  {
1140  LogLevel_t level = LOG_ERR;
1141  if (result.startsWith("warn", Qt::CaseInsensitive))
1142  level = LOG_WARNING;
1143  else
1144  m_bError = true;
1145 
1146  LOG(VB_GENERAL, level, LOC + QString("StartStreaming failed: '%1'")
1147  .arg(result));
1148 
1149  return false;
1150  }
1151 
1152  LOG(VB_RECORD, LOG_INFO, LOC + "Streaming started");
1153  }
1154  else
1155  LOG(VB_RECORD, LOG_INFO, LOC + "Already streaming");
1156 
1157  m_streamingCnt.ref();
1158 
1159  LOG(VB_RECORD, LOG_INFO, LOC +
1160  QString("StartStreaming %1 listeners")
1161  .arg(StreamingCount()));
1162 
1163  return true;
1164 }
1165 
1167 {
1168  QMutexLocker locker(&m_streamLock);
1169 
1170  LOG(VB_RECORD, LOG_INFO, LOC +
1171  QString("StopStreaming %1 listeners")
1172  .arg(StreamingCount()));
1173 
1174  if (StreamingCount() == 0)
1175  {
1176  LOG(VB_RECORD, LOG_INFO, LOC +
1177  "StopStreaming requested, but we are not streaming!");
1178  return true;
1179  }
1180 
1181  if (m_streamingCnt.deref())
1182  {
1183  LOG(VB_RECORD, LOG_INFO, LOC +
1184  QString("StopStreaming delayed, still have %1 listeners")
1185  .arg(StreamingCount()));
1186  return true;
1187  }
1188 
1189  LOG(VB_RECORD, LOG_INFO, LOC + "StopStreaming");
1190 
1191  if (!m_pollMode && m_xon)
1192  {
1193  QString result;
1194  ProcessCommand(QString("XOFF"), result);
1195  m_xon = false;
1196  }
1197 
1198  if (!IsAppOpen())
1199  {
1200  LOG(VB_GENERAL, LOG_ERR, LOC + "External Recorder not started.");
1201  return false;
1202  }
1203 
1204  QString result;
1205  if (!ProcessCommand("StopStreaming", result, 10000))
1206  {
1207  LogLevel_t level = LOG_ERR;
1208  if (result.startsWith("warn", Qt::CaseInsensitive))
1209  level = LOG_WARNING;
1210  else
1211  m_bError = true;
1212 
1213  LOG(VB_GENERAL, level, LOC + QString("StopStreaming: '%1'")
1214  .arg(result));
1215 
1216  return false;
1217  }
1218 
1219  PurgeBuffer();
1220  LOG(VB_RECORD, LOG_INFO, LOC + "Streaming stopped");
1221 
1222  return true;
1223 }
1224 
1225 bool ExternalStreamHandler::ProcessCommand(const QString & cmd,
1226  QString & result, int timeout,
1227  uint retry_cnt)
1228 {
1229  QMutexLocker locker(&m_processLock);
1230 
1231  if (m_apiVersion == 2)
1232  return ProcessVer2(cmd, result, timeout, retry_cnt);
1233  if (m_apiVersion == 1)
1234  return ProcessVer1(cmd, result, timeout, retry_cnt);
1235 
1236  LOG(VB_RECORD, LOG_ERR, LOC +
1237  QString("Invalid API version %1. Expected 1 or 2").arg(m_apiVersion));
1238  return false;
1239 }
1240 
1241 bool ExternalStreamHandler::ProcessVer1(const QString & cmd,
1242  QString & result, int timeout,
1243  uint retry_cnt)
1244 {
1245  LOG(VB_RECORD, LOG_DEBUG, LOC + QString("ProcessVer1('%1')")
1246  .arg(cmd));
1247 
1248  for (uint cnt = 0; cnt < retry_cnt; ++cnt)
1249  {
1250  QMutexLocker locker(&m_ioLock);
1251 
1252  if (!m_io)
1253  {
1254  LOG(VB_RECORD, LOG_ERR, LOC + "External I/O not ready!");
1255  return false;
1256  }
1257 
1258  QByteArray buf(cmd.toUtf8(), cmd.size());
1259  buf += '\n';
1260 
1261  if (m_io->Error())
1262  {
1263  LOG(VB_GENERAL, LOG_ERR, LOC + "External Recorder in bad state: " +
1264  m_io->ErrorString());
1265  return false;
1266  }
1267 
1268  /* Try to keep in sync, if External app was too slow in responding
1269  * to previous query, consume the response before sending new query */
1270  m_io->GetStatus(0);
1271 
1272  /* Send new query */
1273  m_io->Write(buf);
1274 
1276  while (timer.elapsed() < timeout)
1277  {
1278  result = m_io->GetStatus(timeout);
1279  if (m_io->Error())
1280  {
1281  LOG(VB_GENERAL, LOG_ERR, LOC +
1282  "Failed to read from External Recorder: " +
1283  m_io->ErrorString());
1284  m_bError = true;
1285  return false;
1286  }
1287 
1288  // Out-of-band error message
1289  if (result.startsWith("STATUS:ERR") ||
1290  result.startsWith("0:STATUS:ERR"))
1291  {
1292  LOG(VB_RECORD, LOG_ERR, LOC + result);
1293  result.remove(0, result.indexOf(":ERR") + 1);
1294  return false;
1295  }
1296  // STATUS message are "out of band".
1297  // Ignore them while waiting for a responds to a command
1298  if (!result.startsWith("STATUS") && !result.startsWith("0:STATUS"))
1299  break;
1300  LOG(VB_RECORD, LOG_INFO, LOC +
1301  QString("Ignoring response '%1'").arg(result));
1302  }
1303 
1304  if (result.size() < 1)
1305  {
1306  LOG(VB_GENERAL, LOG_WARNING, LOC +
1307  QString("External Recorder did not respond to '%1'").arg(cmd));
1308  }
1309  else
1310  {
1311  bool okay = result.startsWith("OK");
1312  if (okay || result.startsWith("WARN") || result.startsWith("ERR"))
1313  {
1314  LogLevel_t level = LOG_INFO;
1315 
1316  m_ioErrCnt = 0;
1317  if (!okay)
1318  level = LOG_WARNING;
1319  else if (cmd.startsWith("SendBytes"))
1320  level = LOG_DEBUG;
1321 
1322  LOG(VB_RECORD, level,
1323  LOC + QString("ProcessCommand('%1') = '%2' took %3ms %4")
1324  .arg(cmd).arg(result)
1325  .arg(timer.elapsed())
1326  .arg(okay ? "" : "<-- NOTE"));
1327 
1328  return okay;
1329  }
1330  LOG(VB_GENERAL, LOG_WARNING, LOC +
1331  QString("External Recorder invalid response to '%1': '%2'")
1332  .arg(cmd).arg(result));
1333  }
1334 
1335  if (++m_ioErrCnt > 10)
1336  {
1337  LOG(VB_GENERAL, LOG_ERR, LOC + "Too many I/O errors.");
1338  m_bError = true;
1339  break;
1340  }
1341  }
1342 
1343  return false;
1344 }
1345 
1346 bool ExternalStreamHandler::ProcessVer2(const QString & command,
1347  QString & result, int timeout,
1348  uint retry_cnt)
1349 {
1350  QString status;
1351  QString raw;
1352 
1353  for (uint cnt = 0; cnt < retry_cnt; ++cnt)
1354  {
1355  QString cmd = QString("%1:%2").arg(++m_serialNo).arg(command);
1356 
1357  LOG(VB_RECORD, LOG_DEBUG, LOC + QString("ProcessVer2('%1') serial(%2)")
1358  .arg(cmd).arg(m_serialNo));
1359 
1360  QMutexLocker locker(&m_ioLock);
1361 
1362  if (!m_io)
1363  {
1364  LOG(VB_RECORD, LOG_ERR, LOC + "External I/O not ready!");
1365  return false;
1366  }
1367 
1368  QByteArray buf(cmd.toUtf8(), cmd.size());
1369  buf += '\n';
1370 
1371  if (m_io->Error())
1372  {
1373  LOG(VB_GENERAL, LOG_ERR, LOC + "External Recorder in bad state: " +
1374  m_io->ErrorString());
1375  return false;
1376  }
1377 
1378  /* Send query */
1379  m_io->Write(buf);
1380 
1381  QStringList tokens;
1382 
1384  while (timer.elapsed() < timeout)
1385  {
1386  result = m_io->GetStatus(timeout);
1387  if (m_io->Error())
1388  {
1389  LOG(VB_GENERAL, LOG_ERR, LOC +
1390  "Failed to read from External Recorder: " +
1391  m_io->ErrorString());
1392  m_bError = true;
1393  return false;
1394  }
1395 
1396  if (!result.isEmpty())
1397  {
1398  raw = result;
1399 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1400  tokens = result.split(':', QString::SkipEmptyParts);
1401 #else
1402  tokens = result.split(':', Qt::SkipEmptyParts);
1403 #endif
1404 
1405  // Look for result with the serial number of this query
1406  if (tokens.size() > 1 && tokens[0].toUInt() >= m_serialNo)
1407  break;
1408 
1409  /* Other messages are "out of band" */
1410 
1411  // Check for error message missing serial#
1412  if (tokens[0].startsWith("ERR"))
1413  break;
1414 
1415  // Remove serial#
1416  tokens.removeFirst();
1417  result = tokens.join(':');
1418  bool err = (tokens.size() > 1 && tokens[1].startsWith("ERR"));
1419  LOG(VB_RECORD, (err ? LOG_WARNING : LOG_INFO), LOC + raw);
1420  if (err)
1421  {
1422  // Remove "STATUS"
1423  tokens.removeFirst();
1424  result = tokens.join(':');
1425  return false;
1426  }
1427  }
1428  }
1429 
1430  if (timer.elapsed() >= timeout)
1431  {
1432  LOG(VB_RECORD, LOG_ERR, LOC +
1433  QString("ProcessVer2: Giving up waiting for response for "
1434  "command '%2'").arg(cmd));
1435  }
1436  else if (tokens.size() < 2)
1437  {
1438  LOG(VB_RECORD, LOG_ERR, LOC +
1439  QString("Did not receive a valid response "
1440  "for command '%1', received '%2'").arg(cmd).arg(result));
1441  }
1442  else if (tokens[0].toUInt() > m_serialNo)
1443  {
1444  LOG(VB_RECORD, LOG_ERR, LOC +
1445  QString("ProcessVer2: Looking for serial no %1, "
1446  "but received %2 for command '%2'")
1447  .arg(m_serialNo).arg(tokens[0]).arg(cmd));
1448  }
1449  else
1450  {
1451  tokens.removeFirst();
1452  status = tokens[0].trimmed();
1453  result = tokens.join(':');
1454 
1455  bool okay = (status == "OK");
1456  if (okay || status.startsWith("WARN") || status.startsWith("ERR"))
1457  {
1458  LogLevel_t level = LOG_INFO;
1459 
1460  m_ioErrCnt = 0;
1461  if (!okay)
1462  level = LOG_WARNING;
1463  else if (command.startsWith("SendBytes") ||
1464  (command.startsWith("TuneStatus") &&
1465  result == "OK:InProgress"))
1466  level = LOG_DEBUG;
1467 
1468  LOG(VB_RECORD, level,
1469  LOC + QString("ProcessV2('%1') = '%2' took %3ms %4")
1470  .arg(cmd).arg(result).arg(timer.elapsed())
1471  .arg(okay ? "" : "<-- NOTE"));
1472 
1473  return okay;
1474  }
1475  LOG(VB_GENERAL, LOG_WARNING, LOC +
1476  QString("External Recorder invalid response to '%1': '%2'")
1477  .arg(cmd).arg(result));
1478  }
1479 
1480  if (++m_ioErrCnt > 10)
1481  {
1482  LOG(VB_GENERAL, LOG_ERR, LOC + "Too many I/O errors.");
1483  m_bError = true;
1484  break;
1485  }
1486  }
1487 
1488  return false;
1489 }
1490 
1492 {
1493  QString result;
1494  bool err = false;
1495 
1496  QMutexLocker locker(&m_ioLock);
1497 
1498  if (!m_io)
1499  {
1500  LOG(VB_RECORD, LOG_ERR, LOC + "External I/O not ready!");
1501  return true;
1502  }
1503 
1504  if (m_io->Error())
1505  {
1506  LOG(VB_GENERAL, LOG_ERR, "External Recorder in bad state: " +
1507  m_io->ErrorString());
1508  return true;
1509  }
1510 
1511  do
1512  {
1513  result = m_io->GetStatus(0);
1514  if (!result.isEmpty())
1515  {
1516  if (m_apiVersion > 1)
1517  {
1518 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1519  QStringList tokens = result.split(':', QString::SkipEmptyParts);
1520 #else
1521  QStringList tokens = result.split(':', Qt::SkipEmptyParts);
1522 #endif
1523 
1524  tokens.removeFirst();
1525  result = tokens.join(':');
1526  for (int idx = 1; idx < tokens.size(); ++idx)
1527  err |= tokens[idx].startsWith("ERR");
1528  }
1529  else
1530  err |= result.startsWith("STATUS:ERR");
1531 
1532  LOG(VB_RECORD, (err ? LOG_WARNING : LOG_INFO), LOC + result);
1533  }
1534  }
1535  while (!result.isEmpty());
1536 
1537  return err;
1538 }
1539 
1541 {
1542  if (m_io)
1543  {
1544  QByteArray buffer;
1545  m_io->Read(buffer, PACKET_SIZE, 1);
1546  m_io->GetStatus(1);
1547  }
1548 }
1549 
1551 {
1552  // TODO report on buffer overruns, etc.
1553 }
WEXITSTATUS
#define WEXITSTATUS(w)
Definition: compat.h:329
ExternalStreamHandler::ExternalStreamHandler
ExternalStreamHandler(const QString &path, int inputid, int majorid)
Definition: ExternalStreamHandler.cpp:529
build_compdb.args
args
Definition: build_compdb.py:11
ExternalStreamHandler::m_pollMode
bool m_pollMode
Definition: ExternalStreamHandler.h:120
ExternalStreamHandler::IsTSOpen
bool IsTSOpen(void)
Definition: ExternalStreamHandler.cpp:1006
ExternalStreamHandler::m_ioLock
QMutex m_ioLock
Definition: ExternalStreamHandler.h:114
ENO
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:72
hardwareprofile.smolt.timeout
float timeout
Definition: smolt.py:103
ExternalStreamHandler::m_apiVersion
int m_apiVersion
Definition: ExternalStreamHandler.h:122
ExternalStreamHandler::ProcessCommand
bool ProcessCommand(const QString &cmd, QString &result, int timeout=4000, uint retry_cnt=3)
Definition: ExternalStreamHandler.cpp:1225
ExternalStreamHandler::m_serialNo
uint m_serialNo
Definition: ExternalStreamHandler.h:123
error
static void error(const char *str,...)
Definition: vbi.cpp:42
StreamHandler::RemoveAllPIDFilters
bool RemoveAllPIDFilters(void)
Definition: streamhandler.cpp:234
ExternalStreamHandler::ProcessVer2
bool ProcessVer2(const QString &command, QString &result, int timeout, uint retry_cnt)
Definition: ExternalStreamHandler.cpp:1346
StreamHandler::SetRunning
void SetRunning(bool running, bool using_buffering, bool using_section_reader)
Definition: streamhandler.cpp:171
ExternalStreamHandler::PriorityEvent
void PriorityEvent(int fd) override
Definition: ExternalStreamHandler.cpp:1550
MythTimer
A QElapsedTimer based timer to replace use of QTime as a timer.
Definition: mythtimer.h:14
StreamHandler
Definition: streamhandler.h:55
ExternalStreamHandler::m_hasPictureAttributes
bool m_hasPictureAttributes
Definition: ExternalStreamHandler.h:125
ExternalStreamHandler::StreamingCount
int StreamingCount(void) const
Definition: ExternalStreamHandler.cpp:563
discid.disc.read
def read(device=None, features=[])
Definition: disc.py:35
ExternIO::~ExternIO
~ExternIO(void)
Definition: ExternalStreamHandler.cpp:63
ExternalStreamHandler::run
void run(void) override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
Definition: ExternalStreamHandler.cpp:568
mythburn.write
def write(text, progress=True)
Definition: mythburn.py:308
ExternalStreamHandler::m_io
ExternIO * m_io
Definition: ExternalStreamHandler.h:115
MThread::setObjectName
void setObjectName(const QString &name)
Definition: mthread.cpp:243
MythTimer::stop
void stop(void)
Stops timer, next call to isRunning() will return false and any calls to elapsed() or restart() will ...
Definition: mythtimer.cpp:77
arg
arg(title).arg(filename).arg(doDelete))
MythTimer::isRunning
bool isRunning(void) const
Returns true if start() or restart() has been called at least once since construction and since any c...
Definition: mythtimer.cpp:134
ExternalStreamHandler::StopStreaming
bool StopStreaming(void)
Definition: ExternalStreamHandler.cpp:1166
ExternIO::Read
int Read(QByteArray &buffer, int maxlen, int timeout=2500)
Definition: ExternalStreamHandler.cpp:105
ExternIO::m_error
QString m_error
Definition: ExternalStreamHandler.h:47
ExternalStreamHandler::m_xon
bool m_xon
Definition: ExternalStreamHandler.h:129
MythTimer::start
void start(void)
starts measuring elapsed time.
Definition: mythtimer.cpp:47
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
MThread::RunProlog
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:198
ExternalStreamHandler::m_processLock
QMutex m_processLock
Definition: ExternalStreamHandler.h:139
streamlisteners.h
ExternIO::ExternIO
ExternIO(const QString &app, const QStringList &args)
Definition: ExternalStreamHandler.cpp:33
ExternalStreamHandler::s_handlers
static QMap< int, ExternalStreamHandler * > s_handlers
Definition: ExternalStreamHandler.h:133
ExternIO::m_bufSize
int m_bufSize
Definition: ExternalStreamHandler.h:49
ExternIO::Error
bool Error(void) const
Definition: ExternalStreamHandler.h:32
ExternIO::m_buffer
char * m_buffer
Definition: ExternalStreamHandler.h:50
ExternalStreamHandler.h
ExternIO
Definition: ExternalStreamHandler.h:20
ExternalStreamHandler::s_handlersRefCnt
static QMap< int, uint > s_handlersRefCnt
Definition: ExternalStreamHandler.h:134
ExternalStreamHandler::TOO_FAST_SIZE
@ TOO_FAST_SIZE
Definition: ExternalStreamHandler.h:64
close
#define close
Definition: compat.h:16
ExternalStreamHandler
Definition: ExternalStreamHandler.h:60
ExternalStreamHandler::m_hasTuner
bool m_hasTuner
Definition: ExternalStreamHandler.h:124
ExternIO::KillIfRunning
static bool KillIfRunning(const QString &cmd)
Definition: ExternalStreamHandler.cpp:239
ExternIO::GetStatus
QString GetStatus(int timeout=2500)
Definition: ExternalStreamHandler.cpp:166
ExternalStreamHandler::ReplayStream
void ReplayStream(void)
Definition: ExternalStreamHandler.cpp:1075
ExternalStreamHandler::m_tsOpen
bool m_tsOpen
Definition: ExternalStreamHandler.h:118
ExternIO::Fork
void Fork(void)
Definition: ExternalStreamHandler.cpp:294
ExternalStreamHandler::m_replayBuffer
QByteArray m_replayBuffer
Definition: ExternalStreamHandler.h:127
ExternalStreamHandler::CloseApp
void CloseApp(void)
Definition: ExternalStreamHandler.cpp:1020
ExternIO::m_appErr
int m_appErr
Definition: ExternalStreamHandler.h:45
ExternIO::m_pid
pid_t m_pid
Definition: ExternalStreamHandler.h:46
O_NONBLOCK
#define O_NONBLOCK
Definition: mythmedia.cpp:24
ExternIO::ErrorString
QString ErrorString(void) const
Definition: ExternalStreamHandler.h:33
StreamHandler::UpdateFiltersFromStreamData
bool UpdateFiltersFromStreamData(void)
Definition: streamhandler.cpp:284
ExternalStreamHandler::PurgeBuffer
void PurgeBuffer(void)
Definition: ExternalStreamHandler.cpp:1540
MThread::RunEpilog
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:211
ExternIO::m_app
QFileInfo m_app
Definition: ExternalStreamHandler.h:41
ExternalStreamHandler::Get
static ExternalStreamHandler * Get(const QString &devname, int inputid, int majorid)
Definition: ExternalStreamHandler.cpp:454
ExternIO::Run
bool Run(void)
Definition: ExternalStreamHandler.cpp:228
ExternalStreamHandler::RestartStream
bool RestartStream(void)
Definition: ExternalStreamHandler.cpp:1058
ExternalStreamHandler::m_majorId
int m_majorId
Definition: ExternalStreamHandler.h:113
ExternalStreamHandler::SetAPIVersion
bool SetAPIVersion(void)
Definition: ExternalStreamHandler.cpp:855
uint
unsigned int uint
Definition: compat.h:140
ExternalStreamHandler::s_handlersLock
static QMutex s_handlersLock
Definition: ExternalStreamHandler.h:132
ExternIO::m_appOut
int m_appOut
Definition: ExternalStreamHandler.h:44
ExternIO::m_appIn
int m_appIn
Definition: ExternalStreamHandler.h:43
ExternalStreamHandler::TS_PACKET_SIZE
@ TS_PACKET_SIZE
Definition: ExternalStreamHandler.h:62
ExternalStreamHandler::StartStreaming
bool StartStreaming(void)
Definition: ExternalStreamHandler.cpp:1118
LOC
#define LOC
Definition: ExternalStreamHandler.cpp:31
ExternalStreamHandler::UpdateDescription
QString UpdateDescription(void)
Definition: ExternalStreamHandler.cpp:885
mpegstreamdata.h
ExternalStreamHandler::IsAppOpen
bool IsAppOpen(void)
Definition: ExternalStreamHandler.cpp:993
ExternalStreamHandler::CheckForError
bool CheckForError(void)
Definition: ExternalStreamHandler.cpp:1491
ExternalStreamHandler::m_ioErrCnt
int m_ioErrCnt
Definition: ExternalStreamHandler.h:119
ExternalStreamHandler::m_replay
bool m_replay
Definition: ExternalStreamHandler.h:128
MythTimer::elapsed
int elapsed(void)
Returns milliseconds elapsed since last start() or restart()
Definition: mythtimer.cpp:90
StreamHandler::m_listenerLock
QMutex m_listenerLock
Definition: streamhandler.h:141
MythTimer::kStartRunning
@ kStartRunning
Definition: mythtimer.h:17
cardutil.h
StreamHandler::m_runningDesired
volatile bool m_runningDesired
Definition: streamhandler.h:117
ExternalStreamHandler::MAX_API_VERSION
@ MAX_API_VERSION
Definition: ExternalStreamHandler.h:61
ExternalStreamHandler::ProcessVer1
bool ProcessVer1(const QString &cmd, QString &result, int timeout, uint retry_cnt)
Definition: ExternalStreamHandler.cpp:1241
ExternalStreamHandler::PACKET_SIZE
@ PACKET_SIZE
Definition: ExternalStreamHandler.h:63
ExternalChannel.h
ExternIO::Write
int Write(const QByteArray &buffer)
Definition: ExternalStreamHandler.cpp:195
ExternIO::m_statusBuf
QString m_statusBuf
Definition: ExternalStreamHandler.h:52
ExternalStreamHandler::m_args
QStringList m_args
Definition: ExternalStreamHandler.h:116
ExternIO::kMaxErrorCnt
@ kMaxErrorCnt
Definition: ExternalStreamHandler.h:21
GENERIC_EXIT_PIPE_FAILURE
#define GENERIC_EXIT_PIPE_FAILURE
Error creating I/O pipes.
Definition: exitcodes.h:26
StreamHandler::m_streamDataList
StreamDataList m_streamDataList
Definition: streamhandler.h:142
ExternalStreamHandler::m_streamingCnt
QAtomicInt m_streamingCnt
Definition: ExternalStreamHandler.h:136
GENERIC_EXIT_DAEMONIZING_ERROR
#define GENERIC_EXIT_DAEMONIZING_ERROR
Error daemonizing or execl.
Definition: exitcodes.h:28
StreamHandler::m_bError
volatile bool m_bError
Definition: streamhandler.h:122
logPropagateArgs
QString logPropagateArgs
Definition: logging.cpp:87
ExternalStreamHandler::m_loc
QString m_loc
Definition: ExternalStreamHandler.h:112
ExternIO::m_status
QTextStream m_status
Definition: ExternalStreamHandler.h:53
exitcodes.h
StreamHandler::m_device
QString m_device
Definition: streamhandler.h:109
ExternalStreamHandler::m_streamLock
QMutex m_streamLock
Definition: ExternalStreamHandler.h:137
ExternalStreamHandler::Return
static void Return(ExternalStreamHandler *&ref, int inputid)
Definition: ExternalStreamHandler.cpp:483
dtvsignalmonitor.h
ExternIO::Ready
bool Ready(int fd, int timeout, const QString &what)
Definition: ExternalStreamHandler.cpp:73
ExternalStreamHandler::OpenApp
bool OpenApp(void)
Definition: ExternalStreamHandler.cpp:900
ExternIO::m_args
QStringList m_args
Definition: ExternalStreamHandler.h:42
MythTimer::restart
int restart(void)
Returns milliseconds elapsed since last start() or restart() and resets the count.
Definition: mythtimer.cpp:62
ExternIO::m_errCnt
int m_errCnt
Definition: ExternalStreamHandler.h:54
ExternalStreamHandler::m_app
QString m_app
Definition: ExternalStreamHandler.h:117