9 #if !defined( USING_MINGW ) && !defined( _MSC_VER )
11 #include <sys/ioctl.h>
22 #include <QJsonDocument>
23 #include <QJsonObject>
37 #define LOC QString("ExternSH[%1](%2): ").arg(m_inputId).arg(m_loc)
40 const QStringList &
args)
41 : m_app(QFileInfo(app)),
42 m_status(&m_statusBuf, QIODevice::ReadWrite)
46 m_error = QString(
"ExternIO: '%1' does not exist.").arg(app);
51 m_error = QString(
"ExternIO: '%1' is not readable.")
52 .arg(
m_app.canonicalFilePath());
55 if (!
m_app.isExecutable())
57 m_error = QString(
"ExternIO: '%1' is not executable.")
58 .arg(
m_app.canonicalFilePath());
79 [[maybe_unused]] std::chrono::milliseconds
timeout,
80 [[maybe_unused]]
const QString & what)
82 #if !defined( USING_MINGW ) && !defined( _MSC_VER )
83 std::array<struct pollfd,2> m_poll {};
86 m_poll[0].events = POLLIN | POLLPRI;
87 int ret = poll(m_poll.data(), 1,
timeout.count());
89 if (m_poll[0].revents & POLLHUP)
91 m_error = what +
" poll eof (POLLHUP)";
94 if (m_poll[0].revents & POLLNVAL)
96 LOG(VB_GENERAL, LOG_ERR,
"poll error");
99 if (m_poll[0].revents & POLLIN)
104 if ((EOVERFLOW == errno))
108 #endif // !defined( USING_MINGW ) && !defined( _MSC_VER )
116 LOG(VB_RECORD, LOG_ERR,
117 QString(
"ExternIO::Read: already in error state: '%1'")
140 m_error =
"Failed to read from External Recorder: " +
ENO;
141 LOG(VB_RECORD, LOG_WARNING,
142 "External Recorder not ready. Giving up.");
146 LOG(VB_RECORD, LOG_WARNING,
147 QString(
"External Recorder not ready. Will retry (%1/%2).")
149 std::this_thread::sleep_for(100ms);
154 m_error =
"Failed to read from External Recorder: " +
ENO;
168 LOG(VB_RECORD, LOG_DEBUG,
169 QString(
"ExternIO::Read '%1' bytes, buffer size %2")
170 .arg(len).arg(buffer.size()));
179 LOG(VB_RECORD, LOG_ERR,
180 QString(
"ExternIO::GetStatus: already in error state: '%1'")
188 std::array<char,2048> buffer {};
190 m_status << QString::fromLatin1(buffer.data(), len);
198 LOG(VB_RECORD, LOG_DEBUG, QString(
"ExternIO::GetStatus '%1'")
208 LOG(VB_RECORD, LOG_ERR,
209 QString(
"ExternIO::Write: already in error state: '%1'")
214 LOG(VB_RECORD, LOG_DEBUG, QString(
"ExternIO::Write('%1')")
215 .arg(QString(buffer).simplified()));
217 int len =
write(
m_appIn, buffer.constData(), buffer.size());
218 if (len != buffer.size())
222 LOG(VB_RECORD, LOG_WARNING,
223 QString(
"ExternIO::Write: only wrote %1 of %2 bytes '%3'")
224 .arg(len).arg(buffer.size()).arg(QString(buffer)));
228 m_error = QString(
"ExternIO: Failed to write '%1' to app's stdin: ")
229 .arg(QString(buffer)) +
ENO;
239 LOG(VB_RECORD, LOG_INFO, QString(
"ExternIO::Run()"));
250 #if defined(Q_OS_DARWIN) || defined(__FreeBSD__) || defined(__OpenBSD__)
252 #elif defined USING_MINGW
254 #elif defined( _MSC_VER )
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"));
302 #if !defined( USING_MINGW ) && !defined( _MSC_VER )
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;
453 #endif // !defined( USING_MINGW ) && !defined( _MSC_VER )
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_INFO,
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.find(
"serial") == elements.end())
1592 serial = elements[
"serial"].toInt();
1596 if (elements.find(
"status") != elements.end() &&
1597 elements[
"status"] !=
"OK")
1599 LOG(VB_RECORD, LOG_WARNING,
LOC + QString(
"%1: %2")
1600 .arg(elements[
"status"].
toString(),
1609 LOG(VB_RECORD, LOG_ERR,
LOC +
1610 QString(
"ProcessJson: Giving up waiting for response for "
1611 "command '%2'").arg(QString(cmdbuf)));
1617 LOG(VB_RECORD, LOG_ERR,
LOC +
1618 QString(
"ProcessJson: Looking for serial no %1, "
1619 "but received %2 for command '%2'")
1622 .arg(QString(cmdbuf)));
1624 else if (elements.find(
"status") == elements.end())
1626 LOG(VB_RECORD, LOG_ERR,
LOC +
1627 QString(
"ProcessJson: ExternalRecorder 'status' not found in %1")
1628 .arg(QString(response)));
1632 QString status = elements[
"status"].toString();
1633 bool okay = (status ==
"OK");
1634 if (okay || status ==
"WARN" || status ==
"ERR")
1636 LogLevel_t level = LOG_INFO;
1640 level = LOG_WARNING;
1641 else if (cmd ==
"SendBytes" ||
1642 (cmd ==
"TuneStatus?" &&
1643 elements[
"message"] ==
"InProgress"))
1646 LOG(VB_RECORD, level,
1647 LOC + QString(
"ProcessJson('%1') = %2:%3:%4 took %5ms %6")
1648 .arg(QString(cmdbuf))
1649 .arg(elements[
"serial"].toInt())
1650 .arg(elements[
"status"].
toString(),
1652 QString::number(timer.
elapsed().count()),
1653 okay ?
"" :
"<-- NOTE")
1658 LOG(VB_GENERAL, LOG_WARNING,
LOC +
1659 QString(
"External Recorder invalid response to '%1': '%2'")
1660 .arg(QString(cmdbuf),
1661 QString(response)));
1666 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Too many I/O errors.");
1677 QByteArray response;
1684 LOG(VB_RECORD, LOG_ERR,
LOC +
"External I/O not ready!");
1690 LOG(VB_GENERAL, LOG_ERR,
"External Recorder in bad state: " +
1698 if (!response.isEmpty())
1702 QJsonParseError parseError {};
1704 QVariantMap elements;
1706 doc = QJsonDocument::fromJson(response, &parseError);
1708 if (parseError.error != QJsonParseError::NoError)
1710 LOG(VB_GENERAL, LOG_ERR,
LOC +
1711 QString(
"ExternalRecorder returned invalid JSON message: %1: %2\n%3\n")
1712 .arg(parseError.offset)
1713 .arg(parseError.errorString(), QString(response)));
1717 elements = doc.toVariant().toMap();
1718 if (elements.find(
"command") != elements.end() &&
1719 elements[
"command"] ==
"STATUS")
1721 LogLevel_t level { LOG_INFO };
1722 QString status = elements[
"status"].toString();
1723 if (status.startsWith(
"err", Qt::CaseInsensitive))
1728 else if (status.startsWith(
"warn",
1729 Qt::CaseInsensitive))
1731 level = LOG_WARNING;
1733 else if (status.startsWith(
"damage",
1734 Qt::CaseInsensitive))
1736 level = LOG_WARNING;
1739 LOG(VB_RECORD, level,
1746 QString res = QString(response);
1749 QStringList tokens = res.split(
':', Qt::SkipEmptyParts);
1750 tokens.removeFirst();
1751 res = tokens.join(
':');
1752 for (
int idx = 1; idx < tokens.size(); ++idx)
1754 err |= tokens[idx].startsWith(
"ERR",
1755 Qt::CaseInsensitive);
1756 m_damaged |= tokens[idx].startsWith(
"damage",
1757 Qt::CaseInsensitive);
1762 err |= res.startsWith(
"STATUS:ERR",
1763 Qt::CaseInsensitive);
1764 m_damaged |= res.startsWith(
"STATUS:DAMAGE",
1765 Qt::CaseInsensitive);
1768 LOG(VB_RECORD, (err ? LOG_WARNING : LOG_INFO),
LOC + res);
1772 while (!response.isEmpty());