27 #include <QElapsedTimer>
30 #include <QtCore/QtCore>
45 : m_recCommand(
std::move(command))
46 , m_logFile(
std::move(log_file))
47 , m_logging(
std::move(logging))
48 , m_configIni(
std::move(conf_file))
55 LOG(VB_CHANNEL, LOG_INFO,
LOC +
56 QString(
"Channels in '%1', Tuner: '%2', Scanner: '%3'")
60 m_desc.replace(
"%URL%",
"");
61 m_desc.replace(
"%CHANNUM%",
"");
62 m_desc.replace(
"%CHANNAME%",
"");
63 m_desc.replace(
"%CALLSIGN%",
"");
75 if (
m_proc.processId() > 0)
76 extra = QString(
"(pid %1) ").arg(
m_proc.processId());
78 return QString(
"%1%2 ").arg(extra,
m_desc);
83 QSettings settings(
m_configIni, QSettings::IniFormat);
85 if (!settings.contains(
"RECORDER/command"))
87 LOG(VB_GENERAL, LOG_CRIT,
"ini file missing [RECORDER]/command");
92 m_recCommand = settings.value(
"RECORDER/command").toString();
93 m_recDesc = settings.value(
"RECORDER/desc").toString();
94 m_cleanup = settings.value(
"RECORDER/cleanup").toString();
95 m_tuneCommand = settings.value(
"TUNER/command",
"").toString();
97 m_onDataStart = settings.value(
"TUNER/ondatastart",
"").toString();
98 m_channelsIni = settings.value(
"TUNER/channels",
"").toString();
100 m_scanCommand = settings.value(
"SCANNER/command",
"").toString();
101 m_scanTimeout = settings.value(
"SCANNER/timeout",
"").toInt();
103 settings.beginGroup(
"ENVIRONMENT");
106 QStringList keys = settings.childKeys();
107 QStringList::const_iterator Ienv;
108 for (Ienv = keys.constBegin(); Ienv != keys.constEnd(); ++Ienv)
110 if (!(*Ienv).isEmpty() && (*Ienv)[0] !=
'#')
111 m_appEnv.insert((*Ienv).toLocal8Bit().constData(),
112 settings.value(*Ienv).toString());
121 QDir conf_path = QFileInfo(
m_configIni).absolutePath();
134 emit
SendMessage(
"Open",
"0",
"ERR:Already dead.");
140 LOG(VB_RECORD, LOG_ERR,
LOC +
": No recorder provided.");
141 emit
SendMessage(
"Open",
"0",
"ERR:No recorder provided.");
147 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
148 QMap<QString, QString>::const_iterator Ienv;
150 Ienv !=
m_appEnv.constEnd(); ++Ienv)
152 env.insert(Ienv.key(), Ienv.value());
153 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
" ENV: '%1' = '%2'")
154 .arg(Ienv.key(), Ienv.value()));
156 m_proc.setProcessEnvironment(env);
159 QObject::connect(&
m_proc, &QProcess::started,
this,
162 QObject::connect(&
m_proc, &QProcess::readyReadStandardOutput,
this,
165 QObject::connect(&
m_proc, &QProcess::readyReadStandardError,
this,
168 qRegisterMetaType<QProcess::ProcessError>(
"QProcess::ProcessError");
169 QObject::connect(&
m_proc, &QProcess::errorOccurred,
172 qRegisterMetaType<QProcess::ExitStatus>(
"QProcess::ExitStatus");
174 static_cast<void (QProcess::*)
175 (
int,QProcess::ExitStatus exitStatus)
>
176 (&QProcess::finished),
179 qRegisterMetaType<QProcess::ProcessState>(
"QProcess::ProcessState");
180 QObject::connect(&
m_proc, &QProcess::stateChanged,
this,
183 LOG(VB_RECORD, LOG_INFO,
LOC +
": Opened");
191 if (proc.state() == QProcess::Running)
193 LOG(VB_RECORD, LOG_INFO,
LOC +
194 QString(
"Sending SIGINT to %1(%2)").arg(desc).arg(proc.processId()));
195 kill(proc.processId(), SIGINT);
196 proc.waitForFinished(5000);
198 if (proc.state() == QProcess::Running)
200 LOG(VB_RECORD, LOG_INFO,
LOC +
201 QString(
"Sending SIGTERM to %1(%2)").arg(desc).arg(proc.processId()));
203 proc.waitForFinished();
205 if (proc.state() == QProcess::Running)
207 LOG(VB_RECORD, LOG_INFO,
LOC +
208 QString(
"Sending SIGKILL to %1(%2)").arg(desc).arg(proc.processId()));
210 proc.waitForFinished();
218 LOG(VB_RECORD, LOG_INFO,
LOC +
": Closing application.");
221 std::this_thread::sleep_for(50us);
226 m_tuneProc.closeReadChannel(QProcess::StandardOutput);
230 if (
m_proc.state() == QProcess::Running)
232 m_proc.closeReadChannel(QProcess::StandardOutput);
234 std::this_thread::sleep_for(50us);
248 m_runCond.wait_for(lk, std::chrono::milliseconds(10));
251 if (
m_proc.state() == QProcess::Running)
253 if (
m_proc.waitForReadyRead(50))
261 qApp->processEvents();
264 if (
m_proc.state() == QProcess::Running)
266 m_proc.closeReadChannel(QProcess::StandardOutput);
281 QString cmd =
args.takeFirst();
283 LOG(VB_RECORD, LOG_WARNING,
LOC +
284 QString(
" Beginning cleanup: '%1'").arg(cmd));
290 LOG(VB_RECORD, LOG_ERR,
LOC +
": Failed to start cleanup process: "
295 if (
cleanup.state() == QProcess::NotRunning)
297 if (
cleanup.exitStatus() != QProcess::NormalExit)
299 LOG(VB_RECORD, LOG_ERR,
LOC +
": Cleanup process failed: " +
ENO);
304 LOG(VB_RECORD, LOG_INFO,
LOC +
": Cleanup finished.");
309 LOG(VB_RECORD, LOG_INFO,
LOC +
"DataStarted");
317 bool background =
false;
318 int pos = cmd.lastIndexOf(QChar(
'&'));
326 cmd =
args.takeFirst();
330 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
"Finishing tune: '%1' %3")
336 LOG(VB_RECORD, LOG_ERR,
LOC +
": Failed to finish tune process: "
348 LOG(VB_RECORD, LOG_ERR,
LOC +
": Finish tune failed: " +
ENO);
354 LOG(VB_RECORD, LOG_INFO,
LOC +
": tunning finished.");
361 LOG(VB_CHANNEL, LOG_ERR,
LOC +
": No channels configured.");
362 emit
SendMessage(
"LoadChannels", serial,
"ERR:No channels configured.");
372 cmd =
args.takeFirst();
375 scanner.start(cmd,
args);
377 if (!scanner.waitForStarted())
379 QString errmsg = QString(
"Failed to start '%1': ").arg(cmd) +
ENO;
380 LOG(VB_CHANNEL, LOG_ERR,
LOC +
": " + errmsg);
382 QString(
"ERR:%1").arg(errmsg));
392 if (scanner.waitForReadyRead(50))
394 buf = scanner.readLine();
397 LOG(VB_RECORD, LOG_INFO,
LOC +
": " + buf);
402 if (scanner.state() != QProcess::Running)
405 if (scanner.waitForFinished(50 ))
410 QString errmsg = QString(
"Timedout waiting for '%1'").arg(cmd);
411 LOG(VB_CHANNEL, LOG_ERR,
LOC +
": " + errmsg);
413 QString(
"ERR:%1").arg(errmsg));
431 LOG(VB_CHANNEL, LOG_ERR,
LOC +
": No channels configured.");
433 QString(
"ERR:No channels configured."));
439 LOG(VB_CHANNEL, LOG_WARNING,
LOC +
": Invalid channel configuration.");
441 "ERR:Invalid channel configuration.");
447 LOG(VB_CHANNEL, LOG_WARNING,
LOC +
": No more channels.");
448 emit
SendMessage(func, serial,
"ERR:No more channels.");
463 LOG(VB_CHANNEL, LOG_INFO,
LOC +
464 QString(
": NextChannel Name:'%1',Callsign:'%2',xmltvid:%3,Icon:%4")
465 .arg(name, callsign, xmltvid, icon));
467 emit
SendMessage(func, serial, QString(
"OK:%1,%2,%3,%4,%5")
468 .arg(channum, name, callsign,
486 cmd.replace(
"%CHANNUM%", channum);
489 cmd =
args.takeFirst();
491 LOG(VB_RECORD, LOG_WARNING,
LOC +
492 QString(
" New episode starting on current channel: '%1'").arg(cmd));
495 proc.start(cmd,
args);
496 if (!proc.waitForStarted())
498 LOG(VB_RECORD, LOG_ERR,
LOC +
499 " NewEpisodeStarting: Failed to start process: " +
ENO);
502 proc.waitForFinished(5000);
503 if (proc.state() == QProcess::NotRunning)
505 if (proc.exitStatus() != QProcess::NormalExit)
507 LOG(VB_RECORD, LOG_ERR,
LOC +
508 " NewEpisodeStarting: process failed: " +
ENO);
513 LOG(VB_RECORD, LOG_INFO,
LOC +
"NewEpisodeStarting: finished.");
517 const QString & channum)
521 LOG(VB_CHANNEL, LOG_ERR,
LOC +
": No 'tuner' configured.");
522 emit
SendMessage(
"TuneChannel", serial,
"ERR:No 'tuner' configured.");
531 LOG(VB_CHANNEL, LOG_INFO,
LOC +
532 QString(
"TuneChanne: Already on %1").arg(channum));
534 QString(
"OK:Tunned to %1").arg(channum));
543 bool background =
false;
545 int pos = tunecmd.lastIndexOf(QChar(
'&'));
549 tunecmd = tunecmd.left(pos);
555 settings.beginGroup(channum);
557 url = settings.value(
"URL").toString();
561 QString msg = QString(
"Channel number [%1] is missing a URL.")
564 LOG(VB_CHANNEL, LOG_ERR,
LOC +
": " + msg);
567 tunecmd.replace(
"%URL%", url);
569 if (!url.isEmpty() &&
m_command.indexOf(
"%URL%") >= 0)
572 LOG(VB_CHANNEL, LOG_DEBUG,
LOC +
573 QString(
": '%URL%' replaced with '%1' in cmd: '%2'")
577 m_desc.replace(
"%CHANNAME%", settings.value(
"NAME").toString());
578 m_desc.replace(
"%CALLSIGN%", settings.value(
"CALLSIGN").toString());
586 tunecmd.replace(
"%CHANNUM%", channum);
592 LOG(VB_RECORD, LOG_DEBUG,
LOC +
593 QString(
": '%LOGFILE%' replaced with '%1' in cmd: '%2'")
600 LOG(VB_RECORD, LOG_DEBUG,
LOC +
601 QString(
": '%LOGGING%' replaced with '%1' in cmd: '%2'")
605 m_desc.replace(
"%URL%", url);
606 m_desc.replace(
"%CHANNUM%", channum);
611 QString cmd =
args.takeFirst();
616 QString errmsg = QString(
"Tune `%1` failed: ").arg(tunecmd) +
ENO;
617 LOG(VB_CHANNEL, LOG_ERR,
LOC +
": " + errmsg);
619 QString(
"ERR:%1").arg(errmsg));
625 LOG(VB_CHANNEL, LOG_INFO,
LOC +
626 QString(
": Started in background `%1` URL '%2'")
637 LOG(VB_CHANNEL, LOG_INFO,
LOC + QString(
": Started `%1` URL '%2'")
640 QString(
"OK:InProgress `%1`").arg(tunecmd));
656 LOG(VB_CHANNEL, LOG_INFO,
LOC +
657 QString(
": Tune process(%1) still running").arg(
m_tuneProc.processId()));
658 emit
SendMessage(
"TuneStatus", serial,
"OK:InProgress");
663 m_tuneProc.exitStatus() != QProcess::NormalExit)
665 QString errmsg = QString(
"'%1' failed: ")
667 LOG(VB_CHANNEL, LOG_ERR,
LOC +
": " + errmsg);
669 QString(
"ERR:%1").arg(errmsg));
686 LOG(VB_CHANNEL, LOG_WARNING,
LOC +
687 "Cannot read LockTimeout from config file.");
688 emit
SendMessage(
"LockTimeout", serial,
"ERR: Not open");
694 LOG(VB_CHANNEL, LOG_INFO,
LOC +
695 QString(
"Using configured LockTimeout of %1").arg(
m_lockTimeout));
700 LOG(VB_CHANNEL, LOG_INFO,
LOC +
701 "No LockTimeout defined in config, defaulting to 12000ms");
702 emit
SendMessage(
"LockTimeout", serial, QString(
"OK:%1")
708 emit
SendMessage(
"HasTuner", serial, QString(
"OK:%1")
715 emit
SendMessage(
"HasPictureAttributes", serial,
"OK:No");
729 LOG(VB_RECORD, LOG_ERR,
LOC +
": No channel has been tuned");
731 "ERR:No channel has been tuned");
735 if (
m_proc.state() == QProcess::Running)
737 LOG(VB_RECORD, LOG_ERR,
LOC +
": Application already running");
739 "WARN:Application already running");
744 QString cmd =
args.takeFirst();
745 m_proc.start(cmd,
args, QIODevice::ReadOnly|QIODevice::Unbuffered);
746 m_proc.setTextModeEnabled(
false);
747 m_proc.setReadChannel(QProcess::StandardOutput);
749 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
": Starting process '%1' args: '%2'")
752 if (!
m_proc.waitForStarted())
754 LOG(VB_RECORD, LOG_ERR,
LOC +
": Failed to start application.");
756 "ERR:Failed to start application.");
760 std::this_thread::sleep_for(50ms);
762 if (
m_proc.state() != QProcess::Running)
764 LOG(VB_RECORD, LOG_ERR,
LOC +
": Application failed to start");
766 "ERR:Application failed to start");
770 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
": Started process '%1' PID %2")
775 emit
SendMessage(
"StartStreaming", serial,
"OK:Streaming Started");
781 if (
m_proc.state() == QProcess::Running)
785 LOG(VB_RECORD, LOG_INFO,
LOC +
": External application terminated.");
787 emit
SendMessage(
"StopStreaming", serial,
"STATUS:Streaming Stopped");
789 emit
SendMessage(
"StopStreaming", serial,
"OK:Streaming Stopped");
796 "STATUS:Already not Streaming");
801 "WARN:Already not Streaming");
811 QString msg = QString(
"Process '%1' started").arg(
m_proc.program());
812 LOG(VB_RECORD, LOG_INFO,
LOC +
": " + msg);
817 QProcess::ExitStatus exitStatus)
820 QString msg = QString(
"%1Finished: %2 (exit code: %3)")
821 .arg(exitStatus != QProcess::NormalExit ?
"WARN:" :
"",
822 exitStatus == QProcess::NormalExit ?
"OK" :
"Abnormal exit",
824 LOG(VB_RECORD, LOG_INFO,
LOC +
": " + msg);
833 bool unexpected =
false;
834 QString msg =
"State Changed: ";
837 case QProcess::NotRunning:
838 msg +=
"Not running";
841 case QProcess::Starting:
844 case QProcess::Running:
845 msg += QString(
"Running PID %1").arg(
m_proc.processId());
849 LOG(VB_RECORD, LOG_INFO,
LOC + msg);
853 MythLog(
"ERR Unexpected " + msg);
859 LOG(VB_RECORD, LOG_ERR,
LOC + QString(
": Error: %1")
860 .arg(
m_proc.errorString()));
866 QByteArray buf =
m_proc.readAllStandardError();
867 QString msg = QString::fromUtf8(buf).trimmed();
872 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
">>> %1")
875 msg = QString(
"Application message: see '%1'").arg(
m_logFile);
883 LOG(VB_RECORD, LOG_WARNING,
LOC +
": Data ready.");