22#include <QJsonDocument>
39#define LOC QString("ExternSH[%1](%2): ").arg(m_inputId).arg(m_loc)
42 const QStringList &
args)
43 : m_app(QFileInfo(app)),
44 m_status(&m_statusBuf, QIODevice::ReadWrite)
48 m_error = QString(
"ExternIO: '%1' does not exist.").arg(app);
53 m_error = QString(
"ExternIO: '%1' is not readable.")
54 .arg(
m_app.canonicalFilePath());
57 if (!
m_app.isExecutable())
59 m_error = QString(
"ExternIO: '%1' is not executable.")
60 .arg(
m_app.canonicalFilePath());
81 [[maybe_unused]] std::chrono::milliseconds
timeout,
82 [[maybe_unused]]
const QString & what)
85 std::array<struct pollfd,2> m_poll {};
88 m_poll[0].events = POLLIN | POLLPRI;
89 int ret = poll(m_poll.data(), 1,
timeout.count());
91 if (m_poll[0].revents & POLLHUP)
93 m_error = what +
" poll eof (POLLHUP)";
96 if (m_poll[0].revents & POLLNVAL)
98 LOG(VB_GENERAL, LOG_ERR,
"poll error");
101 if (m_poll[0].revents & POLLIN)
106 if ((EOVERFLOW == errno))
118 LOG(VB_RECORD, LOG_ERR,
119 QString(
"ExternIO::Read: already in error state: '%1'")
142 m_error =
"Failed to read from External Recorder: " +
ENO;
143 LOG(VB_RECORD, LOG_WARNING,
144 "External Recorder not ready. Giving up.");
148 LOG(VB_RECORD, LOG_WARNING,
149 QString(
"External Recorder not ready. Will retry (%1/%2).")
151 std::this_thread::sleep_for(100ms);
156 m_error =
"Failed to read from External Recorder: " +
ENO;
170 LOG(VB_RECORD, LOG_DEBUG,
171 QString(
"ExternIO::Read '%1' bytes, buffer size %2")
172 .arg(len).arg(buffer.size()));
181 LOG(VB_RECORD, LOG_ERR,
182 QString(
"ExternIO::GetStatus: already in error state: '%1'")
190 std::array<char,2048> buffer {};
192 m_status << QString::fromLatin1(buffer.data(), len);
200 LOG(VB_RECORD, LOG_DEBUG, QString(
"ExternIO::GetStatus '%1'")
210 LOG(VB_RECORD, LOG_ERR,
211 QString(
"ExternIO::Write: already in error state: '%1'")
216 LOG(VB_RECORD, LOG_DEBUG, QString(
"ExternIO::Write('%1')")
217 .arg(QString(buffer).simplified()));
219 int len =
write(
m_appIn, buffer.constData(), buffer.size());
220 if (len != buffer.size())
224 LOG(VB_RECORD, LOG_WARNING,
225 QString(
"ExternIO::Write: only wrote %1 of %2 bytes '%3'")
226 .arg(len).arg(buffer.size()).arg(QString(buffer)));
230 m_error = QString(
"ExternIO: Failed to write '%1' to app's stdin: ")
231 .arg(QString(buffer)) +
ENO;
241 LOG(VB_RECORD, LOG_INFO, QString(
"ExternIO::Run()"));
252#if defined(Q_OS_DARWIN) || defined(__FreeBSD__) || defined(__OpenBSD__)
254#elif defined( _WIN32 )
257 QString grp = QString(
"pgrep -x -f -- \"%1\" 2>&1 > /dev/null").arg(cmd);
258 QString kil = QString(
"pkill --signal 15 -x -f -- \"%1\" 2>&1 > /dev/null")
261 int res_grp = system(grp.toUtf8().constData());
264 LOG(VB_RECORD, LOG_DEBUG, QString(
"'%1' not running.").arg(cmd));
268 LOG(VB_RECORD, LOG_WARNING, QString(
"'%1' already running, killing...")
270 int res_kil = system(kil.toUtf8().constData());
272 LOG(VB_GENERAL, LOG_WARNING, QString(
"'%1' failed: %2")
275 res_grp = system(grp.toUtf8().constData());
278 LOG(
WEXITSTATUS(res_kil) == 0 ? VB_RECORD : VB_GENERAL, LOG_WARNING,
279 QString(
"'%1' terminated.").arg(cmd));
283 std::this_thread::sleep_for(50ms);
285 kil = QString(
"pkill --signal 9 -x -f \"%1\" 2>&1 > /dev/null").arg(cmd);
286 res_kil = system(kil.toUtf8().constData());
288 LOG(VB_GENERAL, LOG_WARNING, QString(
"'%1' failed: %2")
291 res_grp = system(grp.toUtf8().constData());
292 LOG(
WEXITSTATUS(res_kil) == 0 ? VB_RECORD : VB_GENERAL, LOG_WARNING,
294 .arg(cmd,
WEXITSTATUS(res_grp) == 0 ?
"sill running" :
"terminated"));
305 LOG(VB_RECORD, LOG_INFO, QString(
"ExternIO in bad state: '%1'")
310 QString full_command = QString(
"%1").arg(
m_args.join(
" "));
315 std::this_thread::sleep_for(50ms);
318 m_error = QString(
"Unable to kill existing '%1'.")
326 LOG(VB_RECORD, LOG_INFO, QString(
"ExternIO::Fork '%1'").arg(full_command));
328 std::array<int,2> in = {-1, -1};
329 std::array<int,2> out = {-1, -1};
330 std::array<int,2> err = {-1, -1};
332 if (pipe(in.data()) < 0)
337 if (pipe(out.data()) < 0)
344 if (pipe(err.data()) < 0)
378 LOG(VB_GENERAL, LOG_WARNING,
379 "ExternIO::Fork(): Failed to set O_NONBLOCK for FD: " +
ENO);
380 std::this_thread::sleep_for(2s);
384 LOG(VB_RECORD, LOG_INFO,
"Spawned");
392 if (dup2( in[0], 0) < 0)
394 std::cerr <<
"dup2(stdin) failed: " << strerror(errno);
397 else if (dup2(out[1], 1) < 0)
399 std::cerr <<
"dup2(stdout) failed: " << strerror(errno);
402 else if (dup2(err[1], 2) < 0)
404 std::cerr <<
"dup2(stderr) failed: " << strerror(errno);
410 close_range(3, sysconf(_SC_OPEN_MAX) - 1, 0);
412 for (
int i = sysconf(_SC_OPEN_MAX) - 1; i > 2; --i)
419 if (setpgid(0,0) < 0)
421 std::cerr <<
"ExternIO: "
422 <<
"setpgid() failed: "
423 << strerror(errno) << std::endl;
427 char *command = strdup(
m_app.canonicalFilePath()
428 .toUtf8().constData());
430 char **arguments =
new char*[
m_args.size() + 1];
431 for (
int i = 0; i <
m_args.size(); ++i)
433 int len =
m_args[i].size() + 1;
434 arguments[i] =
new char[len];
435 memcpy(arguments[i],
m_args[i].toStdString().c_str(), len);
437 arguments[
m_args.size()] =
nullptr;
439 if (execv(command, arguments) < 0)
442 std::cerr <<
"ExternIO: "
443 <<
"execv() failed: "
444 << strerror(errno) << std::endl;
448 std::cerr <<
"ExternIO: "
449 <<
"execv() should not be here?: "
450 << strerror(errno) << std::endl;
465 int inputid,
int majorid)
469 QMap<int, ExternalStreamHandler*>::iterator it =
s_handlers.find(majorid);
477 LOG(VB_RECORD, LOG_INFO,
478 QString(
"ExternSH[%1:%2]: Creating new stream handler for %3 "
480 .arg(inputid).arg(majorid).arg(devname));
486 LOG(VB_RECORD, LOG_INFO,
487 QString(
"ExternSH[%1:%2]: Using existing stream handler for %3")
488 .arg(inputid).arg(majorid).arg(devname) +
489 QString(
" (%1 in use)").arg(rcount));
506 QMap<int, ExternalStreamHandler*>::iterator it =
514 LOG(VB_RECORD, LOG_INFO,
515 QString(
"ExternSH[%1:%2]: Return handler (%3 still in use)")
516 .arg(inputid).arg(majorid).arg(*rit));
523 LOG(VB_RECORD, LOG_INFO,
524 QString(
"ExternSH[%1:%2]: Closing handler (0 in use)")
525 .arg(inputid).arg(majorid));
531 LOG(VB_GENERAL, LOG_ERR,
532 QString(
"ExternSH[%1:%2]: Error: No handler to return!")
533 .arg(inputid).arg(majorid));
553 m_args = path.split(
' ',Qt::SkipEmptyParts) +
560 if (!
m_args.contains(
"--quiet") && !
m_args.contains(
"-q"))
563 m_args <<
"--inputid" << QString::number(majorid);
564 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
"args \"%1\"")
569 LOG(VB_GENERAL, LOG_ERR,
LOC +
570 QString(
"Failed to start %1").arg(
m_device));
587 uint restart_cnt = 0;
591 bool good_data =
false;
592 uint data_proc_err = 0;
593 uint data_short_err = 0;
597 LOG(VB_GENERAL, LOG_ERR,
LOC +
598 QString(
"%1 is not running.").arg(
m_device));
601 status_timer.
start();
605 LOG(VB_RECORD, LOG_INFO,
LOC +
"run(): begin");
610 ready_cmd =
"SendBytes";
619 LOG(VB_RECORD, LOG_WARNING,
LOC +
"TS not open yet.");
620 std::this_thread::sleep_for(10ms);
626 std::this_thread::sleep_for(10ms);
636 LOG(VB_RECORD, LOG_WARNING,
LOC +
637 "Internal buffer too full to accept more data from "
638 "external application.");
644 if (result.startsWith(
"ERR"))
646 LOG(VB_GENERAL, LOG_ERR,
LOC +
647 QString(
"Aborting: %1 -> %2")
648 .arg(ready_cmd, result));
654 std::this_thread::sleep_for(20s);
657 LOG(VB_RECORD, LOG_ERR,
LOC +
658 "Failed to restart stream.");
669 if (status_timer.
elapsed() >= 2s)
677 std::this_thread::sleep_for(20s);
680 LOG(VB_RECORD, LOG_ERR,
LOC +
681 "Failed to restart stream.");
698 if (result.startsWith(
"ERR"))
700 LOG(VB_GENERAL, LOG_ERR,
LOC +
701 QString(
"Aborting: XOFF -> %2")
715 read_len =
m_io->
Read(buffer, sz, 100ms);
726 nodata_timer.
start();
729 if (nodata_timer.
elapsed() >= 50s)
731 LOG(VB_GENERAL, LOG_WARNING,
LOC +
732 "No data for 50 seconds, Restarting stream.");
735 LOG(VB_RECORD, LOG_ERR,
LOC +
736 "Failed to restart stream.");
744 std::this_thread::sleep_for(50ms);
758 LOG(VB_GENERAL, LOG_ERR,
LOC +
"I/O thread has disappeared!");
764 LOG(VB_GENERAL, LOG_ERR,
LOC +
765 QString(
"Fatal Error from External Recorder: %1")
772 len = remainder = buffer.size();
779 if (
m_xon && data_short_err++ == 0)
780 LOG(VB_RECORD, LOG_INFO,
LOC +
"Waiting for a full TS packet.");
781 std::this_thread::sleep_for(50us);
786 if (data_short_err > 1)
788 LOG(VB_RECORD, LOG_INFO,
LOC +
789 QString(
"Waited for a full TS packet %1 times.")
790 .arg(data_short_err));
804 remainder = sit.key()->ProcessData
805 (
reinterpret_cast<const uint8_t *
>
806 (buffer.constData()), buffer.size());
817 LOG(VB_RECORD, LOG_WARNING,
LOC +
818 QString(
"Replay size truncated to %1 bytes")
828 good_data = (len != 0U);
830 else if (len > remainder)
832 buffer.remove(0, len - remainder);
833 good_data = (len != 0U);
835 else if (len == remainder)
844 if (data_proc_err > 1)
846 LOG(VB_RECORD, LOG_WARNING,
LOC +
847 QString(
"Failed to process the data received %1 times.")
848 .arg(data_proc_err));
855 if (data_proc_err++ == 0)
857 LOG(VB_RECORD, LOG_WARNING,
LOC +
858 "Failed to process the data received");
863 LOG(VB_RECORD, LOG_INFO,
LOC +
"run(): " +
864 QString(
"%1 shutdown").arg(
m_bError ?
"Error" :
"Normal"));
869 LOG(VB_RECORD, LOG_INFO,
LOC +
"run(): " +
"end");
880 QStringList tokens = result.split(
':', Qt::SkipEmptyParts);
881 if (tokens.size() > 1)
886 LOG(VB_RECORD, LOG_ERR,
LOC +
887 QString(
"Bad response to 'APIVersion?' - '%1'. "
888 "Expecting 1, 2 or 3").arg(result));
906 m_loc = result.mid(3);
921 LOG(VB_RECORD, LOG_WARNING,
LOC +
"OpenApp: already open!");
929 LOG(VB_GENERAL, LOG_ERR,
LOC +
"ExternIO failed: " +
ENO);
934 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
"Spawn '%1'").arg(
m_device));
938 LOG(VB_GENERAL, LOG_ERR,
960 LOG(VB_RECORD, LOG_ERR,
LOC +
"Application is not responding.");
970 LOG(VB_RECORD, LOG_ERR,
LOC +
971 QString(
"Bad response to 'HasTuner?' - '%1'").arg(result));
979 LOG(VB_RECORD, LOG_ERR,
LOC +
980 QString(
"Bad response to 'HasPictureAttributes?' - '%1'")
989 result.startsWith(
"OK:Poll");
991 LOG(VB_RECORD, LOG_INFO,
LOC +
"App opened successfully");
992 LOG(VB_RECORD, LOG_INFO,
LOC +
993 QString(
"Capabilities: tuner(%1) "
994 "Picture attributes(%2) "
1009 if (
m_io ==
nullptr)
1011 LOG(VB_RECORD, LOG_WARNING,
LOC +
1012 "WARNING: Unable to communicate with external app.");
1041 LOG(VB_RECORD, LOG_INFO,
LOC +
"CloseRecorder");
1046 if (!result.startsWith(
"OK"))
1048 LOG(VB_RECORD, LOG_INFO,
LOC +
1049 "CloseRecorder failed, sending kill.");
1051 QString full_command = QString(
"%1").arg(
m_args.join(
" "));
1056 std::this_thread::sleep_for(50ms);
1059 LOG(VB_GENERAL, LOG_ERR,
1060 QString(
"Unable to kill existing '%1'.")
1061 .arg(full_command));
1076 LOG(VB_RECORD, LOG_WARNING,
LOC +
"Restarting stream.");
1082 std::this_thread::sleep_for(1s);
1114 sit.key()->ProcessData(
reinterpret_cast<const uint8_t *
>
1119 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
"Replayed %1 bytes")
1141 LOG(VB_RECORD, LOG_INFO,
LOC +
1142 QString(
"StartStreaming with %1 current listeners")
1147 LOG(VB_GENERAL, LOG_ERR,
LOC +
"External Recorder not started.");
1155 LogLevel_t level = LOG_ERR;
1156 if (result.startsWith(
"warn", Qt::CaseInsensitive))
1157 level = LOG_WARNING;
1161 LOG(VB_GENERAL, level,
LOC + QString(
"StartStreaming failed: '%1'")
1167 LOG(VB_RECORD, LOG_INFO,
LOC +
"Streaming started");
1171 LOG(VB_RECORD, LOG_INFO,
LOC +
"Already streaming");
1176 LOG(VB_RECORD, LOG_INFO,
LOC +
1177 QString(
"StartStreaming %1 listeners")
1187 LOG(VB_RECORD, LOG_INFO,
LOC +
1188 QString(
"StopStreaming %1 listeners")
1193 LOG(VB_RECORD, LOG_INFO,
LOC +
1194 "StopStreaming requested, but we are not streaming!");
1200 LOG(VB_RECORD, LOG_INFO,
LOC +
1201 QString(
"StopStreaming delayed, still have %1 listeners")
1206 LOG(VB_RECORD, LOG_INFO,
LOC +
"StopStreaming");
1217 LOG(VB_GENERAL, LOG_ERR,
LOC +
"External Recorder not started.");
1224 LogLevel_t level = LOG_ERR;
1225 if (result.startsWith(
"warn", Qt::CaseInsensitive))
1226 level = LOG_WARNING;
1230 LOG(VB_GENERAL, level,
LOC + QString(
"StopStreaming: '%1'")
1237 LOG(VB_RECORD, LOG_INFO,
LOC +
"Streaming stopped");
1244 std::chrono::milliseconds
timeout,
1252 QVariantMap vresult;
1253 QByteArray response;
1254 QStringList tokens = cmd.split(
':');
1255 vcmd[
"command"] = tokens[0];
1256 if (tokens.size() > 1)
1257 vcmd[
"value"] = tokens[1];
1259 LOG(VB_RECORD, LOG_DEBUG,
LOC +
1260 QString(
"Arguments: %1").arg(tokens.join(
"\n")));
1263 result = QString(
"%1:%2").arg(vresult[
"status"].
toString(),
1272 LOG(VB_RECORD, LOG_ERR,
LOC +
1273 QString(
"Invalid API version %1. Expected 1 or 2").arg(
m_apiVersion));
1279 std::chrono::milliseconds
timeout,
1282 LOG(VB_RECORD, LOG_DEBUG,
LOC + QString(
"ProcessVer1('%1')")
1285 for (
uint cnt = 0; cnt < retry_cnt; ++cnt)
1291 LOG(VB_RECORD, LOG_ERR,
LOC +
"External I/O not ready!");
1295 QByteArray buf(cmd.toUtf8(), cmd.size());
1300 LOG(VB_GENERAL, LOG_ERR,
LOC +
"External Recorder in bad state: " +
1318 LOG(VB_GENERAL, LOG_ERR,
LOC +
1319 "Failed to read from External Recorder: " +
1326 if (result.startsWith(
"STATUS:ERR") ||
1327 result.startsWith(
"0:STATUS:ERR"))
1329 LOG(VB_RECORD, LOG_ERR,
LOC + result);
1330 result.remove(0, result.indexOf(
":ERR") + 1);
1335 if (!result.startsWith(
"STATUS") && !result.startsWith(
"0:STATUS"))
1337 LOG(VB_RECORD, LOG_INFO,
LOC +
1338 QString(
"Ignoring response '%1'").arg(result));
1341 if (result.size() < 1)
1343 LOG(VB_GENERAL, LOG_WARNING,
LOC +
1344 QString(
"External Recorder did not respond to '%1'").arg(cmd));
1348 bool okay = result.startsWith(
"OK");
1349 if (okay || result.startsWith(
"WARN") || result.startsWith(
"ERR"))
1351 LogLevel_t level = LOG_INFO;
1355 level = LOG_WARNING;
1356 else if (cmd.startsWith(
"SendBytes"))
1359 LOG(VB_RECORD, level,
1360 LOC + QString(
"ProcessCommand('%1') = '%2' took %3ms %4")
1362 QString::number(timer.
elapsed().count()),
1363 okay ?
"" :
"<-- NOTE"));
1367 LOG(VB_GENERAL, LOG_WARNING,
LOC +
1368 QString(
"External Recorder invalid response to '%1': '%2'")
1374 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Too many I/O errors.");
1385 std::chrono::milliseconds
timeout,
1391 for (
uint cnt = 0; cnt < retry_cnt; ++cnt)
1393 QString cmd = QString(
"%1:%2").arg(++
m_serialNo).arg(command);
1395 LOG(VB_RECORD, LOG_DEBUG,
LOC + QString(
"ProcessVer2('%1') serial(%2)")
1402 LOG(VB_RECORD, LOG_ERR,
LOC +
"External I/O not ready!");
1406 QByteArray buf(cmd.toUtf8(), cmd.size());
1411 LOG(VB_GENERAL, LOG_ERR,
LOC +
"External Recorder in bad state: " +
1427 LOG(VB_GENERAL, LOG_ERR,
LOC +
1428 "Failed to read from External Recorder: " +
1434 if (!result.isEmpty())
1437 tokens = result.split(
':', Qt::SkipEmptyParts);
1440 if (tokens.size() > 1 && tokens[0].toUInt() >=
m_serialNo)
1446 if (tokens[0].startsWith(
"ERR"))
1450 tokens.removeFirst();
1451 result = tokens.join(
':');
1452 bool err = (tokens.size() > 1 && tokens[1].startsWith(
"ERR"));
1453 LOG(VB_RECORD, (err ? LOG_WARNING : LOG_INFO),
LOC + raw);
1457 tokens.removeFirst();
1458 result = tokens.join(
':');
1466 LOG(VB_RECORD, LOG_ERR,
LOC +
1467 QString(
"ProcessVer2: Giving up waiting for response for "
1468 "command '%2'").arg(cmd));
1470 else if (tokens.size() < 2)
1472 LOG(VB_RECORD, LOG_ERR,
LOC +
1473 QString(
"Did not receive a valid response "
1474 "for command '%1', received '%2'").arg(cmd, result));
1478 LOG(VB_RECORD, LOG_ERR,
LOC +
1479 QString(
"ProcessVer2: Looking for serial no %1, "
1480 "but received %2 for command '%2'")
1481 .arg(QString::number(
m_serialNo), tokens[0], cmd));
1485 tokens.removeFirst();
1486 status = tokens[0].trimmed();
1487 result = tokens.join(
':');
1489 bool okay = (status ==
"OK");
1490 if (okay || status.startsWith(
"WARN") || status.startsWith(
"ERR"))
1492 LogLevel_t level = LOG_INFO;
1496 level = LOG_WARNING;
1497 else if (command.startsWith(
"SendBytes") ||
1498 (command.startsWith(
"TuneStatus") &&
1499 result ==
"OK:InProgress"))
1502 LOG(VB_RECORD, level,
1503 LOC + QString(
"ProcessV2('%1') = '%2' took %3ms %4")
1504 .arg(cmd, result, QString::number(timer.
elapsed().count()),
1505 okay ?
"" :
"<-- NOTE"));
1509 LOG(VB_GENERAL, LOG_WARNING,
LOC +
1510 QString(
"External Recorder invalid response to '%1': '%2'")
1516 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Too many I/O errors.");
1526 QVariantMap & elements,
1527 QByteArray & response,
1528 std::chrono::milliseconds
timeout,
1531 for (
uint cnt = 0; cnt < retry_cnt; ++cnt)
1533 QVariantMap query(vmsg);
1536 query[
"serial"] = serial;
1537 QString cmd = query[
"command"].toString();
1540 qdoc = QJsonDocument::fromVariant(query);
1541 QByteArray cmdbuf = qdoc.toJson(QJsonDocument::Compact);
1543 LOG(VB_RECORD, LOG_DEBUG,
LOC +
1544 QString(
"ProcessJson: %1").arg(QString(cmdbuf)));
1548 LOG(VB_GENERAL, LOG_ERR,
LOC +
"External Recorder in bad state: " +
1563 LOG(VB_GENERAL, LOG_ERR,
LOC +
1564 "Failed to read from External Recorder: " +
1570 if (!response.isEmpty())
1572 QJsonParseError parseError {};
1575 doc = QJsonDocument::fromJson(response, &parseError);
1577 if (parseError.error != QJsonParseError::NoError)
1579 LOG(VB_GENERAL, LOG_ERR,
LOC +
1580 QString(
"ExternalRecorder returned invalid JSON message: %1: %2\n%3\nfor\n%4")
1581 .arg(parseError.offset)
1582 .arg(parseError.errorString(),
1588 elements = doc.toVariant().toMap();
1589 if (!elements.contains(
"serial"))
1592 serial = elements[
"serial"].toInt();
1596 if (elements.contains(
"status"))
1598 LogLevel_t level { LOG_INFO };
1600 if (elements[
"status"] ==
"ERR")
1602 else if (elements[
"status"] ==
"WARN")
1603 level = LOG_WARNING;
1605 LOG(VB_RECORD, level,
LOC + QString(
"%1: %2")
1606 .arg(elements[
"status"].
toString(),
1615 LOG(VB_RECORD, LOG_ERR,
LOC +
1616 QString(
"ProcessJson: Giving up waiting for response for "
1617 "command '%2'").arg(QString(cmdbuf)));
1623 LOG(VB_RECORD, LOG_ERR,
LOC +
1624 QString(
"ProcessJson: Looking for serial no %1, "
1625 "but received %2 for command '%2'")
1628 .arg(QString(cmdbuf)));
1630 else if (!elements.contains(
"status"))
1632 LOG(VB_RECORD, LOG_ERR,
LOC +
1633 QString(
"ProcessJson: ExternalRecorder 'status' not found in %1")
1634 .arg(QString(response)));
1638 QString status = elements[
"status"].toString();
1639 bool okay = (status ==
"OK");
1640 if (okay || status ==
"WARN" || status ==
"ERR")
1642 LogLevel_t level = LOG_INFO;
1646 level = LOG_WARNING;
1647 else if (cmd ==
"SendBytes" ||
1648 (cmd ==
"TuneStatus?" &&
1649 elements[
"message"] ==
"InProgress"))
1652 LOG(VB_RECORD, level,
1653 LOC + QString(
"ProcessJson('%1') = %2:%3:%4 took %5ms %6")
1654 .arg(QString(cmdbuf))
1655 .arg(elements[
"serial"].toInt())
1656 .arg(elements[
"status"].
toString(),
1658 QString::number(timer.
elapsed().count()),
1659 okay ?
"" :
"<-- NOTE")
1664 LOG(VB_GENERAL, LOG_WARNING,
LOC +
1665 QString(
"External Recorder invalid response to '%1': '%2'")
1666 .arg(QString(cmdbuf),
1667 QString(response)));
1672 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Too many I/O errors.");
1683 QByteArray response;
1690 LOG(VB_RECORD, LOG_ERR,
LOC +
"External I/O not ready!");
1696 LOG(VB_GENERAL, LOG_ERR,
"External Recorder in bad state: " +
1702 while (!response.isEmpty())
1706 QJsonParseError parseError {};
1708 QVariantMap elements;
1710 doc = QJsonDocument::fromJson(response, &parseError);
1712 if (parseError.error != QJsonParseError::NoError)
1714 LOG(VB_GENERAL, LOG_ERR,
LOC +
1715 QString(
"ExternalRecorder returned invalid JSON message: %1: %2\n%3\n")
1716 .arg(parseError.offset)
1717 .arg(parseError.errorString(), QString(response)));
1721 elements = doc.toVariant().toMap();
1722 if (elements.contains(
"command") &&
1723 elements[
"command"] ==
"STATUS")
1725 LogLevel_t level { LOG_INFO };
1726 QString status = elements[
"status"].toString();
1727 if (status.startsWith(
"err", Qt::CaseInsensitive))
1732 else if (status.startsWith(
"warn",
1733 Qt::CaseInsensitive))
1735 level = LOG_WARNING;
1737 else if (status.startsWith(
"damage",
1738 Qt::CaseInsensitive))
1740 level = LOG_WARNING;
1743 LOG(VB_RECORD, level,
1744 LOC + QString(
"%1:%2%3")
1745 .arg(status, elements[
"message"].
toString(),
1752 QString res = QString(response);
1755 QStringList tokens = res.split(
':', Qt::SkipEmptyParts);
1756 tokens.removeFirst();
1757 res = tokens.join(
':');
1758 for (
int idx = 1; idx < tokens.size(); ++idx)
1760 err |= tokens[idx].startsWith(
"ERR",
1761 Qt::CaseInsensitive);
1762 m_damaged |= tokens[idx].startsWith(
"damage",
1763 Qt::CaseInsensitive);
1768 err |= res.startsWith(
"STATUS:ERR",
1769 Qt::CaseInsensitive);
1770 m_damaged |= res.startsWith(
"STATUS:DAMAGE",
1771 Qt::CaseInsensitive);
1774 LOG(VB_RECORD, (err ? LOG_WARNING : LOG_INFO),
LOC + res);
bool Ready(int fd, std::chrono::milliseconds timeout, const QString &what)
int Write(const QByteArray &buffer)
static bool KillIfRunning(const QString &cmd)
int Read(QByteArray &buffer, int maxlen, std::chrono::milliseconds timeout=2500ms)
static constexpr uint8_t kMaxErrorCnt
QByteArray GetStatus(std::chrono::milliseconds timeout=2500ms)
ExternIO(const QString &app, const QStringList &args)
QString ErrorString(void) const
QByteArray m_replayBuffer
bool ProcessVer1(const QString &cmd, QString &result, std::chrono::milliseconds timeout, uint retry_cnt)
static ExternalStreamHandler * Get(const QString &devname, int inputid, int majorid)
void run(void) override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
int StreamingCount(void) const
static QMutex s_handlersLock
bool m_hasPictureAttributes
ExternalStreamHandler(const QString &path, int inputid, int majorid)
QAtomicInt m_streamingCnt
bool ProcessVer2(const QString &command, QString &result, std::chrono::milliseconds timeout, uint retry_cnt)
static QMap< int, uint > s_handlersRefCnt
bool ProcessCommand(const QString &cmd, QString &result, std::chrono::milliseconds timeout=4s, uint retry_cnt=3)
QString UpdateDescription(void)
bool StartStreaming(void)
static QMap< int, ExternalStreamHandler * > s_handlers
bool ProcessJson(const QVariantMap &vmsg, QVariantMap &elements, QByteArray &response, std::chrono::milliseconds timeout=4s, uint retry_cnt=3)
void PriorityEvent(int fd) override
static void Return(ExternalStreamHandler *&ref, int inputid)
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
void setObjectName(const QString &name)
A QElapsedTimer based timer to replace use of QTime as a timer.
std::chrono::milliseconds restart(void)
Returns milliseconds elapsed since last start() or restart() and resets the count.
std::chrono::milliseconds elapsed(void)
Returns milliseconds elapsed since last start() or restart()
bool isRunning(void) const
Returns true if start() or restart() has been called at least once since construction and since any c...
void stop(void)
Stops timer, next call to isRunning() will return false and any calls to elapsed() or restart() will ...
void start(void)
starts measuring elapsed time.
StreamDataList m_streamDataList
volatile bool m_runningDesired
bool RemoveAllPIDFilters(void)
void SetRunning(bool running, bool using_buffering, bool using_section_reader)
bool UpdateFiltersFromStreamData(void)
QRecursiveMutex m_listenerLock
@ GENERIC_EXIT_DAEMONIZING_ERROR
Error daemonizing or execl.
@ GENERIC_EXIT_PIPE_FAILURE
Error creating I/O pipes.
#define ENO
This can be appended to the LOG args with "+".
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Convenience inline random number generator functions.
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
def read(device=None, features=[])
def write(text, progress=True)