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