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