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  if (m_io && (sz = PACKET_SIZE - remainder) > 0)
700  read_len = m_io->Read(buffer, sz, 100ms);
701  else
702  read_len = 0;
703  }
704  else
705  read_len = 0;
706 
707  if (read_len == 0)
708  {
709  if (!nodata_timer.isRunning())
710  nodata_timer.start();
711  else
712  {
713  if (nodata_timer.elapsed() >= 50s)
714  {
715  LOG(VB_GENERAL, LOG_WARNING, LOC +
716  "No data for 50 seconds, Restarting stream.");
717  if (!RestartStream())
718  {
719  LOG(VB_RECORD, LOG_ERR, LOC +
720  "Failed to restart stream.");
721  m_bError = true;
722  }
723  nodata_timer.stop();
724  continue;
725  }
726  }
727 
728  std::this_thread::sleep_for(50ms);
729 
730  // HLS type streams may only produce data every ~10 seconds
731  if (nodata_timer.elapsed() < 12s && buffer.size() < TS_PACKET_SIZE)
732  continue;
733  }
734  else
735  {
736  nodata_timer.stop();
737  restart_cnt = 0;
738  }
739 
740  if (m_io == nullptr)
741  {
742  LOG(VB_GENERAL, LOG_ERR, LOC + "I/O thread has disappeared!");
743  m_bError = true;
744  break;
745  }
746  if (m_io->Error())
747  {
748  LOG(VB_GENERAL, LOG_ERR, LOC +
749  QString("Fatal Error from External Recorder: %1")
750  .arg(m_io->ErrorString()));
751  CloseApp();
752  m_bError = true;
753  break;
754  }
755 
756  len = remainder = buffer.size();
757 
758  if (len == 0)
759  continue;
760 
761  if (len < TS_PACKET_SIZE)
762  {
763  if (m_xon && data_short_err++ == 0)
764  LOG(VB_RECORD, LOG_INFO, LOC + "Waiting for a full TS packet.");
765  std::this_thread::sleep_for(50us);
766  continue;
767  }
768  if (data_short_err)
769  {
770  if (data_short_err > 1)
771  {
772  LOG(VB_RECORD, LOG_INFO, LOC +
773  QString("Waited for a full TS packet %1 times.")
774  .arg(data_short_err));
775  }
776  data_short_err = 0;
777  }
778 
779  if (!m_streamLock.tryLock())
780  continue;
781 
782  if (!m_listenerLock.tryLock())
783  continue;
784 
785  for (auto sit = m_streamDataList.cbegin();
786  sit != m_streamDataList.cend(); ++sit)
787  {
788  remainder = sit.key()->ProcessData
789  (reinterpret_cast<const uint8_t *>
790  (buffer.constData()), buffer.size());
791  }
792 
793  m_listenerLock.unlock();
794 
795  if (m_replay)
796  {
797  m_replayBuffer += buffer.left(len - remainder);
798  if (m_replayBuffer.size() > (50 * PACKET_SIZE))
799  {
800  m_replayBuffer.remove(0, len - remainder);
801  LOG(VB_RECORD, LOG_WARNING, LOC +
802  QString("Replay size truncated to %1 bytes")
803  .arg(m_replayBuffer.size()));
804  }
805  }
806 
807  m_streamLock.unlock();
808 
809  if (remainder == 0)
810  {
811  buffer.clear();
812  good_data = (len != 0U);
813  }
814  else if (len > remainder) // leftover bytes
815  {
816  buffer.remove(0, len - remainder);
817  good_data = (len != 0U);
818  }
819  else if (len == remainder)
820  good_data = false;
821 
822  if (good_data)
823  {
824  if (data_proc_err)
825  {
826  if (data_proc_err > 1)
827  {
828  LOG(VB_RECORD, LOG_WARNING, LOC +
829  QString("Failed to process the data received %1 times.")
830  .arg(data_proc_err));
831  }
832  data_proc_err = 0;
833  }
834  }
835  else
836  {
837  if (data_proc_err++ == 0)
838  {
839  LOG(VB_RECORD, LOG_WARNING, LOC +
840  "Failed to process the data received");
841  }
842  }
843  }
844 
845  LOG(VB_RECORD, LOG_INFO, LOC + "run(): " +
846  QString("%1 shutdown").arg(m_bError ? "Error" : "Normal"));
847 
849  SetRunning(false, true, false);
850 
851  LOG(VB_RECORD, LOG_INFO, LOC + "run(): " + "end");
852 
853  RunEpilog();
854 }
855 
857 {
858  QString result;
859 
860  if (ProcessCommand("APIVersion?", result, 10s))
861  {
862  QStringList tokens = result.split(':', Qt::SkipEmptyParts);
863  if (tokens.size() > 1)
864  m_apiVersion = tokens[1].toUInt();
865  m_apiVersion = std::min(m_apiVersion, static_cast<int>(MAX_API_VERSION));
866  if (m_apiVersion < 1)
867  {
868  LOG(VB_RECORD, LOG_ERR, LOC +
869  QString("Bad response to 'APIVersion?' - '%1'. "
870  "Expecting 1 or 2").arg(result));
871  m_apiVersion = 1;
872  }
873 
874  ProcessCommand(QString("APIVersion:%1").arg(m_apiVersion), result);
875  return true;
876  }
877 
878  return false;
879 }
880 
882 {
883  if (m_apiVersion > 1)
884  {
885  QString result;
886 
887  if (ProcessCommand("Description?", result))
888  m_loc = result.mid(3);
889  else
890  m_loc = m_device;
891  }
892 
893  return m_loc;
894 }
895 
897 {
898  {
899  QMutexLocker locker(&m_ioLock);
900 
901  if (m_io)
902  {
903  LOG(VB_RECORD, LOG_WARNING, LOC + "OpenApp: already open!");
904  return true;
905  }
906 
907  m_io = new ExternIO(m_app, m_args);
908 
909  if (m_io == nullptr)
910  {
911  LOG(VB_GENERAL, LOG_ERR, LOC + "ExternIO failed: " + ENO);
912  m_bError = true;
913  }
914  else
915  {
916  LOG(VB_RECORD, LOG_INFO, LOC + QString("Spawn '%1'").arg(m_device));
917  m_io->Run();
918  if (m_io->Error())
919  {
920  LOG(VB_GENERAL, LOG_ERR,
921  "Failed to start External Recorder: " + m_io->ErrorString());
922  delete m_io;
923  m_io = nullptr;
924  m_bError = true;
925  return false;
926  }
927  }
928  }
929 
930  QString result;
931 
932  if (!SetAPIVersion())
933  {
934  // Try again using API version 2
935  m_apiVersion = 2;
936  if (!SetAPIVersion())
937  m_apiVersion = 1;
938  }
939 
940  if (!IsAppOpen())
941  {
942  LOG(VB_RECORD, LOG_ERR, LOC + "Application is not responding.");
943  m_bError = true;
944  return false;
945  }
946 
948 
949  // Gather capabilities
950  if (!ProcessCommand("HasTuner?", result))
951  {
952  LOG(VB_RECORD, LOG_ERR, LOC +
953  QString("Bad response to 'HasTuner?' - '%1'").arg(result));
954  m_bError = true;
955  return false;
956  }
957  m_hasTuner = result.startsWith("OK:Yes");
958 
959  if (!ProcessCommand("HasPictureAttributes?", result))
960  {
961  LOG(VB_RECORD, LOG_ERR, LOC +
962  QString("Bad response to 'HasPictureAttributes?' - '%1'")
963  .arg(result));
964  m_bError = true;
965  return false;
966  }
967  m_hasPictureAttributes = result.startsWith("OK:Yes");
968 
969  /* Operate in "poll" or "xon/xoff" mode */
970  m_pollMode = ProcessCommand("FlowControl?", result) &&
971  result.startsWith("OK:Poll");
972 
973  LOG(VB_RECORD, LOG_INFO, LOC + "App opened successfully");
974  LOG(VB_RECORD, LOG_INFO, LOC +
975  QString("Capabilities: tuner(%1) "
976  "Picture attributes(%2) "
977  "Flow control(%3)")
978  .arg(m_hasTuner ? "yes" : "no",
979  m_hasPictureAttributes ? "yes" : "no",
980  m_pollMode ? "Polling" : "XON/XOFF")
981  );
982 
983  /* Let the external app know how many bytes will read without blocking */
984  ProcessCommand(QString("BlockSize:%1").arg(PACKET_SIZE), result);
985 
986  return true;
987 }
988 
990 {
991  if (m_io == nullptr)
992  {
993  LOG(VB_RECORD, LOG_WARNING, LOC +
994  "WARNING: Unable to communicate with external app.");
995  return false;
996  }
997 
998  QString result;
999  return ProcessCommand("Version?", result, 10s);
1000 }
1001 
1003 {
1004  if (m_tsOpen)
1005  return true;
1006 
1007  QString result;
1008 
1009  if (!ProcessCommand("IsOpen?", result))
1010  return false;
1011 
1012  m_tsOpen = true;
1013  return m_tsOpen;
1014 }
1015 
1017 {
1018  m_ioLock.lock();
1019  if (m_io)
1020  {
1021  QString result;
1022 
1023  LOG(VB_RECORD, LOG_INFO, LOC + "CloseRecorder");
1024  m_ioLock.unlock();
1025  ProcessCommand("CloseRecorder", result, 10s);
1026  m_ioLock.lock();
1027 
1028  if (!result.startsWith("OK"))
1029  {
1030  LOG(VB_RECORD, LOG_INFO, LOC +
1031  "CloseRecorder failed, sending kill.");
1032 
1033  QString full_command = QString("%1").arg(m_args.join(" "));
1034 
1035  if (!m_io->KillIfRunning(full_command))
1036  {
1037  // Give it one more chance.
1038  std::this_thread::sleep_for(50ms);
1039  if (!m_io->KillIfRunning(full_command))
1040  {
1041  LOG(VB_GENERAL, LOG_ERR,
1042  QString("Unable to kill existing '%1'.")
1043  .arg(full_command));
1044  return;
1045  }
1046  }
1047  }
1048  delete m_io;
1049  m_io = nullptr;
1050  }
1051  m_ioLock.unlock();
1052 }
1053 
1055 {
1056  bool streaming = (StreamingCount() > 0);
1057 
1058  LOG(VB_RECORD, LOG_INFO, LOC + "Restarting stream.");
1059 
1060  if (streaming)
1061  StopStreaming();
1062 
1063  std::this_thread::sleep_for(1s);
1064 
1065  if (streaming)
1066  return StartStreaming();
1067 
1068  return true;
1069 }
1070 
1072 {
1073  if (m_replay)
1074  {
1075  QString result;
1076 
1077  // Let the external app know that we could be busy for a little while
1078  if (!m_pollMode)
1079  {
1080  ProcessCommand(QString("XOFF"), result);
1081  m_xon = false;
1082  }
1083 
1084  /* If the input is not a 'broadcast' it may only have one
1085  * copy of the SPS right at the beginning of the stream,
1086  * so make sure we don't miss it!
1087  */
1088  QMutexLocker listen_lock(&m_listenerLock);
1089 
1090  if (!m_streamDataList.empty())
1091  {
1092  for (auto sit = m_streamDataList.cbegin();
1093  sit != m_streamDataList.cend(); ++sit)
1094  {
1095  sit.key()->ProcessData(reinterpret_cast<const uint8_t *>
1096  (m_replayBuffer.constData()),
1097  m_replayBuffer.size());
1098  }
1099  }
1100  LOG(VB_RECORD, LOG_INFO, LOC + QString("Replayed %1 bytes")
1101  .arg(m_replayBuffer.size()));
1102  m_replayBuffer.clear();
1103  m_replay = false;
1104 
1105  // Let the external app know that we are ready
1106  if (!m_pollMode)
1107  {
1108  if (ProcessCommand(QString("XON"), result))
1109  m_xon = true;
1110  }
1111  }
1112 }
1113 
1115 {
1116  QString result;
1117 
1118  QMutexLocker locker(&m_streamLock);
1119 
1121 
1122  LOG(VB_RECORD, LOG_INFO, LOC +
1123  QString("StartStreaming with %1 current listeners")
1124  .arg(StreamingCount()));
1125 
1126  if (!IsAppOpen())
1127  {
1128  LOG(VB_GENERAL, LOG_ERR, LOC + "External Recorder not started.");
1129  return false;
1130  }
1131 
1132  if (StreamingCount() == 0)
1133  {
1134  if (!ProcessCommand("StartStreaming", result, 15s))
1135  {
1136  LogLevel_t level = LOG_ERR;
1137  if (result.startsWith("warn", Qt::CaseInsensitive))
1138  level = LOG_WARNING;
1139  else
1140  m_bError = true;
1141 
1142  LOG(VB_GENERAL, level, LOC + QString("StartStreaming failed: '%1'")
1143  .arg(result));
1144 
1145  return false;
1146  }
1147 
1148  LOG(VB_RECORD, LOG_INFO, LOC + "Streaming started");
1149  }
1150  else
1151  LOG(VB_RECORD, LOG_INFO, LOC + "Already streaming");
1152 
1153  m_streamingCnt.ref();
1154 
1155  LOG(VB_RECORD, LOG_INFO, LOC +
1156  QString("StartStreaming %1 listeners")
1157  .arg(StreamingCount()));
1158 
1159  return true;
1160 }
1161 
1163 {
1164  QMutexLocker locker(&m_streamLock);
1165 
1166  LOG(VB_RECORD, LOG_INFO, LOC +
1167  QString("StopStreaming %1 listeners")
1168  .arg(StreamingCount()));
1169 
1170  if (StreamingCount() == 0)
1171  {
1172  LOG(VB_RECORD, LOG_INFO, LOC +
1173  "StopStreaming requested, but we are not streaming!");
1174  return true;
1175  }
1176 
1177  if (m_streamingCnt.deref())
1178  {
1179  LOG(VB_RECORD, LOG_INFO, LOC +
1180  QString("StopStreaming delayed, still have %1 listeners")
1181  .arg(StreamingCount()));
1182  return true;
1183  }
1184 
1185  LOG(VB_RECORD, LOG_INFO, LOC + "StopStreaming");
1186 
1187  if (!m_pollMode && m_xon)
1188  {
1189  QString result;
1190  ProcessCommand(QString("XOFF"), result);
1191  m_xon = false;
1192  }
1193 
1194  if (!IsAppOpen())
1195  {
1196  LOG(VB_GENERAL, LOG_ERR, LOC + "External Recorder not started.");
1197  return false;
1198  }
1199 
1200  QString result;
1201  if (!ProcessCommand("StopStreaming", result, 10s))
1202  {
1203  LogLevel_t level = LOG_ERR;
1204  if (result.startsWith("warn", Qt::CaseInsensitive))
1205  level = LOG_WARNING;
1206  else
1207  m_bError = true;
1208 
1209  LOG(VB_GENERAL, level, LOC + QString("StopStreaming: '%1'")
1210  .arg(result));
1211 
1212  return false;
1213  }
1214 
1215  PurgeBuffer();
1216  LOG(VB_RECORD, LOG_INFO, LOC + "Streaming stopped");
1217 
1218  return true;
1219 }
1220 
1221 bool ExternalStreamHandler::ProcessCommand(const QString & cmd,
1222  QString & result,
1223  std::chrono::milliseconds timeout,
1224  uint retry_cnt)
1225 {
1226  QMutexLocker locker(&m_processLock);
1227 
1228  if (m_apiVersion == 2)
1229  return ProcessVer2(cmd, result, timeout, retry_cnt);
1230  if (m_apiVersion == 1)
1231  return ProcessVer1(cmd, result, timeout, retry_cnt);
1232 
1233  LOG(VB_RECORD, LOG_ERR, LOC +
1234  QString("Invalid API version %1. Expected 1 or 2").arg(m_apiVersion));
1235  return false;
1236 }
1237 
1238 bool ExternalStreamHandler::ProcessVer1(const QString & cmd,
1239  QString & result,
1240  std::chrono::milliseconds timeout,
1241  uint retry_cnt)
1242 {
1243  LOG(VB_RECORD, LOG_DEBUG, LOC + QString("ProcessVer1('%1')")
1244  .arg(cmd));
1245 
1246  for (uint cnt = 0; cnt < retry_cnt; ++cnt)
1247  {
1248  QMutexLocker locker(&m_ioLock);
1249 
1250  if (!m_io)
1251  {
1252  LOG(VB_RECORD, LOG_ERR, LOC + "External I/O not ready!");
1253  return false;
1254  }
1255 
1256  QByteArray buf(cmd.toUtf8(), cmd.size());
1257  buf += '\n';
1258 
1259  if (m_io->Error())
1260  {
1261  LOG(VB_GENERAL, LOG_ERR, LOC + "External Recorder in bad state: " +
1262  m_io->ErrorString());
1263  return false;
1264  }
1265 
1266  /* Try to keep in sync, if External app was too slow in responding
1267  * to previous query, consume the response before sending new query */
1268  m_io->GetStatus(0ms);
1269 
1270  /* Send new query */
1271  m_io->Write(buf);
1272 
1274  while (timer.elapsed() < timeout)
1275  {
1276  result = m_io->GetStatus(timeout);
1277  if (m_io->Error())
1278  {
1279  LOG(VB_GENERAL, LOG_ERR, LOC +
1280  "Failed to read from External Recorder: " +
1281  m_io->ErrorString());
1282  m_bError = true;
1283  return false;
1284  }
1285 
1286  // Out-of-band error message
1287  if (result.startsWith("STATUS:ERR") ||
1288  result.startsWith("0:STATUS:ERR"))
1289  {
1290  LOG(VB_RECORD, LOG_ERR, LOC + result);
1291  result.remove(0, result.indexOf(":ERR") + 1);
1292  return false;
1293  }
1294  // STATUS message are "out of band".
1295  // Ignore them while waiting for a responds to a command
1296  if (!result.startsWith("STATUS") && !result.startsWith("0:STATUS"))
1297  break;
1298  LOG(VB_RECORD, LOG_INFO, LOC +
1299  QString("Ignoring response '%1'").arg(result));
1300  }
1301 
1302  if (result.size() < 1)
1303  {
1304  LOG(VB_GENERAL, LOG_WARNING, LOC +
1305  QString("External Recorder did not respond to '%1'").arg(cmd));
1306  }
1307  else
1308  {
1309  bool okay = result.startsWith("OK");
1310  if (okay || result.startsWith("WARN") || result.startsWith("ERR"))
1311  {
1312  LogLevel_t level = LOG_INFO;
1313 
1314  m_ioErrCnt = 0;
1315  if (!okay)
1316  level = LOG_WARNING;
1317  else if (cmd.startsWith("SendBytes"))
1318  level = LOG_DEBUG;
1319 
1320  LOG(VB_RECORD, level,
1321  LOC + QString("ProcessCommand('%1') = '%2' took %3ms %4")
1322  .arg(cmd, result,
1323  QString::number(timer.elapsed().count()),
1324  okay ? "" : "<-- NOTE"));
1325 
1326  return okay;
1327  }
1328  LOG(VB_GENERAL, LOG_WARNING, LOC +
1329  QString("External Recorder invalid response to '%1': '%2'")
1330  .arg(cmd, result));
1331  }
1332 
1333  if (++m_ioErrCnt > 10)
1334  {
1335  LOG(VB_GENERAL, LOG_ERR, LOC + "Too many I/O errors.");
1336  m_bError = true;
1337  break;
1338  }
1339  }
1340 
1341  return false;
1342 }
1343 
1344 bool ExternalStreamHandler::ProcessVer2(const QString & command,
1345  QString & result,
1346  std::chrono::milliseconds timeout,
1347  uint retry_cnt)
1348 {
1349  QString status;
1350  QString raw;
1351 
1352  for (uint cnt = 0; cnt < retry_cnt; ++cnt)
1353  {
1354  QString cmd = QString("%1:%2").arg(++m_serialNo).arg(command);
1355 
1356  LOG(VB_RECORD, LOG_DEBUG, LOC + QString("ProcessVer2('%1') serial(%2)")
1357  .arg(cmd).arg(m_serialNo));
1358 
1359  QMutexLocker locker(&m_ioLock);
1360 
1361  if (!m_io)
1362  {
1363  LOG(VB_RECORD, LOG_ERR, LOC + "External I/O not ready!");
1364  return false;
1365  }
1366 
1367  QByteArray buf(cmd.toUtf8(), cmd.size());
1368  buf += '\n';
1369 
1370  if (m_io->Error())
1371  {
1372  LOG(VB_GENERAL, LOG_ERR, LOC + "External Recorder in bad state: " +
1373  m_io->ErrorString());
1374  return false;
1375  }
1376 
1377  /* Send query */
1378  m_io->Write(buf);
1379 
1380  QStringList tokens;
1381 
1383  while (timer.elapsed() < timeout)
1384  {
1385  result = m_io->GetStatus(timeout);
1386  if (m_io->Error())
1387  {
1388  LOG(VB_GENERAL, LOG_ERR, LOC +
1389  "Failed to read from External Recorder: " +
1390  m_io->ErrorString());
1391  m_bError = true;
1392  return false;
1393  }
1394 
1395  if (!result.isEmpty())
1396  {
1397  raw = result;
1398  tokens = result.split(':', Qt::SkipEmptyParts);
1399 
1400  // Look for result with the serial number of this query
1401  if (tokens.size() > 1 && tokens[0].toUInt() >= m_serialNo)
1402  break;
1403 
1404  /* Other messages are "out of band" */
1405 
1406  // Check for error message missing serial#
1407  if (tokens[0].startsWith("ERR"))
1408  break;
1409 
1410  // Remove serial#
1411  tokens.removeFirst();
1412  result = tokens.join(':');
1413  bool err = (tokens.size() > 1 && tokens[1].startsWith("ERR"));
1414  LOG(VB_RECORD, (err ? LOG_WARNING : LOG_INFO), LOC + raw);
1415  if (err)
1416  {
1417  // Remove "STATUS"
1418  tokens.removeFirst();
1419  result = tokens.join(':');
1420  return false;
1421  }
1422  }
1423  }
1424 
1425  if (timer.elapsed() >= timeout)
1426  {
1427  LOG(VB_RECORD, LOG_ERR, LOC +
1428  QString("ProcessVer2: Giving up waiting for response for "
1429  "command '%2'").arg(cmd));
1430  }
1431  else if (tokens.size() < 2)
1432  {
1433  LOG(VB_RECORD, LOG_ERR, LOC +
1434  QString("Did not receive a valid response "
1435  "for command '%1', received '%2'").arg(cmd, result));
1436  }
1437  else if (tokens[0].toUInt() > m_serialNo)
1438  {
1439  LOG(VB_RECORD, LOG_ERR, LOC +
1440  QString("ProcessVer2: Looking for serial no %1, "
1441  "but received %2 for command '%2'")
1442  .arg(QString::number(m_serialNo), tokens[0], cmd));
1443  }
1444  else
1445  {
1446  tokens.removeFirst();
1447  status = tokens[0].trimmed();
1448  result = tokens.join(':');
1449 
1450  bool okay = (status == "OK");
1451  if (okay || status.startsWith("WARN") || status.startsWith("ERR"))
1452  {
1453  LogLevel_t level = LOG_INFO;
1454 
1455  m_ioErrCnt = 0;
1456  if (!okay)
1457  level = LOG_WARNING;
1458  else if (command.startsWith("SendBytes") ||
1459  (command.startsWith("TuneStatus") &&
1460  result == "OK:InProgress"))
1461  level = LOG_DEBUG;
1462 
1463  LOG(VB_RECORD, level,
1464  LOC + QString("ProcessV2('%1') = '%2' took %3ms %4")
1465  .arg(cmd, result, QString::number(timer.elapsed().count()),
1466  okay ? "" : "<-- NOTE"));
1467 
1468  return okay;
1469  }
1470  LOG(VB_GENERAL, LOG_WARNING, LOC +
1471  QString("External Recorder invalid response to '%1': '%2'")
1472  .arg(cmd, result));
1473  }
1474 
1475  if (++m_ioErrCnt > 10)
1476  {
1477  LOG(VB_GENERAL, LOG_ERR, LOC + "Too many I/O errors.");
1478  m_bError = true;
1479  break;
1480  }
1481  }
1482 
1483  return false;
1484 }
1485 
1487 {
1488  QString result;
1489  bool err = false;
1490 
1491  QMutexLocker locker(&m_ioLock);
1492 
1493  if (!m_io)
1494  {
1495  LOG(VB_RECORD, LOG_ERR, LOC + "External I/O not ready!");
1496  return true;
1497  }
1498 
1499  if (m_io->Error())
1500  {
1501  LOG(VB_GENERAL, LOG_ERR, "External Recorder in bad state: " +
1502  m_io->ErrorString());
1503  return true;
1504  }
1505 
1506  do
1507  {
1508  result = m_io->GetStatus(0ms);
1509  if (!result.isEmpty())
1510  {
1511  if (m_apiVersion > 1)
1512  {
1513  QStringList tokens = result.split(':', Qt::SkipEmptyParts);
1514  tokens.removeFirst();
1515  result = tokens.join(':');
1516  for (int idx = 1; idx < tokens.size(); ++idx)
1517  err |= tokens[idx].startsWith("ERR");
1518  }
1519  else
1520  err |= result.startsWith("STATUS:ERR");
1521 
1522  LOG(VB_RECORD, (err ? LOG_WARNING : LOG_INFO), LOC + result);
1523  }
1524  }
1525  while (!result.isEmpty());
1526 
1527  return err;
1528 }
1529 
1531 {
1532  if (m_io)
1533  {
1534  QByteArray buffer;
1535  m_io->Read(buffer, PACKET_SIZE, 1ms);
1536  m_io->GetStatus(1ms);
1537  }
1538 }
1539 
1541 {
1542  // TODO report on buffer overruns, etc.
1543 }
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:1238
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:1002
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:1540
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:1162
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
ExternalStreamHandler::ReplayStream
void ReplayStream(void)
Definition: ExternalStreamHandler.cpp:1071
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:1016
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:1530
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:1054
ExternalStreamHandler::m_majorId
int m_majorId
Definition: ExternalStreamHandler.h:123
ExternalStreamHandler::SetAPIVersion
bool SetAPIVersion(void)
Definition: ExternalStreamHandler.cpp:856
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:1114
LOC
#define LOC
Definition: ExternalStreamHandler.cpp:34
ExternalStreamHandler::UpdateDescription
QString UpdateDescription(void)
Definition: ExternalStreamHandler.cpp:881
mpegstreamdata.h
ExternalStreamHandler::IsAppOpen
bool IsAppOpen(void)
Definition: ExternalStreamHandler.cpp:989
ExternalStreamHandler::CheckForError
bool CheckForError(void)
Definition: ExternalStreamHandler.cpp:1486
ExternalStreamHandler::ProcessVer2
bool ProcessVer2(const QString &command, QString &result, std::chrono::milliseconds timeout, uint retry_cnt)
Definition: ExternalStreamHandler.cpp:1344
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
ExternIO::kMaxErrorCnt
@ kMaxErrorCnt
Definition: ExternalStreamHandler.h:30
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:1221
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:896
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