MythTV  master
ExternalStreamHandler.cpp
Go to the documentation of this file.
1 // -*- Mode: c++ -*-
2 
3 // POSIX headers
4 #include <thread>
5 #include <iostream>
6 #include <fcntl.h>
7 #include <unistd.h>
8 #include <algorithm>
9 #if !defined( USING_MINGW ) && !defined( _MSC_VER )
10 #include <poll.h>
11 #include <sys/ioctl.h>
12 #endif
13 #ifdef ANDROID
14 #include <sys/wait.h>
15 #endif
16 
17 // Qt headers
18 #include <QString>
19 #include <QFile>
20 
21 // MythTV headers
22 #include "ExternalStreamHandler.h"
23 #include "ExternalChannel.h"
24 //#include "ThreadedFileWriter.h"
25 #include "dtvsignalmonitor.h"
26 #include "streamlisteners.h"
27 #include "mpegstreamdata.h"
28 #include "cardutil.h"
29 #include "exitcodes.h"
30 
31 #define LOC QString("ExternSH[%1](%2): ").arg(m_inputId).arg(m_loc)
32 
33 ExternIO::ExternIO(const QString & app,
34  const QStringList & args)
35  : m_status(&m_statusBuf, QIODevice::ReadWrite)
36 {
37  m_app = (app);
38 
39  if (!m_app.exists())
40  {
41  m_error = QString("ExternIO: '%1' does not exist.").arg(app);
42  return;
43  }
44  if (!m_app.isReadable() || !m_app.isFile())
45  {
46  m_error = QString("ExternIO: '%1' is not readable.")
47  .arg(m_app.canonicalFilePath());
48  return;
49  }
50  if (!m_app.isExecutable())
51  {
52  m_error = QString("ExternIO: '%1' is not executable.")
53  .arg(m_app.canonicalFilePath());
54  return;
55  }
56 
57  m_args = args;
58  m_args.prepend(m_app.baseName());
59 
60  m_status.setString(&m_statusBuf);
61 }
62 
64 {
65  close(m_appIn);
66  close(m_appOut);
67  close(m_appErr);
68 
69  // waitpid(m_pid, &status, 0);
70  delete[] m_buffer;
71 }
72 
73 bool ExternIO::Ready(int fd, int timeout, const QString & what)
74 {
75 #if !defined( USING_MINGW ) && !defined( _MSC_VER )
76  struct pollfd m_poll[2];
77  memset(m_poll, 0, sizeof(m_poll));
78 
79  m_poll[0].fd = fd;
80  m_poll[0].events = POLLIN | POLLPRI;
81  int ret = poll(m_poll, 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  char buffer[2048];
181  int len = read(m_appErr, buffer, 2048);
182  m_status << QString::fromLatin1(buffer, 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  int in[2] = {-1, -1};
324  int out[2] = {-1, -1};
325  int err[2] = {-1, -1};
326 
327  if (pipe(in) < 0)
328  {
329  m_error = "pipe(in) failed: " + ENO;
330  return;
331  }
332  if (pipe(out) < 0)
333  {
334  m_error = "pipe(out) failed: " + ENO;
335  close(in[0]);
336  close(in[1]);
337  return;
338  }
339  if (pipe(err) < 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) << 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) << endl;
436  }
437  else
438  {
439  std::cerr << "ExternIO: "
440  << "execv() should not be here?: "
441  << strerror(errno) << 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 cmd;
572  QString result;
573  QString ready_cmd;
574  QByteArray buffer;
575  int sz = 0;
576  uint len = 0;
577  uint read_len = 0;
578  uint restart_cnt = 0;
579  MythTimer status_timer;
580  MythTimer nodata_timer;
581 
582  bool good_data = false;
583  uint data_proc_err = 0;
584  uint data_short_err = 0;
585 
586  if (!m_io)
587  {
588  LOG(VB_GENERAL, LOG_ERR, LOC +
589  QString("%1 is not running.").arg(m_device));
590  }
591 
592  status_timer.start();
593 
594  RunProlog();
595 
596  LOG(VB_RECORD, LOG_INFO, LOC + "run(): begin");
597 
598  SetRunning(true, true, false);
599 
600  if (m_pollMode)
601  ready_cmd = "SendBytes";
602  else
603  ready_cmd = "XON";
604 
605  uint remainder = 0;
606  while (m_runningDesired && !m_bError)
607  {
608  if (!IsTSOpen())
609  {
610  LOG(VB_RECORD, LOG_WARNING, LOC + "TS not open yet.");
611  std::this_thread::sleep_for(std::chrono::milliseconds(10));
612  continue;
613  }
614 
615  if (StreamingCount() == 0)
616  {
617  std::this_thread::sleep_for(std::chrono::milliseconds(10));
618  continue;
619  }
620 
622 
623  if (!m_xon || m_pollMode)
624  {
625  if (buffer.size() > TOO_FAST_SIZE)
626  {
627  LOG(VB_RECORD, LOG_WARNING, LOC +
628  "Internal buffer too full to accept more data from "
629  "external application.");
630  }
631  else
632  {
633  if (!ProcessCommand(ready_cmd, result))
634  {
635  if (result.startsWith("ERR"))
636  {
637  LOG(VB_GENERAL, LOG_ERR, LOC +
638  QString("Aborting: %1 -> %2")
639  .arg(ready_cmd).arg(result));
640  m_bError = true;
641  continue;
642  }
643 
644  if (restart_cnt++)
645  std::this_thread::sleep_for(std::chrono::seconds(20));
646  if (!RestartStream())
647  {
648  LOG(VB_RECORD, LOG_ERR, LOC +
649  "Failed to restart stream.");
650  m_bError = true;
651  }
652  continue;
653  }
654  m_xon = true;
655  }
656  }
657 
658  if (m_xon)
659  {
660  if (status_timer.elapsed() >= 2000)
661  {
662  // Since we may never need to send the XOFF
663  // command, occationally check to see if the
664  // External recorder needs to report an issue.
665  if (CheckForError())
666  {
667  if (restart_cnt++)
668  std::this_thread::sleep_for(std::chrono::seconds(20));
669  if (!RestartStream())
670  {
671  LOG(VB_RECORD, LOG_ERR, LOC + "Failed to restart stream.");
672  m_bError = true;
673  }
674  continue;
675  }
676 
677  status_timer.restart();
678  }
679 
680  if (buffer.size() > TOO_FAST_SIZE)
681  {
682  if (!m_pollMode)
683  {
684  // Data is comming a little too fast, so XOFF
685  // to give us time to process it.
686  if (!ProcessCommand(QString("XOFF"), result))
687  {
688  if (result.startsWith("ERR"))
689  {
690  LOG(VB_GENERAL, LOG_ERR, LOC +
691  QString("Aborting: XOFF -> %2")
692  .arg(result));
693  m_bError = true;
694  }
695  }
696  m_xon = false;
697  }
698  }
699 
700  if (m_io && (sz = PACKET_SIZE - remainder) > 0)
701  read_len = m_io->Read(buffer, sz, 100);
702  else
703  read_len = 0;
704  }
705  else
706  read_len = 0;
707 
708  if (read_len == 0)
709  {
710  if (!nodata_timer.isRunning())
711  nodata_timer.start();
712  else
713  {
714  if (nodata_timer.elapsed() >= 50000)
715  {
716  LOG(VB_GENERAL, LOG_WARNING, LOC +
717  "No data for 50 seconds, Restarting stream.");
718  if (!RestartStream())
719  {
720  LOG(VB_RECORD, LOG_ERR, LOC +
721  "Failed to restart stream.");
722  m_bError = true;
723  }
724  nodata_timer.stop();
725  continue;
726  }
727  }
728 
729  std::this_thread::sleep_for(std::chrono::milliseconds(50));
730 
731  // HLS type streams may only produce data every ~10 seconds
732  if (nodata_timer.elapsed() < 12000 && buffer.size() < TS_PACKET_SIZE)
733  continue;
734  }
735  else
736  {
737  nodata_timer.stop();
738  restart_cnt = 0;
739  }
740 
741  if (m_io == nullptr)
742  {
743  LOG(VB_GENERAL, LOG_ERR, LOC + "I/O thread has disappeared!");
744  m_bError = true;
745  break;
746  }
747  if (m_io->Error())
748  {
749  LOG(VB_GENERAL, LOG_ERR, LOC +
750  QString("Fatal Error from External Recorder: %1")
751  .arg(m_io->ErrorString()));
752  CloseApp();
753  m_bError = true;
754  break;
755  }
756 
757  len = remainder = buffer.size();
758 
759  if (len == 0)
760  continue;
761 
762  if (len < TS_PACKET_SIZE)
763  {
764  if (m_xon && data_short_err++ == 0)
765  LOG(VB_RECORD, LOG_INFO, LOC + "Waiting for a full TS packet.");
766  std::this_thread::sleep_for(std::chrono::microseconds(50));
767  continue;
768  }
769  if (data_short_err)
770  {
771  if (data_short_err > 1)
772  {
773  LOG(VB_RECORD, LOG_INFO, LOC +
774  QString("Waited for a full TS packet %1 times.")
775  .arg(data_short_err));
776  }
777  data_short_err = 0;
778  }
779 
780  if (!m_streamLock.tryLock())
781  continue;
782 
783  if (!m_listenerLock.tryLock())
784  continue;
785 
786  for (auto sit = m_streamDataList.cbegin();
787  sit != m_streamDataList.cend(); ++sit)
788  {
789  remainder = sit.key()->ProcessData
790  (reinterpret_cast<const uint8_t *>
791  (buffer.constData()), buffer.size());
792  }
793 
794  m_listenerLock.unlock();
795 
796  if (m_replay)
797  {
798  m_replayBuffer += buffer.left(len - remainder);
799  if (m_replayBuffer.size() > (50 * PACKET_SIZE))
800  {
801  m_replayBuffer.remove(0, len - remainder);
802  LOG(VB_RECORD, LOG_WARNING, LOC +
803  QString("Replay size truncated to %1 bytes")
804  .arg(m_replayBuffer.size()));
805  }
806  }
807 
808  m_streamLock.unlock();
809 
810  if (remainder == 0)
811  {
812  buffer.clear();
813  good_data = (len != 0U);
814  }
815  else if (len > remainder) // leftover bytes
816  {
817  buffer.remove(0, len - remainder);
818  good_data = (len != 0U);
819  }
820  else if (len == remainder)
821  good_data = false;
822 
823  if (good_data)
824  {
825  if (data_proc_err)
826  {
827  if (data_proc_err > 1)
828  {
829  LOG(VB_RECORD, LOG_WARNING, LOC +
830  QString("Failed to process the data received %1 times.")
831  .arg(data_proc_err));
832  }
833  data_proc_err = 0;
834  }
835  }
836  else
837  {
838  if (data_proc_err++ == 0)
839  {
840  LOG(VB_RECORD, LOG_WARNING, LOC +
841  "Failed to process the data received");
842  }
843  }
844  }
845 
846  LOG(VB_RECORD, LOG_INFO, LOC + "run(): " +
847  QString("%1 shutdown").arg(m_bError ? "Error" : "Normal"));
848 
850  SetRunning(false, true, false);
851 
852  LOG(VB_RECORD, LOG_INFO, LOC + "run(): " + "end");
853 
854  RunEpilog();
855 }
856 
858 {
859  QString result;
860 
861  if (ProcessCommand("APIVersion?", result, 10000))
862  {
863 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
864  QStringList tokens = result.split(':', QString::SkipEmptyParts);
865 #else
866  QStringList tokens = result.split(':', Qt::SkipEmptyParts);
867 #endif
868 
869  if (tokens.size() > 1)
870  m_apiVersion = tokens[1].toUInt();
871  m_apiVersion = min(m_apiVersion, static_cast<int>(MAX_API_VERSION));
872  if (m_apiVersion < 1)
873  {
874  LOG(VB_RECORD, LOG_ERR, LOC +
875  QString("Bad response to 'APIVersion?' - '%1'. "
876  "Expecting 1 or 2").arg(result));
877  m_apiVersion = 1;
878  }
879 
880  ProcessCommand(QString("APIVersion:%1").arg(m_apiVersion), result);
881  return true;
882  }
883 
884  return false;
885 }
886 
888 {
889  if (m_apiVersion > 1)
890  {
891  QString result;
892 
893  if (ProcessCommand("Description?", result))
894  m_loc = result.mid(3);
895  else
896  m_loc = m_device;
897  }
898 
899  return m_loc;
900 }
901 
903 {
904  {
905  QMutexLocker locker(&m_ioLock);
906 
907  if (m_io)
908  {
909  LOG(VB_RECORD, LOG_WARNING, LOC + "OpenApp: already open!");
910  return true;
911  }
912 
913  m_io = new ExternIO(m_app, m_args);
914 
915  if (m_io == nullptr)
916  {
917  LOG(VB_GENERAL, LOG_ERR, LOC + "ExternIO failed: " + ENO);
918  m_bError = true;
919  }
920  else
921  {
922  LOG(VB_RECORD, LOG_INFO, LOC + QString("Spawn '%1'").arg(m_device));
923  m_io->Run();
924  if (m_io->Error())
925  {
926  LOG(VB_GENERAL, LOG_ERR,
927  "Failed to start External Recorder: " + m_io->ErrorString());
928  delete m_io;
929  m_io = nullptr;
930  m_bError = true;
931  return false;
932  }
933  }
934  }
935 
936  QString result;
937 
938  if (!SetAPIVersion())
939  {
940  // Try again using API version 2
941  m_apiVersion = 2;
942  if (!SetAPIVersion())
943  m_apiVersion = 1;
944  }
945 
946  if (!IsAppOpen())
947  {
948  LOG(VB_RECORD, LOG_ERR, LOC + "Application is not responding.");
949  m_bError = true;
950  return false;
951  }
952 
954 
955  // Gather capabilities
956  if (!ProcessCommand("HasTuner?", result))
957  {
958  LOG(VB_RECORD, LOG_ERR, LOC +
959  QString("Bad response to 'HasTuner?' - '%1'").arg(result));
960  m_bError = true;
961  return false;
962  }
963  m_hasTuner = result.startsWith("OK:Yes");
964 
965  if (!ProcessCommand("HasPictureAttributes?", result))
966  {
967  LOG(VB_RECORD, LOG_ERR, LOC +
968  QString("Bad response to 'HasPictureAttributes?' - '%1'")
969  .arg(result));
970  m_bError = true;
971  return false;
972  }
973  m_hasPictureAttributes = result.startsWith("OK:Yes");
974 
975  /* Operate in "poll" or "xon/xoff" mode */
976  m_pollMode = ProcessCommand("FlowControl?", result) &&
977  result.startsWith("OK:Poll");
978 
979  LOG(VB_RECORD, LOG_INFO, LOC + "App opened successfully");
980  LOG(VB_RECORD, LOG_INFO, LOC +
981  QString("Capabilities: tuner(%1) "
982  "Picture attributes(%2) "
983  "Flow control(%3)")
984  .arg(m_hasTuner ? "yes" : "no")
985  .arg(m_hasPictureAttributes ? "yes" : "no")
986  .arg(m_pollMode ? "Polling" : "XON/XOFF")
987  );
988 
989  /* Let the external app know how many bytes will read without blocking */
990  ProcessCommand(QString("BlockSize:%1").arg(PACKET_SIZE), result);
991 
992  return true;
993 }
994 
996 {
997  if (m_io == nullptr)
998  {
999  LOG(VB_RECORD, LOG_WARNING, LOC +
1000  "WARNING: Unable to communicate with external app.");
1001  return false;
1002  }
1003 
1004  QString result;
1005  return ProcessCommand("Version?", result, 10000);
1006 }
1007 
1009 {
1010  if (m_tsOpen)
1011  return true;
1012 
1013  QString result;
1014 
1015  if (!ProcessCommand("IsOpen?", result))
1016  return false;
1017 
1018  m_tsOpen = true;
1019  return m_tsOpen;
1020 }
1021 
1023 {
1024  m_ioLock.lock();
1025  if (m_io)
1026  {
1027  QString result;
1028 
1029  LOG(VB_RECORD, LOG_INFO, LOC + "CloseRecorder");
1030  m_ioLock.unlock();
1031  ProcessCommand("CloseRecorder", result, 10000);
1032  m_ioLock.lock();
1033 
1034  if (!result.startsWith("OK"))
1035  {
1036  LOG(VB_RECORD, LOG_INFO, LOC +
1037  "CloseRecorder failed, sending kill.");
1038 
1039  QString full_command = QString("%1").arg(m_args.join(" "));
1040 
1041  if (!m_io->KillIfRunning(full_command))
1042  {
1043  // Give it one more chance.
1044  std::this_thread::sleep_for(std::chrono::milliseconds(50));
1045  if (!m_io->KillIfRunning(full_command))
1046  {
1047  LOG(VB_GENERAL, LOG_ERR,
1048  QString("Unable to kill existing '%1'.")
1049  .arg(full_command));
1050  return;
1051  }
1052  }
1053  }
1054  delete m_io;
1055  m_io = nullptr;
1056  }
1057  m_ioLock.unlock();
1058 }
1059 
1061 {
1062  bool streaming = (StreamingCount() > 0);
1063 
1064  LOG(VB_RECORD, LOG_INFO, LOC + "Restarting stream.");
1065 
1066  if (streaming)
1067  StopStreaming();
1068 
1069  std::this_thread::sleep_for(std::chrono::seconds(1));
1070 
1071  if (streaming)
1072  return StartStreaming();
1073 
1074  return true;
1075 }
1076 
1078 {
1079  if (m_replay)
1080  {
1081  QString result;
1082 
1083  // Let the external app know that we could be busy for a little while
1084  if (!m_pollMode)
1085  {
1086  ProcessCommand(QString("XOFF"), result);
1087  m_xon = false;
1088  }
1089 
1090  /* If the input is not a 'broadcast' it may only have one
1091  * copy of the SPS right at the beginning of the stream,
1092  * so make sure we don't miss it!
1093  */
1094  QMutexLocker listen_lock(&m_listenerLock);
1095 
1096  if (!m_streamDataList.empty())
1097  {
1098  for (auto sit = m_streamDataList.cbegin();
1099  sit != m_streamDataList.cend(); ++sit)
1100  {
1101  sit.key()->ProcessData(reinterpret_cast<const uint8_t *>
1102  (m_replayBuffer.constData()),
1103  m_replayBuffer.size());
1104  }
1105  }
1106  LOG(VB_RECORD, LOG_INFO, LOC + QString("Replayed %1 bytes")
1107  .arg(m_replayBuffer.size()));
1108  m_replayBuffer.clear();
1109  m_replay = false;
1110 
1111  // Let the external app know that we are ready
1112  if (!m_pollMode)
1113  {
1114  if (ProcessCommand(QString("XON"), result))
1115  m_xon = true;
1116  }
1117  }
1118 }
1119 
1121 {
1122  QString result;
1123 
1124  QMutexLocker locker(&m_streamLock);
1125 
1127 
1128  LOG(VB_RECORD, LOG_INFO, LOC +
1129  QString("StartStreaming with %1 current listeners")
1130  .arg(StreamingCount()));
1131 
1132  if (!IsAppOpen())
1133  {
1134  LOG(VB_GENERAL, LOG_ERR, LOC + "External Recorder not started.");
1135  return false;
1136  }
1137 
1138  if (StreamingCount() == 0)
1139  {
1140  if (!ProcessCommand("StartStreaming", result, 15000))
1141  {
1142  LogLevel_t level = LOG_ERR;
1143  if (result.toLower().startsWith("warn"))
1144  level = LOG_WARNING;
1145  else
1146  m_bError = true;
1147 
1148  LOG(VB_GENERAL, level, LOC + QString("StartStreaming failed: '%1'")
1149  .arg(result));
1150 
1151  return false;
1152  }
1153 
1154  LOG(VB_RECORD, LOG_INFO, LOC + "Streaming started");
1155  }
1156  else
1157  LOG(VB_RECORD, LOG_INFO, LOC + "Already streaming");
1158 
1159  m_streamingCnt.ref();
1160 
1161  LOG(VB_RECORD, LOG_INFO, LOC +
1162  QString("StartStreaming %1 listeners")
1163  .arg(StreamingCount()));
1164 
1165  return true;
1166 }
1167 
1169 {
1170  QMutexLocker locker(&m_streamLock);
1171 
1172  LOG(VB_RECORD, LOG_INFO, LOC +
1173  QString("StopStreaming %1 listeners")
1174  .arg(StreamingCount()));
1175 
1176  if (StreamingCount() == 0)
1177  {
1178  LOG(VB_RECORD, LOG_INFO, LOC +
1179  "StopStreaming requested, but we are not streaming!");
1180  return true;
1181  }
1182 
1183  if (m_streamingCnt.deref())
1184  {
1185  LOG(VB_RECORD, LOG_INFO, LOC +
1186  QString("StopStreaming delayed, still have %1 listeners")
1187  .arg(StreamingCount()));
1188  return true;
1189  }
1190 
1191  LOG(VB_RECORD, LOG_INFO, LOC + "StopStreaming");
1192 
1193  if (!m_pollMode && m_xon)
1194  {
1195  QString result;
1196  ProcessCommand(QString("XOFF"), result);
1197  m_xon = false;
1198  }
1199 
1200  if (!IsAppOpen())
1201  {
1202  LOG(VB_GENERAL, LOG_ERR, LOC + "External Recorder not started.");
1203  return false;
1204  }
1205 
1206  QString result;
1207  if (!ProcessCommand("StopStreaming", result, 10000))
1208  {
1209  LogLevel_t level = LOG_ERR;
1210  if (result.toLower().startsWith("warn"))
1211  level = LOG_WARNING;
1212  else
1213  m_bError = true;
1214 
1215  LOG(VB_GENERAL, level, LOC + QString("StopStreaming: '%1'")
1216  .arg(result));
1217 
1218  return false;
1219  }
1220 
1221  PurgeBuffer();
1222  LOG(VB_RECORD, LOG_INFO, LOC + "Streaming stopped");
1223 
1224  return true;
1225 }
1226 
1227 bool ExternalStreamHandler::ProcessCommand(const QString & cmd,
1228  QString & result, int timeout,
1229  uint retry_cnt)
1230 {
1231  QMutexLocker locker(&m_processLock);
1232 
1233  if (m_apiVersion == 2)
1234  return ProcessVer2(cmd, result, timeout, retry_cnt);
1235  if (m_apiVersion == 1)
1236  return ProcessVer1(cmd, result, timeout, retry_cnt);
1237 
1238  LOG(VB_RECORD, LOG_ERR, LOC +
1239  QString("Invalid API version %1. Expected 1 or 2").arg(m_apiVersion));
1240  return false;
1241 }
1242 
1243 bool ExternalStreamHandler::ProcessVer1(const QString & cmd,
1244  QString & result, int timeout,
1245  uint retry_cnt)
1246 {
1247  LOG(VB_RECORD, LOG_DEBUG, LOC + QString("ProcessVer1('%1')")
1248  .arg(cmd));
1249 
1250  for (uint cnt = 0; cnt < retry_cnt; ++cnt)
1251  {
1252  QMutexLocker locker(&m_ioLock);
1253 
1254  if (!m_io)
1255  {
1256  LOG(VB_RECORD, LOG_ERR, LOC + "External I/O not ready!");
1257  return false;
1258  }
1259 
1260  QByteArray buf(cmd.toUtf8(), cmd.size());
1261  buf += '\n';
1262 
1263  if (m_io->Error())
1264  {
1265  LOG(VB_GENERAL, LOG_ERR, LOC + "External Recorder in bad state: " +
1266  m_io->ErrorString());
1267  return false;
1268  }
1269 
1270  /* Try to keep in sync, if External app was too slow in responding
1271  * to previous query, consume the response before sending new query */
1272  m_io->GetStatus(0);
1273 
1274  /* Send new query */
1275  m_io->Write(buf);
1276 
1278  while (timer.elapsed() < timeout)
1279  {
1280  result = m_io->GetStatus(timeout);
1281  if (m_io->Error())
1282  {
1283  LOG(VB_GENERAL, LOG_ERR, LOC +
1284  "Failed to read from External Recorder: " +
1285  m_io->ErrorString());
1286  m_bError = true;
1287  return false;
1288  }
1289 
1290  // Out-of-band error message
1291  if (result.startsWith("STATUS:ERR") ||
1292  result.startsWith("0:STATUS:ERR"))
1293  {
1294  LOG(VB_RECORD, LOG_ERR, LOC + result);
1295  result.remove(0, result.indexOf(":ERR") + 1);
1296  return false;
1297  }
1298  // STATUS message are "out of band".
1299  // Ignore them while waiting for a responds to a command
1300  if (!result.startsWith("STATUS") && !result.startsWith("0:STATUS"))
1301  break;
1302  LOG(VB_RECORD, LOG_INFO, LOC +
1303  QString("Ignoring response '%1'").arg(result));
1304  }
1305 
1306  if (result.size() < 1)
1307  {
1308  LOG(VB_GENERAL, LOG_WARNING, LOC +
1309  QString("External Recorder did not respond to '%1'").arg(cmd));
1310  }
1311  else
1312  {
1313  bool okay = result.startsWith("OK");
1314  if (okay || result.startsWith("WARN") || result.startsWith("ERR"))
1315  {
1316  LogLevel_t level = LOG_INFO;
1317 
1318  m_ioErrCnt = 0;
1319  if (!okay)
1320  level = LOG_WARNING;
1321  else if (cmd.startsWith("SendBytes"))
1322  level = LOG_DEBUG;
1323 
1324  LOG(VB_RECORD, level,
1325  LOC + QString("ProcessCommand('%1') = '%2' took %3ms %4")
1326  .arg(cmd).arg(result)
1327  .arg(timer.elapsed())
1328  .arg(okay ? "" : "<-- NOTE"));
1329 
1330  return okay;
1331  }
1332  LOG(VB_GENERAL, LOG_WARNING, LOC +
1333  QString("External Recorder invalid response to '%1': '%2'")
1334  .arg(cmd).arg(result));
1335  }
1336 
1337  if (++m_ioErrCnt > 10)
1338  {
1339  LOG(VB_GENERAL, LOG_ERR, LOC + "Too many I/O errors.");
1340  m_bError = true;
1341  break;
1342  }
1343  }
1344 
1345  return false;
1346 }
1347 
1348 bool ExternalStreamHandler::ProcessVer2(const QString & command,
1349  QString & result, int timeout,
1350  uint retry_cnt)
1351 {
1352  QString status;
1353  QString raw;
1354 
1355  for (uint cnt = 0; cnt < retry_cnt; ++cnt)
1356  {
1357  QString cmd = QString("%1:%2").arg(++m_serialNo).arg(command);
1358 
1359  LOG(VB_RECORD, LOG_DEBUG, LOC + QString("ProcessVer2('%1') serial(%2)")
1360  .arg(cmd).arg(m_serialNo));
1361 
1362  QMutexLocker locker(&m_ioLock);
1363 
1364  if (!m_io)
1365  {
1366  LOG(VB_RECORD, LOG_ERR, LOC + "External I/O not ready!");
1367  return false;
1368  }
1369 
1370  QByteArray buf(cmd.toUtf8(), cmd.size());
1371  buf += '\n';
1372 
1373  if (m_io->Error())
1374  {
1375  LOG(VB_GENERAL, LOG_ERR, LOC + "External Recorder in bad state: " +
1376  m_io->ErrorString());
1377  return false;
1378  }
1379 
1380  /* Send query */
1381  m_io->Write(buf);
1382 
1383  QStringList tokens;
1384 
1386  while (timer.elapsed() < timeout)
1387  {
1388  result = m_io->GetStatus(timeout);
1389  if (m_io->Error())
1390  {
1391  LOG(VB_GENERAL, LOG_ERR, LOC +
1392  "Failed to read from External Recorder: " +
1393  m_io->ErrorString());
1394  m_bError = true;
1395  return false;
1396  }
1397 
1398  if (!result.isEmpty())
1399  {
1400  raw = result;
1401 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1402  tokens = result.split(':', QString::SkipEmptyParts);
1403 #else
1404  tokens = result.split(':', Qt::SkipEmptyParts);
1405 #endif
1406 
1407  // Look for result with the serial number of this query
1408  if (tokens.size() > 1 && tokens[0].toUInt() >= m_serialNo)
1409  break;
1410 
1411  /* Other messages are "out of band" */
1412 
1413  // Check for error message missing serial#
1414  if (tokens[0].startsWith("ERR"))
1415  break;
1416 
1417  // Remove serial#
1418  tokens.removeFirst();
1419  result = tokens.join(':');
1420  bool err = (tokens.size() > 1 && tokens[1].startsWith("ERR"));
1421  LOG(VB_RECORD, (err ? LOG_WARNING : LOG_INFO), LOC + raw);
1422  if (err)
1423  {
1424  // Remove "STATUS"
1425  tokens.removeFirst();
1426  result = tokens.join(':');
1427  return false;
1428  }
1429  }
1430  }
1431 
1432  if (timer.elapsed() >= timeout)
1433  {
1434  LOG(VB_RECORD, LOG_ERR, LOC +
1435  QString("ProcessVer2: Giving up waiting for response for "
1436  "command '%2'").arg(cmd));
1437  }
1438  else if (tokens.size() < 2)
1439  {
1440  LOG(VB_RECORD, LOG_ERR, LOC +
1441  QString("Did not receive a valid response "
1442  "for command '%1', received '%2'").arg(cmd).arg(result));
1443  }
1444  else if (tokens[0].toUInt() > m_serialNo)
1445  {
1446  LOG(VB_RECORD, LOG_ERR, LOC +
1447  QString("ProcessVer2: Looking for serial no %1, "
1448  "but received %2 for command '%2'")
1449  .arg(m_serialNo).arg(tokens[0]).arg(cmd));
1450  }
1451  else
1452  {
1453  tokens.removeFirst();
1454  status = tokens[0].trimmed();
1455  result = tokens.join(':');
1456 
1457  bool okay = (status == "OK");
1458  if (okay || status.startsWith("WARN") || status.startsWith("ERR"))
1459  {
1460  LogLevel_t level = LOG_INFO;
1461 
1462  m_ioErrCnt = 0;
1463  if (!okay)
1464  level = LOG_WARNING;
1465  else if (command.startsWith("SendBytes") ||
1466  (command.startsWith("TuneStatus") &&
1467  result == "OK:InProgress"))
1468  level = LOG_DEBUG;
1469 
1470  LOG(VB_RECORD, level,
1471  LOC + QString("ProcessV2('%1') = '%2' took %3ms %4")
1472  .arg(cmd).arg(result).arg(timer.elapsed())
1473  .arg(okay ? "" : "<-- NOTE"));
1474 
1475  return okay;
1476  }
1477  LOG(VB_GENERAL, LOG_WARNING, LOC +
1478  QString("External Recorder invalid response to '%1': '%2'")
1479  .arg(cmd).arg(result));
1480  }
1481 
1482  if (++m_ioErrCnt > 10)
1483  {
1484  LOG(VB_GENERAL, LOG_ERR, LOC + "Too many I/O errors.");
1485  m_bError = true;
1486  break;
1487  }
1488  }
1489 
1490  return false;
1491 }
1492 
1494 {
1495  QString result;
1496  bool err = false;
1497 
1498  QMutexLocker locker(&m_ioLock);
1499 
1500  if (!m_io)
1501  {
1502  LOG(VB_RECORD, LOG_ERR, LOC + "External I/O not ready!");
1503  return true;
1504  }
1505 
1506  if (m_io->Error())
1507  {
1508  LOG(VB_GENERAL, LOG_ERR, "External Recorder in bad state: " +
1509  m_io->ErrorString());
1510  return true;
1511  }
1512 
1513  do
1514  {
1515  result = m_io->GetStatus(0);
1516  if (!result.isEmpty())
1517  {
1518  if (m_apiVersion > 1)
1519  {
1520 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1521  QStringList tokens = result.split(':', QString::SkipEmptyParts);
1522 #else
1523  QStringList tokens = result.split(':', Qt::SkipEmptyParts);
1524 #endif
1525 
1526  tokens.removeFirst();
1527  result = tokens.join(':');
1528  for (int idx = 1; idx < tokens.size(); ++idx)
1529  err |= tokens[idx].startsWith("ERR");
1530  }
1531  else
1532  err |= result.startsWith("STATUS:ERR");
1533 
1534  LOG(VB_RECORD, (err ? LOG_WARNING : LOG_INFO), LOC + result);
1535  }
1536  }
1537  while (!result.isEmpty());
1538 
1539  return err;
1540 }
1541 
1543 {
1544  if (m_io)
1545  {
1546  QByteArray buffer;
1547  m_io->Read(buffer, PACKET_SIZE, 1);
1548  m_io->GetStatus(1);
1549  }
1550 }
1551 
1553 {
1554  // TODO report on buffer overruns, etc.
1555 }
WEXITSTATUS
#define WEXITSTATUS(w)
Definition: compat.h:328
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:121
ExternalStreamHandler::IsTSOpen
bool IsTSOpen(void)
Definition: ExternalStreamHandler.cpp:1008
ExternalStreamHandler::m_ioLock
QMutex m_ioLock
Definition: ExternalStreamHandler.h:115
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:123
ExternalStreamHandler::ProcessCommand
bool ProcessCommand(const QString &cmd, QString &result, int timeout=4000, uint retry_cnt=3)
Definition: ExternalStreamHandler.cpp:1227
ExternalStreamHandler::m_serialNo
uint m_serialNo
Definition: ExternalStreamHandler.h:124
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:1348
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:1552
MythTimer
A QElapsedTimer based timer to replace use of QTime as a timer.
Definition: mythtimer.h:13
StreamHandler
Definition: streamhandler.h:55
ExternalStreamHandler::m_hasPictureAttributes
bool m_hasPictureAttributes
Definition: ExternalStreamHandler.h:126
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:63
ExternalStreamHandler::run
void run(void) override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
Definition: ExternalStreamHandler.cpp:569
mythburn.write
def write(text, progress=True)
Definition: mythburn.py:308
ExternalStreamHandler::m_io
ExternIO * m_io
Definition: ExternalStreamHandler.h:116
MThread::setObjectName
void setObjectName(const QString &name)
Definition: mthread.cpp:247
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:1168
ExternIO::Read
int Read(QByteArray &buffer, int maxlen, int timeout=2500)
Definition: ExternalStreamHandler.cpp:106
ExternIO::m_error
QString m_error
Definition: ExternalStreamHandler.h:48
ExternalStreamHandler::m_xon
bool m_xon
Definition: ExternalStreamHandler.h:130
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:202
ExternalStreamHandler::m_processLock
QMutex m_processLock
Definition: ExternalStreamHandler.h:140
streamlisteners.h
ExternIO::ExternIO
ExternIO(const QString &app, const QStringList &args)
Definition: ExternalStreamHandler.cpp:33
ExternalStreamHandler::s_handlers
static QMap< int, ExternalStreamHandler * > s_handlers
Definition: ExternalStreamHandler.h:134
ExternIO::m_bufSize
int m_bufSize
Definition: ExternalStreamHandler.h:50
ExternIO::Error
bool Error(void) const
Definition: ExternalStreamHandler.h:33
ExternIO::m_buffer
char * m_buffer
Definition: ExternalStreamHandler.h:51
ExternalStreamHandler.h
ExternIO
Definition: ExternalStreamHandler.h:20
ExternalStreamHandler::s_handlersRefCnt
static QMap< int, uint > s_handlersRefCnt
Definition: ExternalStreamHandler.h:135
ExternalStreamHandler::TOO_FAST_SIZE
@ TOO_FAST_SIZE
Definition: ExternalStreamHandler.h:65
close
#define close
Definition: compat.h:16
ExternalStreamHandler
Definition: ExternalStreamHandler.h:60
ExternalStreamHandler::m_hasTuner
bool m_hasTuner
Definition: ExternalStreamHandler.h:125
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:1077
ExternalStreamHandler::m_tsOpen
bool m_tsOpen
Definition: ExternalStreamHandler.h:119
ExternIO::Fork
void Fork(void)
Definition: ExternalStreamHandler.cpp:295
ExternalStreamHandler::m_replayBuffer
QByteArray m_replayBuffer
Definition: ExternalStreamHandler.h:128
ExternalStreamHandler::CloseApp
void CloseApp(void)
Definition: ExternalStreamHandler.cpp:1022
ExternIO::m_appErr
int m_appErr
Definition: ExternalStreamHandler.h:46
ExternIO::m_pid
pid_t m_pid
Definition: ExternalStreamHandler.h:47
O_NONBLOCK
#define O_NONBLOCK
Definition: mythmedia.cpp:25
ExternIO::ErrorString
QString ErrorString(void) const
Definition: ExternalStreamHandler.h:34
StreamHandler::UpdateFiltersFromStreamData
bool UpdateFiltersFromStreamData(void)
Definition: streamhandler.cpp:284
ExternalStreamHandler::PurgeBuffer
void PurgeBuffer(void)
Definition: ExternalStreamHandler.cpp:1542
MThread::RunEpilog
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:215
ExternIO::m_app
QFileInfo m_app
Definition: ExternalStreamHandler.h:42
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:1060
ExternalStreamHandler::m_majorId
int m_majorId
Definition: ExternalStreamHandler.h:114
ExternalStreamHandler::SetAPIVersion
bool SetAPIVersion(void)
Definition: ExternalStreamHandler.cpp:857
uint
unsigned int uint
Definition: compat.h:140
ExternalStreamHandler::s_handlersLock
static QMutex s_handlersLock
Definition: ExternalStreamHandler.h:133
ExternIO::m_appOut
int m_appOut
Definition: ExternalStreamHandler.h:45
ExternIO::m_appIn
int m_appIn
Definition: ExternalStreamHandler.h:44
ExternalStreamHandler::TS_PACKET_SIZE
@ TS_PACKET_SIZE
Definition: ExternalStreamHandler.h:63
ExternalStreamHandler::StartStreaming
bool StartStreaming(void)
Definition: ExternalStreamHandler.cpp:1120
LOC
#define LOC
Definition: ExternalStreamHandler.cpp:31
ExternalStreamHandler::UpdateDescription
QString UpdateDescription(void)
Definition: ExternalStreamHandler.cpp:887
mpegstreamdata.h
ExternalStreamHandler::IsAppOpen
bool IsAppOpen(void)
Definition: ExternalStreamHandler.cpp:995
ExternalStreamHandler::CheckForError
bool CheckForError(void)
Definition: ExternalStreamHandler.cpp:1493
ExternalStreamHandler::m_ioErrCnt
int m_ioErrCnt
Definition: ExternalStreamHandler.h:120
ExternalStreamHandler::m_replay
bool m_replay
Definition: ExternalStreamHandler.h:129
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:142
MythTimer::kStartRunning
@ kStartRunning
Definition: mythtimer.h:17
cardutil.h
StreamHandler::m_runningDesired
volatile bool m_runningDesired
Definition: streamhandler.h:118
ExternalStreamHandler::MAX_API_VERSION
@ MAX_API_VERSION
Definition: ExternalStreamHandler.h:62
ExternalStreamHandler::ProcessVer1
bool ProcessVer1(const QString &cmd, QString &result, int timeout, uint retry_cnt)
Definition: ExternalStreamHandler.cpp:1243
ExternalStreamHandler::PACKET_SIZE
@ PACKET_SIZE
Definition: ExternalStreamHandler.h:64
ExternalChannel.h
ExternIO::Write
int Write(const QByteArray &buffer)
Definition: ExternalStreamHandler.cpp:196
ExternIO::m_statusBuf
QString m_statusBuf
Definition: ExternalStreamHandler.h:53
ExternalStreamHandler::m_args
QStringList m_args
Definition: ExternalStreamHandler.h:117
ExternIO::kMaxErrorCnt
@ kMaxErrorCnt
Definition: ExternalStreamHandler.h:22
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:143
ExternalStreamHandler::m_streamingCnt
QAtomicInt m_streamingCnt
Definition: ExternalStreamHandler.h:137
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:123
logPropagateArgs
QString logPropagateArgs
Definition: logging.cpp:89
ExternalStreamHandler::m_loc
QString m_loc
Definition: ExternalStreamHandler.h:113
ExternIO::m_status
QTextStream m_status
Definition: ExternalStreamHandler.h:54
exitcodes.h
StreamHandler::m_device
QString m_device
Definition: streamhandler.h:110
ExternalStreamHandler::m_streamLock
QMutex m_streamLock
Definition: ExternalStreamHandler.h:138
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:73
ExternalStreamHandler::OpenApp
bool OpenApp(void)
Definition: ExternalStreamHandler.cpp:902
ExternIO::m_args
QStringList m_args
Definition: ExternalStreamHandler.h:43
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:55
ExternalStreamHandler::m_app
QString m_app
Definition: ExternalStreamHandler.h:118