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