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