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 for (uint i = 0; i < 3; i++)
211 {
212 res.push_back(list.front());
213 list.pop_front();
214 }
215 }
216 if (!successes.empty())
217 {
218 auto *oe = new MythEvent("DELETE_SUCCESSES", successes);
219 QCoreApplication::postEvent(m_pbh.m_listener, oe);
220 }
221 if (!failures.empty())
222 {
223 auto *oe = new MythEvent("DELETE_FAILURES", failures);
224 QCoreApplication::postEvent(m_pbh.m_listener, oe);
225 }
226
227 return true;
228 }
229 if (me->Message() == "UNDELETE_RECORDINGS")
230 {
231 QStringList successes;
232 QStringList failures;
233 QStringList list = me->ExtraDataList();
234 while (!list.empty())
235 {
236 uint recordingID = list[0].toUInt();
237
238 bool ok = RemoteUndeleteRecording(recordingID);
239
240 QStringList &res = ok ? successes : failures;
241
242 res.push_back(QString::number(recordingID));
243 list.pop_front();
244 }
245 if (!successes.empty())
246 {
247 auto *oe = new MythEvent("UNDELETE_SUCCESSES", successes);
248 QCoreApplication::postEvent(m_pbh.m_listener, oe);
249 }
250 if (!failures.empty())
251 {
252 auto *oe = new MythEvent("UNDELETE_FAILURES", failures);
253 QCoreApplication::postEvent(m_pbh.m_listener, oe);
254 }
255
256 return true;
257 }
258 if (me->Message() == "GET_PREVIEW")
259 {
260 const QString& token = me->ExtraData(0);
261 bool check_avail = (bool) me->ExtraData(1).toInt();
262 QStringList list = me->ExtraDataList();
263 QStringList::const_iterator it = list.cbegin()+2;
264 ProgramInfo evinfo(it, list.cend());
265 if (!evinfo.HasPathname())
266 return true;
267
268 list.clear();
269 evinfo.ToStringList(list);
270 list += QString::number(kCheckForCache);
271 if (check_avail && (asAvailable != CheckAvailability(list)))
272 return true;
273 if (asAvailable != evinfo.GetAvailableStatus())
274 return true;
275
276 // Now we can actually request the preview...
278
279 return true;
280 }
281 if (me->Message() == "CHECK_AVAILABILITY")
282 {
283 if (me->ExtraData(0) != QString::number(kCheckForCache))
284 {
287 m_checkAvailabilityTimerId = startTimer(0ms);
288 }
290 {
291 m_checkAvailabilityTimerId = startTimer(50ms);
292 }
293 }
294 else if (me->Message() == "LOCATE_ARTWORK")
295 {
296 const QString& inetref = me->ExtraData(0);
297 uint season = me->ExtraData(1).toUInt();
298 const auto type = (VideoArtworkType)me->ExtraData(2).toInt();
299#if 0 /* const ref for an unused variable doesn't make much sense either */
300 uint recordingID = me->ExtraData(3).toUInt();
301 const QString &group = me->ExtraData(4);
302#endif
303 const QString cacheKey = QString("%1:%2:%3")
304 .arg((int)type).arg(inetref).arg(season);
305
306 ArtworkMap map = GetArtwork(inetref, season);
307
308 ArtworkInfo info = map.value(type);
309
310 QString foundFile;
311
312 if (!info.url.isEmpty())
313 {
314 foundFile = info.url;
315 QMutexLocker locker(&m_pbh.m_lock);
316 m_pbh.m_artworkCache[cacheKey] = foundFile;
317 }
318
319 if (!foundFile.isEmpty())
320 {
321 QStringList list = me->ExtraDataList();
322 list.push_back(foundFile);
323 auto *oe = new MythEvent("FOUND_ARTWORK", list);
324 QCoreApplication::postEvent(m_pbh.m_listener, oe);
325 }
326
327 return true;
328 }
329 }
330
331 return QObject::event(e);
332}
333
335{
337 killTimer(m_freeSpaceTimerId);
340}
341
343
345 MThread("PlaybackBoxHelper"),
346 m_listener(listener), m_eventHandler(new PBHEventHandler(*this))
347{
348 start();
349 m_eventHandler->moveToThread(qthread());
350 // Prime the pump so the disk free display starts updating
352}
353
355{
356 // delete the event handler
357 m_eventHandler->deleteLater();
358 m_eventHandler = nullptr;
359
361 wait();
362}
363
365{
366 QCoreApplication::postEvent(
367 m_eventHandler, new MythEvent("UPDATE_FREE_SPACE"));
368}
369
371{
372 QStringList list;
373 pginfo.ToStringList(list);
374 auto *e = new MythEvent("STOP_RECORDING", list);
375 QCoreApplication::postEvent(m_eventHandler, e);
376}
377
378void PlaybackBoxHelper::DeleteRecording( uint recordingID, bool forceDelete,
379 bool forgetHistory)
380{
381 QStringList list;
382 list.push_back(QString::number(recordingID));
383 list.push_back(forceDelete ? "1" : "0");
384 list.push_back(forgetHistory ? "1" : "0");
385 DeleteRecordings(list);
386}
387
388void PlaybackBoxHelper::DeleteRecordings(const QStringList &list)
389{
390 auto *e = new MythEvent("DELETE_RECORDINGS", list);
391 QCoreApplication::postEvent(m_eventHandler, e);
392}
393
395{
396 QStringList list;
397 list.push_back(QString::number(recordingID));
398 auto *e = new MythEvent("UNDELETE_RECORDINGS", list);
399 QCoreApplication::postEvent(m_eventHandler, e);
400}
401
403{
405
406 QMutexLocker locker(&m_lock);
407 for (const auto& fsInfo : std::as_const(fsInfos))
408 {
409 if (fsInfo.getPath() == "TotalDiskSpace")
410 {
411 m_freeSpaceTotalMB = (uint64_t) (fsInfo.getTotalSpace() >> 10);
412 m_freeSpaceUsedMB = (uint64_t) (fsInfo.getUsedSpace() >> 10);
413 }
414 }
415 auto *e = new MythEvent("UPDATE_USAGE_UI");
416 QCoreApplication::postEvent(m_listener, e);
417}
418
420{
421 QMutexLocker locker(&m_lock);
422 return m_freeSpaceTotalMB;
423}
424
426{
427 QMutexLocker locker(&m_lock);
428 return m_freeSpaceUsedMB;
429}
430
433{
434 QString catstr = QString::number((int)cat);
435 QMutexLocker locker(&m_lock);
436 QHash<uint, QStringList>::iterator it =
438 if (it == m_eventHandler->m_checkAvailability.end())
439 {
440 QStringList list;
441 pginfo.ToStringList(list);
442 list += catstr;
444 }
445 else
446 {
447 (*it).push_back(catstr);
448 }
449 auto *e = new MythEvent("CHECK_AVAILABILITY", QStringList(catstr));
450 QCoreApplication::postEvent(m_eventHandler, e);
451}
452
454 const QString &inetref, uint season,
456 const ProgramInfo *pginfo,
457 const QString &groupname)
458{
459 QString cacheKey = QString("%1:%2:%3")
460 .arg((int)type).arg(inetref).arg(season);
461
462 QMutexLocker locker(&m_lock);
463
464 InfoMap::const_iterator it =
465 m_artworkCache.constFind(cacheKey);
466
467 if (it != m_artworkCache.constEnd())
468 return *it;
469
470 QStringList list(inetref);
471 list.push_back(QString::number(season));
472 list.push_back(QString::number(type));
473 list.push_back(pginfo?QString::number(pginfo->GetRecordingID()):"");
474 list.push_back(groupname);
475 auto *e = new MythEvent("LOCATE_ARTWORK", list);
476 QCoreApplication::postEvent(m_eventHandler, e);
477
478 return {};
479}
480
482 const ProgramInfo &pginfo, bool check_availability)
483{
484 if (!check_availability && pginfo.GetAvailableStatus() != asAvailable)
485 return {};
486
487 if (pginfo.GetAvailableStatus() == asPendingDelete)
488 return {};
489
490 QString token = QString("%1:%2")
491 .arg(pginfo.MakeUniqueKey()).arg(MythRandom());
492
493 QStringList extra(token);
494 extra.push_back(check_availability?"1":"0");
495 pginfo.ToStringList(extra);
496 auto *e = new MythEvent("GET_PREVIEW", extra);
497 QCoreApplication::postEvent(m_eventHandler, e);
498
499 return token;
500}
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:74
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
Definition: programinfo.h:380
bool HasPathname(void) const
Definition: programinfo.h:365
uint GetRecordingID(void) const
Definition: programinfo.h:457
AvailableStatusType GetAvailableStatus(void) const
Definition: programinfo.h:855
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:346
QString GetPathname(void) const
Definition: programinfo.h:350
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:458
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:34
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)