00001 #include <QCoreApplication>
00002 #include <QFileInfo>
00003
00004 #include "previewgeneratorqueue.h"
00005 #include "previewgenerator.h"
00006 #include "mythcorecontext.h"
00007 #include "mythcontext.h"
00008 #include "mythlogging.h"
00009 #include "remoteutil.h"
00010 #include "mythdirs.h"
00011 #include "mthread.h"
00012
00013 #define LOC QString("PreviewQueue: ")
00014
00015 PreviewGeneratorQueue *PreviewGeneratorQueue::s_pgq = NULL;
00016
00017 void PreviewGeneratorQueue::CreatePreviewGeneratorQueue(
00018 PreviewGenerator::Mode mode,
00019 uint maxAttempts, uint minBlockSeconds)
00020 {
00021 s_pgq = new PreviewGeneratorQueue(mode, maxAttempts, minBlockSeconds);
00022 }
00023
00024 void PreviewGeneratorQueue::TeardownPreviewGeneratorQueue()
00025 {
00026 s_pgq->exit(0);
00027 s_pgq->wait();
00028 delete s_pgq;
00029 }
00030
00031 PreviewGeneratorQueue::PreviewGeneratorQueue(
00032 PreviewGenerator::Mode mode,
00033 uint maxAttempts, uint minBlockSeconds) :
00034 MThread("PreviewGeneratorQueue"),
00035 m_mode(mode),
00036 m_running(0), m_maxThreads(2),
00037 m_maxAttempts(maxAttempts), m_minBlockSeconds(minBlockSeconds)
00038 {
00039 if (PreviewGenerator::kLocal & mode)
00040 {
00041 int idealThreads = QThread::idealThreadCount();
00042 m_maxThreads = (idealThreads >= 1) ? idealThreads * 2 : 2;
00043 }
00044
00045 moveToThread(qthread());
00046 start();
00047 }
00048
00049 PreviewGeneratorQueue::~PreviewGeneratorQueue()
00050 {
00051
00052 QMutexLocker locker(&m_lock);
00053 PreviewMap::iterator it = m_previewMap.begin();
00054 for (;it != m_previewMap.end(); ++it)
00055 {
00056 if ((*it).gen)
00057 (*it).gen->deleteLater();
00058 }
00059 locker.unlock();
00060 wait();
00061 }
00062
00063 void PreviewGeneratorQueue::GetPreviewImage(
00064 const ProgramInfo &pginfo,
00065 const QSize &outputsize,
00066 const QString &outputfile,
00067 long long time, bool in_seconds,
00068 QString token)
00069 {
00070 if (!s_pgq)
00071 return;
00072
00073 if (pginfo.GetPathname().isEmpty() ||
00074 pginfo.GetBasename() == pginfo.GetPathname())
00075 {
00076 return;
00077 }
00078
00079 QStringList extra;
00080 pginfo.ToStringList(extra);
00081 extra += token;
00082 extra += QString::number(outputsize.width());
00083 extra += QString::number(outputsize.height());
00084 extra += outputfile;
00085 extra += QString::number(time);
00086 extra += (in_seconds ? "1" : "0");
00087 MythEvent *e = new MythEvent("GET_PREVIEW", extra);
00088 QCoreApplication::postEvent(s_pgq, e);
00089 }
00090
00091 void PreviewGeneratorQueue::AddListener(QObject *listener)
00092 {
00093 if (!s_pgq)
00094 return;
00095
00096 QMutexLocker locker(&s_pgq->m_lock);
00097 s_pgq->m_listeners.insert(listener);
00098 }
00099
00100 void PreviewGeneratorQueue::RemoveListener(QObject *listener)
00101 {
00102 if (!s_pgq)
00103 return;
00104
00105 QMutexLocker locker(&s_pgq->m_lock);
00106 s_pgq->m_listeners.remove(listener);
00107 }
00108
00109 bool PreviewGeneratorQueue::event(QEvent *e)
00110 {
00111 if (e->type() != (QEvent::Type) MythEvent::MythEventMessage)
00112 return QObject::event(e);
00113
00114 MythEvent *me = (MythEvent*)e;
00115 if (me->Message() == "GET_PREVIEW")
00116 {
00117 const QStringList list = me->ExtraDataList();
00118 QStringList::const_iterator it = list.begin();
00119 ProgramInfo evinfo(it, list.end());
00120 QString token;
00121 QSize outputsize;
00122 QString outputfile;
00123 long long time = -1LL;
00124 bool time_fmt_sec;
00125 if (it != list.end())
00126 token = (*it++);
00127 if (it != list.end())
00128 outputsize.setWidth((*it++).toInt());
00129 if (it != list.end())
00130 outputsize.setHeight((*it++).toInt());
00131 if (it != list.end())
00132 outputfile = (*it++);
00133 if (it != list.end())
00134 time = (*it++).toLongLong();
00135 QString fn;
00136 if (it != list.end())
00137 {
00138 time_fmt_sec = (*it++).toInt() != 0;
00139 fn = GeneratePreviewImage(evinfo, outputsize, outputfile,
00140 time, time_fmt_sec, token);
00141 }
00142 return true;
00143 }
00144 else if (me->Message() == "PREVIEW_SUCCESS" ||
00145 me->Message() == "PREVIEW_FAILED")
00146 {
00147 QString pginfokey = me->ExtraData(0);
00148 QString filename = me->ExtraData(1);
00149 QString msg = me->ExtraData(2);
00150 QString datetime = me->ExtraData(3);
00151 QString token = me->ExtraData(4);
00152
00153 {
00154 QMutexLocker locker(&m_lock);
00155 QMap<QString,QString>::iterator kit = m_tokenToKeyMap.find(token);
00156 if (kit == m_tokenToKeyMap.end())
00157 {
00158 LOG(VB_GENERAL, LOG_ERR, LOC +
00159 QString("Failed to find token %1 in map.").arg(token));
00160 return true;
00161 }
00162 PreviewMap::iterator it = m_previewMap.find(*kit);
00163 if (it == m_previewMap.end())
00164 {
00165 LOG(VB_GENERAL, LOG_ERR, LOC +
00166 QString("Failed to find key %1 in map.").arg(*kit));
00167 return true;
00168 }
00169
00170 if ((*it).gen)
00171 (*it).gen->deleteLater();
00172 (*it).gen = NULL;
00173 (*it).genStarted = false;
00174 if (me->Message() == "PREVIEW_SUCCESS")
00175 {
00176 (*it).attempts = 0;
00177 (*it).lastBlockTime = 0;
00178 (*it).blockRetryUntil = QDateTime();
00179 }
00180 else
00181 {
00182 (*it).lastBlockTime =
00183 max(m_minBlockSeconds, (*it).lastBlockTime * 2);
00184 (*it).blockRetryUntil =
00185 QDateTime::currentDateTime().addSecs((*it).lastBlockTime);
00186 }
00187
00188 QStringList list;
00189 list.push_back(pginfokey);
00190 list.push_back(filename);
00191 list.push_back(msg);
00192 list.push_back(datetime);
00193 QSet<QString>::const_iterator tit = (*it).tokens.begin();
00194 for (; tit != (*it).tokens.end(); ++tit)
00195 {
00196 kit = m_tokenToKeyMap.find(*tit);
00197 if (kit != m_tokenToKeyMap.end())
00198 m_tokenToKeyMap.erase(kit);
00199 list.push_back(*tit);
00200 }
00201
00202 if (list.size() > 4)
00203 {
00204 QSet<QObject*>::iterator sit = m_listeners.begin();
00205 for (; sit != m_listeners.end(); ++sit)
00206 {
00207 MythEvent *e = new MythEvent(me->Message(), list);
00208 QCoreApplication::postEvent(*sit, e);
00209 }
00210 (*it).tokens.clear();
00211 }
00212
00213 m_running = (m_running > 0) ? m_running - 1 : 0;
00214 }
00215
00216 UpdatePreviewGeneratorThreads();
00217
00218 return true;
00219 }
00220 return false;
00221 }
00222
00223 void PreviewGeneratorQueue::SendEvent(
00224 const ProgramInfo &pginfo,
00225 const QString &eventname,
00226 const QString &fn, const QString &token, const QString &msg,
00227 const QDateTime &dt)
00228 {
00229 QStringList list;
00230 list.push_back(pginfo.MakeUniqueKey());
00231 list.push_back(fn);
00232 list.push_back(msg);
00233 list.push_back(dt.toString(Qt::ISODate));
00234 list.push_back(token);
00235
00236 QMutexLocker locker(&m_lock);
00237 QSet<QObject*>::iterator it = m_listeners.begin();
00238 for (; it != m_listeners.end(); ++it)
00239 {
00240 MythEvent *e = new MythEvent(eventname, list);
00241 QCoreApplication::postEvent(*it, e);
00242 }
00243 }
00244
00245 QString PreviewGeneratorQueue::GeneratePreviewImage(
00246 ProgramInfo &pginfo,
00247 const QSize &size,
00248 const QString &outputfile,
00249 long long time, bool in_seconds,
00250 QString token)
00251 {
00252 QString key = QString("%1_%2x%3_%4%5")
00253 .arg(pginfo.GetBasename()).arg(size.width()).arg(size.height())
00254 .arg(time).arg(in_seconds?"s":"f");
00255
00256 if (pginfo.GetAvailableStatus() == asPendingDelete)
00257 {
00258 SendEvent(pginfo, "PREVIEW_FAILED", key, token,
00259 "Pending Delete", QDateTime());
00260 return QString();
00261 }
00262
00263 QString filename = (outputfile.isEmpty()) ?
00264 pginfo.GetPathname() + ".png" : outputfile;
00265 QString ret_file = filename;
00266 QString ret;
00267
00268 bool is_special = !outputfile.isEmpty() || time >= 0 ||
00269 size.width() || size.height();
00270
00271 bool needs_gen = true;
00272 if (!is_special)
00273 {
00274 QDateTime previewLastModified;
00275 bool streaming = filename.left(1) != "/";
00276 bool locally_accessible = false;
00277 bool bookmark_updated = false;
00278
00279 QDateTime bookmark_ts = pginfo.QueryBookmarkTimeStamp();
00280 QDateTime cmp_ts;
00281 if (bookmark_ts.isValid())
00282 cmp_ts = bookmark_ts;
00283 else if (QDateTime::currentDateTime() >= pginfo.GetRecordingEndTime())
00284 cmp_ts = pginfo.GetLastModifiedTime();
00285 else
00286 cmp_ts = pginfo.GetRecordingStartTime();
00287
00288 if (streaming)
00289 {
00290 ret_file = QString("%1/remotecache/%2")
00291 .arg(GetConfDir()).arg(filename.section('/', -1));
00292
00293 QFileInfo finfo(ret_file);
00294 if (finfo.isReadable() && finfo.lastModified() >= cmp_ts)
00295 {
00296
00297
00298
00299
00300
00301 previewLastModified = finfo.lastModified();
00302 }
00303 else if (!IsGeneratingPreview(key))
00304 {
00305 previewLastModified =
00306 RemoteGetPreviewIfModified(pginfo, ret_file);
00307 }
00308 }
00309 else
00310 {
00311 QFileInfo fi(filename);
00312 if ((locally_accessible = fi.isReadable()))
00313 previewLastModified = fi.lastModified();
00314 }
00315
00316 bookmark_updated =
00317 (!previewLastModified.isValid() || (previewLastModified <= cmp_ts));
00318
00319 if (bookmark_updated && bookmark_ts.isValid() &&
00320 previewLastModified.isValid())
00321 {
00322 ClearPreviewGeneratorAttempts(key);
00323 }
00324
00325 bool preview_exists = previewLastModified.isValid();
00326
00327 if (0)
00328 {
00329 QString alttext = (bookmark_ts.isValid()) ? QString() :
00330 QString("\n\t\t\tcmp_ts: %1")
00331 .arg(cmp_ts.toString(Qt::ISODate));
00332 LOG(VB_GENERAL, LOG_INFO,
00333 QString("previewLastModified: %1\n\t\t\t"
00334 "bookmark_ts: %2%3\n\t\t\t"
00335 "pginfo.lastmodified: %4")
00336 .arg(previewLastModified.toString(Qt::ISODate))
00337 .arg(bookmark_ts.toString(Qt::ISODate))
00338 .arg(alttext)
00339 .arg(pginfo.GetLastModifiedTime(ISODate)) +
00340 QString("Title: %1\n\t\t\t")
00341 .arg(pginfo.toString(ProgramInfo::kTitleSubtitle)) +
00342 QString("File '%1' \n\t\t\tCache '%2'")
00343 .arg(filename).arg(ret_file) +
00344 QString("\n\t\t\tPreview Exists: %1, Bookmark Updated: %2, "
00345 "Need Preview: %3")
00346 .arg(preview_exists).arg(bookmark_updated)
00347 .arg((bookmark_updated || !preview_exists)));
00348 }
00349
00350 needs_gen = bookmark_updated || !preview_exists;
00351
00352 if (!needs_gen)
00353 {
00354 if (locally_accessible)
00355 ret = filename;
00356 else if (preview_exists && QFileInfo(ret_file).isReadable())
00357 ret = ret_file;
00358 }
00359 }
00360
00361 if (needs_gen && !IsGeneratingPreview(key))
00362 {
00363 uint attempts = IncPreviewGeneratorAttempts(key);
00364 if (attempts < m_maxAttempts)
00365 {
00366 LOG(VB_PLAYBACK, LOG_INFO, LOC +
00367 QString("Requesting preview for '%1'") .arg(key));
00368 PreviewGenerator *pg = new PreviewGenerator(&pginfo, token, m_mode);
00369 if (!outputfile.isEmpty() || time >= 0 ||
00370 size.width() || size.height())
00371 {
00372 pg->SetPreviewTime(time, in_seconds);
00373 pg->SetOutputFilename(outputfile);
00374 pg->SetOutputSize(size);
00375 }
00376
00377 SetPreviewGenerator(key, pg);
00378
00379 LOG(VB_PLAYBACK, LOG_INFO, LOC +
00380 QString("Requested preview for '%1'").arg(key));
00381 }
00382 else if (attempts >= m_maxAttempts)
00383 {
00384 LOG(VB_GENERAL, LOG_ERR, LOC +
00385 QString("Attempted to generate preview for '%1' "
00386 "%2 times; >= max(%3)")
00387 .arg(key).arg(attempts).arg(m_maxAttempts));
00388 }
00389 }
00390 else if (needs_gen)
00391 {
00392 LOG(VB_PLAYBACK, LOG_INFO, LOC +
00393 QString("Not requesting preview for %1,"
00394 "as it is already being generated")
00395 .arg(pginfo.toString(ProgramInfo::kTitleSubtitle)));
00396 IncPreviewGeneratorPriority(key, token);
00397 }
00398
00399 UpdatePreviewGeneratorThreads();
00400
00401 if (!ret.isEmpty())
00402 {
00403 QString msg = "On Disk";
00404 QDateTime dt = QFileInfo(ret).lastModified();
00405 SendEvent(pginfo, "PREVIEW_SUCCESS", ret, token, msg, dt);
00406 }
00407 else
00408 {
00409 uint queue_depth, token_cnt;
00410 GetInfo(key, queue_depth, token_cnt);
00411 QString msg = QString("Queue depth %1, our tokens %2")
00412 .arg(queue_depth).arg(token_cnt);
00413 SendEvent(pginfo, "PREVIEW_QUEUED", ret, token, msg, QDateTime());
00414 }
00415
00416 return ret;
00417 }
00418
00419 void PreviewGeneratorQueue::GetInfo(
00420 const QString &key, uint &queue_depth, uint &token_cnt)
00421 {
00422 QMutexLocker locker(&m_lock);
00423 queue_depth = m_queue.size();
00424 PreviewMap::iterator pit = m_previewMap.find(key);
00425 token_cnt = (pit == m_previewMap.end()) ? 0 : (*pit).tokens.size();
00426 }
00427
00428 void PreviewGeneratorQueue::IncPreviewGeneratorPriority(
00429 const QString &key, QString token)
00430 {
00431 QMutexLocker locker(&m_lock);
00432 m_queue.removeAll(key);
00433
00434 PreviewMap::iterator pit = m_previewMap.find(key);
00435 if (pit == m_previewMap.end())
00436 return;
00437
00438 if ((*pit).gen && !(*pit).genStarted)
00439 m_queue.push_back(key);
00440
00441 if (!token.isEmpty())
00442 {
00443 m_tokenToKeyMap[token] = key;
00444 (*pit).tokens.insert(token);
00445 }
00446 }
00447
00448 void PreviewGeneratorQueue::UpdatePreviewGeneratorThreads(void)
00449 {
00450 QMutexLocker locker(&m_lock);
00451 QStringList &q = m_queue;
00452 if (!q.empty() && (m_running < m_maxThreads))
00453 {
00454 QString fn = q.back();
00455 q.pop_back();
00456 PreviewMap::iterator it = m_previewMap.find(fn);
00457 if (it != m_previewMap.end() && (*it).gen && !(*it).genStarted)
00458 {
00459 m_running++;
00460 (*it).gen->start();
00461 (*it).genStarted = true;
00462 }
00463 }
00464 }
00465
00469 void PreviewGeneratorQueue::SetPreviewGenerator(
00470 const QString &key, PreviewGenerator *g)
00471 {
00472 {
00473 QMutexLocker locker(&m_lock);
00474 m_tokenToKeyMap[g->GetToken()] = key;
00475 PreviewGenState &state = m_previewMap[key];
00476 if (state.gen)
00477 {
00478 if (g && state.gen != g)
00479 {
00480 if (!g->GetToken().isEmpty())
00481 state.tokens.insert(g->GetToken());
00482 g->deleteLater();
00483 }
00484 }
00485 else if (g)
00486 {
00487 g->AttachSignals(this);
00488 state.gen = g;
00489 state.genStarted = false;
00490 if (!g->GetToken().isEmpty())
00491 state.tokens.insert(g->GetToken());
00492 }
00493 }
00494
00495 IncPreviewGeneratorPriority(key, "");
00496 }
00497
00501 bool PreviewGeneratorQueue::IsGeneratingPreview(const QString &key) const
00502 {
00503 PreviewMap::const_iterator it;
00504 QMutexLocker locker(&m_lock);
00505
00506 if ((it = m_previewMap.find(key)) == m_previewMap.end())
00507 return false;
00508
00509 if ((*it).blockRetryUntil.isValid())
00510 return QDateTime::currentDateTime() < (*it).blockRetryUntil;
00511
00512 return (*it).gen;
00513 }
00514
00519 uint PreviewGeneratorQueue::IncPreviewGeneratorAttempts(const QString &key)
00520 {
00521 QMutexLocker locker(&m_lock);
00522 return m_previewMap[key].attempts++;
00523 }
00524
00529 void PreviewGeneratorQueue::ClearPreviewGeneratorAttempts(const QString &key)
00530 {
00531 QMutexLocker locker(&m_lock);
00532 m_previewMap[key].attempts = 0;
00533 m_previewMap[key].lastBlockTime = 0;
00534 m_previewMap[key].blockRetryUntil =
00535 QDateTime::currentDateTime().addSecs(-60);
00536 }