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 
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)), m_file(nullptr),
31  m_loop(loopinput),
32  m_bufferMax(188 * 100000), m_blockSize(m_bufferMax / 4),
33  m_data_rate(data_rate), m_data_read(0)
34 {
35  setObjectName("Streamer");
36  OpenFile();
37  LOG(VB_RECORD, LOG_INFO, LOC + QString("Data Rate: %1").arg(m_data_rate));
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, buf_size = 0, write_len = 0, wrote = 0;
73 
74  LOG(VB_RECORD, LOG_DEBUG, LOC + "SendBytes -- start");
75 
76  if (!m_file)
77  {
78  LOG(VB_GENERAL, LOG_ERR, LOC + "SendBytes -- file not open");
79  return;
80  }
81 
82  if (m_file->atEnd())
83  {
84  if (m_loop)
85  m_file->reset();
86  else if (m_buffer.isEmpty())
87  m_parent->setEoF();
88  }
89 
90  if (!m_file->atEnd())
91  {
92  int read_sz = m_blockSize.loadAcquire();
93  if (!m_start_time.isValid())
95  int delta = m_start_time.secsTo(MythDate::current()) + 1;
96  int rate = (delta * m_data_rate) - m_data_read;
97 
98  read_sz = min(rate, read_sz);
99  read_sz = min(m_bufferMax - m_buffer.size(), read_sz);
100 
101  if (read_sz > 0)
102  {
103  QByteArray buffer = m_file->read(read_sz);
104 
105  pkt_size = buffer.size();
106  if (pkt_size > 0)
107  {
108  m_data_read += pkt_size;
109  if (m_buffer.size() + pkt_size > m_bufferMax)
110  {
111  // This should never happen
112  LOG(VB_RECORD, LOG_WARNING, LOC +
113  QString("SendBytes -- Overflow: %1 > %2, "
114  "dropping first %3 bytes.")
115  .arg(m_buffer.size() + pkt_size)
116  .arg(m_bufferMax).arg(pkt_size));
117  m_buffer.remove(0, pkt_size);
118  }
119  m_buffer += buffer;
120  }
121  }
122  }
123  if (m_buffer.isEmpty())
124  {
125  LOG(VB_RECORD, LOG_DEBUG, LOC + "SendBytes -- Buffer is empty.");
126  return;
127  }
128 
129  buf_size = m_buffer.size();
130  LOG(VB_RECORD, LOG_DEBUG, LOC +
131  QString("SendBytes -- Read %1 from file. %2 bytes buffered")
132  .arg(pkt_size).arg(buf_size));
133 
134  write_len = m_blockSize.loadAcquire();
135  if (write_len > buf_size)
136  write_len = buf_size;
137  LOG(VB_RECORD, LOG_DEBUG, LOC +
138  QString("SendBytes -- writing %1 bytes").arg(write_len));
139 
140  wrote = write(1, m_buffer.constData(), write_len);
141 
142  LOG(VB_RECORD, LOG_DEBUG, LOC +
143  QString("SendBytes -- wrote %1 bytes").arg(wrote));
144 
145  if (wrote < buf_size)
146  {
147  m_buffer.remove(0, wrote);
148  LOG(VB_RECORD, LOG_WARNING, LOC +
149  QString("%1 bytes unwritten").arg(m_buffer.size()));
150  }
151  else
152  m_buffer.clear();
153 
154  LOG(VB_RECORD, LOG_DEBUG, LOC + "SendBytes -- end");
155 }
156 
157 
158 Commands::Commands(void) : m_streamer(nullptr), m_timeout(10), m_run(true),
159  m_eof(0)
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("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  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 = cmdline.ConfigureLogging();
416  if (retval != 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().empty())
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
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.
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
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.
uint m_data_rate
void CloseFile(void)
RemoteEncoder * recorder
void SendBytes(void)
Streamer * m_streamer
#define GENERIC_EXIT_INVALID_CMDLINE
Command line parse error.
Definition: exitcodes.h:15
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)