MythTV  master
mythfilerecorder.cpp
Go to the documentation of this file.
1 
2 // C/C++
3 #include <array>
4 #include <fcntl.h>
5 #include <iostream>
6 #include <sys/poll.h>
7 #include <sys/stat.h>
8 #include <termios.h>
9 
10 // Qt
11 #include <QCoreApplication>
12 #include <QDir>
13 #include <QThread>
14 #include <QTime>
15 
16 // MythTV
17 #include "libmyth/mythcontext.h"
18 #include "libmythbase/exitcodes.h"
20 #include "libmythbase/mythversion.h"
21 
22 // MythFileRecorder
23 #include "mythfilerecorder.h"
25 
26 static constexpr int API_VERSION { 1 };
27 static constexpr const char* VERSION { "1.0.0" };
28 #define LOC QString("File(%1): ").arg(m_fileName)
29 
30 Streamer::Streamer(Commands *parent, QString fname,
31  int data_rate, bool loopinput) :
32  m_parent(parent), m_fileName(std::move(fname)),
33  m_loop(loopinput), m_dataRate(data_rate)
34 {
35  setObjectName("Streamer");
36  OpenFile();
37  LOG(VB_RECORD, LOG_INFO, LOC + QString("Data Rate: %1").arg(m_dataRate));
38 }
39 
41 {
42  LOG(VB_RECORD, LOG_INFO, LOC + "Streamer::dtor -- begin");
43  CloseFile();
44  LOG(VB_RECORD, LOG_INFO, LOC + "Streamer::dtor -- end");
45 }
46 
48 {
49  m_file = new QFile(m_fileName);
50  if (!m_file || !m_file->open(QIODevice::ReadOnly))
51  {
52  LOG(VB_RECORD, LOG_ERR, LOC + QString("Failed to open '%1' - ")
53  .arg(m_fileName) + ENO);
54  }
55 }
56 
58 {
59  LOG(VB_RECORD, LOG_INFO, LOC + "Streamer::Close -- begin");
60 
61  if (m_file)
62  {
63  delete m_file;
64  m_file = nullptr;
65  }
66 
67  LOG(VB_RECORD, LOG_INFO, LOC + "Streamer::Close -- end");
68 }
69 
71 {
72  int pkt_size = 0;
73  int buf_size = 0;
74 
75  LOG(VB_RECORD, LOG_DEBUG, LOC + "SendBytes -- start");
76 
77  if (!m_file)
78  {
79  LOG(VB_GENERAL, LOG_ERR, LOC + "SendBytes -- file not open");
80  return;
81  }
82 
83  if (m_file->atEnd())
84  {
85  if (m_loop)
86  m_file->reset();
87  else if (m_buffer.isEmpty())
88  m_parent->setEoF();
89  }
90 
91  if (!m_file->atEnd())
92  {
93  int read_sz = m_blockSize.loadAcquire();
94  if (!m_startTime.isValid())
96  qint64 delta = m_startTime.secsTo(MythDate::current()) + 1;
97  int rate = (delta * m_dataRate) - m_dataRead;
98 
99  read_sz = std::min(rate, read_sz);
100  read_sz = std::min(static_cast<int>(m_bufferMax - m_buffer.size()), read_sz);
101 
102  if (read_sz > 0)
103  {
104  QByteArray buffer = m_file->read(read_sz);
105 
106  pkt_size = buffer.size();
107  if (pkt_size > 0)
108  {
109  m_dataRead += pkt_size;
110  if (m_buffer.size() + pkt_size > m_bufferMax)
111  {
112  // This should never happen
113  LOG(VB_RECORD, LOG_WARNING, LOC +
114  QString("SendBytes -- Overflow: %1 > %2, "
115  "dropping first %3 bytes.")
116  .arg(m_buffer.size() + pkt_size)
117  .arg(m_bufferMax).arg(pkt_size));
118  m_buffer.remove(0, pkt_size);
119  }
120  m_buffer += buffer;
121  }
122  }
123  }
124  if (m_buffer.isEmpty())
125  {
126  LOG(VB_RECORD, LOG_DEBUG, LOC + "SendBytes -- Buffer is empty.");
127  return;
128  }
129 
130  buf_size = m_buffer.size();
131  LOG(VB_RECORD, LOG_DEBUG, LOC +
132  QString("SendBytes -- Read %1 from file. %2 bytes buffered")
133  .arg(pkt_size).arg(buf_size));
134 
135  int write_len = m_blockSize.loadAcquire();
136  if (write_len > buf_size)
137  write_len = buf_size;
138  LOG(VB_RECORD, LOG_DEBUG, LOC +
139  QString("SendBytes -- writing %1 bytes").arg(write_len));
140 
141  int wrote = write(1, m_buffer.constData(), write_len);
142 
143  LOG(VB_RECORD, LOG_DEBUG, LOC +
144  QString("SendBytes -- wrote %1 bytes").arg(wrote));
145 
146  if (wrote < buf_size)
147  {
148  m_buffer.remove(0, wrote);
149  LOG(VB_RECORD, LOG_WARNING, LOC +
150  QString("%1 bytes unwritten").arg(m_buffer.size()));
151  }
152  else
153  m_buffer.clear();
154 
155  LOG(VB_RECORD, LOG_DEBUG, LOC + "SendBytes -- end");
156 }
157 
158 
160 {
161  setObjectName("Command");
162 }
163 
164 bool Commands::send_status(const QString & status) const
165 {
166  QByteArray buf = status.toLatin1() + '\n';
167  int len = write(2, buf.constData(), buf.size());
168  if (len != buf.size())
169  {
170  LOG(VB_RECORD, LOG_ERR, LOC +
171  QString("Status -- Wrote %1 of %2 bytes of status '%3'")
172  .arg(len).arg(status.size()).arg(status));
173  return false;
174  }
175  LOG(VB_RECORD, LOG_DEBUG, "Status: " + status);
176  return true;
177 }
178 
179 bool Commands::process_command(QString & cmd)
180 {
181  LOG(VB_RECORD, LOG_DEBUG, LOC + cmd);
182 
183  if (cmd.startsWith("Version?"))
184  {
185  send_status(QString("OK:%1").arg(VERSION));
186  return true;
187  }
188  if (cmd.startsWith("APIVersion?"))
189  {
190  send_status(QString("OK:%1").arg(API_VERSION));
191  return true;
192  }
193  if (cmd.startsWith("APIVersion:1"))
194  {
195  QString reply = (API_VERSION == 1) ? "OK:Yes": "OK:No";
196  send_status(reply);
197  return true;
198  }
199  if (cmd.startsWith("HasLock?"))
200  {
201  send_status(m_streamer->IsOpen() ? "OK:Yes" : "OK:No");
202  return true;
203  }
204  if (cmd.startsWith("SignalStrengthPercent"))
205  {
206  send_status(m_streamer->IsOpen() ? "OK:100" : "OK:0");
207  return true;
208  }
209  if (cmd.startsWith("LockTimeout"))
210  {
211  send_status("OK:1000");
212  return true;
213  }
214  if (cmd.startsWith("HasTuner?"))
215  {
216  send_status("OK:No");
217  return true;
218  }
219  if (cmd.startsWith("HasPictureAttributes?"))
220  {
221  send_status("OK:No");
222  return true;
223  }
224 
225  if (!m_streamer)
226  {
227  send_status("ERR:Not Initialized");
228  LOG(VB_RECORD, LOG_ERR, LOC + QString("%1 failed - not initialized!")
229  .arg(cmd));
230  return false;
231  }
232 
233  if (cmd.startsWith("SendBytes"))
234  {
235  if (!m_streamer->IsOpen())
236  {
237  send_status("ERR:file not open");
238  LOG(VB_RECORD, LOG_ERR, LOC + "SendBytes - file not open.");
239  }
240  else
241  {
242  if (m_eof.loadAcquire() != 0)
243  send_status("ERR:End of file");
244  else
245  {
246  send_status("OK");
247  emit SendBytes();
248  }
249  }
250  }
251 #if 0
252  else if (cmd.startsWith("XON"))
253  {
254  // Used when FlowControl is XON/XOFF
255  }
256  else if (cmd.startsWith("XOFF"))
257  {
258  // Used when FlowControl is XON/XOFF
259  }
260  else if (cmd.startsWith("TuneChannel"))
261  {
262  // Used if we announce that we have a 'tuner'
263 
267  }
268 #endif
269  else if (cmd.startsWith("IsOpen?"))
270  {
271  if (m_streamer->IsOpen())
272  send_status("OK:Open");
273  else
274  send_status(QString("ERR:Not Open: '%2'")
275  .arg(m_streamer->ErrorString()));
276  }
277  else if (cmd.startsWith("CloseRecorder"))
278  {
279  send_status("OK:Terminating");
280  emit CloseFile();
281  return false;
282  }
283  else if (cmd.startsWith("FlowControl?"))
284  {
285  send_status("OK:Polling");
286  }
287  else if (cmd.startsWith("BlockSize"))
288  {
289  m_streamer->BlockSize(cmd.mid(10).toInt());
290  send_status("OK");
291  }
292  else if (cmd.startsWith("StartStreaming"))
293  {
294  send_status("OK:Started");
295  }
296  else if (cmd.startsWith("StopStreaming"))
297  {
298  /* This does not close the stream! When Myth is done with
299  * this 'recording' ExternalChannel::EnterPowerSavingMode()
300  * will be called, which invokes CloseRecorder() */
301  send_status("OK:Stopped");
302  }
303  else
304  {
305  send_status(QString("ERR:Unknown command '%1'").arg(cmd));
306  LOG(VB_RECORD, LOG_ERR, LOC + QString("Unknown command '%1'")
307  .arg(cmd));
308  }
309 
310  return true;
311 }
312 
313 bool Commands::Run(const QString & filename, int data_rate, bool loopinput)
314 {
315  QString cmd;
316 
317  int poll_cnt = 1;
318  std::array<struct pollfd,2> polls {};
319 
320  polls[0].fd = 0;
321  polls[0].events = POLLIN | POLLPRI;
322  polls[0].revents = 0;
323 
325 
326  m_streamer = new Streamer(this, m_fileName, data_rate, loopinput);
327  auto *streamThread = new QThread(this);
328 
329  m_streamer->moveToThread(streamThread);
330  connect(streamThread, &QThread::finished,
331  m_streamer, &QObject::deleteLater);
332 
333  connect(this, &Commands::SendBytes,
335 
336  streamThread->start();
337 
338  QFile input;
339  input.open(stdin, QIODevice::ReadOnly);
340  QTextStream qtin(&input);
341 
342  LOG(VB_RECORD, LOG_INFO, LOC + "Listening for commands");
343 
344  while (m_run)
345  {
346  int ret = poll(polls.data(), poll_cnt, m_timeout);
347 
348  if (polls[0].revents & POLLHUP)
349  {
350  LOG(VB_RECORD, LOG_ERR, LOC + "poll eof (POLLHUP)");
351  break;
352  }
353  if (polls[0].revents & POLLNVAL)
354  {
355  LOG(VB_RECORD, LOG_ERR, LOC + "poll error");
356  return false;
357  }
358 
359  if (polls[0].revents & POLLIN)
360  {
361  if (ret > 0)
362  {
363  cmd = qtin.readLine();
364  if (!process_command(cmd))
365  {
366  streamThread->quit();
367  streamThread->wait();
368  delete streamThread;
369  streamThread = nullptr;
370  m_streamer = nullptr;
371  m_run = false;
372  }
373  }
374  else if (ret < 0)
375  {
376  if ((EOVERFLOW == errno))
377  {
378  LOG(VB_RECORD, LOG_ERR, LOC + "command overflow.");
379  break; // we have an error to handle
380  }
381 
382  if ((EAGAIN == errno) || (EINTR == errno))
383  {
384  LOG(VB_RECORD, LOG_ERR, LOC + "retry command read.");
385  continue; // errors that tell you to try again
386  }
387 
388  LOG(VB_RECORD, LOG_ERR, LOC + "unknown error reading command.");
389  }
390  }
391  }
392 
393  return true;
394 }
395 
396 
397 int main(int argc, char *argv[])
398 {
400 
401  if (!cmdline.Parse(argc, argv))
402  {
403  cmdline.PrintHelp();
405  }
406 
407  if (cmdline.toBool("showhelp"))
408  {
409  cmdline.PrintHelp();
410  return GENERIC_EXIT_OK;
411  }
412 
413  if (cmdline.toBool("showversion"))
414  {
416  return GENERIC_EXIT_OK;
417  }
418 
419  bool loopinput = !cmdline.toBool("noloop");
420  int data_rate = cmdline.toInt("data_rate");
421 
422  QCoreApplication a(argc, argv);
423  QCoreApplication::setApplicationName("mythfilerecorder");
424 
425  int retval = cmdline.ConfigureLogging();
426  if (retval != GENERIC_EXIT_OK)
427  return retval;
428 
429  QString filename = "";
430  if (!cmdline.toString("infile").isEmpty())
431  filename = cmdline.toString("infile");
432  else if (!cmdline.GetArgs().empty())
433  filename = cmdline.GetArgs().at(0);
434 
436  recorder.Run(filename, data_rate, loopinput);
437 
438  return GENERIC_EXIT_OK;
439 }
440 
441 /* vim: set expandtab tabstop=4 shiftwidth=4: */
Streamer::m_buffer
QByteArray m_buffer
Definition: mythfilerecorder.h:43
ENO
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:73
VERSION
static constexpr const char * VERSION
Definition: mythfilerecorder.cpp:27
Commands::process_command
bool process_command(QString &cmd)
Definition: mythfilerecorder.cpp:179
cmdline
MythCommFlagCommandLineParser cmdline
Definition: mythcommflag.cpp:72
Streamer::OpenFile
void OpenFile(void)
Definition: mythfilerecorder.cpp:47
Streamer::m_dataRate
uint m_dataRate
Definition: mythfilerecorder.h:48
Streamer::m_bufferMax
int m_bufferMax
Definition: mythfilerecorder.h:44
mythburn.write
def write(text, progress=True)
Definition: mythburn.py:308
Streamer::ErrorString
QString ErrorString(void) const
Definition: mythfilerecorder.h:30
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
GENERIC_EXIT_OK
@ GENERIC_EXIT_OK
Exited with no error.
Definition: exitcodes.h:11
Commands::send_status
bool send_status(const QString &status) const
Definition: mythfilerecorder.cpp:164
LOC
#define LOC
Definition: mythfilerecorder.cpp:28
MythDate::current
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:14
Streamer
Definition: mythfilerecorder.h:16
MythCommandLineParser::Parse
virtual bool Parse(int argc, const char *const *argv)
Loop through argv and populate arguments with values.
Definition: mythcommandlineparser.cpp:1553
Commands::Run
void Run(void)
Definition: MythExternControl.cpp:438
Commands::SendBytes
void SendBytes(void)
Streamer::IsOpen
bool IsOpen(void) const
Definition: mythfilerecorder.h:29
Streamer::m_dataRead
quint64 m_dataRead
Definition: mythfilerecorder.h:50
mythlogging.h
Streamer::SendBytes
void SendBytes(void)
Definition: mythfilerecorder.cpp:70
Commands::m_timeout
int m_timeout
Definition: mythfilerecorder.h:74
MythCommandLineParser::GetArgs
QStringList GetArgs(void) const
Return list of additional values provided on the command line independent of any keyword.
Definition: mythcommandlineparser.cpp:2088
mythfilerecorder_commandlineparser.h
MythCommandLineParser::PrintVersion
static void PrintVersion(void)
Print application version information.
Definition: mythcommandlineparser.cpp:1381
Commands::setEoF
void setEoF(void)
Definition: mythfilerecorder.h:65
recorder
RemoteEncoder * recorder
Definition: mythcommflag.cpp:76
MythCommandLineParser::PrintHelp
void PrintHelp(void) const
Print command line option help.
Definition: mythcommandlineparser.cpp:1397
Streamer::m_loop
bool m_loop
Definition: mythfilerecorder.h:39
Streamer::m_fileName
QString m_fileName
Definition: mythfilerecorder.h:37
Streamer::m_file
QFile * m_file
Definition: mythfilerecorder.h:38
Streamer::m_parent
Commands * m_parent
Definition: mythfilerecorder.h:36
Commands::m_fileName
QString m_fileName
Definition: mythfilerecorder.h:72
Commands::CloseFile
void CloseFile(void)
Commands::m_streamer
Streamer * m_streamer
Definition: mythfilerecorder.h:73
MythFileRecorderCommandLineParser
Definition: mythfilerecorder_commandlineparser.h:6
Commands::m_eof
QAtomicInt m_eof
Definition: mythfilerecorder.h:76
Streamer::~Streamer
~Streamer(void) override
Definition: mythfilerecorder.cpp:40
mythfilerecorder.h
Commands
Definition: MythExternControl.h:74
MythCommandLineParser::toString
QString toString(const QString &key) const
Returns stored QVariant as a QString, falling to default if not provided.
Definition: mythcommandlineparser.cpp:2358
MythCommandLineParser::toBool
bool toBool(const QString &key) const
Returns stored QVariant as a boolean.
Definition: mythcommandlineparser.cpp:2201
Streamer::m_blockSize
QAtomicInt m_blockSize
Definition: mythfilerecorder.h:45
std
Definition: mythchrono.h:23
mythcontext.h
Commands::m_run
bool m_run
Definition: mythfilerecorder.h:75
Streamer::m_startTime
QDateTime m_startTime
Definition: mythfilerecorder.h:49
Streamer::Streamer
Streamer(Commands *parent, QString fname, int data_rate, bool loopinput)
Definition: mythfilerecorder.cpp:30
MythCommandLineParser::ConfigureLogging
int ConfigureLogging(const QString &mask="general", bool progress=false)
Read in logging options and initialize the logging interface.
Definition: mythcommandlineparser.cpp:2863
Streamer::CloseFile
void CloseFile(void)
Definition: mythfilerecorder.cpp:57
exitcodes.h
MythCommandLineParser::toInt
int toInt(const QString &key) const
Returns stored QVariant as an integer, falling to default if not provided.
Definition: mythcommandlineparser.cpp:2223
build_compdb.filename
filename
Definition: build_compdb.py:21
Commands::Commands
Commands(void)
Definition: mythfilerecorder.cpp:159
GENERIC_EXIT_INVALID_CMDLINE
@ GENERIC_EXIT_INVALID_CMDLINE
Command line parse error.
Definition: exitcodes.h:16
Streamer::BlockSize
void BlockSize(int val)
Definition: mythfilerecorder.h:28
main
int main(int argc, char *argv[])
Definition: mythfilerecorder.cpp:397
API_VERSION
static constexpr int API_VERSION
Definition: mythfilerecorder.cpp:26