MythTV  master
MythExternRecApp.cpp
Go to the documentation of this file.
1 /* -*- Mode: c++ -*-
2  *
3  * Copyright (C) John Poet 2018
4  *
5  * This file is part of MythTV
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program. If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include <thread>
22 #include <csignal>
23 #include "commandlineparser.h"
24 #include "mythmiscutil.h"
25 #include "MythExternRecApp.h"
26 
27 #include <QElapsedTimer>
28 #include <QFileInfo>
29 #include <QProcess>
30 #include <QtCore/QtCore>
31 #include <unistd.h>
32 
33 #define LOC Desc()
34 
36  QString conf_file,
37  QString log_file,
38  QString logging)
39  : m_recCommand(std::move(command))
40  , m_logFile(std::move(log_file))
41  , m_logging(std::move(logging))
42  , m_configIni(std::move(conf_file))
43 {
44  if (m_configIni.isEmpty() || !config())
46 
48 
49  LOG(VB_CHANNEL, LOG_INFO, LOC +
50  QString("Channels in '%1', Tuner: '%2', Scanner: '%3'")
52 
53  m_desc = m_recDesc;
54  m_desc.replace("%URL%", "");
55  m_desc.replace("%CHANNUM%", "");
56  m_desc.replace("%CHANNAME%", "");
57  m_desc.replace("%CALLSIGN%", "");
58  emit SetDescription(m_desc);
59 }
60 
62 {
63  Close();
64 }
65 
66 QString MythExternRecApp::Desc(void) const
67 {
68  QString extra;
69 
70  if (m_proc.processId() > 0)
71  extra = QString("(pid %1) ").arg(m_proc.processId());
72 
73  return QString("%1%2 ").arg(extra).arg(m_desc);
74 }
75 
77 {
78  QSettings settings(m_configIni, QSettings::IniFormat);
79 
80  if (!settings.contains("RECORDER/command"))
81  {
82  LOG(VB_GENERAL, LOG_CRIT, "ini file missing [RECORDER]/command");
83  m_fatal = true;
84  return false;
85  }
86 
87  m_recCommand = settings.value("RECORDER/command").toString();
88  m_recDesc = settings.value("RECORDER/desc").toString();
89  m_cleanup = settings.value("RECORDER/cleanup").toString();
90  m_tuneCommand = settings.value("TUNER/command", "").toString();
91  m_newEpisodeCommand = settings.value("TUNER/newepisodecommand", "").toString();
92  m_onDataStart = settings.value("TUNER/ondatastart", "").toString();
93  m_channelsIni = settings.value("TUNER/channels", "").toString();
94  m_lockTimeout = settings.value("TUNER/timeout", "").toInt();
95  m_scanCommand = settings.value("SCANNER/command", "").toString();
96  m_scanTimeout = settings.value("SCANNER/timeout", "").toInt();
97 
98  settings.beginGroup("ENVIRONMENT");
99 
100  m_appEnv.clear();
101  QStringList keys = settings.childKeys();
102  QStringList::const_iterator Ienv;
103  for (Ienv = keys.constBegin(); Ienv != keys.constEnd(); ++Ienv)
104  {
105  if (!(*Ienv).isEmpty() && (*Ienv)[0] != '#')
106  m_appEnv.insert((*Ienv).toLocal8Bit().constData(),
107  settings.value(*Ienv).toString());
108  }
109 
110  if (!m_channelsIni.isEmpty())
111  {
112  if (!QFileInfo::exists(m_channelsIni))
113  {
114  // Assume the channels config is in the same directory as
115  // main config
116  QDir conf_path = QFileInfo(m_configIni).absolutePath();
117  QFileInfo ini(conf_path, m_channelsIni);
118  m_channelsIni = ini.absoluteFilePath();
119  }
120  }
121 
122  return true;
123 }
124 
126 {
127  if (m_fatal)
128  {
129  emit SendMessage("Open", "0", "ERR:Already dead.");
130  return false;
131  }
132 
133  if (m_command.isEmpty())
134  {
135  LOG(VB_RECORD, LOG_ERR, LOC + ": No recorder provided.");
136  emit SendMessage("Open", "0", "ERR:No recorder provided.");
137  return false;
138  }
139 
140  if (!m_appEnv.isEmpty())
141  {
142  QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
143  QMap<QString, QString>::const_iterator Ienv;
144  for (Ienv = m_appEnv.constBegin();
145  Ienv != m_appEnv.constEnd(); ++Ienv)
146  {
147  env.insert(Ienv.key(), Ienv.value());
148  LOG(VB_RECORD, LOG_INFO, LOC + QString(" ENV: '%1' = '%2'")
149  .arg(Ienv.key()).arg(Ienv.value()));
150  }
151  m_proc.setProcessEnvironment(env);
152  }
153 
154  QObject::connect(&m_proc, &QProcess::started, this,
156 #if 0
157  QObject::connect(&m_proc, &QProcess::readyReadStandardOutput, this,
159 #endif
160  QObject::connect(&m_proc, &QProcess::readyReadStandardError, this,
162 
163  qRegisterMetaType<QProcess::ProcessError>("QProcess::ProcessError");
164  QObject::connect(&m_proc, &QProcess::errorOccurred,
166 
167  qRegisterMetaType<QProcess::ExitStatus>("QProcess::ExitStatus");
168  QObject::connect(&m_proc,
169  static_cast<void (QProcess::*)
170  (int,QProcess::ExitStatus exitStatus)>
171  (&QProcess::finished),
173 
174  qRegisterMetaType<QProcess::ProcessState>("QProcess::ProcessState");
175  QObject::connect(&m_proc, &QProcess::stateChanged, this,
177 
178  LOG(VB_RECORD, LOG_INFO, LOC + ": Opened");
179 
180  emit Opened();
181  return true;
182 }
183 
184 void MythExternRecApp::TerminateProcess(QProcess & proc, const QString & desc) const
185 {
186  if (proc.state() == QProcess::Running)
187  {
188  LOG(VB_RECORD, LOG_INFO, LOC +
189  QString("Sending SIGINT to %1(%2)").arg(desc).arg(proc.pid()));
190  kill(proc.pid(), SIGINT);
191  proc.waitForFinished(5000);
192  }
193  if (proc.state() == QProcess::Running)
194  {
195  LOG(VB_RECORD, LOG_INFO, LOC +
196  QString("Sending SIGTERM to %1(%2)").arg(desc).arg(proc.pid()));
197  proc.terminate();
198  proc.waitForFinished();
199  }
200  if (proc.state() == QProcess::Running)
201  {
202  LOG(VB_RECORD, LOG_INFO, LOC +
203  QString("Sending SIGKILL to %1(%2)").arg(desc).arg(proc.pid()));
204  proc.kill();
205  proc.waitForFinished();
206  }
207 }
208 
209 Q_SLOT void MythExternRecApp::Close(void)
210 {
211  if (m_run)
212  {
213  LOG(VB_RECORD, LOG_INFO, LOC + ": Closing application.");
214  m_run = false;
215  m_runCond.notify_all();
216  std::this_thread::sleep_for(std::chrono::microseconds(50));
217  }
218 
219  if (m_tuneProc.state() == QProcess::Running)
220  {
221  m_tuneProc.closeReadChannel(QProcess::StandardOutput);
223  }
224 
225  if (m_proc.state() == QProcess::Running)
226  {
227  m_proc.closeReadChannel(QProcess::StandardOutput);
228  TerminateProcess(m_proc, "App");
229  std::this_thread::sleep_for(std::chrono::microseconds(50));
230  }
231 
232  emit Done();
233 }
234 
236 {
237  QByteArray buf;
238 
239  while (m_run)
240  {
241  {
242  std::unique_lock<std::mutex> lk(m_runMutex);
243  m_runCond.wait_for(lk, std::chrono::milliseconds(10));
244  }
245 
246  if (m_proc.state() == QProcess::Running)
247  {
248  if (m_proc.waitForReadyRead(50))
249  {
250  buf = m_proc.read(m_blockSize);
251  if (!buf.isEmpty())
252  emit Fill(buf);
253  }
254  }
255 
256  qApp->processEvents();
257  }
258 
259  if (m_proc.state() == QProcess::Running)
260  {
261  m_proc.closeReadChannel(QProcess::StandardOutput);
262  TerminateProcess(m_proc, "App");
263  }
264 
265  emit Done();
266 }
267 
268 Q_SLOT void MythExternRecApp::Cleanup(void)
269 {
270  m_tunedChannel.clear();
271 
272  if (m_cleanup.isEmpty())
273  return;
274 
275  QStringList args = MythSplitCommandString(m_cleanup);
276  QString cmd = args.takeFirst();
277 
278  LOG(VB_RECORD, LOG_WARNING, LOC +
279  QString(" Beginning cleanup: '%1'").arg(cmd));
280 
281  QProcess cleanup;
282  cleanup.start(cmd, args);
283  if (!cleanup.waitForStarted())
284  {
285  LOG(VB_RECORD, LOG_ERR, LOC + ": Failed to start cleanup process: "
286  + ENO);
287  return;
288  }
289  cleanup.waitForFinished(5000);
290  if (cleanup.state() == QProcess::NotRunning)
291  {
292  if (cleanup.exitStatus() != QProcess::NormalExit)
293  {
294  LOG(VB_RECORD, LOG_ERR, LOC + ": Cleanup process failed: " + ENO);
295  return;
296  }
297  }
298 
299  LOG(VB_RECORD, LOG_INFO, LOC + ": Cleanup finished.");
300 }
301 
303 {
304  if (m_onDataStart.isEmpty())
305  return;
306 
307  QString cmd = m_onDataStart;
308  cmd.replace("%CHANNUM%", m_tunedChannel);
309 
310  QStringList args = MythSplitCommandString(cmd);
311  cmd = args.takeFirst();
312 
313  LOG(VB_RECORD, LOG_INFO, LOC +
314  QString(" Data started, finishing tune: '%1'").arg(cmd));
315 
316  QProcess finish;
317  finish.start(cmd, args);
318  if (!finish.waitForStarted())
319  {
320  LOG(VB_RECORD, LOG_ERR, LOC + ": Failed to finish tune process: "
321  + ENO);
322  return;
323  }
324  finish.waitForFinished(5000);
325  if (finish.state() == QProcess::NotRunning)
326  {
327  if (finish.exitStatus() != QProcess::NormalExit)
328  {
329  LOG(VB_RECORD, LOG_ERR, LOC + ": Finish tune failed: " + ENO);
330  return;
331  }
332  }
333 
334  LOG(VB_RECORD, LOG_INFO, LOC + ": tunning finished.");
335 }
336 
337 Q_SLOT void MythExternRecApp::LoadChannels(const QString & serial)
338 {
339  if (m_channelsIni.isEmpty())
340  {
341  LOG(VB_CHANNEL, LOG_ERR, LOC + ": No channels configured.");
342  emit SendMessage("LoadChannels", serial, "ERR:No channels configured.");
343  return;
344  }
345 
346  if (!m_scanCommand.isEmpty())
347  {
348  QString cmd = m_scanCommand;
349  cmd.replace("%CHANCONF%", m_channelsIni);
350 
351  QStringList args = MythSplitCommandString(cmd);
352  cmd = args.takeFirst();
353 
354  QProcess scanner;
355  scanner.start(cmd, args);
356 
357  if (!scanner.waitForStarted())
358  {
359  QString errmsg = QString("Failed to start '%1': ").arg(cmd) + ENO;
360  LOG(VB_CHANNEL, LOG_ERR, LOC + ": " + errmsg);
361  emit SendMessage("LoadChannels", serial,
362  QString("ERR:%1").arg(errmsg));
363  return;
364  }
365 
366  QByteArray buf;
367  QElapsedTimer timer;
368 
369  timer.start();
370  while (timer.elapsed() < m_scanTimeout)
371  {
372  if (scanner.waitForReadyRead(50))
373  {
374  buf = scanner.readLine();
375  if (!buf.isEmpty())
376  {
377  LOG(VB_RECORD, LOG_INFO, LOC + ": " + buf);
378  MythLog(buf);
379  }
380  }
381 
382  if (scanner.state() != QProcess::Running)
383  break;
384 
385  if (scanner.waitForFinished(50 /* msecs */))
386  break;
387  }
388  if (timer.elapsed() >= m_scanTimeout)
389  {
390  QString errmsg = QString("Timedout waiting for '%1'").arg(cmd);
391  LOG(VB_CHANNEL, LOG_ERR, LOC + ": " + errmsg);
392  emit SendMessage("LoadChannels", serial,
393  QString("ERR:%1").arg(errmsg));
394  return;
395  }
396  }
397 
398  if (m_chanSettings == nullptr)
399  m_chanSettings = new QSettings(m_channelsIni, QSettings::IniFormat);
400  m_chanSettings->sync();
401  m_channels = m_chanSettings->childGroups();
402 
403  emit SendMessage("LoadChannels", serial,
404  QString("OK:%1").arg(m_channels.size()));
405 }
406 
407 void MythExternRecApp::GetChannel(const QString & serial, const QString & func)
408 {
409  if (m_channelsIni.isEmpty() || m_channels.empty())
410  {
411  LOG(VB_CHANNEL, LOG_ERR, LOC + ": No channels configured.");
412  emit SendMessage("FirstChannel", serial,
413  QString("ERR:No channels configured."));
414  return;
415  }
416 
417  if (m_chanSettings == nullptr)
418  {
419  LOG(VB_CHANNEL, LOG_WARNING, LOC + ": Invalid channel configuration.");
420  emit SendMessage(func, serial,
421  "ERR:Invalid channel configuration.");
422  return;
423  }
424 
425  if (m_channels.size() <= m_channelIdx)
426  {
427  LOG(VB_CHANNEL, LOG_WARNING, LOC + ": No more channels.");
428  emit SendMessage(func, serial, "ERR:No more channels.");
429  return;
430  }
431 
432  QString channum = m_channels[m_channelIdx++];
433 
434  m_chanSettings->beginGroup(channum);
435 
436  QString name = m_chanSettings->value("NAME").toString();
437  QString callsign = m_chanSettings->value("CALLSIGN").toString();
438  QString xmltvid = m_chanSettings->value("XMLTVID").toString();
439  QString icon = m_chanSettings->value("ICON").toString();
440 
441  m_chanSettings->endGroup();
442 
443  LOG(VB_CHANNEL, LOG_INFO, LOC +
444  QString(": NextChannel Name:'%1',Callsign:'%2',xmltvid:%3,Icon:%4")
445  .arg(name).arg(callsign).arg(xmltvid).arg(icon));
446 
447  emit SendMessage(func, serial, QString("OK:%1,%2,%3,%4,%5")
448  .arg(channum).arg(name).arg(callsign)
449  .arg(xmltvid).arg(icon));
450 }
451 
452 Q_SLOT void MythExternRecApp::FirstChannel(const QString & serial)
453 {
454  m_channelIdx = 0;
455  GetChannel(serial, "FirstChannel");
456 }
457 
458 Q_SLOT void MythExternRecApp::NextChannel(const QString & serial)
459 {
460  GetChannel(serial, "NextChannel");
461 }
462 
463 void MythExternRecApp::NewEpisodeStarting(const QString & channum)
464 {
465  QString cmd = m_newEpisodeCommand;
466  cmd.replace("%CHANNUM%", channum);
467 
468  QStringList args = MythSplitCommandString(cmd);
469  cmd = args.takeFirst();
470 
471  LOG(VB_RECORD, LOG_WARNING, LOC +
472  QString(" New episode starting on current channel: '%1'").arg(cmd));
473 
474  QProcess proc;
475  proc.start(cmd, args);
476  if (!proc.waitForStarted())
477  {
478  LOG(VB_RECORD, LOG_ERR, LOC +
479  " NewEpisodeStarting: Failed to start process: " + ENO);
480  return;
481  }
482  proc.waitForFinished(5000);
483  if (proc.state() == QProcess::NotRunning)
484  {
485  if (proc.exitStatus() != QProcess::NormalExit)
486  {
487  LOG(VB_RECORD, LOG_ERR, LOC +
488  " NewEpisodeStarting: process failed: " + ENO);
489  return;
490  }
491  }
492 
493  LOG(VB_RECORD, LOG_INFO, LOC + "NewEpisodeStarting: finished.");
494 }
495 
496 Q_SLOT void MythExternRecApp::TuneChannel(const QString & serial,
497  const QString & channum)
498 {
499  if (m_tuneCommand.isEmpty() && m_channelsIni.isEmpty())
500  {
501  LOG(VB_CHANNEL, LOG_ERR, LOC + ": No 'tuner' configured.");
502  emit SendMessage("TuneChannel", serial, "ERR:No 'tuner' configured.");
503  return;
504  }
505 
506  if (m_tunedChannel == channum)
507  {
508  if (!m_newEpisodeCommand.isEmpty())
509  NewEpisodeStarting(channum);
510 
511  LOG(VB_CHANNEL, LOG_INFO, LOC +
512  QString("TuneChanne: Already on %1").arg(channum));
513  emit SendMessage("TuneChannel", serial,
514  QString("OK:Tunned to %1").arg(channum));
515  return;
516  }
517 
518  m_desc = m_recDesc;
520 
521  QString tunecmd = m_tuneCommand;
522  QString url;
523 
524  if (!m_channelsIni.isEmpty())
525  {
526  QSettings settings(m_channelsIni, QSettings::IniFormat);
527  settings.beginGroup(channum);
528 
529  url = settings.value("URL").toString();
530 
531  if (url.isEmpty())
532  {
533  QString msg = QString("Channel number [%1] is missing a URL.")
534  .arg(channum);
535 
536  LOG(VB_CHANNEL, LOG_ERR, LOC + ": " + msg);
537  }
538  else
539  tunecmd.replace("%URL%", url);
540 
541  if (!url.isEmpty() && m_command.indexOf("%URL%") >= 0)
542  {
543  m_command.replace("%URL%", url);
544  LOG(VB_CHANNEL, LOG_DEBUG, LOC +
545  QString(": '%URL%' replaced with '%1' in cmd: '%2'")
546  .arg(url).arg(m_command));
547  }
548 
549  m_desc.replace("%CHANNAME%", settings.value("NAME").toString());
550  m_desc.replace("%CALLSIGN%", settings.value("CALLSIGN").toString());
551 
552  settings.endGroup();
553  }
554 
555  if (m_tuneProc.state() == QProcess::Running)
556  TerminateProcess(m_tuneProc, "Tune");
557 
558  tunecmd.replace("%CHANNUM%", channum);
559  m_command.replace("%CHANNUM%", channum);
560 
561  if (!m_logFile.isEmpty() && m_command.indexOf("%LOGFILE%") >= 0)
562  {
563  m_command.replace("%LOGFILE%", m_logFile);
564  LOG(VB_RECORD, LOG_DEBUG, LOC +
565  QString(": '%LOGFILE%' replaced with '%1' in cmd: '%2'")
567  }
568 
569  if (!m_logging.isEmpty() && m_command.indexOf("%LOGGING%") >= 0)
570  {
571  m_command.replace("%LOGGING%", m_logging);
572  LOG(VB_RECORD, LOG_DEBUG, LOC +
573  QString(": '%LOGGING%' replaced with '%1' in cmd: '%2'")
575  }
576 
577  m_desc.replace("%URL%", url);
578  m_desc.replace("%CHANNUM%", channum);
579 
580  if (!m_tuneCommand.isEmpty())
581  {
582  QStringList args = MythSplitCommandString(tunecmd);
583  QString cmd = args.takeFirst();
584  m_tuningChannel = channum;
585  m_tuneProc.start(cmd, args);
586  if (!m_tuneProc.waitForStarted())
587  {
588  QString errmsg = QString("Tune `%1` failed: ").arg(tunecmd) + ENO;
589  LOG(VB_CHANNEL, LOG_ERR, LOC + ": " + errmsg);
590  emit SendMessage("TuneChannel", serial,
591  QString("ERR:%1").arg(errmsg));
592  return;
593  }
594 
595  LOG(VB_CHANNEL, LOG_INFO, LOC + QString(": Started `%1` URL '%2'")
596  .arg(tunecmd).arg(url));
597  emit SendMessage("TuneChannel", serial,
598  QString("OK:InProgress `%1`").arg(tunecmd));
599  }
600  else
601  {
602  m_tunedChannel = channum;
603  emit SetDescription(Desc());
604  emit SendMessage("TuneChannel", serial,
605  QString("OK:Tuned to %1").arg(m_tunedChannel));
606  }
607 }
608 
609 Q_SLOT void MythExternRecApp::TuneStatus(const QString & serial)
610 {
611  if (m_tuneProc.state() == QProcess::Running)
612  {
613  LOG(VB_CHANNEL, LOG_INFO, LOC +
614  QString(": Tune process(%1) still running").arg(m_tuneProc.pid()));
615  emit SendMessage("TuneStatus", serial, "OK:InProgress");
616  return;
617  }
618 
619  if (!m_tuneCommand.isEmpty() &&
620  m_tuneProc.exitStatus() != QProcess::NormalExit)
621  {
622  QString errmsg = QString("'%1' failed: ")
623  .arg(m_tuneProc.program()) + ENO;
624  LOG(VB_CHANNEL, LOG_ERR, LOC + ": " + errmsg);
625  emit SendMessage("TuneStatus", serial,
626  QString("ERR:%1").arg(errmsg));
627  return;
628  }
629 
631  m_tuningChannel.clear();
632 
633  LOG(VB_CHANNEL, LOG_INFO, LOC + QString(": Tuned %1").arg(m_tunedChannel));
634  emit SetDescription(Desc());
635  emit SendMessage("TuneChannel", serial,
636  QString("OK:Tuned to %1").arg(m_tunedChannel));
637 }
638 
639 Q_SLOT void MythExternRecApp::LockTimeout(const QString & serial)
640 {
641  if (!Open())
642  {
643  LOG(VB_CHANNEL, LOG_WARNING, LOC +
644  "Cannot read LockTimeout from config file.");
645  emit SendMessage("LockTimeout", serial, "ERR: Not open");
646  return;
647  }
648 
649  if (m_lockTimeout > 0)
650  {
651  LOG(VB_CHANNEL, LOG_INFO, LOC +
652  QString("Using configured LockTimeout of %1").arg(m_lockTimeout));
653  emit SendMessage("LockTimeout", serial,
654  QString("OK:%1").arg(m_lockTimeout));
655  return;
656  }
657  LOG(VB_CHANNEL, LOG_INFO, LOC +
658  "No LockTimeout defined in config, defaulting to 12000ms");
659  emit SendMessage("LockTimeout", serial, QString("OK:%1")
660  .arg(m_scanCommand.isEmpty() ? 12000 : 120000));
661 }
662 
663 Q_SLOT void MythExternRecApp::HasTuner(const QString & serial)
664 {
665  emit SendMessage("HasTuner", serial, QString("OK:%1")
666  .arg(m_tuneCommand.isEmpty() &&
667  m_channelsIni.isEmpty() ? "No" : "Yes"));
668 }
669 
670 Q_SLOT void MythExternRecApp::HasPictureAttributes(const QString & serial)
671 {
672  emit SendMessage("HasPictureAttributes", serial, "OK:No");
673 }
674 
675 Q_SLOT void MythExternRecApp::SetBlockSize(const QString & serial, int blksz)
676 {
677  m_blockSize = blksz;
678  emit SendMessage("BlockSize", serial, "OK");
679 }
680 
681 Q_SLOT void MythExternRecApp::StartStreaming(const QString & serial)
682 {
683  m_streaming = true;
684  if (m_tunedChannel.isEmpty() && !m_channelsIni.isEmpty())
685  {
686  LOG(VB_RECORD, LOG_ERR, LOC + ": No channel has been tuned");
687  emit SendMessage("StartStreaming", serial,
688  "ERR:No channel has been tuned");
689  return;
690  }
691 
692  if (m_proc.state() == QProcess::Running)
693  {
694  LOG(VB_RECORD, LOG_ERR, LOC + ": Application already running");
695  emit SendMessage("StartStreaming", serial,
696  "WARN:Application already running");
697  return;
698  }
699 
700  QStringList args = MythSplitCommandString(m_command);
701  QString cmd = args.takeFirst();
702  m_proc.start(cmd, args, QIODevice::ReadOnly|QIODevice::Unbuffered);
703  m_proc.setTextModeEnabled(false);
704  m_proc.setReadChannel(QProcess::StandardOutput);
705 
706  LOG(VB_RECORD, LOG_INFO, LOC + QString(": Starting process '%1' args: '%2'")
707  .arg(m_proc.program()).arg(m_proc.arguments().join(' ')));
708 
709  if (!m_proc.waitForStarted())
710  {
711  LOG(VB_RECORD, LOG_ERR, LOC + ": Failed to start application.");
712  emit SendMessage("StartStreaming", serial,
713  "ERR:Failed to start application.");
714  return;
715  }
716 
717  std::this_thread::sleep_for(std::chrono::milliseconds(50));
718 
719  if (m_proc.state() != QProcess::Running)
720  {
721  LOG(VB_RECORD, LOG_ERR, LOC + ": Application failed to start");
722  emit SendMessage("StartStreaming", serial,
723  "ERR:Application failed to start");
724  return;
725  }
726 
727  LOG(VB_RECORD, LOG_INFO, LOC + QString(": Started process '%1' PID %2")
728  .arg(m_proc.program()).arg(m_proc.processId()));
729 
730  emit Streaming(true);
731  emit SetDescription(Desc());
732  emit SendMessage("StartStreaming", serial, "OK:Streaming Started");
733 }
734 
735 Q_SLOT void MythExternRecApp::StopStreaming(const QString & serial, bool silent)
736 {
737  m_streaming = false;
738  if (m_proc.state() == QProcess::Running)
739  {
740  TerminateProcess(m_proc, "App");
741 
742  LOG(VB_RECORD, LOG_INFO, LOC + ": External application terminated.");
743  if (silent)
744  emit SendMessage("StopStreaming", serial, "STATUS:Streaming Stopped");
745  else
746  emit SendMessage("StopStreaming", serial, "OK:Streaming Stopped");
747  }
748  else
749  {
750  if (silent)
751  {
752  emit SendMessage("StopStreaming", serial,
753  "STATUS:Already not Streaming");
754  }
755  else
756  {
757  emit SendMessage("StopStreaming", serial,
758  "WARN:Already not Streaming");
759  }
760  }
761 
762  emit Streaming(false);
763  emit SetDescription(Desc());
764 }
765 
767 {
768  QString msg = QString("Process '%1' started").arg(m_proc.program());
769  LOG(VB_RECORD, LOG_INFO, LOC + ": " + msg);
770  MythLog(msg);
771 }
772 
773 Q_SLOT void MythExternRecApp::ProcFinished(int exitCode,
774  QProcess::ExitStatus exitStatus)
775 {
776  m_result = exitCode;
777  QString msg = QString("%1Finished: %2 (exit code: %3)")
778  .arg(exitStatus != QProcess::NormalExit ? "WARN:" : "")
779  .arg(exitStatus == QProcess::NormalExit ? "OK" :
780  "Abnormal exit")
781  .arg(m_result);
782  LOG(VB_RECORD, LOG_INFO, LOC + ": " + msg);
783 
784  if (m_streaming)
785  emit Streaming(false);
786  MythLog(msg);
787 }
788 
789 Q_SLOT void MythExternRecApp::ProcStateChanged(QProcess::ProcessState newState)
790 {
791  bool unexpected = false;
792  QString msg = "State Changed: ";
793  switch (newState)
794  {
795  case QProcess::NotRunning:
796  msg += "Not running";
797  unexpected = m_streaming;
798  break;
799  case QProcess::Starting:
800  msg += "Starting ";
801  break;
802  case QProcess::Running:
803  msg += QString("Running PID %1").arg(m_proc.processId());
804  break;
805  }
806 
807  LOG(VB_RECORD, LOG_INFO, LOC + msg);
808  if (unexpected)
809  {
810  emit Streaming(false);
811  MythLog("ERR Unexpected " + msg);
812  }
813 }
814 
815 Q_SLOT void MythExternRecApp::ProcError(QProcess::ProcessError /*error */)
816 {
817  LOG(VB_RECORD, LOG_ERR, LOC + QString(": Error: %1")
818  .arg(m_proc.errorString()));
819  MythLog(m_proc.errorString());
820 }
821 
823 {
824  QByteArray buf = m_proc.readAllStandardError();
825  QString msg = QString::fromUtf8(buf).trimmed();
826 
827  // Log any error messages
828  if (!msg.isEmpty())
829  {
830  LOG(VB_RECORD, LOG_INFO, LOC + QString(">>> %1")
831  .arg(msg));
832  if (msg.size() > 79)
833  msg = QString("Application message: see '%1'").arg(m_logFile);
834 
835  MythLog(msg);
836  }
837 }
838 
840 {
841  LOG(VB_RECORD, LOG_WARNING, LOC + ": Data ready.");
842 
843  QByteArray buf = m_proc.read(m_blockSize);
844  if (!buf.isEmpty())
845  emit Fill(buf);
846 }
MythExternRecApp::SetDescription
void SetDescription(const QString &desc)
commandlineparser.h
build_compdb.args
args
Definition: build_compdb.py:11
MythExternRecApp::HasTuner
void HasTuner(const QString &serial)
Definition: MythExternRecApp.cpp:663
MythExternRecApp::m_runMutex
std::mutex m_runMutex
Definition: MythExternRecApp.h:95
ENO
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:72
MythExternRecApp::m_recCommand
QString m_recCommand
Definition: MythExternRecApp.h:106
MythExternRecApp::m_onDataStart
QString m_onDataStart
Definition: MythExternRecApp.h:113
MythExternRecApp::StopStreaming
void StopStreaming(const QString &serial, bool silent)
Definition: MythExternRecApp.cpp:735
MythExternRecApp::Streaming
void Streaming(bool val)
MythExternRecApp::MythExternRecApp
MythExternRecApp(QString command, QString conf_file, QString log_file, QString logging)
Definition: MythExternRecApp.cpp:35
MythExternRecApp::ProcFinished
void ProcFinished(int exitCode, QProcess::ExitStatus exitStatus)
Definition: MythExternRecApp.cpp:773
MythExternRecApp::m_blockSize
uint m_blockSize
Definition: MythExternRecApp.h:100
MythExternRecApp::m_run
std::atomic< bool > m_run
Definition: MythExternRecApp.h:93
MythExternRecApp::m_command
QString m_command
Definition: MythExternRecApp.h:103
MythExternRecApp::TerminateProcess
void TerminateProcess(QProcess &proc, const QString &desc) const
Definition: MythExternRecApp.cpp:184
MythExternRecApp::m_tuneProc
QProcess m_tuneProc
Definition: MythExternRecApp.h:111
MythExternRecApp::MythLog
void MythLog(const QString &msg)
Definition: MythExternRecApp.h:45
MythExternRecApp::Run
void Run(void)
Definition: MythExternRecApp.cpp:235
arg
arg(title).arg(filename).arg(doDelete))
MythExternRecApp::Done
void Done(void)
MythExternRecApp::m_channels
QStringList m_channels
Definition: MythExternRecApp.h:131
LOC
#define LOC
Definition: MythExternRecApp.cpp:33
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
MythExternRecApp::Open
bool Open(void)
Definition: MythExternRecApp.cpp:125
MythExternRecApp::NewEpisodeStarting
void NewEpisodeStarting(const QString &channum)
Definition: MythExternRecApp.cpp:463
MythExternRecApp::SetBlockSize
void SetBlockSize(const QString &serial, int blksz)
Definition: MythExternRecApp.cpp:675
MythExternRecApp::m_tuneCommand
QString m_tuneCommand
Definition: MythExternRecApp.h:112
MythExternRecApp::m_recDesc
QString m_recDesc
Definition: MythExternRecApp.h:107
MythExternRecApp::HasPictureAttributes
void HasPictureAttributes(const QString &serial)
Definition: MythExternRecApp.cpp:670
MythExternRecApp::TuneStatus
void TuneStatus(const QString &serial)
Definition: MythExternRecApp.cpp:609
MythExternRecApp::TuneChannel
void TuneChannel(const QString &serial, const QString &channum)
Definition: MythExternRecApp.cpp:496
MythExternRecApp::LockTimeout
void LockTimeout(const QString &serial)
Definition: MythExternRecApp.cpp:639
MythExternRecApp::m_cleanup
QString m_cleanup
Definition: MythExternRecApp.h:104
MythExternRecApp::Close
void Close(void)
Definition: MythExternRecApp.cpp:209
MythExternRecApp::m_streaming
std::atomic< bool > m_streaming
Definition: MythExternRecApp.h:96
MythExternRecApp::m_logging
QString m_logging
Definition: MythExternRecApp.h:122
MythExternRecApp::Opened
void Opened(void)
MythExternRecApp::m_newEpisodeCommand
QString m_newEpisodeCommand
Definition: MythExternRecApp.h:114
MythExternRecApp.h
MythExternRecApp::LoadChannels
void LoadChannels(const QString &serial)
Definition: MythExternRecApp.cpp:337
MythExternRecApp::m_tuningChannel
QString m_tuningChannel
Definition: MythExternRecApp.h:126
MythExternRecApp::m_result
int m_result
Definition: MythExternRecApp.h:97
MythExternRecApp::Cleanup
void Cleanup(void)
Definition: MythExternRecApp.cpp:268
MythExternRecApp::m_configIni
QString m_configIni
Definition: MythExternRecApp.h:123
MythExternRecApp::m_lockTimeout
uint m_lockTimeout
Definition: MythExternRecApp.h:116
MythExternRecApp::ProcReadStandardOutput
void ProcReadStandardOutput(void)
Definition: MythExternRecApp.cpp:839
MythExternRecApp::ProcReadStandardError
void ProcReadStandardError(void)
Definition: MythExternRecApp.cpp:822
MythExternRecApp::GetChannel
void GetChannel(const QString &serial, const QString &func)
Definition: MythExternRecApp.cpp:407
MythExternRecApp::NextChannel
void NextChannel(const QString &serial)
Definition: MythExternRecApp.cpp:458
MythExternRecApp::m_chanSettings
QSettings * m_chanSettings
Definition: MythExternRecApp.h:130
cleanup
static QString cleanup(const QString &str)
Definition: remoteencoder.cpp:674
MythExternRecApp::ProcError
void ProcError(QProcess::ProcessError error)
Definition: MythExternRecApp.cpp:815
MythExternRecApp::~MythExternRecApp
~MythExternRecApp(void) override
Definition: MythExternRecApp.cpp:61
MythExternRecApp::m_desc
QString m_desc
Definition: MythExternRecApp.h:124
MythExternRecApp::m_logFile
QString m_logFile
Definition: MythExternRecApp.h:121
MythExternRecApp::m_channelIdx
int m_channelIdx
Definition: MythExternRecApp.h:132
MythExternRecApp::m_tunedChannel
QString m_tunedChannel
Definition: MythExternRecApp.h:127
MythExternRecApp::FirstChannel
void FirstChannel(const QString &serial)
Definition: MythExternRecApp.cpp:452
mythmiscutil.h
MythExternRecApp::config
bool config(void)
Definition: MythExternRecApp.cpp:76
MythExternRecApp::m_fatal
bool m_fatal
Definition: MythExternRecApp.h:91
MythExternRecApp::SendMessage
void SendMessage(const QString &func, const QString &serial, const QString &msg)
MythExternRecApp::DataStarted
void DataStarted(void)
Definition: MythExternRecApp.cpp:302
MythExternRecApp::Desc
QString Desc(void) const
Definition: MythExternRecApp.cpp:66
MythExternRecApp::StartStreaming
void StartStreaming(const QString &serial)
Definition: MythExternRecApp.cpp:681
MythSplitCommandString
QStringList MythSplitCommandString(const QString &line)
Definition: mythmiscutil.cpp:1259
MythExternRecApp::m_scanCommand
QString m_scanCommand
Definition: MythExternRecApp.h:118
MythExternRecApp::m_channelsIni
QString m_channelsIni
Definition: MythExternRecApp.h:115
MythExternRecApp::ProcStarted
void ProcStarted(void)
Definition: MythExternRecApp.cpp:766
MythExternRecApp::m_appEnv
QMap< QString, QString > m_appEnv
Definition: MythExternRecApp.h:109
MythExternRecApp::m_runCond
std::condition_variable m_runCond
Definition: MythExternRecApp.h:94
MythExternRecApp::m_proc
QProcess m_proc
Definition: MythExternRecApp.h:102
MythExternRecApp::m_scanTimeout
uint m_scanTimeout
Definition: MythExternRecApp.h:119
MythExternRecApp::Fill
void Fill(const QByteArray &buffer)
MythExternRecApp::ProcStateChanged
void ProcStateChanged(QProcess::ProcessState newState)
Definition: MythExternRecApp.cpp:789