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