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