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 <QDir>
12 #include <QCoreApplication>
13 #include <QTime>
14 #include <QThread>
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 
25 #define VERSION "1.0.0"
26 #define LOC QString("File(%1): ").arg(m_fileName)
27 
28 Streamer::Streamer(Commands *parent, const QString &fname,
29  int data_rate, bool loopinput) :
30  m_parent(parent), m_fileName(fname), m_file(nullptr), m_loop(loopinput),
31  m_bufferMax(188 * 100000), m_blockSize(m_bufferMax / 4),
32  m_data_rate(data_rate), m_data_read(0)
33 {
34  setObjectName("Streamer");
35  OpenFile();
36  LOG(VB_RECORD, LOG_INFO, LOC + QString("Data Rate: %1").arg(m_data_rate));
37 }
38 
40 {
41  LOG(VB_RECORD, LOG_INFO, LOC + "Streamer::dtor -- begin");
42  CloseFile();
43  LOG(VB_RECORD, LOG_INFO, LOC + "Streamer::dtor -- end");
44 }
45 
47 {
48  m_file = new QFile(m_fileName);
49  if (!m_file || !m_file->open(QIODevice::ReadOnly))
50  {
51  LOG(VB_RECORD, LOG_ERR, LOC + QString("Failed to open '%1' - ")
52  .arg(m_fileName) + ENO);
53  }
54 }
55 
57 {
58  LOG(VB_RECORD, LOG_INFO, LOC + "Streamer::Close -- begin");
59 
60  if (m_file)
61  {
62  delete m_file;
63  m_file = nullptr;
64  }
65 
66  LOG(VB_RECORD, LOG_INFO, LOC + "Streamer::Close -- end");
67 }
68 
70 {
71  int pkt_size = 0, buf_size = 0, write_len = 0, wrote = 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_start_time.isValid())
94  int delta = m_start_time.secsTo(MythDate::current()) + 1;
95  int rate = (delta * m_data_rate) - m_data_read;
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_data_read += 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  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  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 
157 Commands::Commands(void) : m_streamer(nullptr), m_timeout(10), m_run(true),
158  m_eof(0)
159 {
160  setObjectName("Command");
161 }
162 
163 bool Commands::send_status(const QString & status) const
164 {
165  QByteArray buf = status.toLatin1() + '\n';
166  int len = write(2, buf.constData(), buf.size());
167  if (len != buf.size())
168  {
169  LOG(VB_RECORD, LOG_ERR, LOC +
170  QString("Status -- Wrote %1 of %2 bytes of status '%3'")
171  .arg(len).arg(status.size()).arg(status));
172  return false;
173  }
174  LOG(VB_RECORD, LOG_DEBUG, "Status: " + status);
175  return true;
176 }
177 
178 bool Commands::process_command(QString & cmd)
179 {
180  LOG(VB_RECORD, LOG_DEBUG, LOC + cmd);
181 
182  if (cmd.startsWith("Version?"))
183  {
184  send_status(QString("OK:%1").arg(VERSION));
185  return true;
186  }
187  if (cmd.startsWith("HasLock?"))
188  {
189  send_status(m_streamer->IsOpen() ? "OK:Yes" : "OK:No");
190  return true;
191  }
192  if (cmd.startsWith("SignalStrengthPercent"))
193  {
194  send_status(m_streamer->IsOpen() ? "OK:100" : "OK:0");
195  return true;
196  }
197  if (cmd.startsWith("LockTimeout"))
198  {
199  send_status("OK:1000");
200  return true;
201  }
202  if (cmd.startsWith("HasTuner?"))
203  {
204  send_status("OK:No");
205  return true;
206  }
207  if (cmd.startsWith("HasPictureAttributes?"))
208  {
209  send_status("OK:No");
210  return true;
211  }
212 
213  if (!m_streamer)
214  {
215  send_status("ERR:Not Initialized");
216  LOG(VB_RECORD, LOG_ERR, LOC + QString("%1 failed - not initialized!")
217  .arg(cmd));
218  return false;
219  }
220 
221  if (cmd.startsWith("SendBytes"))
222  {
223  if (!m_streamer->IsOpen())
224  {
225  send_status("ERR:file not open");
226  LOG(VB_RECORD, LOG_ERR, LOC + "SendBytes - file not open.");
227  }
228  else
229  {
230  if (m_eof.loadAcquire() != 0)
231  send_status("ERR:End of file");
232  else
233  {
234  send_status("OK");
235  emit SendBytes();
236  }
237  }
238  }
239 #if 0
240  else if (cmd.startsWith("XON"))
241  {
242  // Used when FlowControl is XON/XOFF
243  }
244  else if (cmd.startsWith("XOFF"))
245  {
246  // Used when FlowControl is XON/XOFF
247  }
248  else if (cmd.startsWith("TuneChannel"))
249  {
250  // Used if we announce that we have a 'tuner'
251 
255  }
256 #endif
257  else if (cmd.startsWith("IsOpen?"))
258  {
259  if (m_streamer->IsOpen())
260  send_status("OK:Open");
261  else
262  send_status(QString("ERR:Not Open: '%2'")
263  .arg(m_streamer->ErrorString()));
264  }
265  else if (cmd.startsWith("CloseRecorder"))
266  {
267  send_status("OK:Terminating");
268  emit CloseFile();
269  return false;
270  }
271  else if (cmd.startsWith("FlowControl?"))
272  {
273  send_status("OK:Polling");
274  }
275  else if (cmd.startsWith("BlockSize"))
276  {
277  m_streamer->BlockSize(cmd.mid(10).toInt());
278  send_status("OK");
279  }
280  else if (cmd.startsWith("StartStreaming"))
281  {
282  send_status("OK:Started");
283  }
284  else if (cmd.startsWith("StopStreaming"))
285  {
286  /* This does not close the stream! When Myth is done with
287  * this 'recording' ExternalChannel::EnterPowerSavingMode()
288  * will be called, which invokes CloseRecorder() */
289  send_status("OK:Stopped");
290  }
291  else
292  {
293  send_status(QString("ERR:Unknown command '%1'").arg(cmd));
294  LOG(VB_RECORD, LOG_ERR, LOC + QString("Unknown command '%1'")
295  .arg(cmd));
296  }
297 
298  return true;
299 }
300 
301 bool Commands::Run(const QString & filename, int data_rate, bool loopinput)
302 {
303  QString cmd;
304 
305  int poll_cnt = 1;
306  struct pollfd polls[2];
307  memset(polls, 0, sizeof(polls));
308 
309  polls[0].fd = 0;
310  polls[0].events = POLLIN | POLLPRI;
311  polls[0].revents = 0;
312 
313  m_fileName = filename;
314 
315  m_streamer = new Streamer(this, m_fileName, data_rate, loopinput);
316  QThread *streamThread = new QThread(this);
317 
318  m_streamer->moveToThread(streamThread);
319  connect(streamThread, SIGNAL(finished(void)),
320  m_streamer, SLOT(deleteLater(void)));
321 
322  connect(this, SIGNAL(SendBytes(void)),
323  m_streamer, SLOT(SendBytes(void)));
324 
325  streamThread->start();
326 
327  QFile input;
328  input.open(stdin, QIODevice::ReadOnly);
329  QTextStream qtin(&input);
330 
331  LOG(VB_RECORD, LOG_INFO, LOC + "Listening for commands");
332 
333  while (m_run)
334  {
335  int ret = poll(polls, poll_cnt, m_timeout);
336 
337  if (polls[0].revents & POLLHUP)
338  {
339  LOG(VB_RECORD, LOG_ERR, LOC + "poll eof (POLLHUP)");
340  break;
341  }
342  if (polls[0].revents & POLLNVAL)
343  {
344  LOG(VB_RECORD, LOG_ERR, LOC + "poll error");
345  return false;
346  }
347 
348  if (polls[0].revents & POLLIN)
349  {
350  if (ret > 0)
351  {
352  cmd = qtin.readLine();
353  if (!process_command(cmd))
354  {
355  streamThread->quit();
356  streamThread->wait();
357  delete streamThread;
358  streamThread = nullptr;
359  m_streamer = nullptr;
360  m_run = false;
361  }
362  }
363  else if (ret < 0)
364  {
365  if ((EOVERFLOW == errno))
366  {
367  LOG(VB_RECORD, LOG_ERR, LOC + "command overflow.");
368  break; // we have an error to handle
369  }
370 
371  if ((EAGAIN == errno) || (EINTR == errno))
372  {
373  LOG(VB_RECORD, LOG_ERR, LOC + "retry command read.");
374  continue; // errors that tell you to try again
375  }
376 
377  LOG(VB_RECORD, LOG_ERR, LOC + "unknown error reading command.");
378  }
379  }
380  }
381 
382  return true;
383 }
384 
385 
386 int main(int argc, char *argv[])
387 {
389 
390  if (!cmdline.Parse(argc, argv))
391  {
392  cmdline.PrintHelp();
394  }
395 
396  if (cmdline.toBool("showhelp"))
397  {
398  cmdline.PrintHelp();
399  return GENERIC_EXIT_OK;
400  }
401 
402  if (cmdline.toBool("showversion"))
403  {
405  return GENERIC_EXIT_OK;
406  }
407 
408  bool loopinput = !cmdline.toBool("noloop");
409  int data_rate = cmdline.toInt("data_rate");
410 
411  QCoreApplication a(argc, argv);
412  QCoreApplication::setApplicationName("mythfilerecorder");
413 
414  int retval;
415  if ((retval = cmdline.ConfigureLogging()) != GENERIC_EXIT_OK)
416  return retval;
417 
418  QString filename = "";
419  if (!cmdline.toString("infile").isEmpty())
420  filename = cmdline.toString("infile");
421  else if (!cmdline.GetArgs().empty())
422  filename = cmdline.GetArgs()[0];
423 
425  recorder.Run(filename, data_rate, loopinput);
426 
427  return GENERIC_EXIT_OK;
428 }
429 
430 /* vim: set expandtab tabstop=4 shiftwidth=4: */
void CloseFile(void)
void SendBytes(void)
def write(text, progress=True)
Definition: mythburn.py:279
bool IsOpen(void) const
QAtomicInt m_eof
virtual ~Streamer(void)
QAtomicInt m_blockSize
VERBOSE_PREAMBLE Most true
Definition: verbosedefs.h:91
#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)
QByteArray m_buffer
void PrintHelp(void) const
Print command line option help.
QDateTime m_start_time
void BlockSize(int val)
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
quint64 m_data_read
MythCommFlagCommandLineParser cmdline
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:99
QFile * m_file
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
virtual bool Parse(int argc, const char *const *argv)
Loop through argv and populate arguments with values.
uint m_data_rate
void CloseFile(void)
Streamer(Commands *parent, const QString &fname, int data_rate, bool loopinput)
RemoteEncoder * recorder
void SendBytes(void)
int ConfigureLogging(QString mask="general", unsigned int progress=0)
Read in logging options and initialize the logging interface.
Streamer * m_streamer
#define GENERIC_EXIT_INVALID_CMDLINE
Command line parse error.
Definition: exitcodes.h:15
QString m_fileName
void PrintVersion(void) const
Print application version information.
int toInt(const QString &key) const
Returns stored QVariant as an integer, falling to default if not provided.
void OpenFile(void)