MythTV  master
remoteutil.cpp
Go to the documentation of this file.
1 #include <unistd.h>
2 
3 #include <QFileInfo>
4 #include <QFile>
5 #include <QDir>
6 #include <QList>
7 
8 #include "compat.h"
9 #include "remoteutil.h"
10 #include "programinfo.h"
11 #include "mythcorecontext.h"
12 #include "storagegroup.h"
13 #include "mythevent.h"
14 #include "mythsocket.h"
15 
16 vector<ProgramInfo *> *RemoteGetRecordedList(int sort)
17 {
18  QString str = "QUERY_RECORDINGS ";
19  if (sort < 0)
20  str += "Descending";
21  else if (sort > 0)
22  str += "Ascending";
23  else
24  str += "Unsorted";
25 
26  QStringList strlist(str);
27 
28  auto *info = new vector<ProgramInfo *>;
29 
30  if (!RemoteGetRecordingList(*info, strlist))
31  {
32  delete info;
33  return nullptr;
34  }
35 
36  return info;
37 }
38 
39 bool RemoteGetLoad(double load[3])
40 {
41  QStringList strlist(QString("QUERY_LOAD"));
42 
43  if (gCoreContext->SendReceiveStringList(strlist) && strlist.size() >= 3)
44  {
45  load[0] = strlist[0].toDouble();
46  load[1] = strlist[1].toDouble();
47  load[2] = strlist[2].toDouble();
48  return true;
49  }
50 
51  return false;
52 }
53 
54 bool RemoteGetUptime(time_t &uptime)
55 {
56  QStringList strlist(QString("QUERY_UPTIME"));
57 
58  if (!gCoreContext->SendReceiveStringList(strlist) || strlist.isEmpty())
59  return false;
60 
61  if (strlist[0].isEmpty() || !strlist[0].at(0).isNumber())
62  return false;
63 
64  if (sizeof(time_t) == sizeof(int))
65  uptime = strlist[0].toUInt();
66  else if (sizeof(time_t) == sizeof(long))
67  uptime = strlist[0].toULong();
68  else if (sizeof(time_t) == sizeof(long long))
69  uptime = strlist[0].toULongLong();
70 
71  return true;
72 }
73 
74 bool RemoteGetMemStats(int &totalMB, int &freeMB, int &totalVM, int &freeVM)
75 {
76  QStringList strlist(QString("QUERY_MEMSTATS"));
77 
78  if (gCoreContext->SendReceiveStringList(strlist) && strlist.size() >= 4)
79  {
80  totalMB = strlist[0].toInt();
81  freeMB = strlist[1].toInt();
82  totalVM = strlist[2].toInt();
83  freeVM = strlist[3].toInt();
84  return true;
85  }
86 
87  return false;
88 }
89 
90 bool RemoteCheckFile(ProgramInfo *pginfo, bool checkSlaves)
91 {
92  QStringList strlist("QUERY_CHECKFILE");
93  strlist << QString::number((int)checkSlaves);
94  pginfo->ToStringList(strlist);
95 
96  if (!gCoreContext->SendReceiveStringList(strlist) ||
97  (strlist.size() < 2) || !strlist[0].toInt())
98  return false;
99 
100  // Only modify the pathname if the recording file is available locally on
101  // this host
102  QString localpath = strlist[1];
103  QFile checkFile(localpath);
104  if (checkFile.exists())
105  pginfo->SetPathname(localpath);
106 
107  return true;
108 }
109 
110 bool RemoteDeleteRecording(uint recordingID, bool forceMetadataDelete,
111  bool forgetHistory)
112 {
113  // FIXME: Remove when DELETE_RECORDING has been updated to use recording id
114  ProgramInfo recInfo(recordingID);
115  bool result = true;
116  QString cmd =
117  QString("DELETE_RECORDING %1 %2 %3 %4")
118  .arg(QString::number(recInfo.GetChanID()))
119  .arg(recInfo.GetRecordingStartTime().toString(Qt::ISODate))
120  .arg(forceMetadataDelete ? "FORCE" : "NO_FORCE")
121  .arg(forgetHistory ? "FORGET" : "NO_FORGET");
122  QStringList strlist(cmd);
123 
124  if ((!gCoreContext->SendReceiveStringList(strlist) || strlist.isEmpty()) ||
125  (strlist[0].toInt() == -2))
126  result = false;
127 
128  if (!result)
129  {
130  LOG(VB_GENERAL, LOG_ALERT,
131  QString("Failed to delete recording %1:%2")
132  .arg(recInfo.GetChanID())
133  .arg(recInfo.GetRecordingStartTime().toString(Qt::ISODate)));
134  }
135 
136  return result;
137 }
138 
139 bool RemoteUndeleteRecording(uint recordingID)
140 {
141  // FIXME: Remove when UNDELETE_RECORDING has been updated to use recording id
142  ProgramInfo recInfo(recordingID);
143  bool result = false;
144 
145 #if 0
146  if (!gCoreContext->GetNumSetting("AutoExpireInsteadOfDelete", 0))
147  return result;
148 #endif
149 
150  QStringList strlist(QString("UNDELETE_RECORDING"));
151  strlist.push_back(QString::number(recInfo.GetChanID()));
152  strlist.push_back(recInfo.GetRecordingStartTime().toString(Qt::ISODate));
153 
155 
156  if (!strlist.isEmpty() && strlist[0].toInt() == 0)
157  result = true;
158 
159  return result;
160 }
161 
162 void RemoteGetAllScheduledRecordings(vector<ProgramInfo *> &scheduledlist)
163 {
164  QStringList strList(QString("QUERY_GETALLSCHEDULED"));
165  RemoteGetRecordingList(scheduledlist, strList);
166 }
167 
168 void RemoteGetAllExpiringRecordings(vector<ProgramInfo *> &expiringlist)
169 {
170  QStringList strList(QString("QUERY_GETEXPIRING"));
171  RemoteGetRecordingList(expiringlist, strList);
172 }
173 
175  vector<ProgramInfo *> &reclist, QStringList &strList)
176 {
177  if (!gCoreContext->SendReceiveStringList(strList) || strList.isEmpty())
178  return 0;
179 
180  int numrecordings = strList[0].toInt();
181  if (numrecordings <= 0)
182  return 0;
183 
184  if (numrecordings * NUMPROGRAMLINES + 1 > strList.size())
185  {
186  LOG(VB_GENERAL, LOG_ERR,
187  "RemoteGetRecordingList() list size appears to be incorrect.");
188  return 0;
189  }
190 
191  uint reclist_initial_size = (uint) reclist.size();
192  QStringList::const_iterator it = strList.begin() + 1;
193  for (int i = 0; i < numrecordings; i++)
194  {
195  auto *pginfo = new ProgramInfo(it, strList.end());
196  reclist.push_back(pginfo);
197  }
198 
199  return ((uint) reclist.size()) - reclist_initial_size;
200 }
201 
202 vector<ProgramInfo *> *RemoteGetConflictList(const ProgramInfo *pginfo)
203 {
204  QString cmd = QString("QUERY_GETCONFLICTING");
205  QStringList strlist( cmd );
206  pginfo->ToStringList(strlist);
207 
208  auto *retlist = new vector<ProgramInfo *>;
209 
210  RemoteGetRecordingList(*retlist, strlist);
211  return retlist;
212 }
213 
215 {
216  QStringList strlist( "QUERY_PIXMAP_LASTMODIFIED" );
217  pginfo->ToStringList(strlist);
218 
219  if (!gCoreContext->SendReceiveStringList(strlist))
220  return QDateTime();
221 
222  if (!strlist.isEmpty() && !strlist[0].isEmpty() && (strlist[0] != "BAD"))
223  {
224 #if QT_VERSION < QT_VERSION_CHECK(5,8,0)
225  uint timet = strlist[0].toUInt();
226  return MythDate::fromTime_t(timet);
227 #else
228  qint64 timet = strlist[0].toLongLong();
229  return MythDate::fromSecsSinceEpoch(timet);
230 #endif
231  }
232 
233  return QDateTime();
234 }
235 
239  const ProgramInfo &pginfo, const QString &cachefile)
240 {
241  QString loc("RemoteGetPreviewIfModified: ");
242  QDateTime cacheLastModified;
243  QFileInfo cachefileinfo(cachefile);
244  if (cachefileinfo.exists())
245  cacheLastModified = cachefileinfo.lastModified();
246 
247  QStringList strlist("QUERY_PIXMAP_GET_IF_MODIFIED");
248  strlist << ((cacheLastModified.isValid()) ? // unix secs, UTC
249 #if QT_VERSION < QT_VERSION_CHECK(5,8,0)
250  QString::number(cacheLastModified.toTime_t()) :
251 #else
252  QString::number(cacheLastModified.toSecsSinceEpoch()) :
253 #endif
254  QString("-1"));
255  strlist << QString::number(200 * 1024); // max size of preview file
256  pginfo.ToStringList(strlist);
257 
258  if (!gCoreContext->SendReceiveStringList(strlist) ||
259  strlist.isEmpty() || strlist[0] == "ERROR")
260  {
261  LOG(VB_GENERAL, LOG_ERR, loc + "Remote error" +
262  ((strlist.size() >= 2) ? (":\n\t\t\t" + strlist[1]) : ""));
263 
264  return QDateTime();
265  }
266 
267  if (strlist[0] == "WARNING")
268  {
269  LOG(VB_NETWORK, LOG_WARNING, loc + "Remote warning" +
270  ((strlist.size() >= 2) ? (":\n\t\t\t" + strlist[1]) : ""));
271 
272  return QDateTime();
273  }
274 
275  QDateTime retdatetime;
276  qlonglong timet = strlist[0].toLongLong();
277  if (timet >= 0)
278  {
279 #if QT_VERSION < QT_VERSION_CHECK(5,8,0)
280  retdatetime = MythDate::fromTime_t(timet);
281 #else
282  retdatetime = MythDate::fromSecsSinceEpoch(timet);
283 #endif
284  }
285 
286  if (strlist.size() < 4)
287  {
288  return retdatetime;
289  }
290 
291  size_t length = strlist[1].toULongLong();
292  quint16 checksum16 = strlist[2].toUInt();
293  QByteArray data = QByteArray::fromBase64(strlist[3].toLatin1());
294  if ((size_t) data.size() < length)
295  { // (note data.size() may be up to 3 bytes longer after decoding
296  LOG(VB_GENERAL, LOG_ERR, loc +
297  QString("Preview size check failed %1 < %2")
298  .arg(data.size()).arg(length));
299  return QDateTime();
300  }
301  data.resize(length);
302 
303  if (checksum16 != qChecksum(data.constData(), data.size()))
304  {
305  LOG(VB_GENERAL, LOG_ERR, loc + "Preview checksum failed");
306  return QDateTime();
307  }
308 
309  QString pdir(cachefile.section("/", 0, -2));
310  QDir cfd(pdir);
311  if (!cfd.exists() && !cfd.mkdir(pdir))
312  {
313  LOG(VB_GENERAL, LOG_ERR, loc +
314  QString("Unable to create remote cache directory '%1'")
315  .arg(pdir));
316 
317  return QDateTime();
318  }
319 
320  QFile file(cachefile);
321  if (!file.open(QIODevice::WriteOnly|QIODevice::Truncate))
322  {
323  LOG(VB_GENERAL, LOG_ERR, loc +
324  QString("Unable to open cached preview file for writing '%1'")
325  .arg(cachefile));
326 
327  return QDateTime();
328  }
329 
330  off_t offset = 0;
331  size_t remaining = length;
332  uint failure_cnt = 0;
333  while ((remaining > 0) && (failure_cnt < 5))
334  {
335  ssize_t written = file.write(data.data() + offset, remaining);
336  if (written < 0)
337  {
338  failure_cnt++;
339  usleep(50000);
340  continue;
341  }
342 
343  failure_cnt = 0;
344  offset += written;
345  remaining -= written;
346  }
347 
348  if (remaining)
349  {
350  LOG(VB_GENERAL, LOG_ERR, loc +
351  QString("Failed to write cached preview file '%1'")
352  .arg(cachefile));
353 
354  file.resize(0); // in case unlink fails..
355  file.remove(); // closes fd
356  return QDateTime();
357  }
358 
359  file.close();
360 
361  return retdatetime;
362 }
363 
364 bool RemoteFillProgramInfo(ProgramInfo &pginfo, const QString &playbackhost)
365 {
366  QStringList strlist( "FILL_PROGRAM_INFO" );
367  strlist << playbackhost;
368  pginfo.ToStringList(strlist);
369 
370  if (gCoreContext->SendReceiveStringList(strlist))
371  {
372  ProgramInfo tmp(strlist);
373  if (tmp.HasPathname() || tmp.GetChanID())
374  {
375  pginfo = tmp;
376  return true;
377  }
378  }
379 
380  return false;
381 }
382 
383 QStringList RemoteRecordings(void)
384 {
385  QStringList strlist("QUERY_ISRECORDING");
386 
387  if (!gCoreContext->SendReceiveStringList(strlist, false, false))
388  {
389  QStringList empty;
390  empty << "0" << "0";
391  return empty;
392  }
393 
394  return strlist;
395 }
396 
398 {
399  int mask = 0;
400 
401  QString cmd = "QUERY_ISRECORDING";
402 
403  QStringList strlist( cmd );
404 
405  if (!gCoreContext->SendReceiveStringList(strlist) || strlist.isEmpty())
406  return mask;
407 
408  int recCount = strlist[0].toInt();
409 
410  for (int i = 0, j = 0; j < recCount; i++)
411  {
412  cmd = QString("QUERY_RECORDER %1").arg(i + 1);
413 
414  strlist = QStringList( cmd );
415  strlist << "IS_RECORDING";
416 
417  if (gCoreContext->SendReceiveStringList(strlist) && !strlist.isEmpty())
418  {
419  if (strlist[0].toInt())
420  {
421  mask |= 1<<i;
422  j++; // count active recorder
423  }
424  }
425  else
426  {
427  break;
428  }
429  }
430 
431  return mask;
432 }
433 
434 bool RemoteGetFileList(const QString& host, const QString& path, QStringList* list,
435  QString sgroup, bool fileNamesOnly)
436 {
437 
438  // Make sure the list is empty when we get started
439  list->clear();
440 
441  if (sgroup.isEmpty())
442  sgroup = "Videos";
443 
444  *list << "QUERY_SG_GETFILELIST";
445  *list << host;
446  *list << StorageGroup::GetGroupToUse(host, sgroup);
447  *list << path;
448  *list << QString::number(static_cast<int>(fileNamesOnly));
449 
450  bool ok = false;
451 
453  {
454  // since the master backend cannot connect back around to
455  // itself, and the libraries do not have access to the list
456  // of connected slave backends to query an existing connection
457  // start up a new temporary connection directly to the slave
458  // backend to query the file list
459  QString ann = QString("ANN Playback %1 0")
460  .arg(gCoreContext->GetHostName());
461  QString addr = gCoreContext->GetBackendServerIP(host);
462  int port = gCoreContext->GetBackendServerPort(host);
463  bool mismatch = false;
464 
466  addr, port, ann, &mismatch);
467  if (sock)
468  {
469  ok = sock->SendReceiveStringList(*list);
470  sock->DecrRef();
471  }
472  else
473  list->clear();
474  }
475  else
476  ok = gCoreContext->SendReceiveStringList(*list);
477 
478 // Should the SLAVE UNREACH test be here ?
479  return ok;
480 }
481 
488 {
489  QStringList strlist( QString("CHECK_RECORDING") );
490  pginfo->ToStringList(strlist);
491 
492  if (gCoreContext->SendReceiveStringList(strlist) && !strlist.isEmpty())
493  return strlist[0].toInt();
494 
495  return 0;
496 }
497 
507  const ProgramInfo *pginfo, int overrecsecs, int underrecsecs)
508 {
509  QDateTime curtime = MythDate::current();
510 
511  int retval = 0;
512 
513  if (pginfo)
514  {
515  if (curtime >= pginfo->GetScheduledStartTime().addSecs(-underrecsecs) &&
516  curtime < pginfo->GetScheduledEndTime().addSecs(overrecsecs))
517  {
518  if (curtime >= pginfo->GetScheduledStartTime() &&
519  curtime < pginfo->GetScheduledEndTime())
520  retval = 1;
521  else if (curtime < pginfo->GetScheduledStartTime() &&
522  RemoteCheckForRecording(pginfo) > 0)
523  retval = 2;
524  else if (curtime > pginfo->GetScheduledEndTime() &&
525  RemoteCheckForRecording(pginfo) > 0)
526  retval = 3;
527  }
528  }
529 
530  return retval;
531 }
532 
536 vector<ProgramInfo *> *RemoteGetCurrentlyRecordingList(void)
537 {
538  QString str = "QUERY_RECORDINGS ";
539  str += "Recording";
540  QStringList strlist( str );
541 
542  auto *reclist = new vector<ProgramInfo *>;
543  auto *info = new vector<ProgramInfo *>;
544  if (!RemoteGetRecordingList(*info, strlist))
545  {
546  delete info;
547  return reclist;
548  }
549 
550  // make sure whatever RemoteGetRecordingList() returned
551  // only has RecStatus::Recording shows
552  for (auto & p : *info)
553  {
554  if (p->GetRecordingStatus() == RecStatus::Recording ||
555  p->GetRecordingStatus() == RecStatus::Tuning ||
556  p->GetRecordingStatus() == RecStatus::Failing ||
557  (p->GetRecordingStatus() == RecStatus::Recorded &&
558  p->GetRecordingGroup() == "LiveTV"))
559  {
560  reclist->push_back(new ProgramInfo(*p));
561  }
562  }
563 
564  while (!info->empty())
565  {
566  delete info->back();
567  info->pop_back();
568  }
569  delete info;
570 
571  return reclist;
572 }
573 
577 bool RemoteGetActiveBackends(QStringList *list)
578 {
579  list->clear();
580  *list << "QUERY_ACTIVE_BACKENDS";
581 
582  if (!gCoreContext->SendReceiveStringList(*list))
583  return false;
584 
585  list->removeFirst();
586  return true;
587 }
588 
589 /* vim: set expandtab tabstop=4 shiftwidth=4: */
int RemoteCheckForRecording(const ProgramInfo *pginfo)
Get recorder for a programme.
Definition: remoteutil.cpp:487
vector< ProgramInfo * > * RemoteGetCurrentlyRecordingList(void)
return list of currently recording shows
Definition: remoteutil.cpp:536
QString GetBackendServerIP(void)
Returns the IP address of the locally defined backend IP.
void ToStringList(QStringList &list) const
Serializes ProgramInfo into a QStringList which can be passed over a socket.
bool RemoteGetMemStats(int &totalMB, int &freeMB, int &totalVM, int &freeVM)
Definition: remoteutil.cpp:74
void RemoteGetAllExpiringRecordings(vector< ProgramInfo * > &expiringlist)
Definition: remoteutil.cpp:168
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
bool RemoteFillProgramInfo(ProgramInfo &pginfo, const QString &playbackhost)
Definition: remoteutil.cpp:364
static guint32 * tmp
Definition: goom_core.cpp:30
#define off_t
bool RemoteDeleteRecording(uint recordingID, bool forceMetadataDelete, bool forgetHistory)
Definition: remoteutil.cpp:110
int GetBackendServerPort(void)
Returns the locally defined backend control port.
QDateTime GetScheduledStartTime(void) const
The scheduled start time of program.
Definition: programinfo.h:384
Holds information on recordings and videos.
Definition: programinfo.h:67
bool RemoteGetActiveBackends(QStringList *list)
return list of backends currently connected to the master
Definition: remoteutil.cpp:577
MythSocket * ConnectCommandSocket(const QString &hostname, int port, const QString &announcement, bool *proto_mismatch=nullptr, int maxConnTry=-1, int setup_timeout=-1)
bool SendReceiveStringList(QStringList &strlist, bool quickTimeout=false, bool block=true)
Send a message to the backend and wait for a response.
MBASE_PUBLIC QDateTime fromSecsSinceEpoch(uint seconds)
This function takes the number of seconds since the start of the epoch and returns a QDateTime with t...
Definition: mythdate.cpp:88
QDateTime RemoteGetPreviewIfModified(const ProgramInfo &pginfo, const QString &cachefile)
Download preview & get timestamp if newer than cachefile's last modified time, otherwise just get the...
Definition: remoteutil.cpp:238
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
void RemoteGetAllScheduledRecordings(vector< ProgramInfo * > &scheduledlist)
Definition: remoteutil.cpp:162
virtual int DecrRef(void)
Decrements reference count and deletes on 0.
bool RemoteGetFileList(const QString &host, const QString &path, QStringList *list, QString sgroup, bool fileNamesOnly)
Definition: remoteutil.cpp:434
unsigned int uint
Definition: compat.h:140
QDateTime GetScheduledEndTime(void) const
The scheduled end time of the program.
Definition: programinfo.h:391
QDateTime RemoteGetPreviewLastModified(const ProgramInfo *pginfo)
Definition: remoteutil.cpp:214
vector< ProgramInfo * > * RemoteGetConflictList(const ProgramInfo *pginfo)
Definition: remoteutil.cpp:202
int GetNumSetting(const QString &key, int defaultval=0)
static QString GetGroupToUse(const QString &host, const QString &sgroup)
#define NUMPROGRAMLINES
Definition: programinfo.h:28
uint RemoteGetRecordingList(vector< ProgramInfo * > &reclist, QStringList &strList)
Definition: remoteutil.cpp:174
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
Definition: programinfo.h:366
bool RemoteUndeleteRecording(uint recordingID)
Definition: remoteutil.cpp:139
int RemoteGetRecordingMask(void)
Definition: remoteutil.cpp:397
QDateTime GetRecordingStartTime(void) const
Approximate time the recording started.
Definition: programinfo.h:398
bool RemoteCheckFile(ProgramInfo *pginfo, bool checkSlaves)
Definition: remoteutil.cpp:90
Class for communcating between myth backends and frontends.
Definition: mythsocket.h:26
bool SendReceiveStringList(QStringList &list, uint min_reply_length=0, uint timeoutMS=kLongTimeout)
Definition: mythsocket.cpp:336
Default UTC.
Definition: mythdate.h:14
bool RemoteGetUptime(time_t &uptime)
Definition: remoteutil.cpp:54
bool IsMasterBackend(void)
is this the actual MBE process
int RemoteGetRecordingStatus(const ProgramInfo *pginfo, int overrecsecs, int underrecsecs)
Get status of an individual programme (with pre-post roll?).
Definition: remoteutil.cpp:506
vector< ProgramInfo * > * RemoteGetRecordedList(int sort)
Definition: remoteutil.cpp:16
QString GetHostName(void)
QStringList RemoteRecordings(void)
Definition: remoteutil.cpp:383
void SetPathname(const QString &pn)
bool RemoteGetLoad(double load[3])
Definition: remoteutil.cpp:39
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23