MythTV  0.27pre
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Groups Pages
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 
10 #include "previewgeneratorqueue.h"
11 #include "metadataimagehelper.h"
12 #include "playbackboxhelper.h"
13 #include "mythcorecontext.h"
14 #include "filesysteminfo.h"
15 #include "tvremoteutil.h"
16 #include "storagegroup.h"
17 #include "mythlogging.h"
18 #include "programinfo.h"
19 #include "remoteutil.h"
20 #include "mythevent.h"
21 #include "mythdirs.h"
22 #include "compat.h" // for random()
23 
24 #define LOC QString("PlaybackBoxHelper: ")
25 #define LOC_WARN QString("PlaybackBoxHelper Warning: ")
26 #define LOC_ERR QString("PlaybackBoxHelper Error: ")
27 
28 class PBHEventHandler : public QObject
29 {
30  public:
32  m_pbh(pbh), m_freeSpaceTimerId(0), m_checkAvailabilityTimerId(0)
33  {
35  }
36  virtual bool event(QEvent*); // QObject
37  void UpdateFreeSpaceEvent(void);
38  AvailableStatusType CheckAvailability(const QStringList &slist);
43  QMap<QString, QStringList> m_fileListCache;
44  QHash<QString, QStringList> m_checkAvailability;
45 };
46 
47 const uint PBHEventHandler::kUpdateFreeSpaceInterval = 15000; // 15 seconds
48 
50 {
51  QTime tm = QTime::currentTime();
52 
53  QStringList::const_iterator it = slist.begin();
54  ProgramInfo evinfo(it, slist.end());
55  QSet<CheckAvailabilityType> cats;
56  for (; it != slist.end(); ++it)
57  cats.insert((CheckAvailabilityType)(*it).toUInt());
58 
59  {
60  QMutexLocker locker(&m_pbh.m_lock);
61  QHash<QString, QStringList>::iterator it =
62  m_checkAvailability.find(evinfo.MakeUniqueKey());
63  if (it != m_checkAvailability.end())
64  m_checkAvailability.erase(it);
65  if (m_checkAvailability.empty() && m_checkAvailabilityTimerId)
66  {
67  killTimer(m_checkAvailabilityTimerId);
68  m_checkAvailabilityTimerId = 0;
69  }
70  }
71 
72  if (cats.empty())
73  return asFileNotFound;
74 
75  AvailableStatusType availableStatus = asAvailable;
76  if (!evinfo.HasPathname() && !evinfo.GetChanID())
77  availableStatus = asFileNotFound;
78  else
79  {
80  // Note IsFileReadable() implicitly calls GetPlaybackURL
81  // when necessary, we rely on this.
82  if (!evinfo.IsFileReadable())
83  {
84  LOG(VB_GENERAL, LOG_ERR, LOC +
85  QString("CHECK_AVAILABILITY '%1' file not found")
86  .arg(evinfo.GetPathname()));
87  availableStatus = asFileNotFound;
88  }
89  else if (!evinfo.GetFilesize())
90  {
91  evinfo.SetFilesize(evinfo.QueryFilesize());
92  if (!evinfo.GetFilesize())
93  {
94  availableStatus =
95  (evinfo.GetRecordingStatus() == rsRecording) ?
97  }
98  }
99  }
100 
101  QStringList list;
102  list.push_back(evinfo.MakeUniqueKey());
103  list.push_back(evinfo.GetPathname());
104  MythEvent *e0 = new MythEvent("SET_PLAYBACK_URL", list);
105  QCoreApplication::postEvent(m_pbh.m_listener, e0);
106 
107  list.clear();
108  list.push_back(evinfo.MakeUniqueKey());
109  list.push_back(QString::number((int)*cats.begin()));
110  list.push_back(QString::number((int)availableStatus));
111  list.push_back(QString::number(evinfo.GetFilesize()));
112  list.push_back(QString::number(tm.hour()));
113  list.push_back(QString::number(tm.minute()));
114  list.push_back(QString::number(tm.second()));
115  list.push_back(QString::number(tm.msec()));
116 
117  QSet<CheckAvailabilityType>::iterator cit = cats.begin();
118  for (; cit != cats.end(); ++cit)
119  {
120  if (*cit == kCheckForCache && cats.size() > 1)
121  continue;
122  list[1] = QString::number((int)*cit);
123  MythEvent *e = new MythEvent("AVAILABILITY", list);
124  QCoreApplication::postEvent(m_pbh.m_listener, e);
125  }
126 
127  return availableStatus;
128 }
129 
131 {
132  if (e->type() == QEvent::Timer)
133  {
134  QTimerEvent *te = (QTimerEvent*)e;
135  const int timer_id = te->timerId();
136  if (timer_id == m_freeSpaceTimerId)
137  UpdateFreeSpaceEvent();
138  if (timer_id == m_checkAvailabilityTimerId)
139  {
140  QStringList slist;
141  {
142  QMutexLocker locker(&m_pbh.m_lock);
143  QHash<QString, QStringList>::iterator it =
144  m_checkAvailability.begin();
145  if (it != m_checkAvailability.end())
146  slist = *it;
147  }
148 
149  if (slist.size() >= 1 + NUMPROGRAMLINES)
150  CheckAvailability(slist);
151  }
152  return true;
153  }
154  else if (e->type() == (QEvent::Type) MythEvent::MythEventMessage)
155  {
156  MythEvent *me = (MythEvent*)e;
157  if (me->Message() == "UPDATE_FREE_SPACE")
158  {
159  UpdateFreeSpaceEvent();
160  return true;
161  }
162  else if (me->Message() == "STOP_RECORDING")
163  {
164  ProgramInfo pginfo(me->ExtraDataList());
165  if (pginfo.GetChanID())
166  RemoteStopRecording(&pginfo);
167  return true;
168  }
169  else if (me->Message() == "DELETE_RECORDINGS")
170  {
171  QStringList successes;
172  QStringList failures;
173  QStringList list = me->ExtraDataList();
174  while (list.size() >= 4)
175  {
176  uint chanid = list[0].toUInt();
177  QDateTime recstartts = MythDate::fromString(list[1]);
178  bool forceDelete = list[2].toUInt();
179  bool forgetHistory = list[3].toUInt();
180 
181  bool ok = RemoteDeleteRecording(
182  chanid, recstartts, forceDelete, forgetHistory);
183 
184  QStringList &res = (ok) ? successes : failures;
185  for (uint i = 0; i < 4; i++)
186  {
187  res.push_back(list.front());
188  list.pop_front();
189  }
190  }
191  if (!successes.empty())
192  {
193  MythEvent *e = new MythEvent("DELETE_SUCCESSES", successes);
194  QCoreApplication::postEvent(m_pbh.m_listener, e);
195  }
196  if (!failures.empty())
197  {
198  MythEvent *e = new MythEvent("DELETE_FAILURES", failures);
199  QCoreApplication::postEvent(m_pbh.m_listener, e);
200  }
201 
202  return true;
203  }
204  else if (me->Message() == "UNDELETE_RECORDINGS")
205  {
206  QStringList successes;
207  QStringList failures;
208  QStringList list = me->ExtraDataList();
209  while (list.size() >= 2)
210  {
211  uint chanid = list[0].toUInt();
212  QDateTime recstartts = MythDate::fromString(list[1]);
213 
214  bool ok = RemoteUndeleteRecording(chanid, recstartts);
215 
216  QStringList &res = (ok) ? successes : failures;
217  for (uint i = 0; i < 2; i++)
218  {
219  res.push_back(list.front());
220  list.pop_front();
221  }
222  }
223  if (!successes.empty())
224  {
225  MythEvent *e = new MythEvent("UNDELETE_SUCCESSES", successes);
226  QCoreApplication::postEvent(m_pbh.m_listener, e);
227  }
228  if (!failures.empty())
229  {
230  MythEvent *e = new MythEvent("UNDELETE_FAILURES", failures);
231  QCoreApplication::postEvent(m_pbh.m_listener, e);
232  }
233 
234  return true;
235  }
236  else if (me->Message() == "GET_PREVIEW")
237  {
238  QString token = me->ExtraData(0);
239  bool check_avail = (bool) me->ExtraData(1).toInt();
240  QStringList list = me->ExtraDataList();
241  QStringList::const_iterator it = list.begin()+2;
242  ProgramInfo evinfo(it, list.end());
243  if (!evinfo.HasPathname())
244  return true;
245 
246  list.clear();
247  evinfo.ToStringList(list);
248  list += QString::number(kCheckForCache);
249  if (check_avail && (asAvailable != CheckAvailability(list)))
250  return true;
251  else if (asAvailable != evinfo.GetAvailableStatus())
252  return true;
253 
254  // Now we can actually request the preview...
256 
257  return true;
258  }
259  else if (me->Message() == "CHECK_AVAILABILITY")
260  {
261  if (me->ExtraData(0) != QString::number(kCheckForCache))
262  {
263  if (m_checkAvailabilityTimerId)
264  killTimer(m_checkAvailabilityTimerId);
265  m_checkAvailabilityTimerId = startTimer(0);
266  }
267  else if (!m_checkAvailabilityTimerId)
268  m_checkAvailabilityTimerId = startTimer(50);
269  }
270  else if (me->Message() == "LOCATE_ARTWORK")
271  {
272  QString inetref = me->ExtraData(0);
273  uint season = me->ExtraData(1).toUInt();
274  const VideoArtworkType type = (VideoArtworkType)me->ExtraData(2).toInt();
275  const QString pikey = me->ExtraData(3);
276  const QString group = me->ExtraData(4);
277  const QString cacheKey = QString("%1:%2:%3")
278  .arg((int)type).arg(inetref).arg(season);
279 
280  ArtworkMap map = GetArtwork(inetref, season);
281 
282  ArtworkInfo info = map.value(type);
283 
284  QString foundFile;
285 
286  if (!info.url.isEmpty())
287  {
288  foundFile = info.url;
289  QMutexLocker locker(&m_pbh.m_lock);
290  m_pbh.m_artworkCache[cacheKey] = foundFile;
291  }
292 
293  if (!foundFile.isEmpty())
294  {
295  QStringList list = me->ExtraDataList();
296  list.push_back(foundFile);
297  MythEvent *e = new MythEvent("FOUND_ARTWORK", list);
298  QCoreApplication::postEvent(m_pbh.m_listener, e);
299  }
300 
301  return true;
302  }
303  }
304 
305  return QObject::event(e);
306 }
307 
309 {
310  if (m_freeSpaceTimerId)
311  killTimer(m_freeSpaceTimerId);
312  m_pbh.UpdateFreeSpace();
313  m_freeSpaceTimerId = startTimer(kUpdateFreeSpaceInterval);
314 }
315 
317 
319  MThread("PlaybackBoxHelper"),
320  m_listener(listener), m_eventHandler(new PBHEventHandler(*this)),
321  // Free Space Tracking Variables
322  m_freeSpaceTotalMB(0ULL), m_freeSpaceUsedMB(0ULL)
323 {
324  start();
325  m_eventHandler->moveToThread(qthread());
326  // Prime the pump so the disk free display starts updating
328 }
329 
331 {
332  exit();
333  wait();
334 
335  // delete the event handler
336  delete m_eventHandler;
337  m_eventHandler = NULL;
338 }
339 
341 {
342  QCoreApplication::postEvent(
343  m_eventHandler, new MythEvent("UPDATE_FREE_SPACE"));
344 }
345 
347 {
348  QStringList list;
349  pginfo.ToStringList(list);
350  MythEvent *e = new MythEvent("STOP_RECORDING", list);
351  QCoreApplication::postEvent(m_eventHandler, e);
352 }
353 
355  uint chanid, const QDateTime &recstartts, bool forceDelete,
356  bool forgetHistory)
357 {
358  QStringList list;
359  list.push_back(QString::number(chanid));
360  list.push_back(recstartts.toString(Qt::ISODate));
361  list.push_back((forceDelete) ? "1" : "0");
362  list.push_back((forgetHistory) ? "1" : "0");
363  DeleteRecordings(list);
364 }
365 
366 void PlaybackBoxHelper::DeleteRecordings(const QStringList &list)
367 {
368  MythEvent *e = new MythEvent("DELETE_RECORDINGS", list);
369  QCoreApplication::postEvent(m_eventHandler, e);
370 }
371 
373  uint chanid, const QDateTime &recstartts)
374 {
375  QStringList list;
376  list.push_back(QString::number(chanid));
377  list.push_back(recstartts.toString(Qt::ISODate));
378  MythEvent *e = new MythEvent("UNDELETE_RECORDINGS", list);
379  QCoreApplication::postEvent(m_eventHandler, e);
380 }
381 
383 {
384  QList<FileSystemInfo> fsInfos = FileSystemInfo::RemoteGetInfo();
385 
386  QMutexLocker locker(&m_lock);
387  for (int i = 0; i < fsInfos.size(); i++)
388  {
389  if (fsInfos[i].getPath() == "TotalDiskSpace")
390  {
391  m_freeSpaceTotalMB = (uint64_t) (fsInfos[i].getTotalSpace() >> 10);
392  m_freeSpaceUsedMB = (uint64_t) (fsInfos[i].getUsedSpace() >> 10);
393  }
394  }
395  MythEvent *e = new MythEvent("UPDATE_USAGE_UI");
396  QCoreApplication::postEvent(m_listener, e);
397 }
398 
400 {
401  QMutexLocker locker(&m_lock);
402  return m_freeSpaceTotalMB;
403 }
404 
406 {
407  QMutexLocker locker(&m_lock);
408  return m_freeSpaceUsedMB;
409 }
410 
412  const ProgramInfo &pginfo, CheckAvailabilityType cat)
413 {
414  QString catstr = QString::number((int)cat);
415  QMutexLocker locker(&m_lock);
416  QHash<QString, QStringList>::iterator it =
418  if (it == m_eventHandler->m_checkAvailability.end())
419  {
420  QStringList list;
421  pginfo.ToStringList(list);
422  list += catstr;
424  }
425  else
426  {
427  (*it).push_back(catstr);
428  }
429  MythEvent *e = new MythEvent("CHECK_AVAILABILITY", QStringList(catstr));
430  QCoreApplication::postEvent(m_eventHandler, e);
431 }
432 
434  const QString &inetref, uint season,
435  const VideoArtworkType type,
436  const ProgramInfo *pginfo,
437  const QString &groupname)
438 {
439  QString cacheKey = QString("%1:%2:%3")
440  .arg((int)type).arg(inetref).arg(season);
441 
442  QMutexLocker locker(&m_lock);
443 
444  QHash<QString,QString>::const_iterator it =
445  m_artworkCache.find(cacheKey);
446 
447  if (it != m_artworkCache.end())
448  return *it;
449 
450  QStringList list(inetref);
451  list.push_back(QString::number(season));
452  list.push_back(QString::number(type));
453  list.push_back((pginfo)?pginfo->MakeUniqueKey():"");
454  list.push_back(groupname);
455  MythEvent *e = new MythEvent("LOCATE_ARTWORK", list);
456  QCoreApplication::postEvent(m_eventHandler, e);
457 
458  return QString();
459 }
460 
462  const ProgramInfo &pginfo, bool check_availability)
463 {
464  if (!check_availability && pginfo.GetAvailableStatus() != asAvailable)
465  return QString();
466 
467  if (pginfo.GetAvailableStatus() == asPendingDelete)
468  return QString();
469 
470  QString token = QString("%1:%2")
471  .arg(pginfo.MakeUniqueKey()).arg(random());
472 
473  QStringList extra(token);
474  extra.push_back(check_availability?"1":"0");
475  pginfo.ToStringList(extra);
476  MythEvent *e = new MythEvent("GET_PREVIEW", extra);
477  QCoreApplication::postEvent(m_eventHandler, e);
478 
479  return token;
480 }