Ticket #7195: 7191-gen2-v3.patch
File 7191-gen2-v3.patch, 80.5 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), 23 attempts(0), lastBlockTime(0) {} 24 PreviewGenerator *gen; 25 bool genStarted; 26 uint attempts; 27 uint lastBlockTime; 28 QDateTime blockRetryUntil; 29 QSet<QString> tokens; 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 &pginfo, QString token) 44 { 45 GetPreviewImage(pginfo, QSize(0,0), "", -1, true, token); 46 } 47 static void GetPreviewImage(const ProgramInfo&, const QSize&, 48 const QString &outputfile, 49 long long time, bool in_seconds, 50 QString token); 51 static void AddListener(QObject*); 52 static void RemoveListener(QObject*); 53 54 private: 55 PreviewGeneratorQueue(PreviewGenerator::Mode mode, 56 uint maxAttempts, uint minBlockSeconds); 57 ~PreviewGeneratorQueue(); 58 59 QString GeneratePreviewImage(ProgramInfo &pginfo, const QSize&, 60 const QString &outputfile, 61 long long time, bool in_seconds, 62 QString token); 63 64 void GetInfo(const QString &key, uint &queue_depth, uint &preview_tokens); 65 void SetPreviewGenerator(const QString &key, PreviewGenerator *g); 66 void IncPreviewGeneratorPriority(const QString &key, QString token); 67 void UpdatePreviewGeneratorThreads(void); 68 bool IsGeneratingPreview(const QString &key) const; 69 uint IncPreviewGeneratorAttempts(const QString &key); 70 void ClearPreviewGeneratorAttempts(const QString &key); 71 72 virtual bool event(QEvent *e); // QObject 73 74 void SendEvent(const ProgramInfo &pginfo, 75 const QString &eventname, 76 const QString &fn, 77 const QString &token, 78 const QString &msg); 79 80 private: 81 static PreviewGeneratorQueue *s_pgq; 82 QSet<QObject*> m_listeners; 83 84 mutable QMutex m_lock; 85 PreviewGenerator::Mode m_mode; 86 PreviewMap m_previewMap; 87 QMap<QString,QString> m_tokenToKeyMap; 88 QStringList m_queue; 89 uint m_running; 90 uint m_maxThreads; 91 uint m_maxAttempts; 92 uint m_minBlockSeconds; 93 }; 94 95 #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 … … 47 51 * is not local. 48 52 * 49 53 * The PreviewGenerator will send Qt signals when the preview is ready 50 * and when the preview thread finishes running if Start(void) was called.54 * and when the preview thread finishes running if start(void) was called. 51 55 */ 52 56 53 57 /** … … 65 69 */ 66 70 PreviewGenerator::PreviewGenerator(const ProgramInfo *pginfo, 67 71 PreviewGenerator::Mode _mode) 68 : programInfo(*pginfo), mode(_mode), isConnected(false),69 createSockets(false), serverSock(NULL),pathname(pginfo->GetPathname()),72 : programInfo(*pginfo), mode(_mode), listener(NULL), 73 pathname(pginfo->GetPathname()), 70 74 timeInSeconds(true), captureTime(-1), outFileName(QString::null), 71 outSize(0,0) 75 outSize(0,0), gotReply(false), pixmapOk(false) 72 76 { 73 77 } 74 78 … … 84 88 85 89 void PreviewGenerator::TeardownAll(void) 86 90 { 87 if (!isConnected) 88 return; 89 90 const QString filename = programInfo.GetPathname() + ".png"; 91 92 MythTimer t; 93 t.start(); 94 for (bool done = false; !done;) 95 { 96 previewLock.lock(); 97 if (isConnected) 98 emit previewThreadDone(filename, done); 99 else 100 done = true; 101 previewLock.unlock(); 102 usleep(5000); 103 } 104 VERBOSE(VB_PLAYBACK, LOC + "previewThreadDone took "<<t.elapsed()<<"ms"); 105 disconnectSafe(); 91 previewWaitCondition.wakeAll(); 92 listener = NULL; 106 93 } 107 94 108 95 void PreviewGenerator::deleteLater() … … 114 101 void PreviewGenerator::AttachSignals(QObject *obj) 115 102 { 116 103 QMutexLocker locker(&previewLock); 117 qRegisterMetaType<bool>("bool &"); 118 connect(this, SIGNAL(previewThreadDone(const QString&,bool&)), 119 obj, SLOT( previewThreadDone(const QString&,bool&)), 120 Qt::DirectConnection); 121 connect(this, SIGNAL(previewReady(const ProgramInfo*)), 122 obj, SLOT( previewReady(const ProgramInfo*)), 123 Qt::DirectConnection); 124 isConnected = true; 104 listener = obj; 125 105 } 126 106 127 /** \fn PreviewGenerator::disconnectSafe(void)128 * \brief disconnects signals while holding previewLock, ensuring that129 * no one will receive a signal from this class after this call.130 */131 void PreviewGenerator::disconnectSafe(void)132 {133 QMutexLocker locker(&previewLock);134 QObject::disconnect(this, NULL, NULL, NULL);135 isConnected = false;136 }137 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 107 /** \fn PreviewGenerator::RunReal(void) 149 108 * \brief This call creates a preview without starting a new thread. 150 109 */ 151 110 bool PreviewGenerator::RunReal(void) 152 111 { 112 QString msg; 113 QTime tm = QTime::currentTime(); 153 114 bool ok = false; 154 115 bool is_local = IsLocal(); 155 116 if (is_local && (mode && kLocal) && LocalPreviewRun()) 156 117 { 157 118 ok = true; 119 msg = QString("Generated on %1 in %2 seconds, starting at %3") 120 .arg(gCoreContext->GetHostName()) 121 .arg(tm.elapsed()*0.001) 122 .arg(tm.toString(Qt::ISODate)); 158 123 } 159 124 else if (mode & kRemote) 160 125 { … … 166 131 "\n\t\t\tAttempting to regenerate preview on backend.\n"); 167 132 } 168 133 ok = RemotePreviewRun(); 134 if (ok) 135 { 136 msg = QString("Generated remotely in %1 seconds, starting at %2") 137 .arg(tm.elapsed()*0.001) 138 .arg(tm.toString(Qt::ISODate)); 139 } 169 140 } 170 141 else 171 142 { 172 143 VERBOSE(VB_IMPORTANT, LOC_ERR + QString("Run() file not local: '%1'") 173 144 .arg(pathname)); 145 msg = "Could not access recording"; 174 146 } 175 147 148 QMutexLocker locker(&previewLock); 149 if (listener) 150 { 151 QString message = (ok) ? "PREVIEW_SUCCESS" : "PREVIEW_FAILED"; 152 QStringList list; 153 list.push_back(programInfo.MakeUniqueKey()); 154 list.push_back(outFileName.isEmpty() ? 155 (programInfo.GetPathname()+".png") : outFileName); 156 list.push_back(msg); 157 list.push_back(token); 158 QCoreApplication::postEvent(listener, new MythEvent(message, list)); 159 } 160 176 161 return ok; 177 162 } 178 163 179 164 bool PreviewGenerator::Run(void) 180 165 { 166 VERBOSE(VB_IMPORTANT, LOC + "Run() -- begin"); 167 168 QString msg; 169 QTime tm = QTime::currentTime(); 181 170 bool ok = false; 182 171 QString command = GetInstallPrefix() + "/bin/mythpreviewgen"; 183 172 bool local_ok = (IsLocal() && (mode & kLocal) && … … 187 176 if (mode & kRemote) 188 177 { 189 178 ok = RemotePreviewRun(); 179 if (ok) 180 { 181 msg = 182 QString("Generated remotely in %1 seconds, starting at %2") 183 .arg(tm.elapsed()*0.001) 184 .arg(tm.toString(Qt::ISODate)); 185 } 190 186 } 191 187 else 192 188 { 193 189 VERBOSE(VB_IMPORTANT, LOC_ERR + 194 190 QString("Run() can not generate preview locally for: '%1'") 195 191 .arg(pathname)); 192 msg = "Failed, local preview requested for remote file."; 196 193 } 197 194 } 198 195 else … … 215 212 if (!outFileName.isEmpty()) 216 213 command += QString("--outfile \"%1\" ").arg(outFileName); 217 214 215 command += " >> /tmp/previewgen.txt"; 216 218 217 int ret = myth_system(command, MYTH_SYSTEM_DONT_BLOCK_LIRC | 219 218 MYTH_SYSTEM_DONT_BLOCK_JOYSTICK_MENU | 220 219 MYTH_SYSTEM_DONT_BLOCK_PARENT); 221 220 if (ret) 222 221 { 223 VERBOSE(VB_IMPORTANT, LOC_ERR + "Encountered problems running " +224 QString("'%1'").arg(command));222 msg = QString("Encountered problems running '%1'").arg(command); 223 VERBOSE(VB_IMPORTANT, LOC_ERR + msg); 225 224 } 226 225 else 227 226 { … … 240 239 QFileInfo fi(outname); 241 240 ok = (fi.exists() && fi.isReadable() && fi.size()); 242 241 if (ok) 242 { 243 243 VERBOSE(VB_PLAYBACK, LOC + "Preview process ran ok."); 244 msg = QString("Generated on %1 in %2 seconds, starting at %3") 245 .arg(gCoreContext->GetHostName()) 246 .arg(tm.elapsed()*0.001) 247 .arg(tm.toString(Qt::ISODate)); 248 } 244 249 else 245 250 { 246 251 VERBOSE(VB_IMPORTANT, LOC_ERR + "Preview process not ok." + … … 251 256 VERBOSE(VB_IMPORTANT, LOC_ERR + 252 257 QString("Despite command '%1' returning success") 253 258 .arg(command)); 259 msg = QString("Failed to read preview image despite " 260 "preview process returning success."); 254 261 } 255 262 } 256 263 } 257 264 258 if (ok) 265 VERBOSE(VB_IMPORTANT, LOC + "Run() -- waiting on lock"); 266 QMutexLocker locker(&previewLock); 267 VERBOSE(VB_IMPORTANT, LOC + "Run() -- got lock"); 268 269 QString message = (ok) ? "PREVIEW_SUCCESS" : "PREVIEW_FAILED"; 270 if (listener) 259 271 { 260 QMutexLocker locker(&previewLock); 261 emit previewReady(&programInfo); 272 QStringList list; 273 list.push_back(programInfo.MakeUniqueKey()); 274 list.push_back(outFileName.isEmpty() ? 275 (programInfo.GetPathname()+".png") : outFileName); 276 list.push_back(msg); 277 list.push_back(token); 278 QCoreApplication::postEvent(listener, new MythEvent(message, list)); 262 279 } 280 VERBOSE(VB_IMPORTANT, LOC + "Run() -- end"); 263 281 264 282 return ok; 265 283 } 266 284 267 void *PreviewGenerator::PreviewRun(void *param)285 void PreviewGenerator::run(void) 268 286 { 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; 287 VERBOSE(VB_IMPORTANT, LOC + "run() -- begin"); 288 setPriority(QThread::LowPriority); 289 Run(); 290 connect(this, SIGNAL(finished()), 291 this, SLOT(deleteLater())); 292 VERBOSE(VB_IMPORTANT, LOC + "run() -- end"); 277 293 } 278 294 279 bool PreviewGenerator::RemotePreview Setup(void)295 bool PreviewGenerator::RemotePreviewRun(void) 280 296 { 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); 297 VERBOSE(VB_IMPORTANT, LOC + "RemotePreviewRun() -- begin"); 285 298 286 serverSock = gCoreContext->ConnectCommandSocket(server, port, ann);287 return serverSock;288 } 289 290 bool PreviewGenerator::RemotePreviewRun(void) 291 { 292 QStringList strlist( "QUERY_GENPIXMAP");299 QStringList strlist( "QUERY_GENPIXMAP2" ); 300 if (token.isEmpty()) 301 { 302 token = QString("%1:%2") 303 .arg(programInfo.MakeUniqueKey()).arg(rand()); 304 } 305 strlist.push_back(token); 293 306 programInfo.ToStringList(strlist); 294 307 strlist.push_back(timeInSeconds ? "s" : "f"); 295 308 encodeLongLong(strlist, captureTime); … … 305 318 strlist.push_back(QString::number(outSize.width())); 306 319 strlist.push_back(QString::number(outSize.height())); 307 320 308 bool ok = false; 321 gCoreContext->addListener(this); 322 pixmapOk = false; 309 323 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 324 bool ok = gCoreContext->SendReceiveStringList(strlist); 331 325 if (!ok || strlist.empty() || (strlist[0] != "OK")) 332 326 { 333 327 if (!ok) … … 340 334 VERBOSE(VB_IMPORTANT, LOC_ERR + 341 335 "Remote Preview failed, reason given: " <<strlist[1]); 342 336 } 343 else 337 338 gCoreContext->removeListener(this); 339 340 return false; 341 } 342 343 VERBOSE(VB_IMPORTANT, LOC + "RemotePreviewRun() -- waiting on lock"); 344 QMutexLocker locker(&previewLock); 345 VERBOSE(VB_IMPORTANT, LOC + "RemotePreviewRun() -- got lock"); 346 347 // wait up to 30 seconds for the preview to complete 348 if (!gotReply) 349 previewWaitCondition.wait(&previewLock, 30 * 1000); 350 351 if (!gotReply) 352 VERBOSE(VB_IMPORTANT, LOC + "RemotePreviewRun() -- no reply.."); 353 354 gCoreContext->removeListener(this); 355 356 VERBOSE(VB_IMPORTANT, LOC + "RemotePreviewRun() -- end"); 357 358 return pixmapOk; 359 } 360 361 bool PreviewGenerator::event(QEvent *e) 362 { 363 if (e->type() == (QEvent::Type) MythEvent::MythEventMessage) 364 { 365 MythEvent *me = (MythEvent*)e; 366 if (me->Message() == "GENERATED_PIXMAP" && (me->ExtraDataCount() >= 3)) 344 367 { 345 VERBOSE(VB_IMPORTANT, LOC_ERR + 346 "Remote Preview failed due to an unknown error."); 368 bool ok = me->ExtraData(0) == "OK"; 369 bool ours = false; 370 uint i = ok ? 3 : 2; 371 for (; i < (uint) me->ExtraDataCount() && !ours; i++) 372 ours |= me->ExtraData(i) == token; 373 374 if (!ours) 375 return false; 376 377 VERBOSE(VB_IMPORTANT, LOC + "GOT OUR GENERATED_PIXMAP EVENT"); 378 379 QMutexLocker locker(&previewLock); 380 gotReply = true; 381 pixmapOk = ok; 382 if (!pixmapOk) 383 { 384 QString msg = me->ExtraData(1); 385 if (!msg.isEmpty()) 386 VERBOSE(VB_IMPORTANT, LOC_ERR + msg); 387 return true; 388 } 389 390 if (me->ExtraDataCount() < 4) 391 return true; // could only happen with very broken client... 392 393 QByteArray data; 394 size_t length = me->ExtraData(1).toULongLong(); 395 quint16 checksum16 = me->ExtraData(2).toUInt(); 396 data = QByteArray::fromBase64(me->ExtraData(3).toAscii()); 397 if ((size_t) data.size() < length) 398 { // (note data.size() may be up to 3 399 // bytes longer after decoding 400 VERBOSE(VB_IMPORTANT, LOC_ERR + 401 QString("Preview size check failed %1 < %2") 402 .arg(data.size()).arg(length)); 403 data.clear(); 404 } 405 data.resize(length); 406 407 if (checksum16 != qChecksum(data.constData(), data.size())) 408 { 409 VERBOSE(VB_IMPORTANT, LOC_ERR + "Preview checksum failed"); 410 data.clear(); 411 } 412 413 pixmapOk = (data.isEmpty()) ? false : SaveOutFile(data); 414 415 previewWaitCondition.wakeAll(); 416 417 return true; 347 418 } 348 return false;349 419 } 420 return QObject::event(e); 421 } 350 422 423 bool PreviewGenerator::SaveOutFile(QByteArray &data) 424 { 351 425 if (outFileName.isEmpty()) 352 426 { 353 QString remotecachedirname = QString("%1/remotecache").arg(GetConfDir()); 427 QString remotecachedirname = 428 QString("%1/remotecache").arg(GetConfDir()); 354 429 QDir remotecachedir(remotecachedirname); 355 430 356 431 if (!remotecachedir.exists()) … … 368 443 outFileName = QString("%1/%2").arg(remotecachedirname).arg(filename); 369 444 } 370 445 371 // find file, copy/move to output file name & location... 446 bool ok = true; 447 if (data.isEmpty()) 448 { 449 // find file, copy/move to output file name & location... 450 QString url = QString::null; 451 QString fn = QFileInfo(outFileName).fileName(); 452 ok = false; 372 453 373 QString url = QString::null; 374 QString fn = QFileInfo(outFileName).fileName(); 375 QByteArray data; 376 ok = false; 454 QStringList fileNames; 455 fileNames.push_back( 456 CreateAccessibleFilename(programInfo.GetPathname(), fn)); 457 fileNames.push_back( 458 CreateAccessibleFilename(programInfo.GetPathname(), "")); 377 459 378 QStringList fileNames; 379 fileNames.push_back( 380 CreateAccessibleFilename(programInfo.GetPathname(), fn)); 381 fileNames.push_back( 382 CreateAccessibleFilename(programInfo.GetPathname(), "")); 460 QStringList::const_iterator it = fileNames.begin(); 461 for ( ; it != fileNames.end() && (!ok || data.isEmpty()); ++it) 462 { 463 data.resize(0); 464 url = *it; 465 RemoteFile *rf = new RemoteFile(url, false, false, 0); 466 ok = rf->SaveAs(data); 467 delete rf; 468 } 469 } 383 470 384 QStringList::const_iterator it = fileNames.begin(); 385 for ( ; it != fileNames.end() && (!ok || data.isEmpty()); ++it) 471 if (!ok) 472 return false; 473 474 QFile file(outFileName); 475 ok = file.open(QIODevice::Unbuffered|QIODevice::WriteOnly); 476 if (!ok) 386 477 { 387 data.resize(0); 388 url = *it; 389 RemoteFile *rf = new RemoteFile(url, false, false, 0); 390 ok = rf->SaveAs(data); 391 delete rf; 478 VERBOSE(VB_IMPORTANT, LOC_ERR + QString("Failed to open: '%1'") 479 .arg(outFileName)); 392 480 } 393 481 394 if (ok && data.size()) 482 off_t offset = 0; 483 size_t remaining = data.size(); 484 uint failure_cnt = 0; 485 while ((remaining > 0) && (failure_cnt < 5)) 395 486 { 396 QFile file(outFileName); 397 ok = file.open(QIODevice::Unbuffered|QIODevice::WriteOnly); 398 if (!ok) 487 ssize_t written = file.write(data.data() + offset, remaining); 488 if (written < 0) 399 489 { 400 VERBOSE(VB_IMPORTANT, QString("Failed to open: '%1'") 401 .arg(outFileName)); 490 failure_cnt++; 491 usleep(50000); 492 continue; 402 493 } 403 494 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 } 495 failure_cnt = 0; 496 offset += written; 497 remaining -= written; 426 498 } 427 428 return ok && data.size(); 429 } 430 431 void PreviewGenerator::RemotePreviewTeardown(void) 432 { 433 if (serverSock) 499 if (ok && !remaining) 434 500 { 435 serverSock->DownRef(); 436 serverSock = NULL; 501 VERBOSE(VB_PLAYBACK, LOC + QString("Saved: '%1'").arg(outFileName)); 437 502 } 503 else 504 { 505 file.remove(); 506 } 507 508 return ok; 438 509 } 439 510 440 511 bool PreviewGenerator::SavePreview(QString filename, … … 476 547 QImage small_img = img.scaled((int) ppw, (int) pph, 477 548 Qt::IgnoreAspectRatio, Qt::SmoothTransformation); 478 549 479 QByteArray fname = filename.toAscii(); 480 if (small_img.save(fname.constData(), "PNG")) 550 QTemporaryFile f(QFileInfo(filename).absoluteFilePath()+".XXXXXX"); 551 f.setAutoRemove(false); 552 if (f.open() && small_img.save(&f, "PNG")) 481 553 { 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; 554 // Let anybody update it 555 makeFileAccessible(f.fileName().toLocal8Bit().constData()); 556 QFile of(filename); 557 of.remove(); 558 if (f.rename(filename)) 559 { 560 VERBOSE(VB_PLAYBACK, LOC + 561 QString("Saved preview '%0' %1x%2") 562 .arg(filename).arg((int) ppw).arg((int) pph)); 563 return true; 564 } 565 f.remove(); 489 566 } 490 567 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()); 568 VERBOSE(VB_IMPORTANT, LOC_ERR + 569 QString("Failed to save preview '%0' %1x%2") 570 .arg(filename).arg((int) ppw).arg((int) pph)); 499 571 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 572 return false; 509 573 } 510 574 … … 515 579 float aspect = 0; 516 580 int width, height, sz; 517 581 long long captime = captureTime; 582 583 if (captime > 0) 584 VERBOSE(VB_IMPORTANT, "Preview from time spec"); 585 518 586 if (captime < 0) 519 587 { 588 captime = programInfo.QueryBookmark(); 589 if (captime > 0) 590 { 591 VERBOSE(VB_IMPORTANT, "Preview from bookmark..."); 592 timeInSeconds = false; 593 } 594 else 595 { 596 captime = -1; 597 } 598 } 599 600 if (captime < 0) 601 { 520 602 timeInSeconds = true; 521 603 int startEarly = 0; 522 604 int programDuration = 0; … … 544 626 if (captime < 0) 545 627 captime = 600; 546 628 captime += preroll; 629 630 VERBOSE(VB_IMPORTANT, "Preview from pre-roll..."); 547 631 } 548 632 549 633 width = height = sz = 0; -
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->deleteLater(); 58 } 59 } 60 61 void PreviewGeneratorQueue::GetPreviewImage( 62 const ProgramInfo &pginfo, 63 const QSize &outputsize, 64 const QString &outputfile, 65 long long time, bool in_seconds, 66 QString token) 67 { 68 if (!s_pgq) 69 return; 70 71 if (pginfo.GetPathname().isEmpty() || 72 pginfo.GetBasename() == pginfo.GetPathname()) 73 { 74 return; 75 } 76 77 QStringList extra; 78 pginfo.ToStringList(extra); 79 extra += token; 80 extra += QString::number(outputsize.width()); 81 extra += QString::number(outputsize.height()); 82 extra += outputfile; 83 extra += QString::number(time); 84 extra += (in_seconds ? "1" : "0"); 85 MythEvent *e = new MythEvent("GET_PREVIEW", extra); 86 QCoreApplication::postEvent(s_pgq, e); 87 } 88 89 void PreviewGeneratorQueue::AddListener(QObject *listener) 90 { 91 if (!s_pgq) 92 return; 93 94 QMutexLocker locker(&s_pgq->m_lock); 95 s_pgq->m_listeners.insert(listener); 96 } 97 98 void PreviewGeneratorQueue::RemoveListener(QObject *listener) 99 { 100 if (!s_pgq) 101 return; 102 103 QMutexLocker locker(&s_pgq->m_lock); 104 s_pgq->m_listeners.remove(listener); 105 } 106 107 bool PreviewGeneratorQueue::event(QEvent *e) 108 { 109 if (e->type() != (QEvent::Type) MythEvent::MythEventMessage) 110 return QObject::event(e); 111 112 MythEvent *me = (MythEvent*)e; 113 if (me->Message() == "GET_PREVIEW") 114 { 115 const QStringList list = me->ExtraDataList(); 116 QStringList::const_iterator it = list.begin(); 117 ProgramInfo evinfo(it, list.end()); 118 QString token; 119 QSize outputsize; 120 QString outputfile; 121 long long time; 122 bool time_fmt_sec; 123 if (it != list.end()) 124 token = (*it++); 125 if (it != list.end()) 126 outputsize.setWidth((*it++).toInt()); 127 if (it != list.end()) 128 outputsize.setHeight((*it++).toInt()); 129 if (it != list.end()) 130 outputfile = (*it++); 131 if (it != list.end()) 132 time = (*it++).toLongLong(); 133 QString fn; 134 if (it != list.end()) 135 { 136 time_fmt_sec = (*it++).toInt() != 0; 137 fn = GeneratePreviewImage(evinfo, outputsize, outputfile, 138 time, time_fmt_sec, token); 139 } 140 return true; 141 } 142 else if (me->Message() == "PREVIEW_SUCCESS" || 143 me->Message() == "PREVIEW_FAILED") 144 { 145 QString pginfokey = me->ExtraData(0); // pginfo->MakeUniqueKey() 146 QString filename = me->ExtraData(1); // outFileName 147 QString msg = me->ExtraData(2); 148 QString token = me->ExtraData(3); 149 150 VERBOSE(VB_PLAYBACK, LOC + QString("Preview '%1' processed") 151 .arg(token)); 152 153 { 154 QMutexLocker locker(&m_lock); 155 QMap<QString,QString>::iterator kit = m_tokenToKeyMap.find(token); 156 if (kit == m_tokenToKeyMap.end()) 157 { 158 VERBOSE(VB_IMPORTANT, LOC_ERR + 159 QString("Failed to find token %1 in map.").arg(token)); 160 return true; 161 } 162 PreviewMap::iterator it = m_previewMap.find(*kit); 163 if (it == m_previewMap.end()) 164 { 165 VERBOSE(VB_IMPORTANT, LOC_ERR + 166 QString("Failed to find key %1 in map.").arg(*kit)); 167 return true; 168 } 169 170 (*it).gen = NULL; 171 (*it).genStarted = false; 172 if (me->Message() == "PREVIEW_SUCCESS") 173 { 174 (*it).attempts = 0; 175 (*it).lastBlockTime = 0; 176 (*it).blockRetryUntil = QDateTime(); 177 } 178 else 179 { 180 (*it).lastBlockTime = 181 max(m_minBlockSeconds, (*it).lastBlockTime * 2); 182 (*it).blockRetryUntil = 183 QDateTime::currentDateTime().addSecs((*it).lastBlockTime); 184 } 185 186 QStringList list; 187 list.push_back(pginfokey); 188 list.push_back(filename); 189 list.push_back(msg); 190 QSet<QString>::const_iterator tit = (*it).tokens.begin(); 191 for (; tit != (*it).tokens.end(); ++tit) 192 { 193 kit = m_tokenToKeyMap.find(*tit); 194 if (kit != m_tokenToKeyMap.end()) 195 m_tokenToKeyMap.erase(kit); 196 list.push_back(*tit); 197 } 198 199 QSet<QObject*>::iterator sit = m_listeners.begin(); 200 for (; sit != m_listeners.end(); ++sit) 201 { 202 MythEvent *e = new MythEvent(me->Message(), list); 203 QCoreApplication::postEvent(*sit, e); 204 } 205 (*it).tokens.clear(); 206 207 m_running--; 208 } 209 210 UpdatePreviewGeneratorThreads(); 211 212 return true; 213 } 214 return false; 215 } 216 217 void PreviewGeneratorQueue::SendEvent( 218 const ProgramInfo &pginfo, 219 const QString &eventname, 220 const QString &fn, const QString &token, const QString &msg) 221 { 222 VERBOSE(VB_IMPORTANT, QString("SendEvent(%1, %2, %3, %4)") 223 .arg(pginfo.toString(ProgramInfo::kTitleSubtitle)) 224 .arg(eventname).arg(msg).arg(token)); 225 226 QStringList list; 227 list.push_back(pginfo.MakeUniqueKey()); 228 list.push_back(fn); 229 list.push_back(msg); 230 list.push_back(token); 231 232 QMutexLocker locker(&m_lock); 233 QSet<QObject*>::iterator it = m_listeners.begin(); 234 for (; it != m_listeners.end(); ++it) 235 { 236 MythEvent *e = new MythEvent(eventname, list); 237 QCoreApplication::postEvent(*it, e); 238 } 239 } 240 241 QString PreviewGeneratorQueue::GeneratePreviewImage( 242 ProgramInfo &pginfo, 243 const QSize &size, 244 const QString &outputfile, 245 long long time, bool in_seconds, 246 QString token) 247 { 248 QString key = QString("%1_%2x%3_%4%5") 249 .arg(pginfo.GetBasename()).arg(size.width()).arg(size.height()) 250 .arg(time).arg(in_seconds?"s":"f"); 251 252 if (pginfo.GetAvailableStatus() == asPendingDelete) 253 { 254 SendEvent(pginfo, "PREVIEW_FAILED", key, token, "Pending Delete"); 255 return QString(); 256 } 257 258 QString filename = (outputfile.isEmpty()) ? 259 pginfo.GetPathname() + ".png" : outputfile; 260 QString ret_file = filename; 261 QString ret; 262 263 bool is_special = !outputfile.isEmpty() || time >= 0 || 264 size.width() || size.height(); 265 266 bool needs_gen = true; 267 if (!is_special) 268 { 269 QDateTime previewLastModified; 270 bool streaming = filename.left(1) != "/"; 271 bool locally_accessible = false; 272 bool bookmark_updated = false; 273 274 QDateTime bookmark_ts = pginfo.QueryBookmarkTimeStamp(); 275 QDateTime cmp_ts = bookmark_ts.isValid() ? 276 bookmark_ts : pginfo.GetLastModifiedTime(); 277 278 if (streaming) 279 { 280 ret_file = QString("%1/remotecache/%2") 281 .arg(GetConfDir()).arg(filename.section('/', -1)); 282 283 QFileInfo finfo(ret_file); 284 if (finfo.isReadable() && finfo.lastModified() >= cmp_ts) 285 { 286 // This is just an optimization to avoid 287 // hitting the backend if our cached copy 288 // is newer than the bookmark, or if we have 289 // a preview and do not update it when the 290 // bookmark changes. 291 previewLastModified = finfo.lastModified(); 292 } 293 else if (!IsGeneratingPreview(key)) 294 { 295 previewLastModified = 296 RemoteGetPreviewIfModified(pginfo, ret_file); 297 } 298 } 299 else 300 { 301 QFileInfo fi(filename); 302 if ((locally_accessible = fi.isReadable())) 303 previewLastModified = fi.lastModified(); 304 } 305 306 bookmark_updated = 307 (!previewLastModified.isValid() || (previewLastModified < cmp_ts)); 308 309 if (bookmark_updated && bookmark_ts.isValid() && 310 previewLastModified.isValid()) 311 { 312 ClearPreviewGeneratorAttempts(key); 313 } 314 315 if (0) 316 { 317 VERBOSE(VB_IMPORTANT, QString( 318 "previewLastModified: %1\n\t\t\t" 319 "bookmark_ts: %2\n\t\t\t" 320 "pginfo.lastmodified: %3") 321 .arg(previewLastModified.toString(Qt::ISODate)) 322 .arg(bookmark_ts.toString(Qt::ISODate)) 323 .arg(pginfo.GetLastModifiedTime(ISODate))); 324 } 325 326 bool preview_exists = previewLastModified.isValid(); 327 328 if (0) 329 { 330 VERBOSE(VB_IMPORTANT, 331 QString("Title: %1\n\t\t\t") 332 .arg(pginfo.toString(ProgramInfo::kTitleSubtitle)) + 333 QString("File '%1' \n\t\t\tCache '%2'") 334 .arg(filename).arg(ret_file) + 335 QString("\n\t\t\tPreview Exists: %1, " 336 "Bookmark Updated: %2, " 337 "Need Preview: %3") 338 .arg(preview_exists).arg(bookmark_updated) 339 .arg((bookmark_updated || !preview_exists))); 340 } 341 342 needs_gen = bookmark_updated || !preview_exists; 343 344 if (!needs_gen) 345 { 346 if (locally_accessible) 347 ret = filename; 348 else if (preview_exists && QFileInfo(ret_file).isReadable()) 349 ret = ret_file; 350 } 351 } 352 353 if (needs_gen && !IsGeneratingPreview(key)) 354 { 355 uint attempts = IncPreviewGeneratorAttempts(key); 356 if (attempts < m_maxAttempts) 357 { 358 VERBOSE(VB_PLAYBACK, LOC + 359 QString("Requesting preview for '%1'") 360 .arg(key)); 361 PreviewGenerator *pg = new PreviewGenerator(&pginfo, m_mode); 362 if (!outputfile.isEmpty() || time >= 0 || 363 size.width() || size.height()) 364 { 365 pg->SetPreviewTime(time, in_seconds); 366 pg->SetOutputFilename(outputfile); 367 pg->SetOutputSize(size); 368 } 369 pg->token = token; 370 371 SetPreviewGenerator(key, pg); 372 373 VERBOSE(VB_PLAYBACK, LOC + 374 QString("Requested preview for '%1'").arg(key)); 375 } 376 else if (attempts >= m_maxAttempts) 377 { 378 VERBOSE(VB_IMPORTANT, LOC_ERR + 379 QString("Attempted to generate preview for '%1' " 380 "%2 times; >= max(%3)") 381 .arg(key).arg(attempts).arg(m_maxAttempts)); 382 } 383 } 384 else if (needs_gen) 385 { 386 VERBOSE(VB_PLAYBACK, LOC + 387 "Not requesting preview as it " 388 "is already being generated"); 389 IncPreviewGeneratorPriority(key, token); 390 } 391 392 UpdatePreviewGeneratorThreads(); 393 394 if (!ret.isEmpty()) 395 { 396 QString msg = "On Disk"; 397 SendEvent(pginfo, "PREVIEW_SUCCESS", ret, token, msg); 398 } 399 else 400 { 401 uint queue_depth, token_cnt; 402 GetInfo(key, queue_depth, token_cnt); 403 QString msg = QString("Queue depth %1, our tokens %2") 404 .arg(queue_depth).arg(token_cnt); 405 SendEvent(pginfo, "PREVIEW_QUEUED", ret, token, msg); 406 } 407 408 return ret; 409 } 410 411 void PreviewGeneratorQueue::GetInfo( 412 const QString &key, uint &queue_depth, uint &token_cnt) 413 { 414 QMutexLocker locker(&m_lock); 415 queue_depth = m_queue.size(); 416 PreviewMap::iterator pit = m_previewMap.find(key); 417 token_cnt = (pit == m_previewMap.end()) ? 0 : (*pit).tokens.size(); 418 } 419 420 void PreviewGeneratorQueue::IncPreviewGeneratorPriority( 421 const QString &key, QString token) 422 { 423 QMutexLocker locker(&m_lock); 424 m_queue.removeAll(key); 425 426 PreviewMap::iterator pit = m_previewMap.find(key); 427 if (pit == m_previewMap.end()) 428 return; 429 430 if ((*pit).gen && !(*pit).genStarted) 431 m_queue.push_back(key); 432 433 if (!token.isEmpty()) 434 { 435 m_tokenToKeyMap[token] = key; 436 (*pit).tokens.insert(token); 437 } 438 } 439 440 void PreviewGeneratorQueue::UpdatePreviewGeneratorThreads(void) 441 { 442 QMutexLocker locker(&m_lock); 443 QStringList &q = m_queue; 444 if (!q.empty() && (m_running < m_maxThreads)) 445 { 446 QString fn = q.back(); 447 q.pop_back(); 448 PreviewMap::iterator it = m_previewMap.find(fn); 449 if (it != m_previewMap.end() && (*it).gen && !(*it).genStarted) 450 { 451 m_running++; 452 VERBOSE(VB_IMPORTANT, LOC + "Starting pg thread"); 453 (*it).gen->start(); 454 (*it).genStarted = true; 455 } 456 } 457 } 458 459 /** \brief Sets the PreviewGenerator for a specific file. 460 * \return true iff call succeeded. 461 */ 462 void PreviewGeneratorQueue::SetPreviewGenerator( 463 const QString &key, PreviewGenerator *g) 464 { 465 { 466 QMutexLocker locker(&m_lock); 467 m_tokenToKeyMap[g->token] = key; 468 PreviewGenState &state = m_previewMap[key]; 469 if (state.gen) 470 { 471 if (!g->token.isEmpty()) 472 state.tokens.insert(g->token); 473 g->deleteLater(); 474 } 475 else 476 { 477 g->AttachSignals(this); 478 state.gen = g; 479 state.genStarted = false; 480 if (!g->token.isEmpty()) 481 state.tokens.insert(g->token); 482 } 483 } 484 485 IncPreviewGeneratorPriority(key, ""); 486 } 487 488 /** \brief Returns true if we have already started a 489 * PreviewGenerator to create this file. 490 */ 491 bool PreviewGeneratorQueue::IsGeneratingPreview(const QString &key) const 492 { 493 PreviewMap::const_iterator it; 494 QMutexLocker locker(&m_lock); 495 496 if ((it = m_previewMap.find(key)) == m_previewMap.end()) 497 return false; 498 499 if ((*it).blockRetryUntil.isValid()) 500 return QDateTime::currentDateTime() < (*it).blockRetryUntil; 501 502 return (*it).gen; 503 } 504 505 /** \fn PreviewGeneratorQueue::IncPreviewGeneratorAttempts(const QString&) 506 * \brief Increments and returns number of times we have 507 * started a PreviewGenerator to create this file. 508 */ 509 uint PreviewGeneratorQueue::IncPreviewGeneratorAttempts(const QString &key) 510 { 511 QMutexLocker locker(&m_lock); 512 return m_previewMap[key].attempts++; 513 } 514 515 /** \fn PreviewGeneratorQueue::ClearPreviewGeneratorAttempts(const QString&) 516 * \brief Clears the number of times we have 517 * started a PreviewGenerator to create this file. 518 */ 519 void PreviewGeneratorQueue::ClearPreviewGeneratorAttempts(const QString &key) 520 { 521 QMutexLocker locker(&m_lock); 522 m_previewMap[key].attempts = 0; 523 m_previewMap[key].lastBlockTime = 0; 524 m_previewMap[key].blockRetryUntil = 525 QDateTime::currentDateTime().addSecs(-60); 526 } -
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" … … 25 26 #include "recordingrule.h" 26 27 #include "eitscanner.h" 27 28 #include "RingBuffer.h" 28 #include "previewgenerator.h"29 29 #include "storagegroup.h" 30 30 #include "remoteutil.h" 31 31 #include "tvremoteutil.h" … … 1132 1132 if (!killFile) 1133 1133 { 1134 1134 if (curRecording->IsLocal()) 1135 { 1136 (new PreviewGenerator( 1137 curRecording, PreviewGenerator::kLocal))->Start(); 1138 } 1135 PreviewGeneratorQueue::GetPreviewImage(*curRecording, ""); 1139 1136 1140 1137 if (!tvchain) 1141 1138 { … … 4471 4468 if (!oldinfo->IsLocal()) 4472 4469 oldinfo->SetPathname(oldinfo->GetPlaybackURL(false,true)); 4473 4470 if (oldinfo->IsLocal()) 4474 { 4475 (new PreviewGenerator( 4476 oldinfo, PreviewGenerator::kLocal))->Start(); 4477 } 4471 PreviewGeneratorQueue::GetPreviewImage(*oldinfo, ""); 4478 4472 } 4479 4473 delete oldinfo; 4480 4474 } -
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 // Update docs 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*); 54 void disconnectSafe(void);55 63 56 signals:57 void previewThreadDone(const QString&, bool&);58 void previewReady(const ProgramInfo*);59 60 64 public slots: 61 65 void deleteLater(); 62 66 … … 64 68 virtual ~PreviewGenerator(); 65 69 void TeardownAll(void); 66 70 67 bool RemotePreviewSetup(void);68 71 bool RemotePreviewRun(void); 69 void RemotePreviewTeardown(void);70 71 72 bool LocalPreviewRun(void); 72 73 bool IsLocal(void) const; 73 74 74 75 bool RunReal(void); 75 76 76 static void *PreviewRun(void*);77 78 77 static char *GetScreenGrab(const ProgramInfo &pginfo, 79 78 const QString &filename, 80 79 long long seektime, … … 93 92 static QString CreateAccessibleFilename( 94 93 const QString &pathname, const QString &outFileName); 95 94 96 protected: 95 virtual bool event(QEvent *e); // QObject 96 bool SaveOutFile(QByteArray &data); 97 98 // protected: 99 public: 100 QWaitCondition previewWaitCondition; 97 101 QMutex previewLock; 98 102 pthread_t previewThread; 99 103 ProgramInfo programInfo; 100 104 101 105 Mode mode; 102 bool isConnected; 103 bool createSockets; 104 MythSocket *serverSock; 106 QObject *listener; 105 107 QString pathname; 106 108 107 109 /// tells us whether to use time as seconds or frame number … … 110 112 long long captureTime; 111 113 QString outFileName; 112 114 QSize outSize; 115 116 QString token; 117 bool gotReply; 118 bool pixmapOk; 113 119 }; 114 120 115 121 #endif // PREVIEW_GENERATOR_H_ -
libs/libmyth/remoteutil.cpp
357 357 return retdatetime; 358 358 } 359 359 360 size_t length = strlist[1].to LongLong();360 size_t length = strlist[1].toULongLong(); 361 361 quint16 checksum16 = strlist[2].toUInt(); 362 362 QByteArray data = QByteArray::fromBase64(strlist[3].toAscii()); 363 363 if ((size_t) data.size() < length) … … 367 367 .arg(data.size()).arg(length)); 368 368 return QDateTime(); 369 369 } 370 data.resize(length); 370 371 371 372 if (checksum16 != qChecksum(data.constData(), data.size())) 372 373 { -
libs/libmythdb/mythversion.h
11 11 /// Update this whenever the plug-in API changes. 12 12 /// Including changes in the libmythdb, libmyth, libmythtv, libmythav* and 13 13 /// libmythui class methods used by plug-ins. 14 #define MYTH_BINARY_VERSION "0.23.2010080 2-1"14 #define MYTH_BINARY_VERSION "0.23.20100805-1" 15 15 16 16 /** \brief Increment this whenever the MythTV network protocol changes. 17 17 * … … 30 30 * mythtv/bindings/python/MythTV/static.py (version number) 31 31 * mythtv/bindings/python/MythTV/mythproto.py (layout) 32 32 */ 33 #define MYTH_PROTO_VERSION "5 8"33 #define MYTH_PROTO_VERSION "59" 34 34 35 35 MPUBLIC const char *GetMythSourceVersion(); 36 36 -
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 "mythplayer.h" 15 #include "mythuibuttonlist.h" 16 #include "mythcorecontext.h" 17 #include "mythsystemevent.h" 18 #include "mythuistatetype.h" 19 #include "mythuicheckbox.h" 20 #include "mythuitextedit.h" 21 #include "mythdialogbox.h" 21 22 #include "recordinginfo.h" 22 #include "playgroup.h" 23 #include "mythsystemevent.h" 24 25 // libmyth 26 #include "mythcorecontext.h" 27 #include "util.h" 23 #include "mythuihelper.h" 28 24 #include "storagegroup.h" 25 #include "mythuibutton.h" 26 #include "mythverbose.h" 27 #include "mythuiimage.h" 29 28 #include "programinfo.h" 30 31 // libmythui 32 #include "mythuihelper.h" 29 #include "oldsettings.h" 33 30 #include "mythuitext.h" 34 #include "mythuibutton.h"35 #include "mythuibuttonlist.h"36 #include "mythuistatetype.h"37 #include "mythdialogbox.h"38 #include "mythuitextedit.h"39 #include "mythuiimage.h"40 #include "mythuicheckbox.h"41 #include "mythuiprogressbar.h"42 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() … … 797 794 798 795 QString oldimgfile = item->GetImage("preview"); 799 796 if (oldimgfile.isEmpty() || force_preview_reload) 800 m_ helper.GetPreviewImage(*pginfo);797 m_preview_tokens.insert(m_helper.GetPreviewImage(*pginfo)); 801 798 802 799 if ((GetFocusWidget() == m_recordingList) && is_sel) 803 800 { … … 863 860 * a preview image UI item in the theme that it's filename property 864 861 * gets updated as well. 865 862 */ 866 void PlaybackBox::HandlePreviewEvent( 867 const QString &piKey, const QString &previewFile) 863 void PlaybackBox::HandlePreviewEvent(const QStringList &list) 868 864 { 865 const QString piKey = list[0]; 866 const QString previewFile = list[1]; 867 const QString message = list[2]; 868 869 bool found = false; 870 for (uint i = 3; i < list.size(); i++) 871 { 872 QString token = list[i]; 873 QSet<QString>::iterator it = m_preview_tokens.find(token); 874 if (it != m_preview_tokens.end()) 875 { 876 found = true; 877 m_preview_tokens.erase(it); 878 } 879 } 880 881 if (!found) 882 { 883 QString tokens("\n\t\t\ttokens: "); 884 for (uint i = 3; i < list.size(); i++) 885 tokens += list[i] + ", "; 886 VERBOSE(VB_IMPORTANT, LOC + 887 "Ignoring PREVIEW_SUCCESS, no matcing token" + tokens); 888 return; 889 } 890 869 891 if (previewFile.isEmpty()) 892 { 893 VERBOSE(VB_IMPORTANT, LOC_ERR + 894 "Ignoring PREVIEW_SUCCESS, no preview file."); 870 895 return; 896 } 871 897 872 898 ProgramInfo *info = m_programInfoCache.GetProgramInfo(piKey); 873 899 MythUIButtonListItem *item = NULL; … … 875 901 if (info) 876 902 item = m_recordingList->GetItemByData(qVariantFromValue(info)); 877 903 904 if (!item) 905 { 906 VERBOSE(VB_IMPORTANT, LOC_ERR + 907 "Ignoring PREVIEW_SUCCESS, item no longer on screen."); 908 } 909 878 910 if (item) 879 911 { 912 VERBOSE(VB_IMPORTANT, LOC + 913 QString("Loading preview %1,\n\t\t\tmsg %2") 914 .arg(previewFile).arg(message)); 915 880 916 item->SetImage(previewFile, "preview", true); 881 917 882 918 if ((GetFocusWidget() == m_recordingList) && … … 3803 3839 // asPendingDelete, we need to put them back now.. 3804 3840 ScheduleUpdateUIList(); 3805 3841 } 3806 else if (message == "PREVIEW_ READY" && me->ExtraDataCount() == 2)3842 else if (message == "PREVIEW_SUCCESS" && me->ExtraDataCount() >= 3) 3807 3843 { 3808 HandlePreviewEvent(me->ExtraData (0), me->ExtraData(1));3844 HandlePreviewEvent(me->ExtraDataList()); 3809 3845 } 3846 else if (message == "PREVIEW_FAILED" && me->ExtraDataCount() >= 3) 3847 { 3848 for (uint i = 3; i < me->ExtraDataCount(); i++) 3849 { 3850 QString token = me->ExtraData(i); 3851 QSet<QString>::iterator it = m_preview_tokens.find(token); 3852 if (it != m_preview_tokens.end()) 3853 m_preview_tokens.erase(it); 3854 } 3855 } 3810 3856 else if (message == "AVAILABILITY" && me->ExtraDataCount() == 8) 3811 3857 { 3812 3858 const uint kMaxUIWaitTime = 100; // ms -
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, … … 72 53 void UndeleteRecording(uint chanid, const QDateTime &recstartts); 73 54 void CheckAvailability(const ProgramInfo&, 74 55 CheckAvailabilityType cat = kCheckForCache); 75 voidGetPreviewImage(const ProgramInfo&);56 QString GetPreviewImage(const ProgramInfo&); 76 57 77 58 QString LocateArtwork(const QString &seriesid, const QString &title, 78 59 ArtworkType, const QString &host, … … 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" … … 277 277 } 278 278 else if (me->Message() == "GET_PREVIEW") 279 279 { 280 ProgramInfo evinfo(me->ExtraDataList()); 280 QString token = me->ExtraData(0); 281 QStringList list = me->ExtraDataList(); 282 QStringList::const_iterator it = list.begin()+1; 283 ProgramInfo evinfo(it, list.end()); 281 284 if (!evinfo.HasPathname()) 282 285 return true; 283 286 284 QStringList list;287 list.clear(); 285 288 evinfo.ToStringList(list); 286 289 list += QString::number(kCheckForCache); 287 290 if (asAvailable != CheckAvailability(list)) 288 291 return true; 289 292 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 } 293 // Now we can actually request the preview... 294 PreviewGeneratorQueue::GetPreviewImage(evinfo, token); 299 295 300 296 return true; 301 297 } … … 458 454 459 455 ////////////////////////////////////////////////////////////////////// 460 456 461 const uint PreviewGenState::maxAttempts = 5;462 const uint PreviewGenState::minBlockSeconds = 60;463 464 457 PlaybackBoxHelper::PlaybackBoxHelper(QObject *listener) : 465 458 m_listener(listener), m_eventHandler(NULL), 466 459 // Free Space Tracking Variables 467 m_freeSpaceTotalMB(0ULL), m_freeSpaceUsedMB(0ULL), 468 // Preview Image Variables 469 m_previewGeneratorRunning(0), m_previewGeneratorMaxThreads(2) 460 m_freeSpaceTotalMB(0ULL), m_freeSpaceUsedMB(0ULL) 470 461 { 471 m_previewGeneratorMode = PreviewGenerator::kRemote;472 473 int idealThreads = QThread::idealThreadCount();474 if (idealThreads >= 1)475 m_previewGeneratorMaxThreads = idealThreads * 2;476 477 462 start(); 478 463 } 479 464 … … 482 467 exit(); 483 468 wait(); 484 469 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 470 // delete the event handler 495 471 delete m_eventHandler; 496 472 m_eventHandler = NULL; … … 620 596 return QString(); 621 597 } 622 598 623 voidPlaybackBoxHelper::GetPreviewImage(const ProgramInfo &pginfo)599 QString PlaybackBoxHelper::GetPreviewImage(const ProgramInfo &pginfo) 624 600 { 625 QStringList extra;626 pginfo.ToStringList(extra);627 MythEvent *e = new MythEvent("GET_PREVIEW", extra);628 QCoreApplication::postEvent(m_eventHandler, e);629 }630 631 QString PlaybackBoxHelper::GeneratePreviewImage(ProgramInfo &pginfo)632 {633 601 if (pginfo.GetAvailableStatus() == asPendingDelete) 634 602 return QString(); 635 603 636 QString filename = pginfo.GetPathname() + ".png"; 604 QString token = QString("%1:%2") 605 .arg(pginfo.MakeUniqueKey()).arg(rand()); 637 606 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);607 QStringList extra(token); 608 pginfo.ToStringList(extra); 609 MythEvent *e = new MythEvent("GET_PREVIEW", extra); 610 QCoreApplication::postEvent(m_eventHandler, e); 642 611 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 avoid 662 // hitting the backend if our cached copy 663 // is newer than the bookmark, or if we have 664 // a preview and do not update it when the 665 // bookmark changes. 666 previewLastModified = finfo.lastModified(); 667 } 668 else if (!IsGeneratingPreview(filename)) 669 { 670 previewLastModified = 671 RemoteGetPreviewIfModified(pginfo, ret_file); 672 } 673 } 674 else 675 { 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; 612 return token; 759 613 } 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" … … 1458 1459 1459 1460 BackendConnectionManager bcm; 1460 1461 1462 PreviewGeneratorQueue::CreatePreviewGeneratorQueue( 1463 PreviewGenerator::kRemote, 50, 60); 1464 1461 1465 int ret = qApp->exec(); 1462 1466 1467 PreviewGeneratorQueue::TeardownPreviewGeneratorQueue(); 1468 1463 1469 delete sysEventHandler; 1464 1470 1465 1471 pmanager->DestroyAllPlugins(); -
programs/mythfrontend/playbackbox.h
16 16 #include <QObject> 17 17 #include <QMutex> 18 18 #include <QMap> 19 #include <QSet> 19 20 20 21 #include "jobqueue.h" 21 22 #include "tv_play.h" … … 317 318 void UpdateUIListItem(MythUIButtonListItem *item, bool is_sel, 318 319 bool force_preview_reload = false); 319 320 320 void HandlePreviewEvent(const QString &piKey, const QString &previewFile);321 void HandlePreviewEvent(const QStringList &list); 321 322 void HandleRecordingRemoveEvent(uint chanid, const QDateTime &recstartts); 322 323 void HandleRecordingAddEvent(const ProgramInfo &evinfo); 323 324 void HandleUpdateProgramInfoEvent(const ProgramInfo &evinfo); … … 443 444 QStringList m_player_selected_new_show; 444 445 /// Main helper thread 445 446 PlaybackBoxHelper m_helper; 447 /// Outstanding preview image requests 448 QSet<QString> m_preview_tokens; 446 449 }; 447 450 448 451 class GroupSelector : public MythScreenType -
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/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
37 37 #include <QTcpServer> 38 38 #include <QTimer> 39 39 40 #include "previewgeneratorqueue.h" 40 41 #include "exitcodes.h" 41 42 #include "mythcontext.h" 42 43 #include "mythverbose.h" … … 53 54 #include "scheduledrecording.h" 54 55 #include "jobqueue.h" 55 56 #include "autoexpire.h" 56 #include "previewgenerator.h"57 57 #include "storagegroup.h" 58 58 #include "compat.h" 59 59 #include "RingBuffer.h" … … 184 184 { 185 185 AutoExpire::Update(true); 186 186 187 PreviewGeneratorQueue::CreatePreviewGeneratorQueue( 188 PreviewGenerator::kLocalAndRemote, ~0, 0); 189 PreviewGeneratorQueue::AddListener(this); 190 187 191 for (int i = 0; i < PRT_STARTUP_THREAD_COUNT; i++) 188 192 { 189 193 ProcessRequestThread *prt = new ProcessRequestThread(this); … … 237 241 238 242 MainServer::~MainServer() 239 243 { 244 PreviewGeneratorQueue::RemoveListener(this); 245 PreviewGeneratorQueue::TeardownPreviewGeneratorQueue(); 246 240 247 if (mythserver) 241 248 { 242 249 mythserver->disconnect(); … … 546 553 else 547 554 HandleFileTransferQuery(listline, tokens, pbs); 548 555 } 549 else if (command == "QUERY_GENPIXMAP ")556 else if (command == "QUERY_GENPIXMAP2") 550 557 { 551 558 HandleGenPreviewPixmap(listline, pbs); 552 559 } … … 734 741 { 735 742 MythEvent *me = (MythEvent *)e; 736 743 744 QString message = me->Message(); 745 QString error; 746 if ((message == "PREVIEW_SUCCESS" || message == "PREVIEW_QUEUED") && 747 me->ExtraDataCount() >= 4) 748 { 749 bool ok = true; 750 QString pginfokey = me->ExtraData(0); // pginfo->MakeUniqueKey() 751 QString filename = me->ExtraData(1); // outFileName 752 QString msg = me->ExtraData(2); 753 754 if (message == "PREVIEW_QUEUED") 755 { 756 VERBOSE(VB_IMPORTANT, QString("Preview Queued: '%1' '%2'") 757 .arg(pginfokey).arg(filename)); 758 return; 759 } 760 761 QFile file(filename); 762 ok = ok && file.open(QIODevice::ReadOnly); 763 764 if (ok) 765 { 766 VERBOSE(VB_IMPORTANT, QString("Preview Success: '%1' '%2'") 767 .arg(pginfokey).arg(filename)); 768 769 QByteArray data = file.readAll(); 770 QStringList extra("OK"); 771 extra.push_back(QString::number(data.size())); 772 extra.push_back( 773 QString::number(qChecksum(data.constData(), data.size()))); 774 extra.push_back(QString(data.toBase64())); 775 776 for (uint i = 3 ; i < (uint) me->ExtraDataCount(); i++) 777 extra.push_back(me->ExtraData(i)); 778 779 MythEvent me("GENERATED_PIXMAP", extra); 780 gCoreContext->dispatch(me); 781 return; 782 } 783 else 784 { 785 message = "PREVIEW_FAILED"; 786 error = QString("Failed to read '%1'").arg(filename); 787 VERBOSE(VB_IMPORTANT, LOC_ERR + error); 788 } 789 } 790 791 if (message == "PREVIEW_FAILED" && me->ExtraDataCount() >= 4) 792 { 793 QString pginfokey = me->ExtraData(0); // pginfo->MakeUniqueKey() 794 QString filename = me->ExtraData(1); // outFileName 795 QString msg = me->ExtraData(2); 796 797 QStringList extra("ERROR"); 798 extra.push_back(msg); 799 for (uint i = 3 ; i < (uint) me->ExtraDataCount(); i++) 800 extra.push_back(me->ExtraData(i)); 801 802 MythEvent me("GENERATED_PIXMAP", extra); 803 gCoreContext->dispatch(me); 804 return; 805 } 806 737 807 if (me->Message().left(11) == "AUTO_EXPIRE") 738 808 { 739 809 QStringList tokens = me->Message() … … 4876 4946 { 4877 4947 MythSocket *pbssock = pbs->getSocket(); 4878 4948 4949 if (slist.size() < 3) 4950 { 4951 VERBOSE(VB_IMPORTANT, LOC_ERR + "Too few params in pixmap request"); 4952 QStringList outputlist("ERROR"); 4953 outputlist += "TOO_FEW_PARAMS"; 4954 SendResponse(pbssock, outputlist); 4955 return; 4956 } 4957 4879 4958 bool time_fmt_sec = true; 4880 4959 long long time = -1; 4881 4960 QString outputfile; … … 4883 4962 int height = -1; 4884 4963 bool has_extra_data = false; 4885 4964 4886 QStringList::const_iterator it = slist.begin() + 1; 4965 QString token = slist[1]; 4966 if (token.isEmpty()) 4967 { 4968 VERBOSE(VB_IMPORTANT, LOC_ERR + "Failed to parse pixmap request. " 4969 "Token absent"); 4970 QStringList outputlist("ERROR"); 4971 outputlist += "TOKEN_ABSENT"; 4972 SendResponse(pbssock, outputlist); 4973 } 4974 4975 QStringList::const_iterator it = slist.begin() + 2; 4887 4976 QStringList::const_iterator end = slist.end(); 4888 4977 ProgramInfo pginfo(it, end); 4889 4978 bool ok = pginfo.HasPathname(); 4890 4979 if (!ok) 4891 4980 { 4892 VERBOSE(VB_IMPORTANT, "MainServer: Failed to parse pixmap request."); 4981 VERBOSE(VB_IMPORTANT, LOC_ERR + "Failed to parse pixmap request. " 4982 "ProgramInfo missing pathname"); 4893 4983 QStringList outputlist("BAD"); 4894 outputlist += " ERROR_INVALID_REQUEST";4984 outputlist += "NO_PATHNAME"; 4895 4985 SendResponse(pbssock, outputlist); 4896 4986 } 4897 4987 if (it != slist.end()) … … 4936 5026 if (has_extra_data) 4937 5027 { 4938 5028 outputlist = slave->GenPreviewPixmap( 4939 &pginfo, time_fmt_sec, time, outputfile, outputsize);5029 token, &pginfo, time_fmt_sec, time, outputfile, outputsize); 4940 5030 } 4941 5031 else 4942 5032 { 4943 outputlist = slave->GenPreviewPixmap( &pginfo);5033 outputlist = slave->GenPreviewPixmap(token, &pginfo); 4944 5034 } 4945 5035 4946 5036 slave->DownRef(); … … 4955 5045 4956 5046 if (!pginfo.IsLocal()) 4957 5047 { 4958 VERBOSE(VB_IMPORTANT, "MainServer:HandleGenPreviewPixmap: Unable to "5048 VERBOSE(VB_IMPORTANT, LOC_ERR + "HandleGenPreviewPixmap: Unable to " 4959 5049 "find file locally, unable to make preview image."); 4960 QStringList outputlist( " BAD" );4961 outputlist += " ERROR_NOFILE";5050 QStringList outputlist( "ERROR" ); 5051 outputlist += "FILE_INACCESSIBLE"; 4962 5052 SendResponse(pbssock, outputlist); 4963 5053 return; 4964 5054 } 4965 5055 4966 PreviewGenerator *previewgen = new PreviewGenerator(&pginfo);4967 5056 if (has_extra_data) 4968 5057 { 4969 previewgen->SetOutputSize(outputsize); 4970 previewgen->SetOutputFilename(outputfile); 4971 previewgen->SetPreviewTime(time, time_fmt_sec); 5058 PreviewGeneratorQueue::GetPreviewImage( 5059 pginfo, outputsize, outputfile, time, time_fmt_sec, token); 4972 5060 } 4973 ok = previewgen->Run();4974 previewgen->deleteLater();4975 4976 if (ok)4977 {4978 QStringList outputlist("OK");4979 if (!outputfile.isEmpty())4980 outputlist += outputfile;4981 SendResponse(pbssock, outputlist);4982 }4983 5061 else 4984 5062 { 4985 VERBOSE(VB_IMPORTANT, "MainServer: Failed to make preview image."); 4986 QStringList outputlist( "BAD" ); 4987 outputlist += "ERROR_UNKNOWN"; 4988 SendResponse(pbssock, outputlist); 5063 PreviewGeneratorQueue::GetPreviewImage(pginfo, token); 4989 5064 } 5065 5066 QStringList outputlist("OK"); 5067 if (!outputfile.isEmpty()) 5068 outputlist += outputfile; 5069 SendResponse(pbssock, outputlist); 4990 5070 } 4991 5071 4992 5072 void MainServer::HandlePixmapLastModified(QStringList &slist, PlaybackSock *pbs)