Ticket #7195: 7191-gen2-v1.patch
File 7191-gen2-v1.patch, 63.8 KB (added by , 14 years ago) |
---|
-
libs/libmythtv/libmythtv.pro
153 153 HEADERS += scheduledrecording.h 154 154 HEADERS += signalmonitorvalue.h signalmonitorlistener.h 155 155 HEADERS += livetvchain.h playgroup.h 156 HEADERS += channelsettings.h previewgenerator.h 156 HEADERS += channelsettings.h 157 HEADERS += previewgenerator.h previewgeneratorqueue.h 157 158 HEADERS += transporteditor.h listingsources.h 158 159 HEADERS += myth_imgconvert.h 159 160 HEADERS += channelgroup.h channelgroupsettings.h … … 177 178 SOURCES += scheduledrecording.cpp 178 179 SOURCES += signalmonitorvalue.cpp 179 180 SOURCES += livetvchain.cpp playgroup.cpp 180 SOURCES += channelsettings.cpp previewgenerator.cpp 181 SOURCES += channelsettings.cpp 182 SOURCES += previewgenerator.cpp previewgeneratorqueue.cpp 181 183 SOURCES += transporteditor.cpp 182 184 SOURCES += channelgroup.cpp channelgroupsettings.cpp 183 185 SOURCES += myth_imgconvert.cpp -
libs/libmythtv/previewgeneratorqueue.h
1 // -*- Mode: c++ -*- 2 #ifndef _PREVIEW_GENERATOR_QUEUE_H_ 3 #define _PREVIEW_GENERATOR_QUEUE_H_ 4 5 #include <QStringList> 6 #include <QDateTime> 7 #include <QThread> 8 #include <QMutex> 9 #include <QMap> 10 #include <QSet> 11 12 #include "previewgenerator.h" 13 #include "mythexp.h" 14 15 class ProgramInfo; 16 class QSize; 17 18 class PreviewGenState 19 { 20 public: 21 PreviewGenState() : 22 gen(NULL), genStarted(false), ready(false), 23 attempts(0), lastBlockTime(0) {} 24 PreviewGenerator *gen; 25 bool genStarted; 26 bool ready; 27 uint attempts; 28 uint lastBlockTime; 29 QDateTime blockRetryUntil; 30 }; 31 typedef QMap<QString,PreviewGenState> PreviewMap; 32 33 class MPUBLIC PreviewGeneratorQueue : public QThread 34 { 35 Q_OBJECT 36 37 public: 38 static void CreatePreviewGeneratorQueue( 39 PreviewGenerator::Mode mode, 40 uint maxAttempts, uint minBlockSeconds); 41 static void TeardownPreviewGeneratorQueue(); 42 43 static void GetPreviewImage(const ProgramInfo&, QString token = ""); 44 static void GetPreviewImage(const ProgramInfo&, const QSize&, 45 const QString &outputfile, 46 long long time, bool in_seconds, 47 QString token = ""); 48 static void AddListener(QObject*); 49 static void RemoveListener(QObject*); 50 51 private: 52 PreviewGeneratorQueue(PreviewGenerator::Mode mode, 53 uint maxAttempts, uint minBlockSeconds); 54 ~PreviewGeneratorQueue(); 55 56 QString GeneratePreviewImage(ProgramInfo &pginfo); 57 QString GeneratePreviewImage(ProgramInfo &pginfo, const QSize&, 58 const QString &outputfile, 59 long long time, bool in_seconds, 60 QString token); 61 62 bool SetPreviewGenerator(const QString &fn, PreviewGenerator *g); 63 void IncPreviewGeneratorPriority(const QString &fn); 64 void UpdatePreviewGeneratorThreads(void); 65 bool IsGeneratingPreview(const QString &fn, bool really = false) const; 66 uint IncPreviewGeneratorAttempts(const QString &fn); 67 void ClearPreviewGeneratorAttempts(const QString &fn); 68 69 virtual bool event(QEvent *e); // QObject 70 71 void SendReadyEvent(const ProgramInfo &pginfo, 72 const QString &fn, const QString &token); 73 74 private slots: 75 void previewThreadDone(const QString &fn, bool &success); 76 void previewReady(const ProgramInfo *pginfo); 77 78 private: 79 static PreviewGeneratorQueue *s_pgq; 80 QSet<QObject*> m_listeners; 81 82 mutable QMutex m_lock; 83 uint m_mode; 84 PreviewMap m_previewMap; 85 QStringList m_queue; 86 uint m_running; 87 uint m_maxThreads; 88 uint m_maxAttempts; 89 uint m_minBlockSeconds; 90 }; 91 92 #endif // _PREVIEW_GENERATOR_QUEUE_H_ -
libs/libmythtv/previewgenerator.cpp
6 6 #include <fcntl.h> 7 7 8 8 // Qt headers 9 #include <QCoreApplication> 10 #include <QTemporaryFile> 9 11 #include <QFileInfo> 12 #include <QMetaType> 13 #include <QThread> 10 14 #include <QImage> 11 #include <Q MetaType>15 #include <QDir> 12 16 #include <QUrl> 13 #include <QDir>14 17 15 18 // MythTV headers 16 19 #include "mythconfig.h" … … 27 30 #include "playercontext.h" 28 31 #include "mythdirs.h" 29 32 #include "mythverbose.h" 33 #include "remoteutil.h" 30 34 31 35 #define LOC QString("Preview: ") 32 36 #define LOC_ERR QString("Preview Error: ") … … 37 41 * 38 42 * The usage is simple: First, pass a ProgramInfo whose pathname points 39 43 * to a local or remote recording to the constructor. Then call either 40 * Start(void) or Run(void) to generate the preview.44 * start(void) or Run(void) to generate the preview. 41 45 * 42 * Start(void) will create a thread that processes the request,46 * start(void) will create a thread that processes the request, 43 47 * creating a sockets the the backend if the recording is not local. 44 48 * 45 49 * Run(void) will process the request in the current thread, and it … … 66 70 PreviewGenerator::PreviewGenerator(const ProgramInfo *pginfo, 67 71 PreviewGenerator::Mode _mode) 68 72 : programInfo(*pginfo), mode(_mode), isConnected(false), 69 createSockets(false), serverSock(NULL),pathname(pginfo->GetPathname()),73 pathname(pginfo->GetPathname()), 70 74 timeInSeconds(true), captureTime(-1), outFileName(QString::null), 71 outSize(0,0) 75 outSize(0,0), pixmapOk(false) 72 76 { 73 77 } 74 78 … … 84 88 85 89 void PreviewGenerator::TeardownAll(void) 86 90 { 91 previewWaitCondition.wakeAll(); 92 87 93 if (!isConnected) 88 94 return; 89 95 … … 114 120 void PreviewGenerator::AttachSignals(QObject *obj) 115 121 { 116 122 QMutexLocker locker(&previewLock); 117 qRegisterMetaType<bool>("bool &");118 123 connect(this, SIGNAL(previewThreadDone(const QString&,bool&)), 119 124 obj, SLOT( previewThreadDone(const QString&,bool&)), 120 125 Qt::DirectConnection); … … 135 140 isConnected = false; 136 141 } 137 142 138 /** \fn PreviewGenerator::Start(void)139 * \brief This call starts a thread that will create a preview.140 */141 void PreviewGenerator::Start(void)142 {143 pthread_create(&previewThread, NULL, PreviewRun, this);144 // detach, so we don't have to join thread to free thread local mem.145 pthread_detach(previewThread);146 }147 148 143 /** \fn PreviewGenerator::RunReal(void) 149 144 * \brief This call creates a preview without starting a new thread. 150 145 */ … … 264 259 return ok; 265 260 } 266 261 267 void *PreviewGenerator::PreviewRun(void *param)262 void PreviewGenerator::run(void) 268 263 { 269 // Lower scheduling priority, to avoid problems with recordings. 270 if (setpriority(PRIO_PROCESS, 0, 9)) 271 VERBOSE(VB_IMPORTANT, LOC + "Setting priority failed." + ENO); 272 PreviewGenerator *gen = (PreviewGenerator*) param; 273 gen->createSockets = true; 274 gen->Run(); 275 gen->deleteLater(); 276 return NULL; 264 setPriority(QThread::LowestPriority); 265 Run(); 266 connect(this, SIGNAL(finished()), 267 this, SLOT(deleteLater())); 277 268 } 278 269 279 bool PreviewGenerator::RemotePreviewSetup(void)280 {281 QString server = gCoreContext->GetSetting("MasterServerIP", "localhost");282 int port = gCoreContext->GetNumSetting("MasterServerPort", 6543);283 QString ann = QString("ANN Monitor %2 %3")284 .arg(gCoreContext->GetHostName()).arg(false);285 286 serverSock = gCoreContext->ConnectCommandSocket(server, port, ann);287 return serverSock;288 }289 290 270 bool PreviewGenerator::RemotePreviewRun(void) 291 271 { 292 QStringList strlist( "QUERY_GENPIXMAP" ); 272 QStringList strlist( "QUERY_GENPIXMAP2" ); 273 token = QString("%1:%2") 274 .arg(programInfo.MakeUniqueKey()).arg(rand()); 275 strlist.push_back(token); 293 276 programInfo.ToStringList(strlist); 294 277 strlist.push_back(timeInSeconds ? "s" : "f"); 295 278 encodeLongLong(strlist, captureTime); … … 305 288 strlist.push_back(QString::number(outSize.width())); 306 289 strlist.push_back(QString::number(outSize.height())); 307 290 308 bool ok = false; 291 gCoreContext->addListener(this); 292 pixmapOk = false; 309 293 310 if (createSockets) 311 { 312 if (!RemotePreviewSetup()) 313 { 314 VERBOSE(VB_IMPORTANT, LOC_ERR + "Failed to open sockets."); 315 return false; 316 } 317 318 if (serverSock) 319 { 320 serverSock->writeStringList(strlist); 321 ok = serverSock->readStringList(strlist, false); 322 } 323 324 RemotePreviewTeardown(); 325 } 326 else 327 { 328 ok = gCoreContext->SendReceiveStringList(strlist); 329 } 330 294 bool ok = gCoreContext->SendReceiveStringList(strlist); 331 295 if (!ok || strlist.empty() || (strlist[0] != "OK")) 332 296 { 333 297 if (!ok) … … 340 304 VERBOSE(VB_IMPORTANT, LOC_ERR + 341 305 "Remote Preview failed, reason given: " <<strlist[1]); 342 306 } 343 else 307 308 gCoreContext->removeListener(this); 309 310 return false; 311 } 312 313 QMutexLocker locker(&previewLock); 314 // wait up to 30 seconds for the preview to complete 315 previewWaitCondition.wait(&previewLock, 30 * 1000); 316 317 gCoreContext->removeListener(this); 318 319 return pixmapOk; 320 } 321 322 bool PreviewGenerator::event(QEvent *e) 323 { 324 if (e->type() == (QEvent::Type) MythEvent::MythEventMessage) 325 { 326 MythEvent *me = (MythEvent*)e; 327 if (me->Message() == "GENERATED_PIXMAP" && me->ExtraDataCount() > 0 && 328 me->ExtraData(0) == token) 344 329 { 345 VERBOSE(VB_IMPORTANT, LOC_ERR + 346 "Remote Preview failed due to an unknown error."); 330 QMutexLocker locker(&previewLock); 331 332 pixmapOk = me->ExtraData(1) == "OK"; 333 if (pixmapOk) 334 { 335 QByteArray data; 336 if (me->ExtraDataCount() >= 3) 337 { 338 size_t length = me->ExtraData(1).toLongLong(); 339 quint16 checksum16 = me->ExtraData(2).toUInt(); 340 data = QByteArray::fromBase64(me->ExtraData(3).toAscii()); 341 if ((size_t) data.size() < length) 342 { // (note data.size() may be up to 3 343 // bytes longer after decoding 344 VERBOSE(VB_IMPORTANT, LOC_ERR + 345 QString("Preview size check failed %1 < %2") 346 .arg(data.size()).arg(length)); 347 data.clear(); 348 } 349 350 if (checksum16 != qChecksum(data.constData(), data.size())) 351 { 352 VERBOSE(VB_IMPORTANT, LOC_ERR + 353 "Preview checksum failed"); 354 data.clear(); 355 } 356 } 357 SaveOutFile(data); 358 } 359 360 previewWaitCondition.wakeAll(); 361 362 return true; 347 363 } 348 return false;349 364 } 365 return QObject::event(e); 366 } 350 367 368 bool PreviewGenerator::SaveOutFile(QByteArray &data) 369 { 351 370 if (outFileName.isEmpty()) 352 371 { 353 QString remotecachedirname = QString("%1/remotecache").arg(GetConfDir()); 372 QString remotecachedirname = 373 QString("%1/remotecache").arg(GetConfDir()); 354 374 QDir remotecachedir(remotecachedirname); 355 375 356 376 if (!remotecachedir.exists()) … … 368 388 outFileName = QString("%1/%2").arg(remotecachedirname).arg(filename); 369 389 } 370 390 371 // find file, copy/move to output file name & location... 391 bool ok = true; 392 if (data.isEmpty()) 393 { 394 // find file, copy/move to output file name & location... 395 QString url = QString::null; 396 QString fn = QFileInfo(outFileName).fileName(); 397 ok = false; 372 398 373 QString url = QString::null; 374 QString fn = QFileInfo(outFileName).fileName(); 375 QByteArray data; 376 ok = false; 399 QStringList fileNames; 400 fileNames.push_back( 401 CreateAccessibleFilename(programInfo.GetPathname(), fn)); 402 fileNames.push_back( 403 CreateAccessibleFilename(programInfo.GetPathname(), "")); 377 404 378 QStringList fileNames; 379 fileNames.push_back( 380 CreateAccessibleFilename(programInfo.GetPathname(), fn)); 381 fileNames.push_back( 382 CreateAccessibleFilename(programInfo.GetPathname(), "")); 405 QStringList::const_iterator it = fileNames.begin(); 406 for ( ; it != fileNames.end() && (!ok || data.isEmpty()); ++it) 407 { 408 data.resize(0); 409 url = *it; 410 RemoteFile *rf = new RemoteFile(url, false, false, 0); 411 ok = rf->SaveAs(data); 412 delete rf; 413 } 414 } 383 415 384 QStringList::const_iterator it = fileNames.begin(); 385 for ( ; it != fileNames.end() && (!ok || data.isEmpty()); ++it) 416 if (!ok) 417 return false; 418 419 QFile file(outFileName); 420 ok = file.open(QIODevice::Unbuffered|QIODevice::WriteOnly); 421 if (!ok) 386 422 { 387 data.resize(0); 388 url = *it; 389 RemoteFile *rf = new RemoteFile(url, false, false, 0); 390 ok = rf->SaveAs(data); 391 delete rf; 423 VERBOSE(VB_IMPORTANT, QString("Failed to open: '%1'") 424 .arg(outFileName)); 392 425 } 393 426 394 if (ok && data.size()) 427 off_t offset = 0; 428 size_t remaining = data.size(); 429 uint failure_cnt = 0; 430 while ((remaining > 0) && (failure_cnt < 5)) 395 431 { 396 QFile file(outFileName); 397 ok = file.open(QIODevice::Unbuffered|QIODevice::WriteOnly); 398 if (!ok) 432 ssize_t written = file.write(data.data() + offset, remaining); 433 if (written < 0) 399 434 { 400 VERBOSE(VB_IMPORTANT, QString("Failed to open: '%1'") 401 .arg(outFileName)); 435 failure_cnt++; 436 usleep(50000); 437 continue; 402 438 } 403 439 404 off_t offset = 0; 405 size_t remaining = (ok) ? data.size() : 0; 406 uint failure_cnt = 0; 407 while ((remaining > 0) && (failure_cnt < 5)) 408 { 409 ssize_t written = file.write(data.data() + offset, remaining); 410 if (written < 0) 411 { 412 failure_cnt++; 413 usleep(50000); 414 continue; 415 } 416 417 failure_cnt = 0; 418 offset += written; 419 remaining -= written; 420 } 421 if (ok && !remaining) 422 { 423 VERBOSE(VB_PLAYBACK, QString("Saved: '%1'") 424 .arg(outFileName)); 425 } 440 failure_cnt = 0; 441 offset += written; 442 remaining -= written; 426 443 } 427 428 return ok && data.size(); 429 } 430 431 void PreviewGenerator::RemotePreviewTeardown(void) 432 { 433 if (serverSock) 444 if (ok && !remaining) 434 445 { 435 serverSock->DownRef(); 436 serverSock = NULL; 446 VERBOSE(VB_PLAYBACK, QString("Saved: '%1'").arg(outFileName)); 437 447 } 448 else 449 { 450 file.remove(); 451 } 452 453 return ok; 438 454 } 439 455 440 456 bool PreviewGenerator::SavePreview(QString filename, … … 476 492 QImage small_img = img.scaled((int) ppw, (int) pph, 477 493 Qt::IgnoreAspectRatio, Qt::SmoothTransformation); 478 494 479 QByteArray fname = filename.toAscii(); 480 if (small_img.save(fname.constData(), "PNG")) 495 QTemporaryFile f(QFileInfo(filename).absoluteFilePath()+".XXXXXX"); 496 f.setAutoRemove(false); 497 if (f.open() && small_img.save(&f, "PNG")) 481 498 { 482 makeFileAccessible(fname.constData()); // Let anybody update it 483 484 VERBOSE(VB_PLAYBACK, LOC + 485 QString("Saved preview '%0' %1x%2") 486 .arg(filename).arg((int) ppw).arg((int) pph)); 487 488 return true; 499 // Let anybody update it 500 makeFileAccessible(f.fileName().toLocal8Bit().constData()); 501 QFile of(filename); 502 of.remove(); 503 if (f.rename(filename)) 504 { 505 VERBOSE(VB_PLAYBACK, LOC + 506 QString("Saved preview '%0' %1x%2") 507 .arg(filename).arg((int) ppw).arg((int) pph)); 508 return true; 509 } 510 f.remove(); 489 511 } 490 512 491 // Save failed; if file exists, try saving to .new and moving over 492 QString newfile = filename + ".new"; 493 QByteArray newfilea = newfile.toAscii(); 494 if (QFileInfo(fname.constData()).exists() && 495 small_img.save(newfilea.constData(), "PNG")) 496 { 497 makeFileAccessible(newfilea.constData()); 498 rename(newfilea.constData(), fname.constData()); 513 VERBOSE(VB_IMPORTANT, LOC_ERR + 514 QString("Failed to save preview '%0' %1x%2") 515 .arg(filename).arg((int) ppw).arg((int) pph)); 499 516 500 VERBOSE(VB_PLAYBACK, LOC +501 QString("Saved preview '%0' %1x%2")502 .arg(filename).arg((int) ppw).arg((int) pph));503 504 return true;505 }506 507 // Couldn't save, nothing else I can do?508 517 return false; 509 518 } 510 519 -
libs/libmythtv/previewgeneratorqueue.cpp
1 #include <QCoreApplication> 2 #include <QFileInfo> 3 #include <QThread> 4 5 #include "previewgeneratorqueue.h" 6 #include "previewgenerator.h" 7 #include "mythcorecontext.h" 8 #include "mythcontext.h" 9 #include "remoteutil.h" 10 #include "mythdirs.h" 11 12 #define LOC QString("PreviewQueue: ") 13 #define LOC_ERR QString("PreviewQueue Error: ") 14 #define LOC_WARN QString("PreviewQueue Warning: ") 15 16 PreviewGeneratorQueue *PreviewGeneratorQueue::s_pgq = NULL; 17 18 void PreviewGeneratorQueue::CreatePreviewGeneratorQueue( 19 PreviewGenerator::Mode mode, 20 uint maxAttempts, uint minBlockSeconds) 21 { 22 s_pgq = new PreviewGeneratorQueue(mode, maxAttempts, minBlockSeconds); 23 } 24 25 void PreviewGeneratorQueue::TeardownPreviewGeneratorQueue() 26 { 27 s_pgq->exit(0); 28 s_pgq->wait(); 29 delete s_pgq; 30 } 31 32 PreviewGeneratorQueue::PreviewGeneratorQueue( 33 PreviewGenerator::Mode mode, 34 uint maxAttempts, uint minBlockSeconds) : 35 m_mode(mode), 36 m_running(0), m_maxThreads(2), 37 m_maxAttempts(maxAttempts), m_minBlockSeconds(minBlockSeconds) 38 { 39 if (PreviewGenerator::kLocal & mode) 40 { 41 int idealThreads = QThread::idealThreadCount(); 42 m_maxThreads = (idealThreads >= 1) ? idealThreads * 2 : 2; 43 } 44 45 moveToThread(this); 46 start(); 47 } 48 49 PreviewGeneratorQueue::~PreviewGeneratorQueue() 50 { 51 // disconnect preview generators 52 QMutexLocker locker(&m_lock); 53 PreviewMap::iterator it = m_previewMap.begin(); 54 for (;it != m_previewMap.end(); ++it) 55 { 56 if ((*it).gen) 57 (*it).gen->disconnectSafe(); 58 } 59 } 60 61 void PreviewGeneratorQueue::GetPreviewImage(const ProgramInfo &pginfo, 62 QString token) 63 { 64 if (!s_pgq) 65 return; 66 67 if (pginfo.GetPathname().isEmpty() || 68 pginfo.GetBasename() == pginfo.GetPathname()) 69 { 70 return; 71 } 72 73 QStringList extra; 74 pginfo.ToStringList(extra); 75 extra += token; 76 MythEvent *e = new MythEvent("GET_PREVIEW", extra); 77 QCoreApplication::postEvent(s_pgq, e); 78 } 79 80 void PreviewGeneratorQueue::GetPreviewImage( 81 const ProgramInfo &pginfo, const QSize &outputsize, 82 const QString &outputfile, 83 long long time, bool in_seconds, 84 QString token) 85 { 86 if (!s_pgq) 87 return; 88 89 if (pginfo.GetPathname().isEmpty() || 90 pginfo.GetBasename() == pginfo.GetPathname()) 91 { 92 return; 93 } 94 95 QStringList extra; 96 pginfo.ToStringList(extra); 97 extra += token; 98 extra += QString::number(outputsize.width()); 99 extra += QString::number(outputsize.height()); 100 extra += outputfile; 101 extra += QString::number(time); 102 extra += (in_seconds ? "1" : "0"); 103 MythEvent *e = new MythEvent("GET_PREVIEW2", extra); 104 QCoreApplication::postEvent(s_pgq, e); 105 } 106 107 void PreviewGeneratorQueue::AddListener(QObject *listener) 108 { 109 if (!s_pgq) 110 return; 111 112 QMutexLocker locker(&s_pgq->m_lock); 113 s_pgq->m_listeners.insert(listener); 114 } 115 116 void PreviewGeneratorQueue::RemoveListener(QObject *listener) 117 { 118 if (!s_pgq) 119 return; 120 121 QMutexLocker locker(&s_pgq->m_lock); 122 s_pgq->m_listeners.remove(listener); 123 } 124 125 bool PreviewGeneratorQueue::event(QEvent *e) 126 { 127 if (e->type() == (QEvent::Type) MythEvent::MythEventMessage) 128 { 129 MythEvent *me = (MythEvent*)e; 130 if (me->Message() == "GET_PREVIEW") 131 { 132 const QStringList list = me->ExtraDataList(); 133 QStringList::const_iterator it = list.begin(); 134 ProgramInfo evinfo(it, list.end()); 135 QString token; 136 if (it != list.end()) 137 token = (*it++); 138 139 QString fn = GeneratePreviewImage(evinfo); 140 141 SendReadyEvent(evinfo, fn, token); 142 return true; 143 } 144 else if (me->Message() == "GET_PREVIEW2") 145 { 146 const QStringList list = me->ExtraDataList(); 147 QStringList::const_iterator it = list.begin(); 148 ProgramInfo evinfo(it, list.end()); 149 QString token; 150 QSize outputsize; 151 QString outputfile; 152 long long time; 153 bool time_fmt_sec; 154 if (it != list.end()) 155 token = (*it++); 156 if (it != list.end()) 157 outputsize.setWidth((*it++).toInt()); 158 if (it != list.end()) 159 outputsize.setHeight((*it++).toInt()); 160 if (it != list.end()) 161 outputfile = (*it++); 162 if (it != list.end()) 163 time = (*it++).toLongLong(); 164 QString fn; 165 if (it != list.end()) 166 { 167 time_fmt_sec = (*it++).toInt() != 0; 168 fn = GeneratePreviewImage(evinfo, outputsize, outputfile, 169 time, time_fmt_sec, token); 170 } 171 SendReadyEvent(evinfo, fn, token); 172 } 173 } 174 return QObject::event(e); 175 } 176 177 void PreviewGeneratorQueue::SendReadyEvent( 178 const ProgramInfo &pginfo, const QString &fn, const QString &token) 179 { 180 VERBOSE(VB_IMPORTANT, QString("SendReadyEvent(%1...)") 181 .arg(pginfo.toString())); 182 183 QStringList list; 184 list.push_back(pginfo.MakeUniqueKey()); 185 list.push_back(fn); 186 list.push_back(token); 187 188 QMutexLocker locker(&m_lock); 189 QSet<QObject*>::iterator it = m_listeners.begin(); 190 for (; it != m_listeners.end(); ++it) 191 { 192 MythEvent *e = new MythEvent( 193 (fn.isEmpty()) ? "PREVIEW_FAILED" : "PREVIEW_READY", list); 194 QCoreApplication::postEvent(*it, e); 195 } 196 } 197 198 QString PreviewGeneratorQueue::GeneratePreviewImage( 199 ProgramInfo &pginfo, 200 const QSize&, 201 const QString &outputfile, 202 long long time, bool in_seconds, 203 QString token) 204 { 205 // TODO FIXME should add these params and change name appropriately 206 return GeneratePreviewImage(pginfo); 207 } 208 209 QString PreviewGeneratorQueue::GeneratePreviewImage(ProgramInfo &pginfo) 210 { 211 if (pginfo.GetAvailableStatus() == asPendingDelete) 212 return QString(); 213 214 QString filename = pginfo.GetPathname() + ".png"; 215 216 // If someone is asking for this preview it must be on screen 217 // and hence higher priority than anything else we may have 218 // queued up recently.... 219 IncPreviewGeneratorPriority(filename); 220 221 QDateTime previewLastModified; 222 QString ret_file = filename; 223 bool streaming = filename.left(1) != "/"; 224 bool locally_accessible = false; 225 bool bookmark_updated = false; 226 227 QDateTime bookmark_ts = pginfo.QueryBookmarkTimeStamp(); 228 QDateTime cmp_ts = bookmark_ts.isValid() ? 229 bookmark_ts : pginfo.GetLastModifiedTime(); 230 231 if (streaming) 232 { 233 ret_file = QString("%1/remotecache/%2") 234 .arg(GetConfDir()).arg(filename.section('/', -1)); 235 236 QFileInfo finfo(ret_file); 237 if (finfo.isReadable() && finfo.lastModified() >= cmp_ts) 238 { 239 // This is just an optimization to avoid 240 // hitting the backend if our cached copy 241 // is newer than the bookmark, or if we have 242 // a preview and do not update it when the 243 // bookmark changes. 244 previewLastModified = finfo.lastModified(); 245 } 246 else if (!IsGeneratingPreview(filename)) 247 { 248 previewLastModified = 249 RemoteGetPreviewIfModified(pginfo, ret_file); 250 } 251 } 252 else 253 { 254 QFileInfo fi(filename); 255 if ((locally_accessible = fi.isReadable())) 256 previewLastModified = fi.lastModified(); 257 } 258 259 bookmark_updated = 260 (!previewLastModified.isValid() || (previewLastModified < cmp_ts)); 261 262 if (bookmark_updated && bookmark_ts.isValid() && 263 previewLastModified.isValid()) 264 { 265 ClearPreviewGeneratorAttempts(filename); 266 } 267 268 if (0) 269 { 270 VERBOSE(VB_IMPORTANT, QString( 271 "previewLastModified: %1\n\t\t\t" 272 "bookmark_ts: %2\n\t\t\t" 273 "pginfo.lastmodified: %3") 274 .arg(previewLastModified.toString(Qt::ISODate)) 275 .arg(bookmark_ts.toString(Qt::ISODate)) 276 .arg(pginfo.GetLastModifiedTime(ISODate))); 277 } 278 279 bool preview_exists = previewLastModified.isValid(); 280 281 if (0) 282 { 283 VERBOSE(VB_IMPORTANT, 284 QString("Title: %1\n\t\t\t") 285 .arg(pginfo.toString(ProgramInfo::kTitleSubtitle)) + 286 QString("File '%1' \n\t\t\tCache '%2'") 287 .arg(filename).arg(ret_file) + 288 QString("\n\t\t\tPreview Exists: %1, " 289 "Bookmark Updated: %2, " 290 "Need Preview: %3") 291 .arg(preview_exists).arg(bookmark_updated) 292 .arg((bookmark_updated || !preview_exists))); 293 } 294 295 if ((bookmark_updated || !preview_exists) && 296 !IsGeneratingPreview(filename)) 297 { 298 uint attempts = IncPreviewGeneratorAttempts(filename); 299 if (attempts < m_maxAttempts) 300 { 301 VERBOSE(VB_PLAYBACK, LOC + 302 QString("Requesting preview for '%1'") 303 .arg(filename)); 304 PreviewGenerator::Mode mode = 305 (PreviewGenerator::Mode) m_mode; 306 PreviewGenerator *pg = new PreviewGenerator(&pginfo, mode); 307 while (!SetPreviewGenerator(filename, pg)) usleep(50000); 308 VERBOSE(VB_PLAYBACK, LOC + 309 QString("Requested preview for '%1'") 310 .arg(filename)); 311 } 312 else if (attempts == m_maxAttempts) 313 { 314 VERBOSE(VB_IMPORTANT, LOC_ERR + 315 QString("Attempted to generate preview for '%1' " 316 "%2 times, giving up.") 317 .arg(filename).arg(m_maxAttempts)); 318 } 319 } 320 else if (bookmark_updated || !preview_exists) 321 { 322 VERBOSE(VB_PLAYBACK, LOC + 323 "Not requesting preview as it " 324 "is already being generated"); 325 } 326 327 UpdatePreviewGeneratorThreads(); 328 329 QString ret = (locally_accessible) ? 330 filename : (previewLastModified.isValid()) ? 331 ret_file : (QFileInfo(ret_file).isReadable()) ? 332 ret_file : QString(); 333 334 VERBOSE(VB_IMPORTANT, QString("Returning: '%1'").arg(ret)); 335 336 return ret; 337 } 338 void PreviewGeneratorQueue::IncPreviewGeneratorPriority(const QString &xfn) 339 { 340 QString fn = xfn.mid(max(xfn.lastIndexOf('/') + 1,0)); 341 342 QMutexLocker locker(&m_lock); 343 m_queue.removeAll(fn); 344 345 PreviewMap::iterator pit = m_previewMap.find(fn); 346 if (pit != m_previewMap.end() && (*pit).gen && !(*pit).genStarted) 347 m_queue.push_back(fn); 348 } 349 350 void PreviewGeneratorQueue::UpdatePreviewGeneratorThreads(void) 351 { 352 QMutexLocker locker(&m_lock); 353 QStringList &q = m_queue; 354 if (!q.empty() && 355 (m_running < m_maxThreads)) 356 { 357 QString fn = q.back(); 358 q.pop_back(); 359 PreviewMap::iterator it = m_previewMap.find(fn); 360 if (it != m_previewMap.end() && (*it).gen && !(*it).genStarted) 361 { 362 m_running++; 363 (*it).gen->start(); 364 (*it).genStarted = true; 365 } 366 } 367 } 368 369 /** \brief Sets the PreviewGenerator for a specific file. 370 * \return true iff call succeeded. 371 */ 372 bool PreviewGeneratorQueue::SetPreviewGenerator( 373 const QString &xfn, PreviewGenerator *g) 374 { 375 QString fn = xfn.mid(max(xfn.lastIndexOf('/') + 1,0)); 376 377 if (!m_lock.tryLock()) 378 return false; 379 380 if (!g) 381 { 382 m_running = max(0, (int)m_running - 1); 383 PreviewMap::iterator it = m_previewMap.find(fn); 384 if (it == m_previewMap.end()) 385 { 386 m_lock.unlock(); 387 return false; 388 } 389 390 (*it).gen = NULL; 391 (*it).genStarted = false; 392 (*it).ready = false; 393 (*it).lastBlockTime = 394 max(m_minBlockSeconds, (*it).lastBlockTime * 2); 395 (*it).blockRetryUntil = 396 QDateTime::currentDateTime().addSecs((*it).lastBlockTime); 397 398 m_lock.unlock(); 399 return true; 400 } 401 402 g->AttachSignals(this); 403 m_previewMap[fn].gen = g; 404 m_previewMap[fn].genStarted = false; 405 m_previewMap[fn].ready = false; 406 407 m_lock.unlock(); 408 IncPreviewGeneratorPriority(xfn); 409 410 return true; 411 } 412 413 /** \brief Returns true if we have already started a 414 * PreviewGenerator to create this file. 415 */ 416 bool PreviewGeneratorQueue::IsGeneratingPreview( 417 const QString &xfn, bool really) const 418 { 419 PreviewMap::const_iterator it; 420 QMutexLocker locker(&m_lock); 421 422 QString fn = xfn.mid(max(xfn.lastIndexOf('/') + 1,0)); 423 if ((it = m_previewMap.find(fn)) == m_previewMap.end()) 424 return false; 425 426 if (really) 427 return ((*it).gen && !(*it).ready); 428 429 if ((*it).blockRetryUntil.isValid()) 430 return QDateTime::currentDateTime() < (*it).blockRetryUntil; 431 432 return (*it).gen; 433 } 434 435 /** \fn PreviewGeneratorQueue::IncPreviewGeneratorAttempts(const QString&) 436 * \brief Increments and returns number of times we have 437 * started a PreviewGenerator to create this file. 438 */ 439 uint PreviewGeneratorQueue::IncPreviewGeneratorAttempts(const QString &xfn) 440 { 441 QMutexLocker locker(&m_lock); 442 QString fn = xfn.mid(max(xfn.lastIndexOf('/') + 1,0)); 443 return m_previewMap[fn].attempts++; 444 } 445 446 /** \fn PreviewGeneratorQueue::ClearPreviewGeneratorAttempts(const QString&) 447 * \brief Clears the number of times we have 448 * started a PreviewGenerator to create this file. 449 */ 450 void PreviewGeneratorQueue::ClearPreviewGeneratorAttempts(const QString &xfn) 451 { 452 QMutexLocker locker(&m_lock); 453 QString fn = xfn.mid(max(xfn.lastIndexOf('/') + 1,0)); 454 m_previewMap[fn].attempts = 0; 455 m_previewMap[fn].lastBlockTime = 0; 456 m_previewMap[fn].blockRetryUntil = 457 QDateTime::currentDateTime().addSecs(-60); 458 } 459 460 void PreviewGeneratorQueue::previewThreadDone(const QString &fn, bool &success) 461 { 462 VERBOSE(VB_PLAYBACK, LOC + QString("Preview for '%1' done").arg(fn)); 463 success = SetPreviewGenerator(fn, NULL); 464 UpdatePreviewGeneratorThreads(); 465 } 466 467 468 /** \brief Callback used by PreviewGenerator to tell us a m_preview 469 * we requested has been returned from the backend. 470 * \param pginfo ProgramInfo describing the previewed recording. 471 */ 472 void PreviewGeneratorQueue::previewReady(const ProgramInfo *pginfo) 473 { 474 if (!pginfo) 475 return; 476 477 QString xfn = pginfo->GetPathname() + ".png"; 478 QString fn = xfn.mid(max(xfn.lastIndexOf('/') + 1,0)); 479 480 VERBOSE(VB_PLAYBACK, LOC + QString("Preview for '%1' ready") 481 .arg(pginfo->GetPathname())); 482 483 QMutexLocker locker(&m_lock); 484 PreviewMap::iterator it = m_previewMap.find(fn); 485 if (it != m_previewMap.end()) 486 { 487 (*it).ready = true; 488 (*it).attempts = 0; 489 (*it).lastBlockTime = 0; 490 } 491 } -
libs/libmythtv/tv_rec.cpp
11 11 using namespace std; 12 12 13 13 // MythTV headers 14 #include "previewgeneratorqueue.h" 14 15 #include "mythconfig.h" 15 16 #include "tv_rec.h" 16 17 #include "osd.h" … … 26 27 #include "recordingrule.h" 27 28 #include "eitscanner.h" 28 29 #include "RingBuffer.h" 29 #include "previewgenerator.h"30 30 #include "storagegroup.h" 31 31 #include "remoteutil.h" 32 32 #include "tvremoteutil.h" … … 1121 1121 if (!killFile) 1122 1122 { 1123 1123 if (curRecording->IsLocal()) 1124 { 1125 (new PreviewGenerator( 1126 curRecording, PreviewGenerator::kLocal))->Start(); 1127 } 1124 PreviewGeneratorQueue::GetPreviewImage(*curRecording); 1128 1125 1129 1126 if (!tvchain) 1130 1127 { … … 4459 4456 if (!oldinfo->IsLocal()) 4460 4457 oldinfo->SetPathname(oldinfo->GetPlaybackURL(false,true)); 4461 4458 if (oldinfo->IsLocal()) 4462 { 4463 (new PreviewGenerator( 4464 oldinfo, PreviewGenerator::kLocal))->Start(); 4465 } 4459 PreviewGeneratorQueue::GetPreviewImage(*oldinfo); 4466 4460 } 4467 4461 delete oldinfo; 4468 4462 } -
libs/libmythtv/previewgenerator.h
4 4 5 5 #include <pthread.h> 6 6 7 #include <QWaitCondition> 7 8 #include <QString> 9 #include <QThread> 8 10 #include <QMutex> 9 11 #include <QSize> 12 #include <QSet> 10 13 11 14 #include "programinfo.h" 12 15 #include "util.h" 13 16 14 17 class MythSocket; 18 class PreviewGenerator; 15 19 16 class MPUBLIC PreviewGenerator : public QObject 20 // TODO Changes to make... 21 // Don't use signals and slots, instead use MythEvents 22 23 typedef QMap<QString,QDateTime> FileTimeStampMap; 24 25 class MPUBLIC PreviewGenerator : public QThread 17 26 { 18 27 friend int preview_helper(const QString &chanid, 19 28 const QString &starttime, … … 47 56 void SetOutputFilename(const QString&); 48 57 void SetOutputSize(const QSize &size) { outSize = size; } 49 58 50 void Start(void);59 void run(void); // QThread 51 60 bool Run(void); 52 61 53 62 void AttachSignals(QObject*); … … 64 73 virtual ~PreviewGenerator(); 65 74 void TeardownAll(void); 66 75 67 bool RemotePreviewSetup(void);68 76 bool RemotePreviewRun(void); 69 void RemotePreviewTeardown(void);70 71 77 bool LocalPreviewRun(void); 72 78 bool IsLocal(void) const; 73 79 74 80 bool RunReal(void); 75 81 76 static void *PreviewRun(void*);77 78 82 static char *GetScreenGrab(const ProgramInfo &pginfo, 79 83 const QString &filename, 80 84 long long seektime, … … 93 97 static QString CreateAccessibleFilename( 94 98 const QString &pathname, const QString &outFileName); 95 99 96 protected: 100 virtual bool event(QEvent *e); // QObject 101 bool SaveOutFile(QByteArray &data); 102 103 // protected: 104 public: 105 QWaitCondition previewWaitCondition; 97 106 QMutex previewLock; 98 107 pthread_t previewThread; 99 108 ProgramInfo programInfo; 100 109 101 110 Mode mode; 102 111 bool isConnected; 103 bool createSockets;104 MythSocket *serverSock;105 112 QString pathname; 106 113 107 114 /// tells us whether to use time as seconds or frame number … … 110 117 long long captureTime; 111 118 QString outFileName; 112 119 QSize outSize; 120 121 QString token; 122 bool pixmapOk; 113 123 }; 114 124 115 125 #endif // PREVIEW_GENERATOR_H_ -
programs/mythfrontend/playbackbox.cpp
8 8 #include <QTimer> 9 9 #include <QMap> 10 10 11 // libmythdb 12 #include "oldsettings.h" 13 #include "mythdb.h" 14 #include "mythdbcon.h" 15 #include "mythverbose.h" 16 #include "mythdirs.h" 17 18 // libmythtv 19 #include "tv.h" 11 // MythTV 12 #include "previewgeneratorqueue.h" 13 #include "mythuiprogressbar.h" 20 14 #include "NuppelVideoPlayer.h" 21 #include "recordinginfo.h"22 #include "playgroup.h"23 #include "mythsystemevent.h"24 25 // libmyth26 #include "mythcorecontext.h"27 #include "util.h"28 #include "storagegroup.h"29 #include "programinfo.h"30 31 // libmythui32 #include "mythuihelper.h"33 #include "mythuitext.h"34 #include "mythuibutton.h"35 15 #include "mythuibuttonlist.h" 16 #include "mythcorecontext.h" 17 #include "mythsystemevent.h" 36 18 #include "mythuistatetype.h" 37 #include "myth dialogbox.h"19 #include "mythuicheckbox.h" 38 20 #include "mythuitextedit.h" 21 #include "mythdialogbox.h" 22 #include "recordinginfo.h" 23 #include "mythuihelper.h" 24 #include "storagegroup.h" 25 #include "mythuibutton.h" 26 #include "mythverbose.h" 39 27 #include "mythuiimage.h" 40 #include " mythuicheckbox.h"41 #include " mythuiprogressbar.h"42 28 #include "programinfo.h" 29 #include "oldsettings.h" 30 #include "mythuitext.h" 43 31 #include "remoteutil.h" 44 32 45 33 // Mythfrontend 46 34 #include "playbackboxlistitem.h" 47 35 #include "customedit.h" 36 #include "mythdbcon.h" 37 #include "playgroup.h" 38 #include "mythdirs.h" 39 #include "proglist.h" 40 #include "mythdb.h" 41 #include "util.h" 42 #include "tv.h" 48 43 49 44 #define LOC QString("PlaybackBox: ") 50 45 #define LOC_WARN QString("PlaybackBox Warning: ") … … 447 442 PlaybackBox::~PlaybackBox(void) 448 443 { 449 444 gCoreContext->removeListener(this); 445 PreviewGeneratorQueue::RemoveListener(this); 450 446 451 447 for (uint i = 0; i < sizeof(m_artImage) / sizeof(MythUIImage*); i++) 452 448 { … … 517 513 void PlaybackBox::Load(void) 518 514 { 519 515 m_programInfoCache.WaitForLoadToComplete(); 516 PreviewGeneratorQueue::AddListener(this); 520 517 } 521 518 522 519 void PlaybackBox::Init() … … 3803 3800 // asPendingDelete, we need to put them back now.. 3804 3801 ScheduleUpdateUIList(); 3805 3802 } 3806 else if (message == "PREVIEW_READY" && me->ExtraDataCount() == 2)3803 else if (message == "PREVIEW_READY" && me->ExtraDataCount() >= 2) 3807 3804 { 3805 VERBOSE(VB_IMPORTANT, "PBB got PREVIEW_READY"); 3808 3806 HandlePreviewEvent(me->ExtraData(0), me->ExtraData(1)); 3809 3807 } 3810 3808 else if (message == "AVAILABILITY" && me->ExtraDataCount() == 8) -
programs/mythfrontend/playbackboxhelper.h
17 17 class QObject; 18 18 class QTimer; 19 19 20 class PreviewGenState21 {22 public:23 PreviewGenState() :24 gen(NULL), genStarted(false), ready(false),25 attempts(0), lastBlockTime(0) {}26 PreviewGenerator *gen;27 bool genStarted;28 bool ready;29 uint attempts;30 uint lastBlockTime;31 QDateTime blockRetryUntil;32 33 static const uint maxAttempts;34 static const uint minBlockSeconds;35 };36 typedef QMap<QString,PreviewGenState> PreviewMap;37 typedef QMap<QString,QDateTime> FileTimeStampMap;38 39 20 typedef enum CheckAvailabilityType { 40 21 kCheckForCache, 41 22 kCheckForMenuAction, … … 83 64 uint64_t GetFreeSpaceTotalMB(void) const; 84 65 uint64_t GetFreeSpaceUsedMB(void) const; 85 66 86 private slots:87 void previewThreadDone(const QString &fn, bool &success);88 void previewReady(const ProgramInfo *pginfo);89 90 67 private: 91 68 void UpdateFreeSpace(void); 92 69 93 QString GeneratePreviewImage(ProgramInfo &pginfo);94 bool SetPreviewGenerator(const QString &fn, PreviewGenerator *g);95 void IncPreviewGeneratorPriority(const QString &fn);96 void UpdatePreviewGeneratorThreads(void);97 bool IsGeneratingPreview(const QString &fn, bool really = false) const;98 uint IncPreviewGeneratorAttempts(const QString &fn);99 void ClearPreviewGeneratorAttempts(const QString &fn);100 101 70 private: 102 71 QObject *m_listener; 103 72 PBHEventHandler *m_eventHandler; … … 107 76 uint64_t m_freeSpaceTotalMB; 108 77 uint64_t m_freeSpaceUsedMB; 109 78 110 // Preview Pixmap Variables ///////////////////////////////////////////////111 mutable QMutex m_previewGeneratorLock;112 uint m_previewGeneratorMode;113 FileTimeStampMap m_previewFileTS;114 bool m_previewSuspend;115 PreviewMap m_previewGenerator;116 QStringList m_previewGeneratorQueue;117 uint m_previewGeneratorRunning;118 uint m_previewGeneratorMaxThreads;119 120 79 // Artwork Variables ////////////////////////////////////////////////////// 121 80 QHash<QString, QString> m_artworkFilenameCache; 122 81 }; -
programs/mythfrontend/playbackboxhelper.cpp
7 7 #include <QFileInfo> 8 8 #include <QDir> 9 9 10 #include "previewgeneratorqueue.h" 10 11 #include "playbackboxhelper.h" 11 #include "previewgenerator.h"12 12 #include "mythcorecontext.h" 13 13 #include "tvremoteutil.h" 14 14 #include "storagegroup.h" … … 287 287 if (asAvailable != CheckAvailability(list)) 288 288 return true; 289 289 290 QString fn = m_pbh.GeneratePreviewImage(evinfo); 291 if (!fn.isEmpty()) 292 { 293 QStringList list; 294 list.push_back(evinfo.MakeUniqueKey()); 295 list.push_back(fn); 296 MythEvent *e = new MythEvent("PREVIEW_READY", list); 297 QCoreApplication::postEvent(m_pbh.m_listener, e); 298 } 290 // Now we can actually request the preview... 291 PreviewGeneratorQueue::GetPreviewImage(evinfo); 299 292 300 293 return true; 301 294 } … … 458 451 459 452 ////////////////////////////////////////////////////////////////////// 460 453 461 const uint PreviewGenState::maxAttempts = 5;462 const uint PreviewGenState::minBlockSeconds = 60;463 464 454 PlaybackBoxHelper::PlaybackBoxHelper(QObject *listener) : 465 455 m_listener(listener), m_eventHandler(NULL), 466 456 // Free Space Tracking Variables 467 m_freeSpaceTotalMB(0ULL), m_freeSpaceUsedMB(0ULL), 468 // Preview Image Variables 469 m_previewGeneratorRunning(0), m_previewGeneratorMaxThreads(2) 457 m_freeSpaceTotalMB(0ULL), m_freeSpaceUsedMB(0ULL) 470 458 { 471 m_previewGeneratorMode = PreviewGenerator::kRemote;472 473 int idealThreads = QThread::idealThreadCount();474 if (idealThreads >= 1)475 m_previewGeneratorMaxThreads = idealThreads * 2;476 477 459 start(); 478 460 } 479 461 … … 482 464 exit(); 483 465 wait(); 484 466 485 // disconnect preview generators486 QMutexLocker locker(&m_previewGeneratorLock);487 PreviewMap::iterator it = m_previewGenerator.begin();488 for (;it != m_previewGenerator.end(); ++it)489 {490 if ((*it).gen)491 (*it).gen->disconnectSafe();492 }493 494 467 // delete the event handler 495 468 delete m_eventHandler; 496 469 m_eventHandler = NULL; … … 622 595 623 596 void PlaybackBoxHelper::GetPreviewImage(const ProgramInfo &pginfo) 624 597 { 598 if (pginfo.GetAvailableStatus() == asPendingDelete) 599 return; 600 625 601 QStringList extra; 626 602 pginfo.ToStringList(extra); 627 603 MythEvent *e = new MythEvent("GET_PREVIEW", extra); 628 604 QCoreApplication::postEvent(m_eventHandler, e); 629 605 } 630 631 QString PlaybackBoxHelper::GeneratePreviewImage(ProgramInfo &pginfo)632 {633 if (pginfo.GetAvailableStatus() == asPendingDelete)634 return QString();635 636 QString filename = pginfo.GetPathname() + ".png";637 638 // If someone is asking for this preview it must be on screen639 // and hence higher priority than anything else we may have640 // queued up recently....641 IncPreviewGeneratorPriority(filename);642 643 QDateTime previewLastModified;644 QString ret_file = filename;645 bool streaming = filename.left(1) != "/";646 bool locally_accessible = false;647 bool bookmark_updated = false;648 649 QDateTime bookmark_ts = pginfo.QueryBookmarkTimeStamp();650 QDateTime cmp_ts = bookmark_ts.isValid() ?651 bookmark_ts : pginfo.GetLastModifiedTime();652 653 if (streaming)654 {655 ret_file = QString("%1/remotecache/%2")656 .arg(GetConfDir()).arg(filename.section('/', -1));657 658 QFileInfo finfo(ret_file);659 if (finfo.isReadable() && finfo.lastModified() >= cmp_ts)660 {661 // This is just an optimization to avoid662 // hitting the backend if our cached copy663 // is newer than the bookmark, or if we have664 // a preview and do not update it when the665 // bookmark changes.666 previewLastModified = finfo.lastModified();667 }668 else if (!IsGeneratingPreview(filename))669 {670 previewLastModified =671 RemoteGetPreviewIfModified(pginfo, ret_file);672 }673 }674 else675 {676 QFileInfo fi(filename);677 if ((locally_accessible = fi.isReadable()))678 previewLastModified = fi.lastModified();679 }680 681 bookmark_updated =682 (!previewLastModified.isValid() || (previewLastModified < cmp_ts));683 684 if (bookmark_updated && bookmark_ts.isValid() &&685 previewLastModified.isValid())686 {687 ClearPreviewGeneratorAttempts(filename);688 }689 690 if (0)691 {692 VERBOSE(VB_IMPORTANT, QString(693 "previewLastModified: %1\n\t\t\t"694 "bookmark_ts: %2\n\t\t\t"695 "pginfo.lastmodified: %3")696 .arg(previewLastModified.toString(Qt::ISODate))697 .arg(bookmark_ts.toString(Qt::ISODate))698 .arg(pginfo.GetLastModifiedTime(ISODate)));699 }700 701 bool preview_exists = previewLastModified.isValid();702 703 if (0)704 {705 VERBOSE(VB_IMPORTANT,706 QString("Title: %1\n\t\t\t")707 .arg(pginfo.toString(ProgramInfo::kTitleSubtitle)) +708 QString("File '%1' \n\t\t\tCache '%2'")709 .arg(filename).arg(ret_file) +710 QString("\n\t\t\tPreview Exists: %1, "711 "Bookmark Updated: %2, "712 "Need Preview: %3")713 .arg(preview_exists).arg(bookmark_updated)714 .arg((bookmark_updated || !preview_exists)));715 }716 717 if ((bookmark_updated || !preview_exists) &&718 !IsGeneratingPreview(filename))719 {720 uint attempts = IncPreviewGeneratorAttempts(filename);721 if (attempts < PreviewGenState::maxAttempts)722 {723 VERBOSE(VB_PLAYBACK, LOC +724 QString("Requesting preview for '%1'")725 .arg(filename));726 PreviewGenerator::Mode mode =727 (PreviewGenerator::Mode) m_previewGeneratorMode;728 PreviewGenerator *pg = new PreviewGenerator(&pginfo, mode);729 while (!SetPreviewGenerator(filename, pg)) usleep(50000);730 VERBOSE(VB_PLAYBACK, LOC +731 QString("Requested preview for '%1'")732 .arg(filename));733 }734 else if (attempts == PreviewGenState::maxAttempts)735 {736 VERBOSE(VB_IMPORTANT, LOC_ERR +737 QString("Attempted to generate preview for '%1' "738 "%2 times, giving up.")739 .arg(filename).arg(PreviewGenState::maxAttempts));740 }741 }742 else if (bookmark_updated || !preview_exists)743 {744 VERBOSE(VB_PLAYBACK, LOC +745 "Not requesting preview as it "746 "is already being generated");747 }748 749 UpdatePreviewGeneratorThreads();750 751 QString ret = (locally_accessible) ?752 filename : (previewLastModified.isValid()) ?753 ret_file : (QFileInfo(ret_file).isReadable()) ?754 ret_file : QString();755 756 //VERBOSE(VB_IMPORTANT, QString("Returning: '%1'").arg(ret));757 758 return ret;759 }760 761 void PlaybackBoxHelper::IncPreviewGeneratorPriority(const QString &xfn)762 {763 QString fn = xfn.mid(max(xfn.lastIndexOf('/') + 1,0));764 765 QMutexLocker locker(&m_previewGeneratorLock);766 m_previewGeneratorQueue.removeAll(fn);767 768 PreviewMap::iterator pit = m_previewGenerator.find(fn);769 if (pit != m_previewGenerator.end() && (*pit).gen && !(*pit).genStarted)770 m_previewGeneratorQueue.push_back(fn);771 }772 773 void PlaybackBoxHelper::UpdatePreviewGeneratorThreads(void)774 {775 QMutexLocker locker(&m_previewGeneratorLock);776 QStringList &q = m_previewGeneratorQueue;777 if (!q.empty() &&778 (m_previewGeneratorRunning < m_previewGeneratorMaxThreads))779 {780 QString fn = q.back();781 q.pop_back();782 PreviewMap::iterator it = m_previewGenerator.find(fn);783 if (it != m_previewGenerator.end() && (*it).gen && !(*it).genStarted)784 {785 m_previewGeneratorRunning++;786 (*it).gen->Start();787 (*it).genStarted = true;788 }789 }790 }791 792 /** \fn PlaybackBoxHelper::SetPreviewGenerator(const QString&, PreviewGenerator*)793 * \brief Sets the PreviewGenerator for a specific file.794 * \return true iff call succeeded.795 */796 bool PlaybackBoxHelper::SetPreviewGenerator(const QString &xfn, PreviewGenerator *g)797 {798 QString fn = xfn.mid(max(xfn.lastIndexOf('/') + 1,0));799 800 if (!m_previewGeneratorLock.tryLock())801 return false;802 803 if (!g)804 {805 m_previewGeneratorRunning = max(0, (int)m_previewGeneratorRunning - 1);806 PreviewMap::iterator it = m_previewGenerator.find(fn);807 if (it == m_previewGenerator.end())808 {809 m_previewGeneratorLock.unlock();810 return false;811 }812 813 (*it).gen = NULL;814 (*it).genStarted = false;815 (*it).ready = false;816 (*it).lastBlockTime =817 max(PreviewGenState::minBlockSeconds, (*it).lastBlockTime * 2);818 (*it).blockRetryUntil =819 QDateTime::currentDateTime().addSecs((*it).lastBlockTime);820 821 m_previewGeneratorLock.unlock();822 return true;823 }824 825 g->AttachSignals(this);826 m_previewGenerator[fn].gen = g;827 m_previewGenerator[fn].genStarted = false;828 m_previewGenerator[fn].ready = false;829 830 m_previewGeneratorLock.unlock();831 IncPreviewGeneratorPriority(xfn);832 833 return true;834 }835 836 /** \fn PlaybackBoxHelper::IsGeneratingPreview(const QString&, bool) const837 * \brief Returns true if we have already started a838 * PreviewGenerator to create this file.839 */840 bool PlaybackBoxHelper::IsGeneratingPreview(const QString &xfn, bool really) const841 {842 PreviewMap::const_iterator it;843 QMutexLocker locker(&m_previewGeneratorLock);844 845 QString fn = xfn.mid(max(xfn.lastIndexOf('/') + 1,0));846 if ((it = m_previewGenerator.find(fn)) == m_previewGenerator.end())847 return false;848 849 if (really)850 return ((*it).gen && !(*it).ready);851 852 if ((*it).blockRetryUntil.isValid())853 return QDateTime::currentDateTime() < (*it).blockRetryUntil;854 855 return (*it).gen;856 }857 858 /** \fn PlaybackBoxHelper::IncPreviewGeneratorAttempts(const QString&)859 * \brief Increments and returns number of times we have860 * started a PreviewGenerator to create this file.861 */862 uint PlaybackBoxHelper::IncPreviewGeneratorAttempts(const QString &xfn)863 {864 QMutexLocker locker(&m_previewGeneratorLock);865 QString fn = xfn.mid(max(xfn.lastIndexOf('/') + 1,0));866 return m_previewGenerator[fn].attempts++;867 }868 869 /** \fn PlaybackBoxHelper::ClearPreviewGeneratorAttempts(const QString&)870 * \brief Clears the number of times we have871 * started a PreviewGenerator to create this file.872 */873 void PlaybackBoxHelper::ClearPreviewGeneratorAttempts(const QString &xfn)874 {875 QMutexLocker locker(&m_previewGeneratorLock);876 QString fn = xfn.mid(max(xfn.lastIndexOf('/') + 1,0));877 m_previewGenerator[fn].attempts = 0;878 m_previewGenerator[fn].lastBlockTime = 0;879 m_previewGenerator[fn].blockRetryUntil =880 QDateTime::currentDateTime().addSecs(-60);881 }882 883 void PlaybackBoxHelper::previewThreadDone(const QString &fn, bool &success)884 {885 VERBOSE(VB_PLAYBACK, LOC + QString("Preview for '%1' done").arg(fn));886 success = SetPreviewGenerator(fn, NULL);887 UpdatePreviewGeneratorThreads();888 }889 890 /** \fn PlaybackBoxHelper::previewReady(const ProgramInfo*)891 * \brief Callback used by PreviewGenerator to tell us a m_preview892 * we requested has been returned from the backend.893 * \param pginfo ProgramInfo describing the previewed recording.894 */895 void PlaybackBoxHelper::previewReady(const ProgramInfo *pginfo)896 {897 if (!pginfo)898 return;899 900 QString xfn = pginfo->GetPathname() + ".png";901 QString fn = xfn.mid(max(xfn.lastIndexOf('/') + 1,0));902 903 VERBOSE(VB_PLAYBACK, LOC + QString("Preview for '%1' ready")904 .arg(pginfo->GetPathname()));905 906 m_previewGeneratorLock.lock();907 PreviewMap::iterator it = m_previewGenerator.find(fn);908 if (it != m_previewGenerator.end())909 {910 (*it).ready = true;911 (*it).attempts = 0;912 (*it).lastBlockTime = 0;913 }914 m_previewGeneratorLock.unlock();915 916 if (pginfo)917 {918 QStringList list;919 list.push_back(pginfo->MakeUniqueKey());920 list.push_back(xfn);921 MythEvent *e = new MythEvent("PREVIEW_READY", list);922 QCoreApplication::postEvent(m_listener, e);923 }924 } -
programs/mythfrontend/main.cpp
19 19 #include <QApplication> 20 20 #include <QTimer> 21 21 22 #include "previewgeneratorqueue.h" 22 23 #include "mythconfig.h" 23 24 #include "tv.h" 24 25 #include "proglist.h" … … 1469 1470 1470 1471 BackendConnectionManager bcm; 1471 1472 1473 PreviewGeneratorQueue::CreatePreviewGeneratorQueue( 1474 PreviewGenerator::kRemote, 50, 60); 1475 1472 1476 int ret = qApp->exec(); 1473 1477 1478 PreviewGeneratorQueue::TeardownPreviewGeneratorQueue(); 1479 1474 1480 delete sysEventHandler; 1475 1481 1476 1482 pmanager->DestroyAllPlugins(); -
programs/mythfrontend/playbackbox.h
11 11 #include <deque> 12 12 using namespace std; 13 13 14 // Qt headers 14 15 #include <QStringList> 15 16 #include <QDateTime> 16 17 #include <QObject> 17 18 #include <QMutex> 18 19 #include <QMap> 19 20 21 // MythTV headers 22 #include "playbackboxhelper.h" 23 #include "programinfocache.h" 24 #include "mythscreentype.h" 25 #include "schedulecommon.h" 20 26 #include "jobqueue.h" 21 27 #include "tv_play.h" 22 28 23 #include "mythscreentype.h"24 25 // mythfrontend26 #include "schedulecommon.h"27 #include "programinfocache.h"28 #include "playbackboxhelper.h"29 30 29 class QKeyEvent; 31 30 class QEvent; 32 31 class QTimer; -
programs/mythbackend/playbacksock.h
70 70 QStringList GetSGFileQuery(QString &host, QString &groupname, 71 71 QString &filename); 72 72 73 QStringList GenPreviewPixmap(const ProgramInfo *pginfo); 74 QStringList GenPreviewPixmap(const ProgramInfo *pginfo, 73 QStringList GenPreviewPixmap(const QString &token, 74 const ProgramInfo *pginfo); 75 QStringList GenPreviewPixmap(const QString &token, 76 const ProgramInfo *pginfo, 75 77 bool time_fmt_sec, 76 78 long long time, 77 79 const QString &outputFile, -
programs/mythbackend/mainserver.h
24 24 #undef DeleteFile 25 25 #endif 26 26 27 class QTimer; 28 class QUrl; 29 27 30 class ProcessRequestThread; 28 class QUrl;29 31 class MythServer; 30 class QTimer;31 32 32 33 class MainServer : public QObject, public MythSocketCBs 33 34 { -
programs/mythbackend/main.cpp
51 51 #include "programinfo.h" 52 52 #include "dbcheck.h" 53 53 #include "jobqueue.h" 54 #include "previewgenerator.h"55 54 #include "mythcommandlineparser.h" 56 55 #include "mythsystemevent.h" 57 56 -
programs/mythbackend/playbacksock.cpp
262 262 return strlist; 263 263 } 264 264 265 QStringList PlaybackSock::GenPreviewPixmap(const ProgramInfo *pginfo) 265 QStringList PlaybackSock::GenPreviewPixmap( 266 const QString &token, const ProgramInfo *pginfo) 266 267 { 267 QStringList strlist( QString("QUERY_GENPIXMAP") ); 268 QStringList strlist( QString("QUERY_GENPIXMAP2") ); 269 strlist += token; 268 270 pginfo->ToStringList(strlist); 269 271 270 272 SendReceiveStringList(strlist); … … 272 274 return strlist; 273 275 } 274 276 275 QStringList PlaybackSock::GenPreviewPixmap(const ProgramInfo *pginfo, 277 QStringList PlaybackSock::GenPreviewPixmap(const QString &token, 278 const ProgramInfo *pginfo, 276 279 bool time_fmt_sec, 277 280 long long time, 278 281 const QString &outputFile, 279 282 const QSize &outputSize) 280 283 { 281 QStringList strlist(QString("QUERY_GENPIXMAP")); 284 QStringList strlist(QString("QUERY_GENPIXMAP2")); 285 strlist += token; 282 286 pginfo->ToStringList(strlist); 283 287 strlist.push_back(time_fmt_sec ? "s" : "f"); 284 288 encodeLongLong(strlist, time); -
programs/mythbackend/mainserver.cpp
26 26 #endif // !__linux__ 27 27 28 28 #include <QCoreApplication> 29 #include <QThreadPool> 29 30 #include <QDateTime> 31 #include <QRunnable> 30 32 #include <QFile> 31 33 #include <QDir> 32 34 #include <QThread> … … 37 39 #include <QTcpServer> 38 40 #include <QTimer> 39 41 42 #include "previewgeneratorqueue.h" 40 43 #include "exitcodes.h" 41 44 #include "mythcontext.h" 42 45 #include "mythverbose.h" … … 53 56 #include "scheduledrecording.h" 54 57 #include "jobqueue.h" 55 58 #include "autoexpire.h" 56 #include "previewgenerator.h"57 59 #include "storagegroup.h" 58 60 #include "compat.h" 59 61 #include "RingBuffer.h" … … 62 64 #include "tv.h" 63 65 #include "mythcorecontext.h" 64 66 #include "mythcoreutil.h" 67 #include "util.h" 65 68 #include "mythdirs.h" 66 69 #include "mythdownloadmanager.h" 67 70 68 69 71 /** Milliseconds to wait for an existing thread from 70 72 * process request thread pool. 71 73 */ … … 184 186 { 185 187 AutoExpire::Update(true); 186 188 189 PreviewGeneratorQueue::CreatePreviewGeneratorQueue( 190 PreviewGenerator::kLocalAndRemote, ~0, 0); 191 PreviewGeneratorQueue::AddListener(this); 192 187 193 for (int i = 0; i < PRT_STARTUP_THREAD_COUNT; i++) 188 194 { 189 195 ProcessRequestThread *prt = new ProcessRequestThread(this); … … 237 243 238 244 MainServer::~MainServer() 239 245 { 246 PreviewGeneratorQueue::RemoveListener(this); 247 PreviewGeneratorQueue::TeardownPreviewGeneratorQueue(); 248 240 249 if (mythserver) 241 250 { 242 251 mythserver->disconnect(); … … 546 555 else 547 556 HandleFileTransferQuery(listline, tokens, pbs); 548 557 } 549 else if (command == "QUERY_GENPIXMAP ")558 else if (command == "QUERY_GENPIXMAP2") 550 559 { 551 560 HandleGenPreviewPixmap(listline, pbs); 552 561 } … … 734 743 { 735 744 MythEvent *me = (MythEvent *)e; 736 745 746 QString message = me->Message(); 747 if (message == "PREVIEW_READY") 748 { 749 bool ok = false; 750 QString filename; 751 QString token; 752 if (me->ExtraDataCount() >= 3) 753 { 754 ok = true; 755 filename = me->ExtraData(1); 756 token = me->ExtraData(2); 757 } 758 759 QFile file(filename); 760 ok = ok && file.open(QIODevice::ReadOnly); 761 762 if (ok) 763 { 764 QByteArray data = file.readAll(); 765 QStringList extra(token); 766 extra += "OK"; 767 extra += QString::number(data.size()); 768 extra += QString::number( 769 qChecksum(data.constData(), data.size())); 770 extra += QString(data.toBase64()); 771 MythEvent me("GENERATED_PIXMAP", extra); 772 gCoreContext->dispatch(me); 773 } 774 else 775 { 776 message = "PREVIEW_FAILED"; 777 } 778 } 779 780 if (message == "PREVIEW_FAILED" && me->ExtraDataCount() >= 3) 781 { 782 QString token = me->ExtraData(2); 783 QStringList extra(token); 784 extra += "ERROR"; 785 MythEvent me("GENERATED_PIXMAP", extra); 786 gCoreContext->dispatch(me); 787 } 788 737 789 if (me->Message().left(11) == "AUTO_EXPIRE") 738 790 { 739 791 QStringList tokens = me->Message() … … 4872 4924 { 4873 4925 MythSocket *pbssock = pbs->getSocket(); 4874 4926 4927 if (slist.size() < 2) 4928 { 4929 VERBOSE(VB_IMPORTANT, "MainServer: Too few params in pixmap request"); 4930 QStringList outputlist("BAD"); 4931 outputlist += "ERROR_INVALID_REQUEST"; 4932 SendResponse(pbssock, outputlist); 4933 return; 4934 } 4935 4875 4936 bool time_fmt_sec = true; 4876 4937 long long time = -1; 4877 4938 QString outputfile; … … 4879 4940 int height = -1; 4880 4941 bool has_extra_data = false; 4881 4942 4882 QStringList::const_iterator it = slist.begin() + 1; 4943 QString token = slist[1]; 4944 QStringList::const_iterator it = slist.begin() + 2; 4883 4945 QStringList::const_iterator end = slist.end(); 4884 4946 ProgramInfo pginfo(it, end); 4885 4947 bool ok = pginfo.HasPathname(); … … 4932 4994 if (has_extra_data) 4933 4995 { 4934 4996 outputlist = slave->GenPreviewPixmap( 4935 &pginfo, time_fmt_sec, time, outputfile, outputsize);4997 token, &pginfo, time_fmt_sec, time, outputfile, outputsize); 4936 4998 } 4937 4999 else 4938 5000 { 4939 outputlist = slave->GenPreviewPixmap( &pginfo);5001 outputlist = slave->GenPreviewPixmap(token, &pginfo); 4940 5002 } 4941 5003 4942 5004 slave->DownRef(); … … 4959 5021 return; 4960 5022 } 4961 5023 4962 PreviewGenerator *previewgen = new PreviewGenerator(&pginfo);4963 5024 if (has_extra_data) 4964 5025 { 4965 previewgen->SetOutputSize(outputsize); 4966 previewgen->SetOutputFilename(outputfile); 4967 previewgen->SetPreviewTime(time, time_fmt_sec); 5026 PreviewGeneratorQueue::GetPreviewImage( 5027 pginfo, outputsize, outputfile, time, time_fmt_sec); 4968 5028 } 4969 ok = previewgen->Run();4970 previewgen->deleteLater();4971 4972 if (ok)4973 {4974 QStringList outputlist("OK");4975 if (!outputfile.isEmpty())4976 outputlist += outputfile;4977 SendResponse(pbssock, outputlist);4978 }4979 5029 else 4980 5030 { 4981 VERBOSE(VB_IMPORTANT, "MainServer: Failed to make preview image."); 4982 QStringList outputlist( "BAD" ); 4983 outputlist += "ERROR_UNKNOWN"; 4984 SendResponse(pbssock, outputlist); 5031 PreviewGeneratorQueue::GetPreviewImage(pginfo); 4985 5032 } 5033 5034 QStringList outputlist("OK"); 5035 if (!outputfile.isEmpty()) 5036 outputlist += outputfile; 5037 SendResponse(pbssock, outputlist); 4986 5038 } 4987 5039 4988 5040 void MainServer::HandlePixmapLastModified(QStringList &slist, PlaybackSock *pbs)