|
MythTV
0.26-pre
|
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 }
1.7.6.1