8#include <QCoreApplication>
21#define LOC QString("PreviewQueue: ")
41 uint maxAttempts, std::chrono::seconds minBlockSeconds)
81 uint maxAttempts, std::chrono::seconds minBlockSeconds) :
82 MThread(
"PreviewGeneratorQueue"),
84 m_maxAttempts(maxAttempts), m_minBlockSeconds(minBlockSeconds)
88 int idealThreads = QThread::idealThreadCount();
89 m_maxThreads = (idealThreads >= 1) ? idealThreads * 2 : 2;
107 QMutexLocker locker(&
m_lock);
112 (*it).m_gen->deleteLater();
113 (*it).m_gen =
nullptr;
142 const QSize outputsize,
143 const QString &outputfile,
144 std::chrono::seconds time,
long long frame,
145 const QString& token)
162 extra += QString::number(outputsize.width());
163 extra += QString::number(outputsize.height());
167 extra += QString::number(time.count());
172 extra += QString::number(frame);
175 auto *e =
new MythEvent(
"GET_PREVIEW", extra);
176 QCoreApplication::postEvent(
s_pgq, e);
230 return QObject::event(e);
234 return QObject::event(e);
235 if (me->Message() ==
"GET_PREVIEW")
237 const QStringList &list = me->ExtraDataList();
238 QStringList::const_iterator it = list.begin();
243 long long time_or_frame = -1LL;
244 if (it != list.end())
246 if (it != list.end())
247 outputsize.setWidth((*it++).toInt());
248 if (it != list.end())
249 outputsize.setHeight((*it++).toInt());
250 if (it != list.end())
251 outputfile = (*it++);
252 if (it != list.end())
253 time_or_frame = (*it++).toLongLong();
254 if (it != list.end())
256 bool time_fmt_sec = (*it++).toInt() != 0;
260 std::chrono::seconds(time_or_frame), -1, token);
265 -1s, time_or_frame, token);
270 if (me->Message() ==
"PREVIEW_SUCCESS" ||
271 me->Message() ==
"PREVIEW_FAILED")
273 uint recordedingID = me->ExtraData(0).toUInt();
274 const QString&
filename = me->ExtraData(1);
275 const QString& msg = me->ExtraData(2);
276 const QString& datetime = me->ExtraData(3);
277 const QString& token = me->ExtraData(4);
280 QMutexLocker locker(&
m_lock);
284 LOG(VB_GENERAL, LOG_ERR,
LOC +
285 QString(
"Failed to find token %1 in map.").arg(token));
291 LOG(VB_GENERAL, LOG_ERR,
LOC +
292 QString(
"Failed to find key %1 in map.").arg(*kit));
297 (*it).m_gen->deleteLater();
298 (*it).m_gen =
nullptr;
299 (*it).m_genStarted =
false;
300 if (me->Message() ==
"PREVIEW_SUCCESS")
302 (*it).m_attempts = 0;
303 (*it).m_lastBlockTime = 0s;
304 (*it).m_blockRetryUntil = QDateTime();
308 (*it).m_lastBlockTime =
310 (*it).m_blockRetryUntil =
315 list.push_back(QString::number(recordedingID));
318 list.push_back(datetime);
319 for (
const auto & tok : std::as_const((*it).m_tokens))
331 auto *le =
new MythEvent(me->Message(), list);
332 QCoreApplication::postEvent(listener, le);
334 (*it).m_tokens.clear();
344 return QObject::event(e);
370 const QString &eventname,
371 const QString &
filename,
const QString &token,
const QString &msg,
379 list.push_back(token);
381 QMutexLocker locker(&
m_lock);
384 auto *e =
new MythEvent(eventname, list);
385 QCoreApplication::postEvent(listener, e);
427 const QString &outputfile,
428 std::chrono::seconds time,
long long frame,
429 const QString& token)
431 auto pos_text = (time >= 0s)
432 ? QString::number(time.count()) +
"s"
433 : QString::number(frame)+
"f";
434 QString key = QString(
"%1_%2x%3_%4")
435 .arg(pginfo.
GetBasename()).arg(size.width()).arg(size.height())
440 SendEvent(pginfo,
"PREVIEW_FAILED", key, token,
441 "Pending Delete", QDateTime());
446 QString
filename = (outputfile.isEmpty()) ?
451 bool is_special = !outputfile.isEmpty() || time >= 0s ||
452 (size.width() != 0) || (size.height() != 0);
454 bool needs_gen =
true;
457 QDateTime previewLastModified;
458 bool streaming = !
filename.startsWith(
"/");
459 bool locally_accessible =
false;
460 bool bookmark_updated =
false;
464 if (bookmark_ts.isValid())
465 cmp_ts = bookmark_ts;
473 ret_file = QString(
"%1/%2")
476 QFileInfo finfo(ret_file);
477 if (finfo.isReadable() && finfo.lastModified() >= cmp_ts)
484 previewLastModified = finfo.lastModified();
488 previewLastModified =
495 locally_accessible = fi.isReadable();
496 if (locally_accessible)
497 previewLastModified = fi.lastModified();
501 (!previewLastModified.isValid() || (previewLastModified <= cmp_ts));
503 if (bookmark_updated && bookmark_ts.isValid() &&
504 previewLastModified.isValid())
509 bool preview_exists = previewLastModified.isValid();
512 QString alttext = (bookmark_ts.isValid()) ? QString() :
513 QString(
"\n\t\t\tcmp_ts: %1")
515 LOG(VB_GENERAL, LOG_INFO,
516 QString(
"previewLastModified: %1\n\t\t\t"
517 "bookmark_ts: %2%3\n\t\t\t"
518 "pginfo.lastmodified: %4")
523 QString(
"Title: %1\n\t\t\t")
525 QString(
"File '%1' \n\t\t\tCache '%2'")
527 QString(
"\n\t\t\tPreview Exists: %1, Bookmark Updated: %2, "
529 .arg(preview_exists).arg(bookmark_updated)
530 .arg((bookmark_updated || !preview_exists)));
533 needs_gen = bookmark_updated || !preview_exists;
537 if (locally_accessible)
539 else if (preview_exists && QFileInfo(ret_file).isReadable())
549 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
550 QString(
"Requesting preview for '%1'") .arg(key));
552 if (!outputfile.isEmpty() || time >= 0s ||
553 size.width() || size.height())
555 pg->SetPreviewTime(time, frame);
556 pg->SetOutputFilename(outputfile);
557 pg->SetOutputSize(size);
562 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
563 QString(
"Requested preview for '%1'").arg(key));
567 LOG(VB_GENERAL, LOG_ERR,
LOC +
568 QString(
"Attempted to generate preview for '%1' "
569 "%2 times; >= max(%3)")
575 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
576 QString(
"Not requesting preview for %1,"
577 "as it is already being generated")
586 QString msg =
"On Disk";
587 QDateTime dt = QFileInfo(ret).lastModified();
588 SendEvent(pginfo,
"PREVIEW_SUCCESS", ret, token, msg, dt);
592 uint queue_depth = 0;
594 GetInfo(key, queue_depth, token_cnt);
595 QString msg = QString(
"Queue depth %1, our tokens %2")
596 .arg(queue_depth).arg(token_cnt);
597 SendEvent(pginfo,
"PREVIEW_QUEUED", ret, token, msg, QDateTime());
616 const QString &key,
uint &queue_depth,
uint &token_cnt)
618 QMutexLocker locker(&
m_lock);
621 token_cnt = (pit ==
m_previewMap.end()) ? 0 : (*pit).m_tokens.size();
632 const QString &key,
const QString& token)
634 QMutexLocker locker(&
m_lock);
641 if ((*pit).m_gen && !(*pit).m_genStarted)
644 if (!token.isEmpty())
647 (*pit).m_tokens.insert(token);
657 QMutexLocker locker(&
m_lock);
661 QString fn = q.back();
664 if (it !=
m_previewMap.end() && (*it).m_gen && !(*it).m_genStarted)
667 (*it).m_gen->start();
668 (*it).m_genStarted =
true;
688 QMutexLocker locker(&
m_lock);
693 if (state.
m_gen != g)
730 QMutexLocker locker(&
m_lock);
736 if ((*it).m_blockRetryUntil.isValid())
752 QMutexLocker locker(&
m_lock);
767 QMutexLocker locker(&
m_lock);
This is a wrapper around QThread that does several additional things.
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
void exit(int retcode=0)
Use this to exit from the thread if you are using a Qt event loop.
QThread * qthread(void)
Returns the thread, this will always return the same pointer no matter how often you restart the thre...
int GetNumSetting(const QString &key, int defaultval=0)
This class is used as a container for messages.
static const Type kMythEventMessage
This class holds all the state information related to a specific preview generator.
bool m_genStarted
The preview generator for this file is currently running.
QSet< QString > m_tokens
The full set of tokens for all callers that have requested this preview.
PreviewGenerator * m_gen
A pointer to the generator that this state object describes.
This class implements a queue of preview generation requests.
uint m_maxAttempts
How many times total will the code attempt to generate a preview for a specific file,...
QMap< QString, QString > m_tokenToKeyMap
A mapping from requestor tokens to internal keys.
QSet< QObject * > m_listeners
The set of all listeners that want messages when a preview request is queued or finishes.
std::chrono::seconds m_minBlockSeconds
How long after a failed preview generation attempt will the code ignore subsequent requests.
static PreviewGeneratorQueue * s_pgq
The singleton queue.
bool IsGeneratingPreview(const QString &key) const
Is a preview currently being generated for this key.
void SetPreviewGenerator(const QString &key, PreviewGenerator *g)
Sets the PreviewGenerator for a specific file.
~PreviewGeneratorQueue() override
Destroy the preview generation queue.
uint IncPreviewGeneratorAttempts(const QString &key)
Increments and returns number of times we have started a PreviewGenerator to create this file.
QString GeneratePreviewImage(ProgramInfo &pginfo, QSize size, const QString &outputfile, std::chrono::seconds time, long long frame, const QString &token)
Generate a preview image for the specified program.
void IncPreviewGeneratorPriority(const QString &key, const QString &token)
QStringList m_queue
The queue of previews to be generated.
void UpdatePreviewGeneratorThreads(void)
As long as there are items in the queue, make sure we're running the maximum allowed number of previe...
PreviewGenerator::Mode m_mode
void GetInfo(const QString &key, uint &queue_depth, uint &token_cnt)
static void GetPreviewImage(const ProgramInfo &pginfo, const QString &token)
Submit a request for the generation of a preview image.
uint m_maxThreads
The maximum number of threads that may concurrently generate previews.
PreviewGeneratorQueue(PreviewGenerator::Mode mode, uint maxAttempts, std::chrono::seconds minBlockSeconds)
void ClearPreviewGeneratorAttempts(const QString &key)
Clears the number of times we have started a PreviewGenerator to create this file.
static void CreatePreviewGeneratorQueue(PreviewGenerator::Mode mode, uint maxAttempts, std::chrono::seconds minBlockSeconds)
Create the singleton queue of preview generators.
static void AddListener(QObject *listener)
Request notifications when a preview event is generated.
static void RemoveListener(QObject *listener)
Stop receiving notifications when a preview event is generated.
PreviewMap m_previewMap
A mapping from the generated preview name to the state information on the progress of generating the ...
void SendEvent(const ProgramInfo &pginfo, const QString &eventname, const QString &filename, const QString &token, const QString &msg, const QDateTime &dt)
Send a message back to all objects that have requested creation of a specific preview.
uint m_running
The number of threads currently generating previews.
QMutex m_lock
The thread interlock for this data structure.
static void TeardownPreviewGeneratorQueue()
Destroy the singleton queue of preview generators.
bool event(QEvent *e) override
The event handler running on the preview generation thread.
This class creates a preview image of a recording.
QString GetToken(void) const
void AttachSignals(QObject *obj)
Holds information on recordings and videos.
QString GetBasename(void) const
QString toString(Verbosity v=kLongDescription, const QString &sep=":", const QString &grp="\"") const
QDateTime GetBookmarkUpdate(void) const
uint GetRecordingID(void) const
AvailableStatusType GetAvailableStatus(void) const
QDateTime GetLastModifiedTime(void) const
QDateTime GetRecordingStartTime(void) const
Approximate time the recording started.
QString GetPathname(void) const
void ToStringList(QStringList &list) const
Serializes ProgramInfo into a QStringList which can be passed over a socket.
QDateTime GetRecordingEndTime(void) const
Approximate time the recording should have ended, did end, or is intended to end.
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
QString GetRemoteCacheDir(void)
Returns the directory for all files cached from the backend.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
QDateTime RemoteGetPreviewIfModified(const ProgramInfo &pginfo, const QString &cachefile)
Download preview & get timestamp if newer than cachefile's last modified time, otherwise just get the...