MythTV  master
playbackboxhelper.cpp
Go to the documentation of this file.
1 #include <algorithm>
2 using namespace std;
3 
4 #include <QCoreApplication>
5 #include <QStringList>
6 #include <QDateTime>
7 #include <QFileInfo>
8 #include <QDir>
9 #include <QMap>
10 #include <QHash>
11 
12 #include "previewgeneratorqueue.h"
13 #include "metadataimagehelper.h"
14 #include "playbackboxhelper.h"
15 #include "mythcorecontext.h"
16 #include "filesysteminfo.h"
17 #include "tvremoteutil.h"
18 #include "storagegroup.h"
19 #include "mythlogging.h"
20 #include "programinfo.h"
21 #include "remoteutil.h"
22 #include "mythevent.h"
23 #include "mythdirs.h"
24 #include "compat.h" // for random()
25 
26 #define LOC QString("PlaybackBoxHelper: ")
27 #define LOC_WARN QString("PlaybackBoxHelper Warning: ")
28 #define LOC_ERR QString("PlaybackBoxHelper Error: ")
29 
30 class PBHEventHandler : public QObject
31 {
32  public:
34  m_pbh(pbh), m_freeSpaceTimerId(0), m_checkAvailabilityTimerId(0)
35  {
37  }
38  ~PBHEventHandler() override
39  {
40  if (m_freeSpaceTimerId)
41  killTimer(m_freeSpaceTimerId);
42  if (m_checkAvailabilityTimerId)
43  killTimer(m_checkAvailabilityTimerId);
44  }
45 
46  bool event(QEvent* /*e*/) override; // QObject
47  void UpdateFreeSpaceEvent(void);
48  AvailableStatusType CheckAvailability(const QStringList &slist);
53  QMap<QString, QStringList> m_fileListCache;
54  QHash<uint, QStringList> m_checkAvailability;
55 };
56 
57 const uint PBHEventHandler::kUpdateFreeSpaceInterval = 15000; // 15 seconds
58 
60 {
61  QTime tm = QTime::currentTime();
62 
63  QStringList::const_iterator it2 = slist.begin();
64  ProgramInfo evinfo(it2, slist.end());
65  QSet<CheckAvailabilityType> cats;
66  for (; it2 != slist.end(); ++it2)
67  cats.insert((CheckAvailabilityType)(*it2).toUInt());
68 
69  {
70  QMutexLocker locker(&m_pbh.m_lock);
71  QHash<uint, QStringList>::iterator cit =
72  m_checkAvailability.find(evinfo.GetRecordingID());
73  if (cit != m_checkAvailability.end())
74  m_checkAvailability.erase(cit);
75  if (m_checkAvailability.empty() && m_checkAvailabilityTimerId)
76  {
77  killTimer(m_checkAvailabilityTimerId);
78  m_checkAvailabilityTimerId = 0;
79  }
80  }
81 
82  if (cats.empty())
83  return asFileNotFound;
84 
85  AvailableStatusType availableStatus = asAvailable;
86  if (!evinfo.HasPathname() && !evinfo.GetChanID())
87  availableStatus = asFileNotFound;
88  else
89  {
90  // Note IsFileReadable() implicitly calls GetPlaybackURL
91  // when necessary, we rely on this.
92  if (!evinfo.IsFileReadable())
93  {
94  LOG(VB_GENERAL, LOG_ERR, LOC +
95  QString("CHECK_AVAILABILITY '%1' file not found")
96  .arg(evinfo.GetPathname()));
97  availableStatus = asFileNotFound;
98  }
99  else if (!evinfo.GetFilesize())
100  {
101  // This query should be unnecessary if the ProgramInfo Updater is working
102 // evinfo.SetFilesize(evinfo.QueryFilesize());
103 // if (!evinfo.GetFilesize())
104 // {
105  availableStatus =
106  (evinfo.GetRecordingStatus() == RecStatus::Recording) ?
108 // }
109  }
110  }
111 
112  QStringList list;
113  list.push_back(QString::number(evinfo.GetRecordingID()));
114  list.push_back(evinfo.GetPathname());
115  MythEvent *e0 = new MythEvent("SET_PLAYBACK_URL", list);
116  QCoreApplication::postEvent(m_pbh.m_listener, e0);
117 
118  list.clear();
119  list.push_back(QString::number(evinfo.GetRecordingID()));
120  list.push_back(QString::number((int)*cats.begin()));
121  list.push_back(QString::number((int)availableStatus));
122  list.push_back(QString::number(evinfo.GetFilesize()));
123  list.push_back(QString::number(tm.hour()));
124  list.push_back(QString::number(tm.minute()));
125  list.push_back(QString::number(tm.second()));
126  list.push_back(QString::number(tm.msec()));
127 
128  QSet<CheckAvailabilityType>::iterator cit = cats.begin();
129  for (; cit != cats.end(); ++cit)
130  {
131  if (*cit == kCheckForCache && cats.size() > 1)
132  continue;
133  list[1] = QString::number((int)*cit);
134  MythEvent *e = new MythEvent("AVAILABILITY", list);
135  QCoreApplication::postEvent(m_pbh.m_listener, e);
136  }
137 
138  return availableStatus;
139 }
140 
141 bool PBHEventHandler::event(QEvent *e)
142 {
143  if (e->type() == QEvent::Timer)
144  {
145  QTimerEvent *te = (QTimerEvent*)e;
146  const int timer_id = te->timerId();
147  if (timer_id == m_freeSpaceTimerId)
148  UpdateFreeSpaceEvent();
149  if (timer_id == m_checkAvailabilityTimerId)
150  {
151  QStringList slist;
152  {
153  QMutexLocker locker(&m_pbh.m_lock);
154  QHash<uint, QStringList>::iterator it =
155  m_checkAvailability.begin();
156  if (it != m_checkAvailability.end())
157  slist = *it;
158  }
159 
160  if (slist.size() >= 1 + NUMPROGRAMLINES)
161  CheckAvailability(slist);
162  }
163  return true;
164  }
165  if (e->type() == MythEvent::MythEventMessage)
166  {
167  MythEvent *me = static_cast<MythEvent*>(e);
168  if (me->Message() == "UPDATE_FREE_SPACE")
169  {
170  UpdateFreeSpaceEvent();
171  return true;
172  }
173  if (me->Message() == "STOP_RECORDING")
174  {
175  ProgramInfo pginfo(me->ExtraDataList());
176  if (pginfo.GetChanID())
177  RemoteStopRecording(&pginfo);
178  return true;
179  }
180  if (me->Message() == "DELETE_RECORDINGS")
181  {
182  QStringList successes;
183  QStringList failures;
184  QStringList list = me->ExtraDataList();
185  while (list.size() >= 3)
186  {
187  uint recordingID = list[0].toUInt();
188  bool forceDelete = list[1].toUInt() != 0U;
189  bool forgetHistory = list[2].toUInt() != 0U;
190 
191  bool ok = RemoteDeleteRecording( recordingID, forceDelete,
192  forgetHistory);
193 
194  QStringList &res = (ok) ? successes : failures;
195  for (uint i = 0; i < 3; i++)
196  {
197  res.push_back(list.front());
198  list.pop_front();
199  }
200  }
201  if (!successes.empty())
202  {
203  MythEvent *oe = new MythEvent("DELETE_SUCCESSES", successes);
204  QCoreApplication::postEvent(m_pbh.m_listener, oe);
205  }
206  if (!failures.empty())
207  {
208  MythEvent *oe = new MythEvent("DELETE_FAILURES", failures);
209  QCoreApplication::postEvent(m_pbh.m_listener, oe);
210  }
211 
212  return true;
213  }
214  if (me->Message() == "UNDELETE_RECORDINGS")
215  {
216  QStringList successes;
217  QStringList failures;
218  QStringList list = me->ExtraDataList();
219  while (!list.empty())
220  {
221  uint recordingID = list[0].toUInt();
222 
223  bool ok = RemoteUndeleteRecording(recordingID);
224 
225  QStringList &res = (ok) ? successes : failures;
226 
227  res.push_back(QString::number(recordingID));
228  list.pop_front();
229  }
230  if (!successes.empty())
231  {
232  MythEvent *oe = new MythEvent("UNDELETE_SUCCESSES", successes);
233  QCoreApplication::postEvent(m_pbh.m_listener, oe);
234  }
235  if (!failures.empty())
236  {
237  MythEvent *oe = new MythEvent("UNDELETE_FAILURES", failures);
238  QCoreApplication::postEvent(m_pbh.m_listener, oe);
239  }
240 
241  return true;
242  }
243  if (me->Message() == "GET_PREVIEW")
244  {
245  const QString& token = me->ExtraData(0);
246  bool check_avail = (bool) me->ExtraData(1).toInt();
247  QStringList list = me->ExtraDataList();
248  QStringList::const_iterator it = list.begin()+2;
249  ProgramInfo evinfo(it, list.end());
250  if (!evinfo.HasPathname())
251  return true;
252 
253  list.clear();
254  evinfo.ToStringList(list);
255  list += QString::number(kCheckForCache);
256  if (check_avail && (asAvailable != CheckAvailability(list)))
257  return true;
258  if (asAvailable != evinfo.GetAvailableStatus())
259  return true;
260 
261  // Now we can actually request the preview...
263 
264  return true;
265  }
266  if (me->Message() == "CHECK_AVAILABILITY")
267  {
268  if (me->ExtraData(0) != QString::number(kCheckForCache))
269  {
270  if (m_checkAvailabilityTimerId)
271  killTimer(m_checkAvailabilityTimerId);
272  m_checkAvailabilityTimerId = startTimer(0);
273  }
274  else if (!m_checkAvailabilityTimerId)
275  m_checkAvailabilityTimerId = startTimer(50);
276  }
277  else if (me->Message() == "LOCATE_ARTWORK")
278  {
279  const QString& inetref = me->ExtraData(0);
280  uint season = me->ExtraData(1).toUInt();
281  const VideoArtworkType type = (VideoArtworkType)me->ExtraData(2).toInt();
282 #if 0 /* const ref for an unused variable doesn't make much sense either */
283  uint recordingID = me->ExtraData(3).toUInt();
284  const QString &group = me->ExtraData(4);
285 #endif
286  const QString cacheKey = QString("%1:%2:%3")
287  .arg((int)type).arg(inetref).arg(season);
288 
289  ArtworkMap map = GetArtwork(inetref, season);
290 
291  ArtworkInfo info = map.value(type);
292 
293  QString foundFile;
294 
295  if (!info.url.isEmpty())
296  {
297  foundFile = info.url;
298  QMutexLocker locker(&m_pbh.m_lock);
299  m_pbh.m_artworkCache[cacheKey] = foundFile;
300  }
301 
302  if (!foundFile.isEmpty())
303  {
304  QStringList list = me->ExtraDataList();
305  list.push_back(foundFile);
306  MythEvent *oe = new MythEvent("FOUND_ARTWORK", list);
307  QCoreApplication::postEvent(m_pbh.m_listener, oe);
308  }
309 
310  return true;
311  }
312  }
313 
314  return QObject::event(e);
315 }
316 
318 {
319  if (m_freeSpaceTimerId)
320  killTimer(m_freeSpaceTimerId);
321  m_pbh.UpdateFreeSpace();
322  m_freeSpaceTimerId = startTimer(kUpdateFreeSpaceInterval);
323 }
324 
326 
328  MThread("PlaybackBoxHelper"),
329  m_listener(listener), m_eventHandler(new PBHEventHandler(*this))
330 {
331  start();
332  m_eventHandler->moveToThread(qthread());
333  // Prime the pump so the disk free display starts updating
335 }
336 
338 {
339  // delete the event handler
340  m_eventHandler->deleteLater();
341  m_eventHandler = nullptr;
342 
343  MThread::exit();
344  wait();
345 }
346 
348 {
349  QCoreApplication::postEvent(
350  m_eventHandler, new MythEvent("UPDATE_FREE_SPACE"));
351 }
352 
354 {
355  QStringList list;
356  pginfo.ToStringList(list);
357  MythEvent *e = new MythEvent("STOP_RECORDING", list);
358  QCoreApplication::postEvent(m_eventHandler, e);
359 }
360 
361 void PlaybackBoxHelper::DeleteRecording( uint recordingID, bool forceDelete,
362  bool forgetHistory)
363 {
364  QStringList list;
365  list.push_back(QString::number(recordingID));
366  list.push_back((forceDelete) ? "1" : "0");
367  list.push_back((forgetHistory) ? "1" : "0");
368  DeleteRecordings(list);
369 }
370 
371 void PlaybackBoxHelper::DeleteRecordings(const QStringList &list)
372 {
373  MythEvent *e = new MythEvent("DELETE_RECORDINGS", list);
374  QCoreApplication::postEvent(m_eventHandler, e);
375 }
376 
378 {
379  QStringList list;
380  list.push_back(QString::number(recordingID));
381  MythEvent *e = new MythEvent("UNDELETE_RECORDINGS", list);
382  QCoreApplication::postEvent(m_eventHandler, e);
383 }
384 
386 {
387  QList<FileSystemInfo> fsInfos = FileSystemInfo::RemoteGetInfo();
388 
389  QMutexLocker locker(&m_lock);
390  for (int i = 0; i < fsInfos.size(); i++)
391  {
392  if (fsInfos[i].getPath() == "TotalDiskSpace")
393  {
394  m_freeSpaceTotalMB = (uint64_t) (fsInfos[i].getTotalSpace() >> 10);
395  m_freeSpaceUsedMB = (uint64_t) (fsInfos[i].getUsedSpace() >> 10);
396  }
397  }
398  MythEvent *e = new MythEvent("UPDATE_USAGE_UI");
399  QCoreApplication::postEvent(m_listener, e);
400 }
401 
403 {
404  QMutexLocker locker(&m_lock);
405  return m_freeSpaceTotalMB;
406 }
407 
409 {
410  QMutexLocker locker(&m_lock);
411  return m_freeSpaceUsedMB;
412 }
413 
415  const ProgramInfo &pginfo, CheckAvailabilityType cat)
416 {
417  QString catstr = QString::number((int)cat);
418  QMutexLocker locker(&m_lock);
419  QHash<uint, QStringList>::iterator it =
421  if (it == m_eventHandler->m_checkAvailability.end())
422  {
423  QStringList list;
424  pginfo.ToStringList(list);
425  list += catstr;
427  }
428  else
429  {
430  (*it).push_back(catstr);
431  }
432  MythEvent *e = new MythEvent("CHECK_AVAILABILITY", QStringList(catstr));
433  QCoreApplication::postEvent(m_eventHandler, e);
434 }
435 
437  const QString &inetref, uint season,
438  const VideoArtworkType type,
439  const ProgramInfo *pginfo,
440  const QString &groupname)
441 {
442  QString cacheKey = QString("%1:%2:%3")
443  .arg((int)type).arg(inetref).arg(season);
444 
445  QMutexLocker locker(&m_lock);
446 
447  InfoMap::const_iterator it =
448  m_artworkCache.find(cacheKey);
449 
450  if (it != m_artworkCache.end())
451  return *it;
452 
453  QStringList list(inetref);
454  list.push_back(QString::number(season));
455  list.push_back(QString::number(type));
456  list.push_back((pginfo)?QString::number(pginfo->GetRecordingID()):"");
457  list.push_back(groupname);
458  MythEvent *e = new MythEvent("LOCATE_ARTWORK", list);
459  QCoreApplication::postEvent(m_eventHandler, e);
460 
461  return QString();
462 }
463 
465  const ProgramInfo &pginfo, bool check_availability)
466 {
467  if (!check_availability && pginfo.GetAvailableStatus() != asAvailable)
468  return QString();
469 
470  if (pginfo.GetAvailableStatus() == asPendingDelete)
471  return QString();
472 
473  QString token = QString("%1:%2")
474  .arg(pginfo.MakeUniqueKey()).arg(random());
475 
476  QStringList extra(token);
477  extra.push_back(check_availability?"1":"0");
478  pginfo.ToStringList(extra);
479  MythEvent *e = new MythEvent("GET_PREVIEW", extra);
480  QCoreApplication::postEvent(m_eventHandler, e);
481 
482  return token;
483 }
void start(QThread::Priority=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:294
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:46
void DeleteRecording(uint recordingID, bool forceDelete, bool forgetHistory)
PBHEventHandler * m_eventHandler
static void GetPreviewImage(const ProgramInfo &pginfo, const QString &token)
Submit a request for the generation of a preview image.
uint64_t GetFreeSpaceTotalMB(void) const
static Type MythEventMessage
Definition: mythevent.h:66
void DeleteRecordings(const QStringList &)
void ToStringList(QStringList &list) const
Serializes ProgramInfo into a QStringList which can be passed over a socket.
bool wait(unsigned long time=ULONG_MAX)
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:311
VideoArtworkType
bool
Definition: pxsup2dast.c:30
void UpdateFreeSpaceEvent(void)
unsigned int uint
Definition: compat.h:140
#define LOC
~PBHEventHandler() override
bool RemoteDeleteRecording(uint recordingID, bool forceMetadataDelete, bool forgetHistory)
Definition: remoteutil.cpp:110
PBHEventHandler(PlaybackBoxHelper &pbh)
QString MakeUniqueKey(void) const
Creates a unique string that can be used to identify an existing recording.
Definition: programinfo.h:331
Holds information on recordings and videos.
Definition: programinfo.h:66
static const uint kUpdateFreeSpaceInterval
This class is used as a container for messages.
Definition: mythevent.h:16
static QList< FileSystemInfo > RemoteGetInfo(MythSocket *sock=nullptr)
void UndeleteRecording(uint recordingID)
PlaybackBoxHelper & m_pbh
PlaybackBoxHelper(QObject *listener)
void exit(int retcode=0)
Use this to exit from the thread if you are using a Qt event loop.
Definition: mthread.cpp:289
AvailableStatusType GetAvailableStatus(void) const
Definition: programinfo.h:810
uint GetRecordingID(void) const
Definition: programinfo.h:438
void CheckAvailability(const ProgramInfo &, CheckAvailabilityType cat=kCheckForCache)
QHash< uint, QStringList > m_checkAvailability
bool RemoteStopRecording(uint inputid)
enum AvailableStatusTypes AvailableStatusType
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
#define NUMPROGRAMLINES
Definition: programinfo.h:27
const QString & ExtraData(int idx=0) const
Definition: mythevent.h:59
QString LocateArtwork(const QString &inetref, uint season, VideoArtworkType type, const ProgramInfo *pginfo, const QString &groupname=nullptr)
virtual void clear(void)
AvailableStatusType CheckAvailability(const QStringList &slist)
bool RemoteUndeleteRecording(uint recordingID)
Definition: remoteutil.cpp:140
QThread * qthread(void)
Returns the thread, this will always return the same pointer no matter how often you restart the thre...
Definition: mthread.cpp:244
static void ClearGroupToUseCache(void)
static long int random(void)
Definition: compat.h:149
const QString & Message() const
Definition: mythevent.h:58
QString GetPreviewImage(const ProgramInfo &, bool check_availability=true)
void ForceFreeSpaceUpdate(void)
QMultiMap< VideoArtworkType, ArtworkInfo > ArtworkMap
CheckAvailabilityType
void StopRecording(const ProgramInfo &)
const QStringList & ExtraDataList() const
Definition: mythevent.h:60
uint64_t GetFreeSpaceUsedMB(void) const
bool event(QEvent *) override
ArtworkMap GetArtwork(const QString &inetref, uint season, bool strict)
QMap< QString, QStringList > m_fileListCache