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