MythTV  0.26-pre
previewgeneratorqueue.cpp
Go to the documentation of this file.
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     // disconnect preview generators
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); // pginfo->MakeUniqueKey()
00148         QString filename  = me->ExtraData(1); // outFileName
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                 // This is just an optimization to avoid
00297                 // hitting the backend if our cached copy
00298                 // is newer than the bookmark, or if we have
00299                 // a preview and do not update it when the
00300                 // bookmark changes.
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 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends