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