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 availableStatus = asFileNotFound;
99 else
100 {
101 // Note IsFileReadable() implicitly calls GetPlaybackURL
102 // when necessary, we rely on this.
103 if (!evinfo.IsFileReadable())
104 {
105 LOG(VB_GENERAL, LOG_ERR, LOC +
106 QString("CHECK_AVAILABILITY '%1' file not found")
107 .arg(evinfo.GetPathname()));
108 availableStatus = asFileNotFound;
109 }
110 else if (!evinfo.GetFilesize())
111 {
112 // This query should be unnecessary if the ProgramInfo Updater is working
113// evinfo.SetFilesize(evinfo.QueryFilesize());
114// if (!evinfo.GetFilesize())
115// {
116 availableStatus =
119// }
120 }
121 }
122
123 QStringList list;
124 list.push_back(QString::number(evinfo.GetRecordingID()));
125 list.push_back(evinfo.GetPathname());
126 auto *e0 = new MythEvent("SET_PLAYBACK_URL", list);
127 QCoreApplication::postEvent(m_pbh.m_listener, e0);
128
129 list.clear();
130 list.push_back(QString::number(evinfo.GetRecordingID()));
131 list.push_back(QString::number((int)firstType));
132 list.push_back(QString::number((int)availableStatus));
133 list.push_back(QString::number(evinfo.GetFilesize()));
134 list.push_back(QString::number(tm.hour()));
135 list.push_back(QString::number(tm.minute()));
136 list.push_back(QString::number(tm.second()));
137 list.push_back(QString::number(tm.msec()));
138
139 for (auto type : std::as_const(cats))
140 {
141 if (type == kCheckForCache && cats.size() > 1)
142 continue;
143 list[1] = QString::number((int)type);
144 auto *e = new MythEvent("AVAILABILITY", list);
145 QCoreApplication::postEvent(m_pbh.m_listener, e);
146 }
147
148 return availableStatus;
149}
150
152{
153 if (e->type() == QEvent::Timer)
154 {
155 auto *te = (QTimerEvent*)e;
156 const int timer_id = te->timerId();
157 if (timer_id == m_freeSpaceTimerId)
159 if (timer_id == m_checkAvailabilityTimerId)
160 {
161 QStringList slist;
162 {
163 QMutexLocker locker(&m_pbh.m_lock);
164 QHash<uint, QStringList>::iterator it =
165 m_checkAvailability.begin();
166 if (it != m_checkAvailability.end())
167 slist = *it;
168 }
169
170 if (slist.size() >= 1 + NUMPROGRAMLINES)
171 CheckAvailability(slist);
172 }
173 return true;
174 }
175 if (e->type() == MythEvent::kMythEventMessage)
176 {
177 auto *me = dynamic_cast<MythEvent*>(e);
178 if (me == nullptr)
179 return QObject::event(e);
180
181 if (me->Message() == "UPDATE_FREE_SPACE")
182 {
184 return true;
185 }
186 if (me->Message() == "STOP_RECORDING")
187 {
188 ProgramInfo pginfo(me->ExtraDataList());
189 if (pginfo.GetChanID())
190 RemoteStopRecording(&pginfo);
191 return true;
192 }
193 if (me->Message() == "DELETE_RECORDINGS")
194 {
195 QStringList successes;
196 QStringList failures;
197 QStringList list = me->ExtraDataList();
198 while (list.size() >= 3)
199 {
200 uint recordingID = list[0].toUInt();
201 bool forceDelete = list[1].toUInt() != 0U;
202 bool forgetHistory = list[2].toUInt() != 0U;
203
204 bool ok = RemoteDeleteRecording( recordingID, forceDelete,
205 forgetHistory);
206
207 QStringList &res = (ok) ? successes : failures;
208 for (uint i = 0; i < 3; i++)
209 {
210 res.push_back(list.front());
211 list.pop_front();
212 }
213 }
214 if (!successes.empty())
215 {
216 auto *oe = new MythEvent("DELETE_SUCCESSES", successes);
217 QCoreApplication::postEvent(m_pbh.m_listener, oe);
218 }
219 if (!failures.empty())
220 {
221 auto *oe = new MythEvent("DELETE_FAILURES", failures);
222 QCoreApplication::postEvent(m_pbh.m_listener, oe);
223 }
224
225 return true;
226 }
227 if (me->Message() == "UNDELETE_RECORDINGS")
228 {
229 QStringList successes;
230 QStringList failures;
231 QStringList list = me->ExtraDataList();
232 while (!list.empty())
233 {
234 uint recordingID = list[0].toUInt();
235
236 bool ok = RemoteUndeleteRecording(recordingID);
237
238 QStringList &res = (ok) ? successes : failures;
239
240 res.push_back(QString::number(recordingID));
241 list.pop_front();
242 }
243 if (!successes.empty())
244 {
245 auto *oe = new MythEvent("UNDELETE_SUCCESSES", successes);
246 QCoreApplication::postEvent(m_pbh.m_listener, oe);
247 }
248 if (!failures.empty())
249 {
250 auto *oe = new MythEvent("UNDELETE_FAILURES", failures);
251 QCoreApplication::postEvent(m_pbh.m_listener, oe);
252 }
253
254 return true;
255 }
256 if (me->Message() == "GET_PREVIEW")
257 {
258 const QString& token = me->ExtraData(0);
259 bool check_avail = (bool) me->ExtraData(1).toInt();
260 QStringList list = me->ExtraDataList();
261 QStringList::const_iterator it = list.cbegin()+2;
262 ProgramInfo evinfo(it, list.cend());
263 if (!evinfo.HasPathname())
264 return true;
265
266 list.clear();
267 evinfo.ToStringList(list);
268 list += QString::number(kCheckForCache);
269 if (check_avail && (asAvailable != CheckAvailability(list)))
270 return true;
271 if (asAvailable != evinfo.GetAvailableStatus())
272 return true;
273
274 // Now we can actually request the preview...
276
277 return true;
278 }
279 if (me->Message() == "CHECK_AVAILABILITY")
280 {
281 if (me->ExtraData(0) != QString::number(kCheckForCache))
282 {
285 m_checkAvailabilityTimerId = startTimer(0ms);
286 }
288 {
289 m_checkAvailabilityTimerId = startTimer(50ms);
290 }
291 }
292 else if (me->Message() == "LOCATE_ARTWORK")
293 {
294 const QString& inetref = me->ExtraData(0);
295 uint season = me->ExtraData(1).toUInt();
296 const auto type = (VideoArtworkType)me->ExtraData(2).toInt();
297#if 0 /* const ref for an unused variable doesn't make much sense either */
298 uint recordingID = me->ExtraData(3).toUInt();
299 const QString &group = me->ExtraData(4);
300#endif
301 const QString cacheKey = QString("%1:%2:%3")
302 .arg((int)type).arg(inetref).arg(season);
303
304 ArtworkMap map = GetArtwork(inetref, season);
305
306 ArtworkInfo info = map.value(type);
307
308 QString foundFile;
309
310 if (!info.url.isEmpty())
311 {
312 foundFile = info.url;
313 QMutexLocker locker(&m_pbh.m_lock);
314 m_pbh.m_artworkCache[cacheKey] = foundFile;
315 }
316
317 if (!foundFile.isEmpty())
318 {
319 QStringList list = me->ExtraDataList();
320 list.push_back(foundFile);
321 auto *oe = new MythEvent("FOUND_ARTWORK", list);
322 QCoreApplication::postEvent(m_pbh.m_listener, oe);
323 }
324
325 return true;
326 }
327 }
328
329 return QObject::event(e);
330}
331
333{
335 killTimer(m_freeSpaceTimerId);
338}
339
341
343 MThread("PlaybackBoxHelper"),
344 m_listener(listener), m_eventHandler(new PBHEventHandler(*this))
345{
346 start();
347 m_eventHandler->moveToThread(qthread());
348 // Prime the pump so the disk free display starts updating
350}
351
353{
354 // delete the event handler
355 m_eventHandler->deleteLater();
356 m_eventHandler = nullptr;
357
359 wait();
360}
361
363{
364 QCoreApplication::postEvent(
365 m_eventHandler, new MythEvent("UPDATE_FREE_SPACE"));
366}
367
369{
370 QStringList list;
371 pginfo.ToStringList(list);
372 auto *e = new MythEvent("STOP_RECORDING", list);
373 QCoreApplication::postEvent(m_eventHandler, e);
374}
375
376void PlaybackBoxHelper::DeleteRecording( uint recordingID, bool forceDelete,
377 bool forgetHistory)
378{
379 QStringList list;
380 list.push_back(QString::number(recordingID));
381 list.push_back((forceDelete) ? "1" : "0");
382 list.push_back((forgetHistory) ? "1" : "0");
383 DeleteRecordings(list);
384}
385
386void PlaybackBoxHelper::DeleteRecordings(const QStringList &list)
387{
388 auto *e = new MythEvent("DELETE_RECORDINGS", list);
389 QCoreApplication::postEvent(m_eventHandler, e);
390}
391
393{
394 QStringList list;
395 list.push_back(QString::number(recordingID));
396 auto *e = new MythEvent("UNDELETE_RECORDINGS", list);
397 QCoreApplication::postEvent(m_eventHandler, e);
398}
399
401{
403
404 QMutexLocker locker(&m_lock);
405 for (const auto& fsInfo : std::as_const(fsInfos))
406 {
407 if (fsInfo.getPath() == "TotalDiskSpace")
408 {
409 m_freeSpaceTotalMB = (uint64_t) (fsInfo.getTotalSpace() >> 10);
410 m_freeSpaceUsedMB = (uint64_t) (fsInfo.getUsedSpace() >> 10);
411 }
412 }
413 auto *e = new MythEvent("UPDATE_USAGE_UI");
414 QCoreApplication::postEvent(m_listener, e);
415}
416
418{
419 QMutexLocker locker(&m_lock);
420 return m_freeSpaceTotalMB;
421}
422
424{
425 QMutexLocker locker(&m_lock);
426 return m_freeSpaceUsedMB;
427}
428
431{
432 QString catstr = QString::number((int)cat);
433 QMutexLocker locker(&m_lock);
434 QHash<uint, QStringList>::iterator it =
436 if (it == m_eventHandler->m_checkAvailability.end())
437 {
438 QStringList list;
439 pginfo.ToStringList(list);
440 list += catstr;
442 }
443 else
444 {
445 (*it).push_back(catstr);
446 }
447 auto *e = new MythEvent("CHECK_AVAILABILITY", QStringList(catstr));
448 QCoreApplication::postEvent(m_eventHandler, e);
449}
450
452 const QString &inetref, uint season,
454 const ProgramInfo *pginfo,
455 const QString &groupname)
456{
457 QString cacheKey = QString("%1:%2:%3")
458 .arg((int)type).arg(inetref).arg(season);
459
460 QMutexLocker locker(&m_lock);
461
462 InfoMap::const_iterator it =
463 m_artworkCache.constFind(cacheKey);
464
465 if (it != m_artworkCache.constEnd())
466 return *it;
467
468 QStringList list(inetref);
469 list.push_back(QString::number(season));
470 list.push_back(QString::number(type));
471 list.push_back((pginfo)?QString::number(pginfo->GetRecordingID()):"");
472 list.push_back(groupname);
473 auto *e = new MythEvent("LOCATE_ARTWORK", list);
474 QCoreApplication::postEvent(m_eventHandler, e);
475
476 return {};
477}
478
480 const ProgramInfo &pginfo, bool check_availability)
481{
482 if (!check_availability && pginfo.GetAvailableStatus() != asAvailable)
483 return {};
484
485 if (pginfo.GetAvailableStatus() == asPendingDelete)
486 return {};
487
488 QString token = QString("%1:%2")
489 .arg(pginfo.MakeUniqueKey()).arg(MythRandom());
490
491 QStringList extra(token);
492 extra.push_back(check_availability?"1":"0");
493 pginfo.ToStringList(extra);
494 auto *e = new MythEvent("GET_PREVIEW", extra);
495 QCoreApplication::postEvent(m_eventHandler, e);
496
497 return token;
498}
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:283
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:300
void exit(int retcode=0)
Use this to exit from the thread if you are using a Qt event loop.
Definition: mthread.cpp:278
QThread * qthread(void)
Returns the thread, this will always return the same pointer no matter how often you restart the thre...
Definition: mthread.cpp:233
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:68
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
Definition: programinfo.h:373
bool HasPathname(void) const
Definition: programinfo.h:359
uint GetRecordingID(void) const
Definition: programinfo.h:450
AvailableStatusType GetAvailableStatus(void) const
Definition: programinfo.h:847
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:340
QString GetPathname(void) const
Definition: programinfo.h:344
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:451
static void ClearGroupToUseCache(void)
QVector< FileSystemInfo > FileSystemInfoList
unsigned int uint
Definition: freesurround.h:24
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:28
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
Definition: pxsup2dast.c:31
bool RemoteDeleteRecording(uint recordingID, bool forceMetadataDelete, bool forgetHistory)
Definition: remoteutil.cpp:113
bool RemoteUndeleteRecording(uint recordingID)
Definition: remoteutil.cpp:142
bool RemoteStopRecording(uint inputid)