4#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
5#include <QtSystemDetection>
26#include <QJsonDocument>
43#define LOC QString("ExternSH[%1](%2): ").arg(m_inputId).arg(m_loc)
46 const QStringList &
args)
47 : m_app(QFileInfo(app)),
48 m_status(&m_statusBuf, QIODevice::ReadWrite)
52 m_error = QString(
"ExternIO: '%1' does not exist.").arg(app);
57 m_error = QString(
"ExternIO: '%1' is not readable.")
58 .arg(
m_app.canonicalFilePath());
61 if (!
m_app.isExecutable())
63 m_error = QString(
"ExternIO: '%1' is not executable.")
64 .arg(
m_app.canonicalFilePath());
85 [[maybe_unused]] std::chrono::milliseconds
timeout,
86 [[maybe_unused]]
const QString & what)
89 std::array<struct pollfd,2> m_poll {};
92 m_poll[0].events = POLLIN | POLLPRI;
93 int ret = poll(m_poll.data(), 1,
timeout.count());
95 if (m_poll[0].revents & POLLHUP)
97 m_error = what +
" poll eof (POLLHUP)";
100 if (m_poll[0].revents & POLLNVAL)
102 LOG(VB_GENERAL, LOG_ERR,
"poll error");
105 if (m_poll[0].revents & POLLIN)
110 if ((EOVERFLOW == errno))
122 LOG(VB_RECORD, LOG_ERR,
123 QString(
"ExternIO::Read: already in error state: '%1'")
146 m_error =
"Failed to read from External Recorder: " +
ENO;
147 LOG(VB_RECORD, LOG_WARNING,
148 "External Recorder not ready. Giving up.");
152 LOG(VB_RECORD, LOG_WARNING,
153 QString(
"External Recorder not ready. Will retry (%1/%2).")
155 std::this_thread::sleep_for(100ms);
160 m_error =
"Failed to read from External Recorder: " +
ENO;
174 LOG(VB_RECORD, LOG_DEBUG,
175 QString(
"ExternIO::Read '%1' bytes, buffer size %2")
176 .arg(len).arg(buffer.size()));
185 LOG(VB_RECORD, LOG_ERR,
186 QString(
"ExternIO::GetStatus: already in error state: '%1'")
194 std::array<char,2048> buffer {};
196 m_status << QString::fromLatin1(buffer.data(), len);
204 LOG(VB_RECORD, LOG_DEBUG, QString(
"ExternIO::GetStatus '%1'")
214 LOG(VB_RECORD, LOG_ERR,
215 QString(
"ExternIO::Write: already in error state: '%1'")
220 LOG(VB_RECORD, LOG_DEBUG, QString(
"ExternIO::Write('%1')")
221 .arg(QString(buffer).simplified()));
223 int len =
write(
m_appIn, buffer.constData(), buffer.size());
224 if (len != buffer.size())
228 LOG(VB_RECORD, LOG_WARNING,
229 QString(
"ExternIO::Write: only wrote %1 of %2 bytes '%3'")
230 .arg(len).arg(buffer.size()).arg(QString(buffer)));
234 m_error = QString(
"ExternIO: Failed to write '%1' to app's stdin: ")
235 .arg(QString(buffer)) +
ENO;
245 LOG(VB_RECORD, LOG_INFO, QString(
"ExternIO::Run()"));
258#elif defined( Q_OS_WINDOWS )
261 QString grp = QString(
"pgrep -x -f -- \"%1\" 2>&1 > /dev/null").arg(cmd);
262 QString kil = QString(
"pkill --signal 15 -x -f -- \"%1\" 2>&1 > /dev/null")
265 int res_grp = system(grp.toUtf8().constData());
268 LOG(VB_RECORD, LOG_DEBUG, QString(
"'%1' not running.").arg(cmd));
272 LOG(VB_RECORD, LOG_WARNING, QString(
"'%1' already running, killing...")
274 int res_kil = system(kil.toUtf8().constData());
276 LOG(VB_GENERAL, LOG_WARNING, QString(
"'%1' failed: %2")
279 res_grp = system(grp.toUtf8().constData());
282 LOG(
WEXITSTATUS(res_kil) == 0 ? VB_RECORD : VB_GENERAL, LOG_WARNING,
283 QString(
"'%1' terminated.").arg(cmd));
287 std::this_thread::sleep_for(50ms);
289 kil = QString(
"pkill --signal 9 -x -f \"%1\" 2>&1 > /dev/null").arg(cmd);
290 res_kil = system(kil.toUtf8().constData());
292 LOG(VB_GENERAL, LOG_WARNING, QString(
"'%1' failed: %2")
295 res_grp = system(grp.toUtf8().constData());
296 LOG(
WEXITSTATUS(res_kil) == 0 ? VB_RECORD : VB_GENERAL, LOG_WARNING,
298 .arg(cmd,
WEXITSTATUS(res_grp) == 0 ?
"sill running" :
"terminated"));
309 LOG(VB_RECORD, LOG_INFO, QString(
"ExternIO in bad state: '%1'")
314 QString full_command = QString(
"%1").arg(
m_args.join(
" "));
319 std::this_thread::sleep_for(50ms);
322 m_error = QString(
"Unable to kill existing '%1'.")
330 LOG(VB_RECORD, LOG_INFO, QString(
"ExternIO::Fork '%1'").arg(full_command));
332 std::array<int,2> in = {-1, -1};
333 std::array<int,2> out = {-1, -1};
334 std::array<int,2> err = {-1, -1};
336 if (pipe(in.data()) < 0)
341 if (pipe(out.data()) < 0)
348 if (pipe(err.data()) < 0)
382 LOG(VB_GENERAL, LOG_WARNING,
383 "ExternIO::Fork(): Failed to set O_NONBLOCK for FD: " +
ENO);
384 std::this_thread::sleep_for(2s);
388 LOG(VB_RECORD, LOG_INFO,
"Spawned");
396 if (dup2( in[0], 0) < 0)
398 std::cerr <<
"dup2(stdin) failed: " << strerror(errno);
401 else if (dup2(out[1], 1) < 0)
403 std::cerr <<
"dup2(stdout) failed: " << strerror(errno);
406 else if (dup2(err[1], 2) < 0)
408 std::cerr <<
"dup2(stderr) failed: " << strerror(errno);
414 close_range(3, sysconf(_SC_OPEN_MAX) - 1, 0);
416 for (
int i = sysconf(_SC_OPEN_MAX) - 1; i > 2; --i)
423 if (setpgid(0,0) < 0)
425 std::cerr <<
"ExternIO: "
426 <<
"setpgid() failed: "
427 << strerror(errno) << std::endl;
431 char *command = strdup(
m_app.canonicalFilePath()
432 .toUtf8().constData());
434 char **arguments =
new char*[
m_args.size() + 1];
435 for (
int i = 0; i <
m_args.size(); ++i)
437 int len =
m_args[i].size() + 1;
438 arguments[i] =
new char[len];
439 memcpy(arguments[i],
m_args[i].toStdString().c_str(), len);
441 arguments[
m_args.size()] =
nullptr;
443 if (execv(command, arguments) < 0)
446 std::cerr <<
"ExternIO: "
447 <<
"execv() failed: "
448 << strerror(errno) << std::endl;
452 std::cerr <<
"ExternIO: "
453 <<
"execv() should not be here?: "
454 << strerror(errno) << std::endl;
469 int inputid,
int majorid)
473 QMap<int, ExternalStreamHandler*>::iterator it =
s_handlers.find(majorid);
481 LOG(VB_RECORD, LOG_INFO,
482 QString(
"ExternSH[%1:%2]: Creating new stream handler for %3 "
484 .arg(inputid).arg(majorid).arg(devname));
490 LOG(VB_RECORD, LOG_INFO,
491 QString(
"ExternSH[%1:%2]: Using existing stream handler for %3")
492 .arg(inputid).arg(majorid).arg(devname) +
493 QString(
" (%1 in use)").arg(rcount));
510 QMap<int, ExternalStreamHandler*>::iterator it =
518 LOG(VB_RECORD, LOG_INFO,
519 QString(
"ExternSH[%1:%2]: Return handler (%3 still in use)")
520 .arg(inputid).arg(majorid).arg(*rit));
527 LOG(VB_RECORD, LOG_INFO,
528 QString(
"ExternSH[%1:%2]: Closing handler (0 in use)")
529 .arg(inputid).arg(majorid));
535 LOG(VB_GENERAL, LOG_ERR,
536 QString(
"ExternSH[%1:%2]: Error: No handler to return!")
537 .arg(inputid).arg(majorid));
557 m_args = path.split(
' ',Qt::SkipEmptyParts) +
564 if (!
m_args.contains(
"--quiet") && !
m_args.contains(
"-q"))
567 m_args <<
"--inputid" << QString::number(majorid);
568 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
"args \"%1\"")
573 LOG(VB_GENERAL, LOG_ERR,
LOC +
574 QString(
"Failed to start %1").arg(
m_device));
591 uint restart_cnt = 0;
595 bool good_data =
false;
596 uint data_proc_err = 0;
597 uint data_short_err = 0;
601 LOG(VB_GENERAL, LOG_ERR,
LOC +
602 QString(
"%1 is not running.").arg(
m_device));
605 status_timer.
start();
609 LOG(VB_RECORD, LOG_INFO,
LOC +
"run(): begin");
614 ready_cmd =
"SendBytes";
623 LOG(VB_RECORD, LOG_WARNING,
LOC +
"TS not open yet.");
624 std::this_thread::sleep_for(10ms);
630 std::this_thread::sleep_for(10ms);
640 LOG(VB_RECORD, LOG_WARNING,
LOC +
641 "Internal buffer too full to accept more data from "
642 "external application.");
648 if (result.startsWith(
"ERR"))
650 LOG(VB_GENERAL, LOG_ERR,
LOC +
651 QString(
"Aborting: %1 -> %2")
652 .arg(ready_cmd, result));
658 std::this_thread::sleep_for(20s);
661 LOG(VB_RECORD, LOG_ERR,
LOC +
662 "Failed to restart stream.");
673 if (status_timer.
elapsed() >= 2s)
681 std::this_thread::sleep_for(20s);
684 LOG(VB_RECORD, LOG_ERR,
LOC +
685 "Failed to restart stream.");
702 if (result.startsWith(
"ERR"))
704 LOG(VB_GENERAL, LOG_ERR,
LOC +
705 QString(
"Aborting: XOFF -> %2")
719 read_len =
m_io->
Read(buffer, sz, 100ms);
730 nodata_timer.
start();
733 if (nodata_timer.
elapsed() >= 50s)
735 LOG(VB_GENERAL, LOG_WARNING,
LOC +
736 "No data for 50 seconds, Restarting stream.");
739 LOG(VB_RECORD, LOG_ERR,
LOC +
740 "Failed to restart stream.");
748 std::this_thread::sleep_for(50ms);
762 LOG(VB_GENERAL, LOG_ERR,
LOC +
"I/O thread has disappeared!");
768 LOG(VB_GENERAL, LOG_ERR,
LOC +
769 QString(
"Fatal Error from External Recorder: %1")
776 len = remainder = buffer.size();
783 if (
m_xon && data_short_err++ == 0)
784 LOG(VB_RECORD, LOG_INFO,
LOC +
"Waiting for a full TS packet.");
785 std::this_thread::sleep_for(50us);
790 if (data_short_err > 1)
792 LOG(VB_RECORD, LOG_INFO,
LOC +
793 QString(
"Waited for a full TS packet %1 times.")
794 .arg(data_short_err));
808 remainder = sit.key()->ProcessData
809 (
reinterpret_cast<const uint8_t *
>
810 (buffer.constData()), buffer.size());
821 LOG(VB_RECORD, LOG_WARNING,
LOC +
822 QString(
"Replay size truncated to %1 bytes")
832 good_data = (len != 0U);
834 else if (len > remainder)
836 buffer.remove(0, len - remainder);
837 good_data = (len != 0U);
839 else if (len == remainder)
848 if (data_proc_err > 1)
850 LOG(VB_RECORD, LOG_WARNING,
LOC +
851 QString(
"Failed to process the data received %1 times.")
852 .arg(data_proc_err));
859 if (data_proc_err++ == 0)
861 LOG(VB_RECORD, LOG_WARNING,
LOC +
862 "Failed to process the data received");
867 LOG(VB_RECORD, LOG_INFO,
LOC +
"run(): " +
868 QString(
"%1 shutdown").arg(
m_bError ?
"Error" :
"Normal"));
873 LOG(VB_RECORD, LOG_INFO,
LOC +
"run(): " +
"end");
884 QStringList tokens = result.split(
':', Qt::SkipEmptyParts);
885 if (tokens.size() > 1)
890 LOG(VB_RECORD, LOG_ERR,
LOC +
891 QString(
"Bad response to 'APIVersion?' - '%1'. "
892 "Expecting 1, 2 or 3").arg(result));
910 m_loc = result.mid(3);
925 LOG(VB_RECORD, LOG_WARNING,
LOC +
"OpenApp: already open!");
933 LOG(VB_GENERAL, LOG_ERR,
LOC +
"ExternIO failed: " +
ENO);
938 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
"Spawn '%1'").arg(
m_device));
942 LOG(VB_GENERAL, LOG_ERR,
964 LOG(VB_RECORD, LOG_ERR,
LOC +
"Application is not responding.");
974 LOG(VB_RECORD, LOG_ERR,
LOC +
975 QString(
"Bad response to 'HasTuner?' - '%1'").arg(result));
983 LOG(VB_RECORD, LOG_ERR,
LOC +
984 QString(
"Bad response to 'HasPictureAttributes?' - '%1'")
993 result.startsWith(
"OK:Poll");
995 LOG(VB_RECORD, LOG_INFO,
LOC +
"App opened successfully");
996 LOG(VB_RECORD, LOG_INFO,
LOC +
997 QString(
"Capabilities: tuner(%1) "
998 "Picture attributes(%2) "
1013 if (
m_io ==
nullptr)
1015 LOG(VB_RECORD, LOG_WARNING,
LOC +
1016 "WARNING: Unable to communicate with external app.");
1045 LOG(VB_RECORD, LOG_INFO,
LOC +
"CloseRecorder");
1050 if (!result.startsWith(
"OK"))
1052 LOG(VB_RECORD, LOG_INFO,
LOC +
1053 "CloseRecorder failed, sending kill.");
1055 QString full_command = QString(
"%1").arg(
m_args.join(
" "));
1060 std::this_thread::sleep_for(50ms);
1063 LOG(VB_GENERAL, LOG_ERR,
1064 QString(
"Unable to kill existing '%1'.")
1065 .arg(full_command));
1080 LOG(VB_RECORD, LOG_WARNING,
LOC +
"Restarting stream.");
1086 std::this_thread::sleep_for(1s);
1118 sit.key()->ProcessData(
reinterpret_cast<const uint8_t *
>
1123 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
"Replayed %1 bytes")
1145 LOG(VB_RECORD, LOG_INFO,
LOC +
1146 QString(
"StartStreaming with %1 current listeners")
1151 LOG(VB_GENERAL, LOG_ERR,
LOC +
"External Recorder not started.");
1159 LogLevel_t level = LOG_ERR;
1160 if (result.startsWith(
"warn", Qt::CaseInsensitive))
1161 level = LOG_WARNING;
1165 LOG(VB_GENERAL, level,
LOC + QString(
"StartStreaming failed: '%1'")
1171 LOG(VB_RECORD, LOG_INFO,
LOC +
"Streaming started");
1175 LOG(VB_RECORD, LOG_INFO,
LOC +
"Already streaming");
1180 LOG(VB_RECORD, LOG_INFO,
LOC +
1181 QString(
"StartStreaming %1 listeners")
1191 LOG(VB_RECORD, LOG_INFO,
LOC +
1192 QString(
"StopStreaming %1 listeners")
1197 LOG(VB_RECORD, LOG_INFO,
LOC +
1198 "StopStreaming requested, but we are not streaming!");
1204 LOG(VB_RECORD, LOG_INFO,
LOC +
1205 QString(
"StopStreaming delayed, still have %1 listeners")
1210 LOG(VB_RECORD, LOG_INFO,
LOC +
"StopStreaming");
1221 LOG(VB_GENERAL, LOG_ERR,
LOC +
"External Recorder not started.");
1228 LogLevel_t level = LOG_ERR;
1229 if (result.startsWith(
"warn", Qt::CaseInsensitive))
1230 level = LOG_WARNING;
1234 LOG(VB_GENERAL, level,
LOC + QString(
"StopStreaming: '%1'")
1241 LOG(VB_RECORD, LOG_INFO,
LOC +
"Streaming stopped");
1248 std::chrono::milliseconds
timeout,
1256 QVariantMap vresult;
1257 QByteArray response;
1258 QStringList tokens = cmd.split(
':');
1259 vcmd[
"command"] = tokens[0];
1260 if (tokens.size() > 1)
1261 vcmd[
"value"] = tokens[1];
1263 LOG(VB_RECORD, LOG_DEBUG,
LOC +
1264 QString(
"Arguments: %1").arg(tokens.join(
"\n")));
1267 result = QString(
"%1:%2").arg(vresult[
"status"].
toString(),
1276 LOG(VB_RECORD, LOG_ERR,
LOC +
1277 QString(
"Invalid API version %1. Expected 1 or 2").arg(
m_apiVersion));
1283 std::chrono::milliseconds
timeout,
1286 LOG(VB_RECORD, LOG_DEBUG,
LOC + QString(
"ProcessVer1('%1')")
1289 for (
uint cnt = 0; cnt < retry_cnt; ++cnt)
1295 LOG(VB_RECORD, LOG_ERR,
LOC +
"External I/O not ready!");
1299 QByteArray buf(cmd.toUtf8(), cmd.size());
1304 LOG(VB_GENERAL, LOG_ERR,
LOC +
"External Recorder in bad state: " +
1322 LOG(VB_GENERAL, LOG_ERR,
LOC +
1323 "Failed to read from External Recorder: " +
1330 if (result.startsWith(
"STATUS:ERR") ||
1331 result.startsWith(
"0:STATUS:ERR"))
1333 LOG(VB_RECORD, LOG_ERR,
LOC + result);
1334 result.remove(0, result.indexOf(
":ERR") + 1);
1339 if (!result.startsWith(
"STATUS") && !result.startsWith(
"0:STATUS"))
1341 LOG(VB_RECORD, LOG_INFO,
LOC +
1342 QString(
"Ignoring response '%1'").arg(result));
1345 if (result.size() < 1)
1347 LOG(VB_GENERAL, LOG_WARNING,
LOC +
1348 QString(
"External Recorder did not respond to '%1'").arg(cmd));
1352 bool okay = result.startsWith(
"OK");
1353 if (okay || result.startsWith(
"WARN") || result.startsWith(
"ERR"))
1355 LogLevel_t level = LOG_INFO;
1359 level = LOG_WARNING;
1360 else if (cmd.startsWith(
"SendBytes"))
1363 LOG(VB_RECORD, level,
1364 LOC + QString(
"ProcessCommand('%1') = '%2' took %3ms %4")
1366 QString::number(timer.
elapsed().count()),
1367 okay ?
"" :
"<-- NOTE"));
1371 LOG(VB_GENERAL, LOG_WARNING,
LOC +
1372 QString(
"External Recorder invalid response to '%1': '%2'")
1378 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Too many I/O errors.");
1389 std::chrono::milliseconds
timeout,
1395 for (
uint cnt = 0; cnt < retry_cnt; ++cnt)
1397 QString cmd = QString(
"%1:%2").arg(++
m_serialNo).arg(command);
1399 LOG(VB_RECORD, LOG_DEBUG,
LOC + QString(
"ProcessVer2('%1') serial(%2)")
1406 LOG(VB_RECORD, LOG_ERR,
LOC +
"External I/O not ready!");
1410 QByteArray buf(cmd.toUtf8(), cmd.size());
1415 LOG(VB_GENERAL, LOG_ERR,
LOC +
"External Recorder in bad state: " +
1431 LOG(VB_GENERAL, LOG_ERR,
LOC +
1432 "Failed to read from External Recorder: " +
1438 if (!result.isEmpty())
1441 tokens = result.split(
':', Qt::SkipEmptyParts);
1444 if (tokens.size() > 1 && tokens[0].toUInt() >=
m_serialNo)
1450 if (tokens[0].startsWith(
"ERR"))
1454 tokens.removeFirst();
1455 result = tokens.join(
':');
1456 bool err = (tokens.size() > 1 && tokens[1].startsWith(
"ERR"));
1457 LOG(VB_RECORD, (err ? LOG_WARNING : LOG_INFO),
LOC + raw);
1461 tokens.removeFirst();
1462 result = tokens.join(
':');
1470 LOG(VB_RECORD, LOG_ERR,
LOC +
1471 QString(
"ProcessVer2: Giving up waiting for response for "
1472 "command '%2'").arg(cmd));
1474 else if (tokens.size() < 2)
1476 LOG(VB_RECORD, LOG_ERR,
LOC +
1477 QString(
"Did not receive a valid response "
1478 "for command '%1', received '%2'").arg(cmd, result));
1482 LOG(VB_RECORD, LOG_ERR,
LOC +
1483 QString(
"ProcessVer2: Looking for serial no %1, "
1484 "but received %2 for command '%2'")
1485 .arg(QString::number(
m_serialNo), tokens[0], cmd));
1489 tokens.removeFirst();
1490 status = tokens[0].trimmed();
1491 result = tokens.join(
':');
1493 bool okay = (status ==
"OK");
1494 if (okay || status.startsWith(
"WARN") || status.startsWith(
"ERR"))
1496 LogLevel_t level = LOG_INFO;
1500 level = LOG_WARNING;
1501 else if (command.startsWith(
"SendBytes") ||
1502 (command.startsWith(
"TuneStatus") &&
1503 result ==
"OK:InProgress"))
1506 LOG(VB_RECORD, level,
1507 LOC + QString(
"ProcessV2('%1') = '%2' took %3ms %4")
1508 .arg(cmd, result, QString::number(timer.
elapsed().count()),
1509 okay ?
"" :
"<-- NOTE"));
1513 LOG(VB_GENERAL, LOG_WARNING,
LOC +
1514 QString(
"External Recorder invalid response to '%1': '%2'")
1520 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Too many I/O errors.");
1530 QVariantMap & elements,
1531 QByteArray & response,
1532 std::chrono::milliseconds
timeout,
1535 for (
uint cnt = 0; cnt < retry_cnt; ++cnt)
1537 QVariantMap query(vmsg);
1540 query[
"serial"] = serial;
1541 QString cmd = query[
"command"].toString();
1544 qdoc = QJsonDocument::fromVariant(query);
1545 QByteArray cmdbuf = qdoc.toJson(QJsonDocument::Compact);
1547 LOG(VB_RECORD, LOG_DEBUG,
LOC +
1548 QString(
"ProcessJson: %1").arg(QString(cmdbuf)));
1552 LOG(VB_GENERAL, LOG_ERR,
LOC +
"External Recorder in bad state: " +
1567 LOG(VB_GENERAL, LOG_ERR,
LOC +
1568 "Failed to read from External Recorder: " +
1574 if (!response.isEmpty())
1576 QJsonParseError parseError {};
1579 doc = QJsonDocument::fromJson(response, &parseError);
1581 if (parseError.error != QJsonParseError::NoError)
1583 LOG(VB_GENERAL, LOG_ERR,
LOC +
1584 QString(
"ExternalRecorder returned invalid JSON message: %1: %2\n%3\nfor\n%4")
1585 .arg(parseError.offset)
1586 .arg(parseError.errorString(),
1592 elements = doc.toVariant().toMap();
1593 if (!elements.contains(
"serial"))
1596 serial = elements[
"serial"].toInt();
1600 if (elements.contains(
"status"))
1602 LogLevel_t level { LOG_INFO };
1604 if (elements[
"status"] ==
"ERR")
1606 else if (elements[
"status"] ==
"WARN")
1607 level = LOG_WARNING;
1609 LOG(VB_RECORD, level,
LOC + QString(
"%1: %2")
1610 .arg(elements[
"status"].
toString(),
1619 LOG(VB_RECORD, LOG_ERR,
LOC +
1620 QString(
"ProcessJson: Giving up waiting for response for "
1621 "command '%2'").arg(QString(cmdbuf)));
1627 LOG(VB_RECORD, LOG_ERR,
LOC +
1628 QString(
"ProcessJson: Looking for serial no %1, "
1629 "but received %2 for command '%2'")
1632 .arg(QString(cmdbuf)));
1634 else if (!elements.contains(
"status"))
1636 LOG(VB_RECORD, LOG_ERR,
LOC +
1637 QString(
"ProcessJson: ExternalRecorder 'status' not found in %1")
1638 .arg(QString(response)));
1642 QString status = elements[
"status"].toString();
1643 bool okay = (status ==
"OK");
1644 if (okay || status ==
"WARN" || status ==
"ERR")
1646 LogLevel_t level = LOG_INFO;
1650 level = LOG_WARNING;
1651 else if (cmd ==
"SendBytes" ||
1652 (cmd ==
"TuneStatus?" &&
1653 elements[
"message"] ==
"InProgress"))
1656 LOG(VB_RECORD, level,
1657 LOC + QString(
"ProcessJson('%1') = %2:%3:%4 took %5ms %6")
1658 .arg(QString(cmdbuf))
1659 .arg(elements[
"serial"].toInt())
1660 .arg(elements[
"status"].
toString(),
1662 QString::number(timer.
elapsed().count()),
1663 okay ?
"" :
"<-- NOTE")
1668 LOG(VB_GENERAL, LOG_WARNING,
LOC +
1669 QString(
"External Recorder invalid response to '%1': '%2'")
1670 .arg(QString(cmdbuf),
1671 QString(response)));
1676 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Too many I/O errors.");
1687 QByteArray response;
1694 LOG(VB_RECORD, LOG_ERR,
LOC +
"External I/O not ready!");
1700 LOG(VB_GENERAL, LOG_ERR,
"External Recorder in bad state: " +
1706 while (!response.isEmpty())
1710 QJsonParseError parseError {};
1712 QVariantMap elements;
1714 doc = QJsonDocument::fromJson(response, &parseError);
1716 if (parseError.error != QJsonParseError::NoError)
1718 LOG(VB_GENERAL, LOG_ERR,
LOC +
1719 QString(
"ExternalRecorder returned invalid JSON message: %1: %2\n%3\n")
1720 .arg(parseError.offset)
1721 .arg(parseError.errorString(), QString(response)));
1725 elements = doc.toVariant().toMap();
1726 if (elements.contains(
"command") &&
1727 elements[
"command"] ==
"STATUS")
1729 LogLevel_t level { LOG_INFO };
1730 QString status = elements[
"status"].toString();
1731 if (status.startsWith(
"err", Qt::CaseInsensitive))
1736 else if (status.startsWith(
"warn",
1737 Qt::CaseInsensitive))
1739 level = LOG_WARNING;
1741 else if (status.startsWith(
"damage",
1742 Qt::CaseInsensitive))
1744 level = LOG_WARNING;
1747 LOG(VB_RECORD, level,
1748 LOC + QString(
"%1:%2%3")
1749 .arg(status, elements[
"message"].
toString(),
1756 QString res = QString(response);
1759 QStringList tokens = res.split(
':', Qt::SkipEmptyParts);
1760 tokens.removeFirst();
1761 res = tokens.join(
':');
1762 for (
int idx = 1; idx < tokens.size(); ++idx)
1764 err |= tokens[idx].startsWith(
"ERR",
1765 Qt::CaseInsensitive);
1766 m_damaged |= tokens[idx].startsWith(
"damage",
1767 Qt::CaseInsensitive);
1772 err |= res.startsWith(
"STATUS:ERR",
1773 Qt::CaseInsensitive);
1774 m_damaged |= res.startsWith(
"STATUS:DAMAGE",
1775 Qt::CaseInsensitive);
1778 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)