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 #include <QtGlobal>
14 #ifdef Q_OS_ANDROID
15 #include <sys/wait.h>
16 #endif
17 
18 // Qt headers
19 #include <QString>
20 #include <QFile>
21 
22 // MythTV headers
23 #include "ExternalStreamHandler.h"
24 #include "ExternalChannel.h"
25 //#include "ThreadedFileWriter.h"
26 #include "dtvsignalmonitor.h"
27 #include "streamlisteners.h"
28 #include "mpegstreamdata.h"
29 #include "cardutil.h"
30 #include "exitcodes.h"
31 
32 #define LOC QString("ExternSH[%1](%2): ").arg(m_inputId).arg(m_loc)
33 
34 ExternIO::ExternIO(const QString & app,
35  const QStringList & args)
36  : m_status(&m_statusBuf, QIODevice::ReadWrite)
37 {
38  m_app = (app);
39 
40  if (!m_app.exists())
41  {
42  m_error = QString("ExternIO: '%1' does not exist.").arg(app);
43  return;
44  }
45  if (!m_app.isReadable() || !m_app.isFile())
46  {
47  m_error = QString("ExternIO: '%1' is not readable.")
48  .arg(m_app.canonicalFilePath());
49  return;
50  }
51  if (!m_app.isExecutable())
52  {
53  m_error = QString("ExternIO: '%1' is not executable.")
54  .arg(m_app.canonicalFilePath());
55  return;
56  }
57 
58  m_args = args;
59  m_args.prepend(m_app.baseName());
60 
61  m_status.setString(&m_statusBuf);
62 }
63 
65 {
66  close(m_appIn);
67  close(m_appOut);
68  close(m_appErr);
69 
70  // waitpid(m_pid, &status, 0);
71  delete[] m_buffer;
72 }
73 
74 bool ExternIO::Ready(int fd, int timeout, const QString & what)
75 {
76 #if !defined( USING_MINGW ) && !defined( _MSC_VER )
77  std::array<struct pollfd,2> m_poll {};
78 
79  m_poll[0].fd = fd;
80  m_poll[0].events = POLLIN | POLLPRI;
81  int ret = poll(m_poll.data(), 1, timeout);
82 
83  if (m_poll[0].revents & POLLHUP)
84  {
85  m_error = what + " poll eof (POLLHUP)";
86  return false;
87  }
88  if (m_poll[0].revents & POLLNVAL)
89  {
90  LOG(VB_GENERAL, LOG_ERR, "poll error");
91  return false;
92  }
93  if (m_poll[0].revents & POLLIN)
94  {
95  if (ret > 0)
96  return true;
97 
98  if ((EOVERFLOW == errno))
99  m_error = "poll overflow";
100  return false;
101  }
102 #endif // !defined( USING_MINGW ) && !defined( _MSC_VER )
103  return false;
104 }
105 
106 int ExternIO::Read(QByteArray & buffer, int maxlen, int timeout)
107 {
108  if (Error())
109  {
110  LOG(VB_RECORD, LOG_ERR,
111  QString("ExternIO::Read: already in error state: '%1'")
112  .arg(m_error));
113  return 0;
114  }
115 
116  if (!Ready(m_appOut, timeout, "data"))
117  return 0;
118 
119  if (m_bufSize < maxlen)
120  {
121  m_bufSize = maxlen;
122  delete [] m_buffer;
123  m_buffer = new char[m_bufSize];
124  }
125 
126  int len = read(m_appOut, m_buffer, maxlen);
127 
128  if (len < 0)
129  {
130  if (errno == EAGAIN)
131  {
132  if (++m_errCnt > kMaxErrorCnt)
133  {
134  m_error = "Failed to read from External Recorder: " + ENO;
135  LOG(VB_RECORD, LOG_WARNING,
136  "External Recorder not ready. Giving up.");
137  }
138  else
139  {
140  LOG(VB_RECORD, LOG_WARNING,
141  QString("External Recorder not ready. Will retry (%1/%2).")
143  std::this_thread::sleep_for(std::chrono::milliseconds(100));
144  }
145  }
146  else
147  {
148  m_error = "Failed to read from External Recorder: " + ENO;
149  LOG(VB_RECORD, LOG_ERR, m_error);
150  }
151  }
152  else
153  m_errCnt = 0;
154 
155  if (len == 0)
156  return 0;
157 
158  buffer.append(m_buffer, len);
159 
160  LOG(VB_RECORD, LOG_DEBUG,
161  QString("ExternIO::Read '%1' bytes, buffer size %2")
162  .arg(len).arg(buffer.size()));
163 
164  return len;
165 }
166 
168 {
169  if (Error())
170  {
171  LOG(VB_RECORD, LOG_ERR,
172  QString("ExternIO::GetStatus: already in error state: '%1'")
173  .arg(m_error));
174  return QByteArray();
175  }
176 
177  int waitfor = m_status.atEnd() ? timeout : 0;
178  if (Ready(m_appErr, waitfor, "status"))
179  {
180  std::array<char,2048> buffer {};
181  int len = read(m_appErr, buffer.data(), buffer.size());
182  m_status << QString::fromLatin1(buffer.data(), len);
183  }
184 
185  if (m_status.atEnd())
186  return QByteArray();
187 
188  QString msg = m_status.readLine();
189 
190  LOG(VB_RECORD, LOG_DEBUG, QString("ExternIO::GetStatus '%1'")
191  .arg(msg));
192 
193  return msg;
194 }
195 
196 int ExternIO::Write(const QByteArray & buffer)
197 {
198  if (Error())
199  {
200  LOG(VB_RECORD, LOG_ERR,
201  QString("ExternIO::Write: already in error state: '%1'")
202  .arg(m_error));
203  return -1;
204  }
205 
206  LOG(VB_RECORD, LOG_DEBUG, QString("ExternIO::Write('%1')")
207  .arg(QString(buffer).simplified()));
208 
209  int len = write(m_appIn, buffer.constData(), buffer.size());
210  if (len != buffer.size())
211  {
212  if (len > 0)
213  {
214  LOG(VB_RECORD, LOG_WARNING,
215  QString("ExternIO::Write: only wrote %1 of %2 bytes '%3'")
216  .arg(len).arg(buffer.size()).arg(QString(buffer)));
217  }
218  else
219  {
220  m_error = QString("ExternIO: Failed to write '%1' to app's stdin: ")
221  .arg(QString(buffer)) + ENO;
222  return -1;
223  }
224  }
225 
226  return len;
227 }
228 
229 bool ExternIO::Run(void)
230 {
231  LOG(VB_RECORD, LOG_INFO, QString("ExternIO::Run()"));
232 
233  Fork();
234  GetStatus(10);
235 
236  return true;
237 }
238 
239 /* Return true if the process is not, or is no longer running */
240 bool ExternIO::KillIfRunning(const QString & cmd)
241 {
242 #if CONFIG_DARWIN || (__FreeBSD__) || defined(__OpenBSD__)
243  Q_UNUSED(cmd);
244  return false;
245 #elif defined USING_MINGW
246  Q_UNUSED(cmd);
247  return false;
248 #elif defined( _MSC_VER )
249  Q_UNUSED(cmd);
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).arg(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(std::chrono::milliseconds(50));
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).arg(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.").arg(cmd)
289  .arg(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(std::chrono::milliseconds(50));
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(std::chrono::seconds(2));
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]: Creating new stream handler %2 for %3")
470  .arg(inputid).arg(majorid).arg(devname));
471  }
472  else
473  {
474  s_handlersRefCnt[majorid]++;
475  uint rcount = s_handlersRefCnt[majorid];
476  LOG(VB_RECORD, LOG_INFO,
477  QString("ExternSH[%1]: Using existing stream handler for %2")
478  .arg(inputid).arg(majorid) + QString(" (%1 in use)").arg(rcount));
479  }
480 
481  return s_handlers[majorid];
482 }
483 
485  int inputid)
486 {
487  QMutexLocker locker(&s_handlersLock);
488 
489  int majorid = ref->m_majorId;
490 
491  QMap<int, uint>::iterator rit = s_handlersRefCnt.find(majorid);
492  if (rit == s_handlersRefCnt.end())
493  return;
494 
495  QMap<int, ExternalStreamHandler*>::iterator it =
496  s_handlers.find(majorid);
497 
498  LOG(VB_RECORD, LOG_INFO, QString("ExternSH[%1]: Return %2 in use %3")
499  .arg(inputid).arg(majorid).arg(*rit));
500 
501  if (*rit > 1)
502  {
503  ref = nullptr;
504  --(*rit);
505  return;
506  }
507 
508  if ((it != s_handlers.end()) && (*it == ref))
509  {
510  LOG(VB_RECORD, LOG_INFO, QString("ExternSH[%1]: Closing handler for %2")
511  .arg(inputid).arg(majorid));
512  delete *it;
513  s_handlers.erase(it);
514  }
515  else
516  {
517  LOG(VB_GENERAL, LOG_ERR,
518  QString("ExternSH[%1]: Error: Couldn't find handler for %2")
519  .arg(inputid).arg(majorid));
520  }
521 
522  s_handlersRefCnt.erase(rit);
523  ref = nullptr;
524 }
525 
526 /*
527  ExternalStreamHandler
528  */
529 
531  int inputid,
532  int majorid)
533  : StreamHandler(path, inputid)
534  , m_loc(m_device)
535  , m_majorId(majorid)
536 {
537  setObjectName("ExternSH");
538 
539 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
540  m_args = path.split(' ',QString::SkipEmptyParts) +
541  logPropagateArgs.split(' ', QString::SkipEmptyParts);
542 #else
543  m_args = path.split(' ',Qt::SkipEmptyParts) +
544  logPropagateArgs.split(' ', Qt::SkipEmptyParts);
545 #endif
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(std::chrono::milliseconds(10));
611  continue;
612  }
613 
614  if (StreamingCount() == 0)
615  {
616  std::this_thread::sleep_for(std::chrono::milliseconds(10));
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).arg(result));
639  m_bError = true;
640  continue;
641  }
642 
643  if (restart_cnt++)
644  std::this_thread::sleep_for(std::chrono::seconds(20));
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() >= 2000)
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(std::chrono::seconds(20));
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, 100);
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() >= 50000)
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(std::chrono::milliseconds(50));
729 
730  // HLS type streams may only produce data every ~10 seconds
731  if (nodata_timer.elapsed() < 12000 && 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(std::chrono::microseconds(50));
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, 10000))
861  {
862 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
863  QStringList tokens = result.split(':', QString::SkipEmptyParts);
864 #else
865  QStringList tokens = result.split(':', Qt::SkipEmptyParts);
866 #endif
867 
868  if (tokens.size() > 1)
869  m_apiVersion = tokens[1].toUInt();
870  m_apiVersion = std::min(m_apiVersion, static_cast<int>(MAX_API_VERSION));
871  if (m_apiVersion < 1)
872  {
873  LOG(VB_RECORD, LOG_ERR, LOC +
874  QString("Bad response to 'APIVersion?' - '%1'. "
875  "Expecting 1 or 2").arg(result));
876  m_apiVersion = 1;
877  }
878 
879  ProcessCommand(QString("APIVersion:%1").arg(m_apiVersion), result);
880  return true;
881  }
882 
883  return false;
884 }
885 
887 {
888  if (m_apiVersion > 1)
889  {
890  QString result;
891 
892  if (ProcessCommand("Description?", result))
893  m_loc = result.mid(3);
894  else
895  m_loc = m_device;
896  }
897 
898  return m_loc;
899 }
900 
902 {
903  {
904  QMutexLocker locker(&m_ioLock);
905 
906  if (m_io)
907  {
908  LOG(VB_RECORD, LOG_WARNING, LOC + "OpenApp: already open!");
909  return true;
910  }
911 
912  m_io = new ExternIO(m_app, m_args);
913 
914  if (m_io == nullptr)
915  {
916  LOG(VB_GENERAL, LOG_ERR, LOC + "ExternIO failed: " + ENO);
917  m_bError = true;
918  }
919  else
920  {
921  LOG(VB_RECORD, LOG_INFO, LOC + QString("Spawn '%1'").arg(m_device));
922  m_io->Run();
923  if (m_io->Error())
924  {
925  LOG(VB_GENERAL, LOG_ERR,
926  "Failed to start External Recorder: " + m_io->ErrorString());
927  delete m_io;
928  m_io = nullptr;
929  m_bError = true;
930  return false;
931  }
932  }
933  }
934 
935  QString result;
936 
937  if (!SetAPIVersion())
938  {
939  // Try again using API version 2
940  m_apiVersion = 2;
941  if (!SetAPIVersion())
942  m_apiVersion = 1;
943  }
944 
945  if (!IsAppOpen())
946  {
947  LOG(VB_RECORD, LOG_ERR, LOC + "Application is not responding.");
948  m_bError = true;
949  return false;
950  }
951 
953 
954  // Gather capabilities
955  if (!ProcessCommand("HasTuner?", result))
956  {
957  LOG(VB_RECORD, LOG_ERR, LOC +
958  QString("Bad response to 'HasTuner?' - '%1'").arg(result));
959  m_bError = true;
960  return false;
961  }
962  m_hasTuner = result.startsWith("OK:Yes");
963 
964  if (!ProcessCommand("HasPictureAttributes?", result))
965  {
966  LOG(VB_RECORD, LOG_ERR, LOC +
967  QString("Bad response to 'HasPictureAttributes?' - '%1'")
968  .arg(result));
969  m_bError = true;
970  return false;
971  }
972  m_hasPictureAttributes = result.startsWith("OK:Yes");
973 
974  /* Operate in "poll" or "xon/xoff" mode */
975  m_pollMode = ProcessCommand("FlowControl?", result) &&
976  result.startsWith("OK:Poll");
977 
978  LOG(VB_RECORD, LOG_INFO, LOC + "App opened successfully");
979  LOG(VB_RECORD, LOG_INFO, LOC +
980  QString("Capabilities: tuner(%1) "
981  "Picture attributes(%2) "
982  "Flow control(%3)")
983  .arg(m_hasTuner ? "yes" : "no")
984  .arg(m_hasPictureAttributes ? "yes" : "no")
985  .arg(m_pollMode ? "Polling" : "XON/XOFF")
986  );
987 
988  /* Let the external app know how many bytes will read without blocking */
989  ProcessCommand(QString("BlockSize:%1").arg(PACKET_SIZE), result);
990 
991  return true;
992 }
993 
995 {
996  if (m_io == nullptr)
997  {
998  LOG(VB_RECORD, LOG_WARNING, LOC +
999  "WARNING: Unable to communicate with external app.");
1000  return false;
1001  }
1002 
1003  QString result;
1004  return ProcessCommand("Version?", result, 10000);
1005 }
1006 
1008 {
1009  if (m_tsOpen)
1010  return true;
1011 
1012  QString result;
1013 
1014  if (!ProcessCommand("IsOpen?", result))
1015  return false;
1016 
1017  m_tsOpen = true;
1018  return m_tsOpen;
1019 }
1020 
1022 {
1023  m_ioLock.lock();
1024  if (m_io)
1025  {
1026  QString result;
1027 
1028  LOG(VB_RECORD, LOG_INFO, LOC + "CloseRecorder");
1029  m_ioLock.unlock();
1030  ProcessCommand("CloseRecorder", result, 10000);
1031  m_ioLock.lock();
1032 
1033  if (!result.startsWith("OK"))
1034  {
1035  LOG(VB_RECORD, LOG_INFO, LOC +
1036  "CloseRecorder failed, sending kill.");
1037 
1038  QString full_command = QString("%1").arg(m_args.join(" "));
1039 
1040  if (!m_io->KillIfRunning(full_command))
1041  {
1042  // Give it one more chance.
1043  std::this_thread::sleep_for(std::chrono::milliseconds(50));
1044  if (!m_io->KillIfRunning(full_command))
1045  {
1046  LOG(VB_GENERAL, LOG_ERR,
1047  QString("Unable to kill existing '%1'.")
1048  .arg(full_command));
1049  return;
1050  }
1051  }
1052  }
1053  delete m_io;
1054  m_io = nullptr;
1055  }
1056  m_ioLock.unlock();
1057 }
1058 
1060 {
1061  bool streaming = (StreamingCount() > 0);
1062 
1063  LOG(VB_RECORD, LOG_INFO, LOC + "Restarting stream.");
1064 
1065  if (streaming)
1066  StopStreaming();
1067 
1068  std::this_thread::sleep_for(std::chrono::seconds(1));
1069 
1070  if (streaming)
1071  return StartStreaming();
1072 
1073  return true;
1074 }
1075 
1077 {
1078  if (m_replay)
1079  {
1080  QString result;
1081 
1082  // Let the external app know that we could be busy for a little while
1083  if (!m_pollMode)
1084  {
1085  ProcessCommand(QString("XOFF"), result);
1086  m_xon = false;
1087  }
1088 
1089  /* If the input is not a 'broadcast' it may only have one
1090  * copy of the SPS right at the beginning of the stream,
1091  * so make sure we don't miss it!
1092  */
1093  QMutexLocker listen_lock(&m_listenerLock);
1094 
1095  if (!m_streamDataList.empty())
1096  {
1097  for (auto sit = m_streamDataList.cbegin();
1098  sit != m_streamDataList.cend(); ++sit)
1099  {
1100  sit.key()->ProcessData(reinterpret_cast<const uint8_t *>
1101  (m_replayBuffer.constData()),
1102  m_replayBuffer.size());
1103  }
1104  }
1105  LOG(VB_RECORD, LOG_INFO, LOC + QString("Replayed %1 bytes")
1106  .arg(m_replayBuffer.size()));
1107  m_replayBuffer.clear();
1108  m_replay = false;
1109 
1110  // Let the external app know that we are ready
1111  if (!m_pollMode)
1112  {
1113  if (ProcessCommand(QString("XON"), result))
1114  m_xon = true;
1115  }
1116  }
1117 }
1118 
1120 {
1121  QString result;
1122 
1123  QMutexLocker locker(&m_streamLock);
1124 
1126 
1127  LOG(VB_RECORD, LOG_INFO, LOC +
1128  QString("StartStreaming with %1 current listeners")
1129  .arg(StreamingCount()));
1130 
1131  if (!IsAppOpen())
1132  {
1133  LOG(VB_GENERAL, LOG_ERR, LOC + "External Recorder not started.");
1134  return false;
1135  }
1136 
1137  if (StreamingCount() == 0)
1138  {
1139  if (!ProcessCommand("StartStreaming", result, 15000))
1140  {
1141  LogLevel_t level = LOG_ERR;
1142  if (result.startsWith("warn", Qt::CaseInsensitive))
1143  level = LOG_WARNING;
1144  else
1145  m_bError = true;
1146 
1147  LOG(VB_GENERAL, level, LOC + QString("StartStreaming failed: '%1'")
1148  .arg(result));
1149 
1150  return false;
1151  }
1152 
1153  LOG(VB_RECORD, LOG_INFO, LOC + "Streaming started");
1154  }
1155  else
1156  LOG(VB_RECORD, LOG_INFO, LOC + "Already streaming");
1157 
1158  m_streamingCnt.ref();
1159 
1160  LOG(VB_RECORD, LOG_INFO, LOC +
1161  QString("StartStreaming %1 listeners")
1162  .arg(StreamingCount()));
1163 
1164  return true;
1165 }
1166 
1168 {
1169  QMutexLocker locker(&m_streamLock);
1170 
1171  LOG(VB_RECORD, LOG_INFO, LOC +
1172  QString("StopStreaming %1 listeners")
1173  .arg(StreamingCount()));
1174 
1175  if (StreamingCount() == 0)
1176  {
1177  LOG(VB_RECORD, LOG_INFO, LOC +
1178  "StopStreaming requested, but we are not streaming!");
1179  return true;
1180  }
1181 
1182  if (m_streamingCnt.deref())
1183  {
1184  LOG(VB_RECORD, LOG_INFO, LOC +
1185  QString("StopStreaming delayed, still have %1 listeners")
1186  .arg(StreamingCount()));
1187  return true;
1188  }
1189 
1190  LOG(VB_RECORD, LOG_INFO, LOC + "StopStreaming");
1191 
1192  if (!m_pollMode && m_xon)
1193  {
1194  QString result;
1195  ProcessCommand(QString("XOFF"), result);
1196  m_xon = false;
1197  }
1198 
1199  if (!IsAppOpen())
1200  {
1201  LOG(VB_GENERAL, LOG_ERR, LOC + "External Recorder not started.");
1202  return false;
1203  }
1204 
1205  QString result;
1206  if (!ProcessCommand("StopStreaming", result, 10000))
1207  {
1208  LogLevel_t level = LOG_ERR;
1209  if (result.startsWith("warn", Qt::CaseInsensitive))
1210  level = LOG_WARNING;
1211  else
1212  m_bError = true;
1213 
1214  LOG(VB_GENERAL, level, LOC + QString("StopStreaming: '%1'")
1215  .arg(result));
1216 
1217  return false;
1218  }
1219 
1220  PurgeBuffer();
1221  LOG(VB_RECORD, LOG_INFO, LOC + "Streaming stopped");
1222 
1223  return true;
1224 }
1225 
1226 bool ExternalStreamHandler::ProcessCommand(const QString & cmd,
1227  QString & result, int timeout,
1228  uint retry_cnt)
1229 {
1230  QMutexLocker locker(&m_processLock);
1231 
1232  if (m_apiVersion == 2)
1233  return ProcessVer2(cmd, result, timeout, retry_cnt);
1234  if (m_apiVersion == 1)
1235  return ProcessVer1(cmd, result, timeout, retry_cnt);
1236 
1237  LOG(VB_RECORD, LOG_ERR, LOC +
1238  QString("Invalid API version %1. Expected 1 or 2").arg(m_apiVersion));
1239  return false;
1240 }
1241 
1242 bool ExternalStreamHandler::ProcessVer1(const QString & cmd,
1243  QString & result, int 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(0);
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).arg(result)
1326  .arg(timer.elapsed())
1327  .arg(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).arg(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, int timeout,
1349  uint retry_cnt)
1350 {
1351  QString status;
1352  QString raw;
1353 
1354  for (uint cnt = 0; cnt < retry_cnt; ++cnt)
1355  {
1356  QString cmd = QString("%1:%2").arg(++m_serialNo).arg(command);
1357 
1358  LOG(VB_RECORD, LOG_DEBUG, LOC + QString("ProcessVer2('%1') serial(%2)")
1359  .arg(cmd).arg(m_serialNo));
1360 
1361  QMutexLocker locker(&m_ioLock);
1362 
1363  if (!m_io)
1364  {
1365  LOG(VB_RECORD, LOG_ERR, LOC + "External I/O not ready!");
1366  return false;
1367  }
1368 
1369  QByteArray buf(cmd.toUtf8(), cmd.size());
1370  buf += '\n';
1371 
1372  if (m_io->Error())
1373  {
1374  LOG(VB_GENERAL, LOG_ERR, LOC + "External Recorder in bad state: " +
1375  m_io->ErrorString());
1376  return false;
1377  }
1378 
1379  /* Send query */
1380  m_io->Write(buf);
1381 
1382  QStringList tokens;
1383 
1385  while (timer.elapsed() < timeout)
1386  {
1387  result = m_io->GetStatus(timeout);
1388  if (m_io->Error())
1389  {
1390  LOG(VB_GENERAL, LOG_ERR, LOC +
1391  "Failed to read from External Recorder: " +
1392  m_io->ErrorString());
1393  m_bError = true;
1394  return false;
1395  }
1396 
1397  if (!result.isEmpty())
1398  {
1399  raw = result;
1400 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1401  tokens = result.split(':', QString::SkipEmptyParts);
1402 #else
1403  tokens = result.split(':', Qt::SkipEmptyParts);
1404 #endif
1405 
1406  // Look for result with the serial number of this query
1407  if (tokens.size() > 1 && tokens[0].toUInt() >= m_serialNo)
1408  break;
1409 
1410  /* Other messages are "out of band" */
1411 
1412  // Check for error message missing serial#
1413  if (tokens[0].startsWith("ERR"))
1414  break;
1415 
1416  // Remove serial#
1417  tokens.removeFirst();
1418  result = tokens.join(':');
1419  bool err = (tokens.size() > 1 && tokens[1].startsWith("ERR"));
1420  LOG(VB_RECORD, (err ? LOG_WARNING : LOG_INFO), LOC + raw);
1421  if (err)
1422  {
1423  // Remove "STATUS"
1424  tokens.removeFirst();
1425  result = tokens.join(':');
1426  return false;
1427  }
1428  }
1429  }
1430 
1431  if (timer.elapsed() >= timeout)
1432  {
1433  LOG(VB_RECORD, LOG_ERR, LOC +
1434  QString("ProcessVer2: Giving up waiting for response for "
1435  "command '%2'").arg(cmd));
1436  }
1437  else if (tokens.size() < 2)
1438  {
1439  LOG(VB_RECORD, LOG_ERR, LOC +
1440  QString("Did not receive a valid response "
1441  "for command '%1', received '%2'").arg(cmd).arg(result));
1442  }
1443  else if (tokens[0].toUInt() > m_serialNo)
1444  {
1445  LOG(VB_RECORD, LOG_ERR, LOC +
1446  QString("ProcessVer2: Looking for serial no %1, "
1447  "but received %2 for command '%2'")
1448  .arg(m_serialNo).arg(tokens[0]).arg(cmd));
1449  }
1450  else
1451  {
1452  tokens.removeFirst();
1453  status = tokens[0].trimmed();
1454  result = tokens.join(':');
1455 
1456  bool okay = (status == "OK");
1457  if (okay || status.startsWith("WARN") || status.startsWith("ERR"))
1458  {
1459  LogLevel_t level = LOG_INFO;
1460 
1461  m_ioErrCnt = 0;
1462  if (!okay)
1463  level = LOG_WARNING;
1464  else if (command.startsWith("SendBytes") ||
1465  (command.startsWith("TuneStatus") &&
1466  result == "OK:InProgress"))
1467  level = LOG_DEBUG;
1468 
1469  LOG(VB_RECORD, level,
1470  LOC + QString("ProcessV2('%1') = '%2' took %3ms %4")
1471  .arg(cmd).arg(result).arg(timer.elapsed())
1472  .arg(okay ? "" : "<-- NOTE"));
1473 
1474  return okay;
1475  }
1476  LOG(VB_GENERAL, LOG_WARNING, LOC +
1477  QString("External Recorder invalid response to '%1': '%2'")
1478  .arg(cmd).arg(result));
1479  }
1480 
1481  if (++m_ioErrCnt > 10)
1482  {
1483  LOG(VB_GENERAL, LOG_ERR, LOC + "Too many I/O errors.");
1484  m_bError = true;
1485  break;
1486  }
1487  }
1488 
1489  return false;
1490 }
1491 
1493 {
1494  QString result;
1495  bool err = false;
1496 
1497  QMutexLocker locker(&m_ioLock);
1498 
1499  if (!m_io)
1500  {
1501  LOG(VB_RECORD, LOG_ERR, LOC + "External I/O not ready!");
1502  return true;
1503  }
1504 
1505  if (m_io->Error())
1506  {
1507  LOG(VB_GENERAL, LOG_ERR, "External Recorder in bad state: " +
1508  m_io->ErrorString());
1509  return true;
1510  }
1511 
1512  do
1513  {
1514  result = m_io->GetStatus(0);
1515  if (!result.isEmpty())
1516  {
1517  if (m_apiVersion > 1)
1518  {
1519 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1520  QStringList tokens = result.split(':', QString::SkipEmptyParts);
1521 #else
1522  QStringList tokens = result.split(':', Qt::SkipEmptyParts);
1523 #endif
1524 
1525  tokens.removeFirst();
1526  result = tokens.join(':');
1527  for (int idx = 1; idx < tokens.size(); ++idx)
1528  err |= tokens[idx].startsWith("ERR");
1529  }
1530  else
1531  err |= result.startsWith("STATUS:ERR");
1532 
1533  LOG(VB_RECORD, (err ? LOG_WARNING : LOG_INFO), LOC + result);
1534  }
1535  }
1536  while (!result.isEmpty());
1537 
1538  return err;
1539 }
1540 
1542 {
1543  if (m_io)
1544  {
1545  QByteArray buffer;
1546  m_io->Read(buffer, PACKET_SIZE, 1);
1547  m_io->GetStatus(1);
1548  }
1549 }
1550 
1552 {
1553  // TODO report on buffer overruns, etc.
1554 }
WEXITSTATUS
#define WEXITSTATUS(w)
Definition: compat.h:330
ExternalStreamHandler::ExternalStreamHandler
ExternalStreamHandler(const QString &path, int inputid, int majorid)
Definition: ExternalStreamHandler.cpp:530
build_compdb.args
args
Definition: build_compdb.py:11
ExternalStreamHandler::m_pollMode
bool m_pollMode
Definition: ExternalStreamHandler.h:120
ExternalStreamHandler::IsTSOpen
bool IsTSOpen(void)
Definition: ExternalStreamHandler.cpp:1007
ExternalStreamHandler::m_ioLock
QMutex m_ioLock
Definition: ExternalStreamHandler.h:114
ENO
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:72
hardwareprofile.smolt.timeout
float timeout
Definition: smolt.py:103
ExternalStreamHandler::m_apiVersion
int m_apiVersion
Definition: ExternalStreamHandler.h:122
ExternalStreamHandler::ProcessCommand
bool ProcessCommand(const QString &cmd, QString &result, int timeout=4000, uint retry_cnt=3)
Definition: ExternalStreamHandler.cpp:1226
ExternalStreamHandler::m_serialNo
uint m_serialNo
Definition: ExternalStreamHandler.h:123
error
static void error(const char *str,...)
Definition: vbi.cpp:42
StreamHandler::RemoveAllPIDFilters
bool RemoveAllPIDFilters(void)
Definition: streamhandler.cpp:234
ExternalStreamHandler::ProcessVer2
bool ProcessVer2(const QString &command, QString &result, int timeout, uint retry_cnt)
Definition: ExternalStreamHandler.cpp:1347
StreamHandler::SetRunning
void SetRunning(bool running, bool using_buffering, bool using_section_reader)
Definition: streamhandler.cpp:171
ExternalStreamHandler::PriorityEvent
void PriorityEvent(int fd) override
Definition: ExternalStreamHandler.cpp:1551
MythTimer
A QElapsedTimer based timer to replace use of QTime as a timer.
Definition: mythtimer.h:13
StreamHandler
Definition: streamhandler.h:54
ExternalStreamHandler::m_hasPictureAttributes
bool m_hasPictureAttributes
Definition: ExternalStreamHandler.h:125
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:64
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
ExternalStreamHandler::m_io
ExternIO * m_io
Definition: ExternalStreamHandler.h:115
MThread::setObjectName
void setObjectName(const QString &name)
Definition: mthread.cpp:243
MythTimer::stop
void stop(void)
Stops timer, next call to isRunning() will return false and any calls to elapsed() or restart() will ...
Definition: mythtimer.cpp:77
arg
arg(title).arg(filename).arg(doDelete))
MythTimer::isRunning
bool isRunning(void) const
Returns true if start() or restart() has been called at least once since construction and since any c...
Definition: mythtimer.cpp:134
ExternalStreamHandler::StopStreaming
bool StopStreaming(void)
Definition: ExternalStreamHandler.cpp:1167
ExternIO::Read
int Read(QByteArray &buffer, int maxlen, int timeout=2500)
Definition: ExternalStreamHandler.cpp:106
ExternIO::m_error
QString m_error
Definition: ExternalStreamHandler.h:47
ExternalStreamHandler::m_xon
bool m_xon
Definition: ExternalStreamHandler.h:129
MythTimer::start
void start(void)
starts measuring elapsed time.
Definition: mythtimer.cpp:47
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
MThread::RunProlog
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:198
ExternalStreamHandler::m_processLock
QMutex m_processLock
Definition: ExternalStreamHandler.h:139
streamlisteners.h
ExternIO::ExternIO
ExternIO(const QString &app, const QStringList &args)
Definition: ExternalStreamHandler.cpp:34
ExternalStreamHandler::s_handlers
static QMap< int, ExternalStreamHandler * > s_handlers
Definition: ExternalStreamHandler.h:133
ExternIO::m_bufSize
int m_bufSize
Definition: ExternalStreamHandler.h:49
ExternIO::Error
bool Error(void) const
Definition: ExternalStreamHandler.h:32
ExternIO::m_buffer
char * m_buffer
Definition: ExternalStreamHandler.h:50
ExternalStreamHandler.h
ExternIO
Definition: ExternalStreamHandler.h:19
ExternalStreamHandler::s_handlersRefCnt
static QMap< int, uint > s_handlersRefCnt
Definition: ExternalStreamHandler.h:134
ExternalStreamHandler::TOO_FAST_SIZE
@ TOO_FAST_SIZE
Definition: ExternalStreamHandler.h:64
close
#define close
Definition: compat.h:17
ExternalStreamHandler
Definition: ExternalStreamHandler.h:59
ExternalStreamHandler::m_hasTuner
bool m_hasTuner
Definition: ExternalStreamHandler.h:124
ExternIO::KillIfRunning
static bool KillIfRunning(const QString &cmd)
Definition: ExternalStreamHandler.cpp:240
ExternIO::GetStatus
QString GetStatus(int timeout=2500)
Definition: ExternalStreamHandler.cpp:167
ExternalStreamHandler::ReplayStream
void ReplayStream(void)
Definition: ExternalStreamHandler.cpp:1076
ExternalStreamHandler::m_tsOpen
bool m_tsOpen
Definition: ExternalStreamHandler.h:118
ExternIO::Fork
void Fork(void)
Definition: ExternalStreamHandler.cpp:295
ExternalStreamHandler::m_replayBuffer
QByteArray m_replayBuffer
Definition: ExternalStreamHandler.h:127
ExternalStreamHandler::CloseApp
void CloseApp(void)
Definition: ExternalStreamHandler.cpp:1021
ExternIO::m_appErr
int m_appErr
Definition: ExternalStreamHandler.h:45
ExternIO::m_pid
pid_t m_pid
Definition: ExternalStreamHandler.h:46
O_NONBLOCK
#define O_NONBLOCK
Definition: mythmedia.cpp:24
ExternIO::ErrorString
QString ErrorString(void) const
Definition: ExternalStreamHandler.h:33
StreamHandler::UpdateFiltersFromStreamData
bool UpdateFiltersFromStreamData(void)
Definition: streamhandler.cpp:284
ExternalStreamHandler::PurgeBuffer
void PurgeBuffer(void)
Definition: ExternalStreamHandler.cpp:1541
MThread::RunEpilog
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:211
ExternIO::m_app
QFileInfo m_app
Definition: ExternalStreamHandler.h:41
ExternalStreamHandler::Get
static ExternalStreamHandler * Get(const QString &devname, int inputid, int majorid)
Definition: ExternalStreamHandler.cpp:455
ExternIO::Run
bool Run(void)
Definition: ExternalStreamHandler.cpp:229
ExternalStreamHandler::RestartStream
bool RestartStream(void)
Definition: ExternalStreamHandler.cpp:1059
ExternalStreamHandler::m_majorId
int m_majorId
Definition: ExternalStreamHandler.h:113
ExternalStreamHandler::SetAPIVersion
bool SetAPIVersion(void)
Definition: ExternalStreamHandler.cpp:856
uint
unsigned int uint
Definition: compat.h:141
ExternalStreamHandler::s_handlersLock
static QMutex s_handlersLock
Definition: ExternalStreamHandler.h:132
ExternIO::m_appOut
int m_appOut
Definition: ExternalStreamHandler.h:44
ExternIO::m_appIn
int m_appIn
Definition: ExternalStreamHandler.h:43
ExternalStreamHandler::TS_PACKET_SIZE
@ TS_PACKET_SIZE
Definition: ExternalStreamHandler.h:62
ExternalStreamHandler::StartStreaming
bool StartStreaming(void)
Definition: ExternalStreamHandler.cpp:1119
LOC
#define LOC
Definition: ExternalStreamHandler.cpp:32
ExternalStreamHandler::UpdateDescription
QString UpdateDescription(void)
Definition: ExternalStreamHandler.cpp:886
mpegstreamdata.h
ExternalStreamHandler::IsAppOpen
bool IsAppOpen(void)
Definition: ExternalStreamHandler.cpp:994
ExternalStreamHandler::CheckForError
bool CheckForError(void)
Definition: ExternalStreamHandler.cpp:1492
ExternalStreamHandler::m_ioErrCnt
int m_ioErrCnt
Definition: ExternalStreamHandler.h:119
ExternalStreamHandler::m_replay
bool m_replay
Definition: ExternalStreamHandler.h:128
MythTimer::elapsed
int elapsed(void)
Returns milliseconds elapsed since last start() or restart()
Definition: mythtimer.cpp:90
StreamHandler::m_listenerLock
QMutex m_listenerLock
Definition: streamhandler.h:141
MythTimer::kStartRunning
@ kStartRunning
Definition: mythtimer.h:17
cardutil.h
StreamHandler::m_runningDesired
volatile bool m_runningDesired
Definition: streamhandler.h:117
ExternalStreamHandler::MAX_API_VERSION
@ MAX_API_VERSION
Definition: ExternalStreamHandler.h:61
ExternalStreamHandler::ProcessVer1
bool ProcessVer1(const QString &cmd, QString &result, int timeout, uint retry_cnt)
Definition: ExternalStreamHandler.cpp:1242
ExternalStreamHandler::PACKET_SIZE
@ PACKET_SIZE
Definition: ExternalStreamHandler.h:63
ExternalChannel.h
ExternIO::Write
int Write(const QByteArray &buffer)
Definition: ExternalStreamHandler.cpp:196
ExternIO::m_statusBuf
QString m_statusBuf
Definition: ExternalStreamHandler.h:52
ExternalStreamHandler::m_args
QStringList m_args
Definition: ExternalStreamHandler.h:116
ExternIO::kMaxErrorCnt
@ kMaxErrorCnt
Definition: ExternalStreamHandler.h:21
GENERIC_EXIT_PIPE_FAILURE
#define GENERIC_EXIT_PIPE_FAILURE
Error creating I/O pipes.
Definition: exitcodes.h:26
StreamHandler::m_streamDataList
StreamDataList m_streamDataList
Definition: streamhandler.h:142
ExternalStreamHandler::m_streamingCnt
QAtomicInt m_streamingCnt
Definition: ExternalStreamHandler.h:136
GENERIC_EXIT_DAEMONIZING_ERROR
#define GENERIC_EXIT_DAEMONIZING_ERROR
Error daemonizing or execl.
Definition: exitcodes.h:28
StreamHandler::m_bError
volatile bool m_bError
Definition: streamhandler.h:122
logPropagateArgs
QString logPropagateArgs
Definition: logging.cpp:86
ExternalStreamHandler::m_loc
QString m_loc
Definition: ExternalStreamHandler.h:112
ExternIO::m_status
QTextStream m_status
Definition: ExternalStreamHandler.h:53
exitcodes.h
StreamHandler::m_device
QString m_device
Definition: streamhandler.h:109
ExternalStreamHandler::m_streamLock
QMutex m_streamLock
Definition: ExternalStreamHandler.h:137
ExternalStreamHandler::Return
static void Return(ExternalStreamHandler *&ref, int inputid)
Definition: ExternalStreamHandler.cpp:484
dtvsignalmonitor.h
ExternIO::Ready
bool Ready(int fd, int timeout, const QString &what)
Definition: ExternalStreamHandler.cpp:74
ExternalStreamHandler::OpenApp
bool OpenApp(void)
Definition: ExternalStreamHandler.cpp:901
ExternIO::m_args
QStringList m_args
Definition: ExternalStreamHandler.h:42
MythTimer::restart
int restart(void)
Returns milliseconds elapsed since last start() or restart() and resets the count.
Definition: mythtimer.cpp:62
ExternIO::m_errCnt
int m_errCnt
Definition: ExternalStreamHandler.h:54
ExternalStreamHandler::m_app
QString m_app
Definition: ExternalStreamHandler.h:117