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(false)
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  else
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("HasLock?"))
189  {
190  send_status(m_streamer->IsOpen() ? "OK:Yes" : "OK:No");
191  return true;
192  }
193  if (cmd.startsWith("SignalStrengthPercent"))
194  {
195  send_status(m_streamer->IsOpen() ? "OK:100" : "OK:0");
196  return true;
197  }
198  if (cmd.startsWith("LockTimeout"))
199  {
200  send_status("OK:1000");
201  return true;
202  }
203  if (cmd.startsWith("HasTuner?"))
204  {
205  send_status("OK:No");
206  return true;
207  }
208  if (cmd.startsWith("HasPictureAttributes?"))
209  {
210  send_status("OK:No");
211  return true;
212  }
213 
214  if (!m_streamer)
215  {
216  send_status("ERR:Not Initialized");
217  LOG(VB_RECORD, LOG_ERR, LOC + QString("%1 failed - not initialized!")
218  .arg(cmd));
219  return false;
220  }
221 
222  if (cmd.startsWith("SendBytes"))
223  {
224  if (!m_streamer->IsOpen())
225  {
226  send_status("ERR:file not open");
227  LOG(VB_RECORD, LOG_ERR, LOC + "SendBytes - file not open.");
228  }
229  else
230  {
231  if (m_eof.loadAcquire() != 0)
232  send_status("ERR:End of file");
233  else
234  {
235  send_status("OK");
236  emit SendBytes();
237  }
238  }
239  }
240 #if 0
241  else if (cmd.startsWith("XON"))
242  {
243  // Used when FlowControl is XON/XOFF
244  }
245  else if (cmd.startsWith("XOFF"))
246  {
247  // Used when FlowControl is XON/XOFF
248  }
249  else if (cmd.startsWith("TuneChannel"))
250  {
251  // Used if we announce that we have a 'tuner'
252 
256  }
257 #endif
258  else if (cmd.startsWith("IsOpen?"))
259  {
260  if (m_streamer->IsOpen())
261  send_status("OK:Open");
262  else
263  send_status(QString("ERR:Not Open: '%2'")
264  .arg(m_streamer->ErrorString()));
265  }
266  else if (cmd.startsWith("CloseRecorder"))
267  {
268  send_status("OK:Terminating");
269  emit CloseFile();
270  return false;
271  }
272  else if (cmd.startsWith("FlowControl?"))
273  {
274  send_status("OK:Polling");
275  }
276  else if (cmd.startsWith("BlockSize"))
277  {
278  m_streamer->BlockSize(cmd.mid(10).toInt());
279  send_status("OK");
280  }
281  else if (cmd.startsWith("StartStreaming"))
282  {
283  send_status("OK:Started");
284  }
285  else if (cmd.startsWith("StopStreaming"))
286  {
287  /* This does not close the stream! When Myth is done with
288  * this 'recording' ExternalChannel::EnterPowerSavingMode()
289  * will be called, which invokes CloseRecorder() */
290  send_status("OK:Stopped");
291  }
292  else
293  {
294  send_status(QString("ERR:Unknown command '%1'").arg(cmd));
295  LOG(VB_RECORD, LOG_ERR, LOC + QString("Unknown command '%1'")
296  .arg(cmd));
297  }
298 
299  return true;
300 }
301 
302 bool Commands::Run(const QString & filename, int data_rate, bool loopinput)
303 {
304  QString cmd;
305 
306  int poll_cnt = 1;
307  struct pollfd polls[2];
308  memset(polls, 0, sizeof(polls));
309 
310  polls[0].fd = 0;
311  polls[0].events = POLLIN | POLLPRI;
312  polls[0].revents = 0;
313 
314  m_fileName = filename;
315 
316  m_streamer = new Streamer(this, m_fileName, data_rate, loopinput);
317  QThread *streamThread = new QThread(this);
318 
319  m_streamer->moveToThread(streamThread);
320  connect(streamThread, SIGNAL(finished(void)),
321  m_streamer, SLOT(deleteLater(void)));
322 
323  connect(this, SIGNAL(SendBytes(void)),
324  m_streamer, SLOT(SendBytes(void)));
325 
326  streamThread->start();
327 
328  QFile input;
329  input.open(stdin, QIODevice::ReadOnly);
330  QTextStream qtin(&input);
331 
332  LOG(VB_RECORD, LOG_INFO, LOC + "Listening for commands");
333 
334  while (m_run)
335  {
336  int ret = poll(polls, poll_cnt, m_timeout);
337 
338  if (polls[0].revents & POLLHUP)
339  {
340  LOG(VB_RECORD, LOG_ERR, LOC + "poll eof (POLLHUP)");
341  break;
342  }
343  else if (polls[0].revents & POLLNVAL)
344  {
345  LOG(VB_RECORD, LOG_ERR, LOC + "poll error");
346  return false;
347  }
348 
349  if (polls[0].revents & POLLIN)
350  {
351  if (ret > 0)
352  {
353  cmd = qtin.readLine();
354  if (!process_command(cmd))
355  {
356  streamThread->quit();
357  streamThread->wait();
358  delete streamThread;
359  streamThread = nullptr;
360  m_streamer = nullptr;
361  m_run = false;
362  }
363  }
364  else if (ret < 0)
365  {
366  if ((EOVERFLOW == errno))
367  {
368  LOG(VB_RECORD, LOG_ERR, LOC + "command overflow.");
369  break; // we have an error to handle
370  }
371 
372  if ((EAGAIN == errno) || (EINTR == errno))
373  {
374  LOG(VB_RECORD, LOG_ERR, LOC + "retry command read.");
375  continue; // errors that tell you to try again
376  }
377 
378  LOG(VB_RECORD, LOG_ERR, LOC + "unknown error reading command.");
379  }
380  }
381  }
382 
383  return true;
384 }
385 
386 
387 int main(int argc, char *argv[])
388 {
390 
391  if (!cmdline.Parse(argc, argv))
392  {
393  cmdline.PrintHelp();
395  }
396 
397  if (cmdline.toBool("showhelp"))
398  {
399  cmdline.PrintHelp();
400  return GENERIC_EXIT_OK;
401  }
402 
403  if (cmdline.toBool("showversion"))
404  {
406  return GENERIC_EXIT_OK;
407  }
408 
409  bool loopinput = !cmdline.toBool("noloop");
410  int data_rate = cmdline.toInt("data_rate");
411 
412  QCoreApplication a(argc, argv);
413  QCoreApplication::setApplicationName("mythfilerecorder");
414 
415  int retval;
416  if ((retval = cmdline.ConfigureLogging()) != GENERIC_EXIT_OK)
417  return retval;
418 
419  QString filename = "";
420  if (!cmdline.toString("infile").isEmpty())
421  filename = cmdline.toString("infile");
422  else if (cmdline.GetArgs().size() >= 1)
423  filename = cmdline.GetArgs()[0];
424 
426  recorder.Run(filename, data_rate, loopinput);
427 
428  return GENERIC_EXIT_OK;
429 }
430 
431 /* 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
void setEoF(void)
void Run(void)
QByteArray m_buffer
void PrintHelp(void) const
Print command line option help.
bool toBool(QString key) const
Returns stored QVariant as a boolean.
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.
VERBOSE_PREAMBLE false
Definition: verbosedefs.h:85
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
Commands * m_parent
quint64 m_data_read
QString toString(QString key) const
Returns stored QVariant as a QString, falling to default if not provided.
MythCommFlagCommandLineParser cmdline
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:99
Streamer(Commands *parent, const QString &filename, int data_rate, bool loopinput)
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)
RemoteEncoder * recorder
void SendBytes(void)
int toInt(QString key) const
Returns stored QVariant as an integer, falling to default if not provided.
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.
void OpenFile(void)