4#if QT_VERSION >= QT_VERSION_CHECK(6,5,0)
5#include <QtSystemDetection>
26#include <QJsonDocument>
30#include "libmythbase/mythconfig.h"
43#define LOC QString("ExternSH[%1](%2): ").arg(m_inputId).arg(m_loc)
46 const QStringList &
args)
47 : m_app(QFileInfo(app)),
48#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
49 m_status(&m_statusBuf, QIODevice::ReadWrite)
51 m_status(&m_statusBuf, QIODeviceBase::ReadWrite)
56 m_error = QString(
"ExternIO: '%1' does not exist.").arg(app);
61 m_error = QString(
"ExternIO: '%1' is not readable.")
62 .arg(
m_app.canonicalFilePath());
65 if (!
m_app.isExecutable())
67 m_error = QString(
"ExternIO: '%1' is not executable.")
68 .arg(
m_app.canonicalFilePath());
89 [[maybe_unused]] std::chrono::milliseconds
timeout,
90 [[maybe_unused]]
const QString & what)
93 std::array<struct pollfd,2> m_poll {};
96 m_poll[0].events = POLLIN | POLLPRI;
97 int ret = poll(m_poll.data(), 1,
timeout.count());
99 if (m_poll[0].revents & POLLHUP)
101 m_error = what +
" poll eof (POLLHUP)";
104 if (m_poll[0].revents & POLLNVAL)
106 LOG(VB_GENERAL, LOG_ERR,
"poll error");
109 if (m_poll[0].revents & POLLIN)
114 if ((EOVERFLOW == errno))
126 LOG(VB_RECORD, LOG_ERR,
127 QString(
"ExternIO::Read: already in error state: '%1'")
150 m_error =
"Failed to read from External Recorder: " +
ENO;
151 LOG(VB_RECORD, LOG_WARNING,
152 "External Recorder not ready. Giving up.");
156 LOG(VB_RECORD, LOG_WARNING,
157 QString(
"External Recorder not ready. Will retry (%1/%2).")
159 std::this_thread::sleep_for(100ms);
164 m_error =
"Failed to read from External Recorder: " +
ENO;
178 LOG(VB_RECORD, LOG_DEBUG,
179 QString(
"ExternIO::Read '%1' bytes, buffer size %2")
180 .arg(len).arg(buffer.size()));
189 LOG(VB_RECORD, LOG_ERR,
190 QString(
"ExternIO::GetStatus: already in error state: '%1'")
198 std::array<char,2048> buffer {};
200 m_status << QString::fromLatin1(buffer.data(), len);
208 LOG(VB_RECORD, LOG_DEBUG, QString(
"ExternIO::GetStatus '%1'")
218 LOG(VB_RECORD, LOG_ERR,
219 QString(
"ExternIO::Write: already in error state: '%1'")
224 LOG(VB_RECORD, LOG_DEBUG, QString(
"ExternIO::Write('%1')")
225 .arg(QString(buffer).simplified()));
227 int len =
write(
m_appIn, buffer.constData(), buffer.size());
228 if (len != buffer.size())
232 LOG(VB_RECORD, LOG_WARNING,
233 QString(
"ExternIO::Write: only wrote %1 of %2 bytes '%3'")
234 .arg(len).arg(buffer.size()).arg(QString(buffer)));
238 m_error = QString(
"ExternIO: Failed to write '%1' to app's stdin: ")
239 .arg(QString(buffer)) +
ENO;
249 LOG(VB_RECORD, LOG_INFO, QString(
"ExternIO::Run()"));
262#elif defined( Q_OS_WINDOWS )
265 QString grp = QString(
"pgrep -x -f -- \"%1\" 2>&1 > /dev/null").arg(cmd);
266 QString kil = QString(
"pkill --signal 15 -x -f -- \"%1\" 2>&1 > /dev/null")
269 int res_grp = system(grp.toUtf8().constData());
272 LOG(VB_RECORD, LOG_DEBUG, QString(
"'%1' not running.").arg(cmd));
276 LOG(VB_RECORD, LOG_WARNING, QString(
"'%1' already running, killing...")
278 int res_kil = system(kil.toUtf8().constData());
280 LOG(VB_GENERAL, LOG_WARNING, QString(
"'%1' failed: %2")
283 res_grp = system(grp.toUtf8().constData());
286 LOG(
WEXITSTATUS(res_kil) == 0 ? VB_RECORD : VB_GENERAL, LOG_WARNING,
287 QString(
"'%1' terminated.").arg(cmd));
291 std::this_thread::sleep_for(50ms);
293 kil = QString(
"pkill --signal 9 -x -f \"%1\" 2>&1 > /dev/null").arg(cmd);
294 res_kil = system(kil.toUtf8().constData());
296 LOG(VB_GENERAL, LOG_WARNING, QString(
"'%1' failed: %2")
299 res_grp = system(grp.toUtf8().constData());
300 LOG(
WEXITSTATUS(res_kil) == 0 ? VB_RECORD : VB_GENERAL, LOG_WARNING,
302 .arg(cmd,
WEXITSTATUS(res_grp) == 0 ?
"sill running" :
"terminated"));
313 LOG(VB_RECORD, LOG_INFO, QString(
"ExternIO in bad state: '%1'")
318 QString full_command = QString(
"%1").arg(
m_args.join(
" "));
323 std::this_thread::sleep_for(50ms);
326 m_error = QString(
"Unable to kill existing '%1'.")
334 LOG(VB_RECORD, LOG_INFO, QString(
"ExternIO::Fork '%1'").arg(full_command));
336 std::array<int,2> in = {-1, -1};
337 std::array<int,2> out = {-1, -1};
338 std::array<int,2> err = {-1, -1};
340 if (pipe(in.data()) < 0)
345 if (pipe(out.data()) < 0)
352 if (pipe(err.data()) < 0)
386 LOG(VB_GENERAL, LOG_WARNING,
387 "ExternIO::Fork(): Failed to set O_NONBLOCK for FD: " +
ENO);
388 std::this_thread::sleep_for(2s);
392 LOG(VB_RECORD, LOG_INFO,
"Spawned");
400 if (dup2( in[0], 0) < 0)
402 std::cerr <<
"dup2(stdin) failed: " << strerror(errno);
405 else if (dup2(out[1], 1) < 0)
407 std::cerr <<
"dup2(stdout) failed: " << strerror(errno);
410 else if (dup2(err[1], 2) < 0)
412 std::cerr <<
"dup2(stderr) failed: " << strerror(errno);
418 close_range(3, sysconf(_SC_OPEN_MAX) - 1, 0);
420 for (
int i = sysconf(_SC_OPEN_MAX) - 1; i > 2; --i)
427 if (setpgid(0,0) < 0)
429 std::cerr <<
"ExternIO: "
430 <<
"setpgid() failed: "
431 << strerror(errno) <<
'\n';
435 char *command = strdup(
m_app.canonicalFilePath()
436 .toUtf8().constData());
437 if (command ==
nullptr)
439 std::cerr <<
"ExternIO: strdup() failed: " << strerror(errno) <<
'\n';
444 char **arguments =
new char*[
m_args.size() + 1];
445 for (
int i = 0; i <
m_args.size(); ++i)
447 int len =
m_args[i].size() + 1;
448 arguments[i] =
new char[len];
449 memcpy(arguments[i],
m_args[i].toStdString().c_str(), len);
451 arguments[
m_args.size()] =
nullptr;
453 if (execv(command, arguments) < 0)
456 std::cerr <<
"ExternIO: "
457 <<
"execv() failed: "
458 << strerror(errno) <<
'\n';
462 std::cerr <<
"ExternIO: "
463 <<
"execv() should not be here?: "
464 << strerror(errno) <<
'\n';
479 int inputid,
int majorid)
483 QMap<int, ExternalStreamHandler*>::iterator it =
s_handlers.find(majorid);
491 LOG(VB_RECORD, LOG_INFO,
492 QString(
"ExternSH[%1:%2]: Creating new stream handler for %3 "
494 .arg(inputid).arg(majorid).arg(devname));
500 LOG(VB_RECORD, LOG_INFO,
501 QString(
"ExternSH[%1:%2]: Using existing stream handler for %3")
502 .arg(inputid).arg(majorid).arg(devname) +
503 QString(
" (%1 in use)").arg(rcount));
520 QMap<int, ExternalStreamHandler*>::iterator it =
528 LOG(VB_RECORD, LOG_INFO,
529 QString(
"ExternSH[%1:%2]: Return handler (%3 still in use)")
530 .arg(inputid).arg(majorid).arg(*rit));
537 LOG(VB_RECORD, LOG_INFO,
538 QString(
"ExternSH[%1:%2]: Closing handler (0 in use)")
539 .arg(inputid).arg(majorid));
545 LOG(VB_GENERAL, LOG_ERR,
546 QString(
"ExternSH[%1:%2]: Error: No handler to return!")
547 .arg(inputid).arg(majorid));
567 m_args = path.split(
' ',Qt::SkipEmptyParts) +
574 if (!
m_args.contains(
"--quiet") && !
m_args.contains(
"-q"))
577 m_args <<
"--inputid" << QString::number(majorid);
578 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
"args \"%1\"")
583 LOG(VB_GENERAL, LOG_ERR,
LOC +
584 QString(
"Failed to start %1").arg(
m_device));
601 uint restart_cnt = 0;
605 bool good_data =
false;
606 uint data_proc_err = 0;
607 uint data_short_err = 0;
611 LOG(VB_GENERAL, LOG_ERR,
LOC +
612 QString(
"%1 is not running.").arg(
m_device));
615 status_timer.
start();
619 LOG(VB_RECORD, LOG_INFO,
LOC +
"run(): begin");
624 ready_cmd =
"SendBytes";
633 LOG(VB_RECORD, LOG_WARNING,
LOC +
"TS not open yet.");
634 std::this_thread::sleep_for(10ms);
640 std::this_thread::sleep_for(10ms);
650 LOG(VB_RECORD, LOG_WARNING,
LOC +
651 "Internal buffer too full to accept more data from "
652 "external application.");
658 if (result.startsWith(
"ERR"))
660 LOG(VB_GENERAL, LOG_ERR,
LOC +
661 QString(
"Aborting: %1 -> %2")
662 .arg(ready_cmd, result));
668 std::this_thread::sleep_for(20s);
671 LOG(VB_RECORD, LOG_ERR,
LOC +
672 "Failed to restart stream.");
683 if (status_timer.
elapsed() >= 2s)
691 std::this_thread::sleep_for(20s);
694 LOG(VB_RECORD, LOG_ERR,
LOC +
695 "Failed to restart stream.");
712 if (result.startsWith(
"ERR"))
714 LOG(VB_GENERAL, LOG_ERR,
LOC +
715 QString(
"Aborting: XOFF -> %2")
729 read_len =
m_io->
Read(buffer, sz, 100ms);
741 nodata_timer.
start();
745 if (nodata_timer.
elapsed() >= 50s)
747 LOG(VB_GENERAL, LOG_WARNING,
LOC +
748 "No data for 50 seconds, Restarting stream.");
751 LOG(VB_RECORD, LOG_ERR,
LOC +
752 "Failed to restart stream.");
760 std::this_thread::sleep_for(50ms);
774 LOG(VB_GENERAL, LOG_ERR,
LOC +
"I/O thread has disappeared!");
780 LOG(VB_GENERAL, LOG_ERR,
LOC +
781 QString(
"Fatal Error from External Recorder: %1")
788 len = remainder = buffer.size();
795 if (
m_xon && data_short_err++ == 0)
796 LOG(VB_RECORD, LOG_INFO,
LOC +
"Waiting for a full TS packet.");
797 std::this_thread::sleep_for(50us);
802 if (data_short_err > 1)
804 LOG(VB_RECORD, LOG_INFO,
LOC +
805 QString(
"Waited for a full TS packet %1 times.")
806 .arg(data_short_err));
820 remainder = sit.key()->ProcessData
821 (
reinterpret_cast<const uint8_t *
>
822 (buffer.constData()), buffer.size());
833 LOG(VB_RECORD, LOG_WARNING,
LOC +
834 QString(
"Replay size truncated to %1 bytes")
844 good_data = (len != 0U);
846 else if (len > remainder)
848 buffer.remove(0, len - remainder);
849 good_data = (len != 0U);
851 else if (len == remainder)
860 if (data_proc_err > 1)
862 LOG(VB_RECORD, LOG_WARNING,
LOC +
863 QString(
"Failed to process the data received %1 times.")
864 .arg(data_proc_err));
871 if (data_proc_err++ == 0)
873 LOG(VB_RECORD, LOG_WARNING,
LOC +
874 "Failed to process the data received");
879 LOG(VB_RECORD, LOG_INFO,
LOC +
"run(): " +
880 QString(
"%1 shutdown").arg(
m_bError ?
"Error" :
"Normal"));
885 LOG(VB_RECORD, LOG_INFO,
LOC +
"run(): " +
"end");
896 QStringList tokens = result.split(
':', Qt::SkipEmptyParts);
897 if (tokens.size() > 1)
902 LOG(VB_RECORD, LOG_ERR,
LOC +
903 QString(
"Bad response to 'APIVersion?' - '%1'. "
904 "Expecting 1, 2 or 3").arg(result));
922 m_loc = result.mid(3);
937 LOG(VB_RECORD, LOG_WARNING,
LOC +
"OpenApp: already open!");
945 LOG(VB_GENERAL, LOG_ERR,
LOC +
"ExternIO failed: " +
ENO);
950 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
"Spawn '%1'").arg(
m_device));
954 LOG(VB_GENERAL, LOG_ERR,
976 LOG(VB_RECORD, LOG_ERR,
LOC +
"Application is not responding.");
986 LOG(VB_RECORD, LOG_ERR,
LOC +
987 QString(
"Bad response to 'HasTuner?' - '%1'").arg(result));
995 LOG(VB_RECORD, LOG_ERR,
LOC +
996 QString(
"Bad response to 'HasPictureAttributes?' - '%1'")
1005 result.startsWith(
"OK:Poll");
1007 LOG(VB_RECORD, LOG_INFO,
LOC +
"App opened successfully");
1008 LOG(VB_RECORD, LOG_INFO,
LOC +
1009 QString(
"Capabilities: tuner(%1) "
1010 "Picture attributes(%2) "
1025 if (
m_io ==
nullptr)
1027 LOG(VB_RECORD, LOG_WARNING,
LOC +
1028 "WARNING: Unable to communicate with external app.");
1057 LOG(VB_RECORD, LOG_INFO,
LOC +
"CloseRecorder");
1062 if (!result.startsWith(
"OK"))
1064 LOG(VB_RECORD, LOG_INFO,
LOC +
1065 "CloseRecorder failed, sending kill.");
1067 QString full_command = QString(
"%1").arg(
m_args.join(
" "));
1072 std::this_thread::sleep_for(50ms);
1075 LOG(VB_GENERAL, LOG_ERR,
1076 QString(
"Unable to kill existing '%1'.")
1077 .arg(full_command));
1092 LOG(VB_RECORD, LOG_WARNING,
LOC +
"Restarting stream.");
1098 std::this_thread::sleep_for(1s);
1130 sit.key()->ProcessData(
reinterpret_cast<const uint8_t *
>
1135 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
"Replayed %1 bytes")
1157 LOG(VB_RECORD, LOG_INFO,
LOC +
1158 QString(
"StartStreaming with %1 current listeners")
1163 LOG(VB_GENERAL, LOG_ERR,
LOC +
"External Recorder not started.");
1171 LogLevel_t level = LOG_ERR;
1172 if (result.startsWith(
"warn", Qt::CaseInsensitive))
1173 level = LOG_WARNING;
1177 LOG(VB_GENERAL, level,
LOC + QString(
"StartStreaming failed: '%1'")
1182 LOG(VB_RECORD, LOG_INFO,
LOC +
"Streaming started");
1186 LOG(VB_RECORD, LOG_INFO,
LOC +
"Already streaming");
1192 LOG(VB_RECORD, LOG_INFO,
LOC +
1193 QString(
"StartStreaming %1 listeners")
1203 LOG(VB_RECORD, LOG_INFO,
LOC +
1204 QString(
"StopStreaming %1 listeners")
1209 LOG(VB_RECORD, LOG_INFO,
LOC +
1210 "StopStreaming requested, but we are not streaming!");
1216 LOG(VB_RECORD, LOG_INFO,
LOC +
1217 QString(
"StopStreaming delayed, still have %1 listeners")
1222 LOG(VB_RECORD, LOG_INFO,
LOC +
"StopStreaming");
1233 LOG(VB_GENERAL, LOG_ERR,
LOC +
"External Recorder not started.");
1240 LogLevel_t level = LOG_ERR;
1241 if (result.startsWith(
"warn", Qt::CaseInsensitive))
1242 level = LOG_WARNING;
1246 LOG(VB_GENERAL, level,
LOC + QString(
"StopStreaming: '%1'")
1254 LOG(VB_RECORD, LOG_INFO,
LOC +
"Streaming stopped");
1261 std::chrono::milliseconds
timeout,
1269 QVariantMap vresult;
1270 QByteArray response;
1271 QStringList tokens = cmd.split(
':');
1272 vcmd[
"command"] = tokens[0];
1273 if (tokens.size() > 1)
1274 vcmd[
"value"] = tokens[1];
1276 LOG(VB_RECORD, LOG_DEBUG,
LOC +
1277 QString(
"Arguments: %1").arg(tokens.join(
"\n")));
1280 result = QString(
"%1:%2").arg(vresult[
"status"].
toString(),
1289 LOG(VB_RECORD, LOG_ERR,
LOC +
1290 QString(
"Invalid API version %1. Expected 1 or 2").arg(
m_apiVersion));
1296 std::chrono::milliseconds
timeout,
1299 LOG(VB_RECORD, LOG_DEBUG,
LOC + QString(
"ProcessVer1('%1')")
1302 for (
uint cnt = 0; cnt < retry_cnt; ++cnt)
1308 LOG(VB_RECORD, LOG_ERR,
LOC +
"External I/O not ready!");
1312 QByteArray buf = cmd.toUtf8();
1317 LOG(VB_GENERAL, LOG_ERR,
LOC +
"External Recorder in bad state: " +
1335 LOG(VB_GENERAL, LOG_ERR,
LOC +
1336 "Failed to read from External Recorder: " +
1343 if (result.startsWith(
"STATUS:ERR") ||
1344 result.startsWith(
"0:STATUS:ERR"))
1346 LOG(VB_RECORD, LOG_ERR,
LOC + result);
1347 result.remove(0, result.indexOf(
":ERR") + 1);
1352 if (!result.startsWith(
"STATUS") && !result.startsWith(
"0:STATUS"))
1354 LOG(VB_RECORD, LOG_INFO,
LOC +
1355 QString(
"Ignoring response '%1'").arg(result));
1358 if (result.size() < 1)
1360 LOG(VB_GENERAL, LOG_WARNING,
LOC +
1361 QString(
"External Recorder did not respond to '%1'").arg(cmd));
1365 bool okay = result.startsWith(
"OK");
1366 if (okay || result.startsWith(
"WARN") || result.startsWith(
"ERR"))
1368 LogLevel_t level = LOG_INFO;
1372 level = LOG_WARNING;
1373 else if (cmd.startsWith(
"SendBytes"))
1376 LOG(VB_RECORD, level,
1377 LOC + QString(
"ProcessCommand('%1') = '%2' took %3ms %4")
1379 QString::number(timer.
elapsed().count()),
1380 okay ?
"" :
"<-- NOTE"));
1384 LOG(VB_GENERAL, LOG_WARNING,
LOC +
1385 QString(
"External Recorder invalid response to '%1': '%2'")
1391 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Too many I/O errors.");
1402 std::chrono::milliseconds
timeout,
1408 for (
uint cnt = 0; cnt < retry_cnt; ++cnt)
1410 QString cmd = QString(
"%1:%2").arg(++
m_serialNo).arg(command);
1412 LOG(VB_RECORD, LOG_DEBUG,
LOC + QString(
"ProcessVer2('%1') serial(%2)")
1419 LOG(VB_RECORD, LOG_ERR,
LOC +
"External I/O not ready!");
1423 QByteArray buf = cmd.toUtf8();
1428 LOG(VB_GENERAL, LOG_ERR,
LOC +
"External Recorder in bad state: " +
1444 LOG(VB_GENERAL, LOG_ERR,
LOC +
1445 "Failed to read from External Recorder: " +
1451 if (!result.isEmpty())
1454 tokens = result.split(
':', Qt::SkipEmptyParts);
1457 if (tokens.size() > 1 && tokens[0].toUInt() >=
m_serialNo)
1463 if (tokens[0].startsWith(
"ERR"))
1467 tokens.removeFirst();
1468 result = tokens.join(
':');
1469 bool err = (tokens.size() > 1 && tokens[1].startsWith(
"ERR"));
1470 LOG(VB_RECORD, (err ? LOG_WARNING : LOG_INFO),
LOC + raw);
1474 tokens.removeFirst();
1475 result = tokens.join(
':');
1483 LOG(VB_RECORD, LOG_ERR,
LOC +
1484 QString(
"ProcessVer2: Giving up waiting for response for "
1485 "command '%2'").arg(cmd));
1487 else if (tokens.size() < 2)
1489 LOG(VB_RECORD, LOG_ERR,
LOC +
1490 QString(
"Did not receive a valid response "
1491 "for command '%1', received '%2'").arg(cmd, result));
1495 LOG(VB_RECORD, LOG_ERR,
LOC +
1496 QString(
"ProcessVer2: Looking for serial no %1, "
1497 "but received %2 for command '%2'")
1498 .arg(QString::number(
m_serialNo), tokens[0], cmd));
1502 tokens.removeFirst();
1503 status = tokens[0].trimmed();
1504 result = tokens.join(
':');
1506 bool okay = (status ==
"OK");
1507 if (okay || status.startsWith(
"WARN") || status.startsWith(
"ERR"))
1509 LogLevel_t level = LOG_INFO;
1513 level = LOG_WARNING;
1514 else if (command.startsWith(
"SendBytes") ||
1515 (command.startsWith(
"TuneStatus") &&
1516 result ==
"OK:InProgress"))
1519 LOG(VB_RECORD, level,
1520 LOC + QString(
"ProcessV2('%1') = '%2' took %3ms %4")
1521 .arg(cmd, result, QString::number(timer.
elapsed().count()),
1522 okay ?
"" :
"<-- NOTE"));
1526 LOG(VB_GENERAL, LOG_WARNING,
LOC +
1527 QString(
"External Recorder invalid response to '%1': '%2'")
1533 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Too many I/O errors.");
1543 QVariantMap & elements,
1544 QByteArray & response,
1545 std::chrono::milliseconds
timeout,
1548 for (
uint cnt = 0; cnt < retry_cnt; ++cnt)
1550 QVariantMap query(vmsg);
1553 query[
"serial"] = serial;
1554 QString cmd = query[
"command"].toString();
1557 qdoc = QJsonDocument::fromVariant(query);
1558 QByteArray cmdbuf = qdoc.toJson(QJsonDocument::Compact);
1560 LOG(VB_RECORD, LOG_DEBUG,
LOC +
1561 QString(
"ProcessJson: %1").arg(QString(cmdbuf)));
1565 LOG(VB_GENERAL, LOG_ERR,
LOC +
"External Recorder in bad state: " +
1580 LOG(VB_GENERAL, LOG_ERR,
LOC +
1581 "Failed to read from External Recorder: " +
1587 if (!response.isEmpty())
1589 QJsonParseError parseError {};
1592 doc = QJsonDocument::fromJson(response, &parseError);
1594 if (parseError.error != QJsonParseError::NoError)
1596 LOG(VB_GENERAL, LOG_ERR,
LOC +
1597 QString(
"ExternalRecorder returned invalid JSON message: %1: %2\n%3\nfor\n%4")
1598 .arg(parseError.offset)
1599 .arg(parseError.errorString(),
1605 elements = doc.toVariant().toMap();
1606 if (!elements.contains(
"serial"))
1609 serial = elements[
"serial"].toInt();
1613 if (elements.contains(
"status"))
1615 LogLevel_t level { LOG_INFO };
1617 if (elements[
"status"] ==
"ERR")
1619 else if (elements[
"status"] ==
"WARN")
1620 level = LOG_WARNING;
1622 LOG(VB_RECORD, level,
LOC + QString(
"%1: %2")
1623 .arg(elements[
"status"].
toString(),
1632 LOG(VB_RECORD, LOG_ERR,
LOC +
1633 QString(
"ProcessJson: Giving up waiting for response for "
1634 "command '%2'").arg(QString(cmdbuf)));
1640 LOG(VB_RECORD, LOG_ERR,
LOC +
1641 QString(
"ProcessJson: Looking for serial no %1, "
1642 "but received %2 for command '%2'")
1645 .arg(QString(cmdbuf)));
1647 else if (!elements.contains(
"status"))
1649 LOG(VB_RECORD, LOG_ERR,
LOC +
1650 QString(
"ProcessJson: ExternalRecorder 'status' not found in %1")
1651 .arg(QString(response)));
1655 QString status = elements[
"status"].toString();
1656 bool okay = (status ==
"OK");
1657 if (okay || status ==
"WARN" || status ==
"ERR")
1659 LogLevel_t level = LOG_INFO;
1663 level = LOG_WARNING;
1664 else if (cmd ==
"SendBytes" ||
1665 (cmd ==
"TuneStatus?" &&
1666 elements[
"message"] ==
"InProgress"))
1669 LOG(VB_RECORD, level,
1670 LOC + QString(
"ProcessJson('%1') = %2:%3:%4 took %5ms %6")
1671 .arg(QString(cmdbuf))
1672 .arg(elements[
"serial"].toInt())
1673 .arg(elements[
"status"].
toString(),
1675 QString::number(timer.
elapsed().count()),
1676 okay ?
"" :
"<-- NOTE")
1681 LOG(VB_GENERAL, LOG_WARNING,
LOC +
1682 QString(
"External Recorder invalid response to '%1': '%2'")
1683 .arg(QString(cmdbuf),
1684 QString(response)));
1689 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Too many I/O errors.");
1700 QByteArray response;
1707 LOG(VB_RECORD, LOG_ERR,
LOC +
"External I/O not ready!");
1713 LOG(VB_GENERAL, LOG_ERR,
"External Recorder in bad state: " +
1719 while (!response.isEmpty())
1723 QJsonParseError parseError {};
1725 QVariantMap elements;
1727 doc = QJsonDocument::fromJson(response, &parseError);
1729 if (parseError.error != QJsonParseError::NoError)
1731 LOG(VB_GENERAL, LOG_ERR,
LOC +
1732 QString(
"ExternalRecorder returned invalid JSON message: %1: %2\n%3\n")
1733 .arg(parseError.offset)
1734 .arg(parseError.errorString(), QString(response)));
1738 elements = doc.toVariant().toMap();
1739 if (elements.contains(
"command") &&
1740 (QString::compare(elements[
"command"].toString(),
1742 Qt::CaseInsensitive) == 0))
1744 LogLevel_t level { LOG_INFO };
1745 QString status = elements[
"status"].toString().trimmed();
1746 QString message = elements[
"message"].toString();
1747 if (status.startsWith(
"crit", Qt::CaseInsensitive))
1751 if (status.startsWith(
"err", Qt::CaseInsensitive))
1755 else if (status.startsWith(
"warn",
1756 Qt::CaseInsensitive))
1758 level = LOG_WARNING;
1760 else if (status.startsWith(
"debug",
1761 Qt::CaseInsensitive))
1765 else if (status.startsWith(
"trace",
1766 Qt::CaseInsensitive))
1770 else if (status.startsWith(
"damage",
1771 Qt::CaseInsensitive))
1773 level = LOG_WARNING;
1778 if (message.trimmed().startsWith(
"damage",
1779 Qt::CaseInsensitive))
1785 LOG(VB_RECORD, level,
1786 LOC + QString(
"%1:%2%3")
1787 .arg(status, message,
1794 QString res = QString(response);
1797 QStringList tokens = res.split(
':', Qt::SkipEmptyParts);
1798 tokens.removeFirst();
1799 res = tokens.join(
':');
1800 for (
int idx = 1; idx < tokens.size(); ++idx)
1802 err |= tokens[idx].startsWith(
"ERR",
1803 Qt::CaseInsensitive);
1805 m_damaged |= tokens[idx].startsWith(
"damage",
1806 Qt::CaseInsensitive);
1811 err |= res.startsWith(
"STATUS:ERR",
1812 Qt::CaseInsensitive);
1814 m_damaged |= res.startsWith(
"STATUS:DAMAGE",
1815 Qt::CaseInsensitive);
1818 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
bool StartStreaming(bool recording)
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)
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)