Ticket #7195: 7191-gen2-v1.patch

File 7191-gen2-v1.patch, 63.8 KB (added by danielk, 14 years ago)

new set of changes to preview generation.

  • libs/libmythtv/libmythtv.pro

     
    153153HEADERS += scheduledrecording.h
    154154HEADERS += signalmonitorvalue.h     signalmonitorlistener.h
    155155HEADERS += livetvchain.h            playgroup.h
    156 HEADERS += channelsettings.h        previewgenerator.h
     156HEADERS += channelsettings.h
     157HEADERS += previewgenerator.h       previewgeneratorqueue.h
    157158HEADERS += transporteditor.h        listingsources.h
    158159HEADERS += myth_imgconvert.h
    159160HEADERS += channelgroup.h           channelgroupsettings.h
     
    177178SOURCES += scheduledrecording.cpp
    178179SOURCES += signalmonitorvalue.cpp
    179180SOURCES += livetvchain.cpp          playgroup.cpp
    180 SOURCES += channelsettings.cpp      previewgenerator.cpp
     181SOURCES += channelsettings.cpp
     182SOURCES += previewgenerator.cpp     previewgeneratorqueue.cpp
    181183SOURCES += transporteditor.cpp
    182184SOURCES += channelgroup.cpp         channelgroupsettings.cpp
    183185SOURCES += 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
     15class ProgramInfo;
     16class QSize;
     17
     18class 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};
     31typedef QMap<QString,PreviewGenState> PreviewMap;
     32
     33class 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

     
    66#include <fcntl.h>
    77
    88// Qt headers
     9#include <QCoreApplication>
     10#include <QTemporaryFile>
    911#include <QFileInfo>
     12#include <QMetaType>
     13#include <QThread>
    1014#include <QImage>
    11 #include <QMetaType>
     15#include <QDir>
    1216#include <QUrl>
    13 #include <QDir>
    1417
    1518// MythTV headers
    1619#include "mythconfig.h"
     
    2730#include "playercontext.h"
    2831#include "mythdirs.h"
    2932#include "mythverbose.h"
     33#include "remoteutil.h"
    3034
    3135#define LOC QString("Preview: ")
    3236#define LOC_ERR QString("Preview Error: ")
     
    3741 *
    3842 *   The usage is simple: First, pass a ProgramInfo whose pathname points
    3943 *   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.
    4145 *
    42  *   Start(void) will create a thread that processes the request,
     46 *   start(void) will create a thread that processes the request,
    4347 *   creating a sockets the the backend if the recording is not local.
    4448 *
    4549 *   Run(void) will process the request in the current thread, and it
     
    6670PreviewGenerator::PreviewGenerator(const ProgramInfo *pginfo,
    6771                                   PreviewGenerator::Mode _mode)
    6872    : programInfo(*pginfo), mode(_mode), isConnected(false),
    69       createSockets(false), serverSock(NULL), pathname(pginfo->GetPathname()),
     73      pathname(pginfo->GetPathname()),
    7074      timeInSeconds(true),  captureTime(-1),  outFileName(QString::null),
    71       outSize(0,0)
     75      outSize(0,0), pixmapOk(false)
    7276{
    7377}
    7478
     
    8488
    8589void PreviewGenerator::TeardownAll(void)
    8690{
     91    previewWaitCondition.wakeAll();
     92
    8793    if (!isConnected)
    8894        return;
    8995
     
    114120void PreviewGenerator::AttachSignals(QObject *obj)
    115121{
    116122    QMutexLocker locker(&previewLock);
    117     qRegisterMetaType<bool>("bool &");
    118123    connect(this, SIGNAL(previewThreadDone(const QString&,bool&)),
    119124            obj,  SLOT(  previewThreadDone(const QString&,bool&)),
    120125            Qt::DirectConnection);
     
    135140    isConnected = false;
    136141}
    137142
    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 
    148143/** \fn PreviewGenerator::RunReal(void)
    149144 *  \brief This call creates a preview without starting a new thread.
    150145 */
     
    264259    return ok;
    265260}
    266261
    267 void *PreviewGenerator::PreviewRun(void *param)
     262void PreviewGenerator::run(void)
    268263{
    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()));
    277268}
    278269
    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 
    290270bool PreviewGenerator::RemotePreviewRun(void)
    291271{
    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);
    293276    programInfo.ToStringList(strlist);
    294277    strlist.push_back(timeInSeconds ? "s" : "f");
    295278    encodeLongLong(strlist, captureTime);
     
    305288    strlist.push_back(QString::number(outSize.width()));
    306289    strlist.push_back(QString::number(outSize.height()));
    307290
    308     bool ok = false;
     291    gCoreContext->addListener(this);
     292    pixmapOk = false;
    309293
    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);
    331295    if (!ok || strlist.empty() || (strlist[0] != "OK"))
    332296    {
    333297        if (!ok)
     
    340304            VERBOSE(VB_IMPORTANT, LOC_ERR +
    341305                    "Remote Preview failed, reason given: " <<strlist[1]);
    342306        }
    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
     322bool 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)
    344329        {
    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;
    347363        }
    348         return false;
    349364    }
     365    return QObject::event(e);
     366}
    350367
     368bool PreviewGenerator::SaveOutFile(QByteArray &data)
     369{
    351370    if (outFileName.isEmpty())
    352371    {
    353         QString remotecachedirname = QString("%1/remotecache").arg(GetConfDir());
     372        QString remotecachedirname =
     373            QString("%1/remotecache").arg(GetConfDir());
    354374        QDir remotecachedir(remotecachedirname);
    355375
    356376        if (!remotecachedir.exists())
     
    368388        outFileName = QString("%1/%2").arg(remotecachedirname).arg(filename);
    369389    }
    370390
    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;
    372398
    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(), ""));
    377404
    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    }
    383415
    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)
    386422    {
    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));
    392425    }
    393426
    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))
    395431    {
    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)
    399434        {
    400             VERBOSE(VB_IMPORTANT, QString("Failed to open: '%1'")
    401                     .arg(outFileName));
     435            failure_cnt++;
     436            usleep(50000);
     437            continue;
    402438        }
    403439
    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;
    426443    }
    427 
    428     return ok && data.size();
    429 }
    430 
    431 void PreviewGenerator::RemotePreviewTeardown(void)
    432 {
    433     if (serverSock)
     444    if (ok && !remaining)
    434445    {
    435         serverSock->DownRef();
    436         serverSock = NULL;
     446        VERBOSE(VB_PLAYBACK, QString("Saved: '%1'").arg(outFileName));
    437447    }
     448    else
     449    {
     450        file.remove();
     451    }
     452
     453    return ok;
    438454}
    439455
    440456bool PreviewGenerator::SavePreview(QString filename,
     
    476492    QImage small_img = img.scaled((int) ppw, (int) pph,
    477493        Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
    478494
    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"))
    481498    {
    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();
    489511    }
    490512
    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));
    499516
    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?
    508517    return false;
    509518}
    510519
  • 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
     16PreviewGeneratorQueue *PreviewGeneratorQueue::s_pgq = NULL;
     17
     18void PreviewGeneratorQueue::CreatePreviewGeneratorQueue(
     19    PreviewGenerator::Mode mode,
     20    uint maxAttempts, uint minBlockSeconds)
     21{
     22    s_pgq = new PreviewGeneratorQueue(mode, maxAttempts, minBlockSeconds);
     23}
     24
     25void PreviewGeneratorQueue::TeardownPreviewGeneratorQueue()
     26{
     27    s_pgq->exit(0);
     28    s_pgq->wait();
     29    delete s_pgq;
     30}
     31
     32PreviewGeneratorQueue::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
     49PreviewGeneratorQueue::~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
     61void 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
     80void 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
     107void 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
     116void 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
     125bool 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
     177void 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
     198QString 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
     209QString 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}
     338void 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
     350void 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 */
     372bool 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 */
     416bool 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 */
     439uint 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 */
     450void 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
     460void 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 */
     472void 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

     
    1111using namespace std;
    1212
    1313// MythTV headers
     14#include "previewgeneratorqueue.h"
    1415#include "mythconfig.h"
    1516#include "tv_rec.h"
    1617#include "osd.h"
     
    2627#include "recordingrule.h"
    2728#include "eitscanner.h"
    2829#include "RingBuffer.h"
    29 #include "previewgenerator.h"
    3030#include "storagegroup.h"
    3131#include "remoteutil.h"
    3232#include "tvremoteutil.h"
     
    11211121        if (!killFile)
    11221122        {
    11231123            if (curRecording->IsLocal())
    1124             {
    1125                 (new PreviewGenerator(
    1126                     curRecording, PreviewGenerator::kLocal))->Start();
    1127             }
     1124                PreviewGeneratorQueue::GetPreviewImage(*curRecording);
    11281125
    11291126            if (!tvchain)
    11301127            {
     
    44594456            if (!oldinfo->IsLocal())
    44604457                oldinfo->SetPathname(oldinfo->GetPlaybackURL(false,true));
    44614458            if (oldinfo->IsLocal())
    4462             {
    4463                 (new PreviewGenerator(
    4464                     oldinfo, PreviewGenerator::kLocal))->Start();
    4465             }
     4459                PreviewGeneratorQueue::GetPreviewImage(*oldinfo);
    44664460        }
    44674461        delete oldinfo;
    44684462    }
  • libs/libmythtv/previewgenerator.h

     
    44
    55#include <pthread.h>
    66
     7#include <QWaitCondition>
    78#include <QString>
     9#include <QThread>
    810#include <QMutex>
    911#include <QSize>
     12#include <QSet>
    1013
    1114#include "programinfo.h"
    1215#include "util.h"
    1316
    1417class MythSocket;
     18class PreviewGenerator;
    1519
    16 class MPUBLIC PreviewGenerator : public QObject
     20// TODO Changes to make...
     21// Don't use signals and slots, instead use MythEvents
     22
     23typedef QMap<QString,QDateTime> FileTimeStampMap;
     24
     25class MPUBLIC PreviewGenerator : public QThread
    1726{
    1827    friend int preview_helper(const QString &chanid,
    1928                              const QString &starttime,
     
    4756    void SetOutputFilename(const QString&);
    4857    void SetOutputSize(const QSize &size) { outSize = size; }
    4958
    50     void Start(void);
     59    void run(void); // QThread
    5160    bool Run(void);
    5261
    5362    void AttachSignals(QObject*);
     
    6473    virtual ~PreviewGenerator();
    6574    void TeardownAll(void);
    6675
    67     bool RemotePreviewSetup(void);
    6876    bool RemotePreviewRun(void);
    69     void RemotePreviewTeardown(void);
    70 
    7177    bool LocalPreviewRun(void);
    7278    bool IsLocal(void) const;
    7379
    7480    bool RunReal(void);
    7581
    76     static void *PreviewRun(void*);
    77 
    7882    static char *GetScreenGrab(const ProgramInfo &pginfo,
    7983                               const QString     &filename,
    8084                               long long          seektime,
     
    9397    static QString CreateAccessibleFilename(
    9498        const QString &pathname, const QString &outFileName);
    9599
    96   protected:
     100    virtual bool event(QEvent *e); // QObject
     101    bool SaveOutFile(QByteArray &data);
     102
     103//  protected:
     104  public:
     105    QWaitCondition     previewWaitCondition;
    97106    QMutex             previewLock;
    98107    pthread_t          previewThread;
    99108    ProgramInfo        programInfo;
    100109
    101110    Mode               mode;
    102111    bool               isConnected;
    103     bool               createSockets;
    104     MythSocket        *serverSock;
    105112    QString            pathname;
    106113
    107114    /// tells us whether to use time as seconds or frame number
     
    110117    long long          captureTime;
    111118    QString            outFileName;
    112119    QSize              outSize;
     120
     121    QString            token;
     122    bool               pixmapOk;
    113123};
    114124
    115125#endif // PREVIEW_GENERATOR_H_
  • programs/mythfrontend/playbackbox.cpp

     
    88#include <QTimer>
    99#include <QMap>
    1010
    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"
    2014#include "NuppelVideoPlayer.h"
    21 #include "recordinginfo.h"
    22 #include "playgroup.h"
    23 #include "mythsystemevent.h"
    24 
    25 // libmyth
    26 #include "mythcorecontext.h"
    27 #include "util.h"
    28 #include "storagegroup.h"
    29 #include "programinfo.h"
    30 
    31 // libmythui
    32 #include "mythuihelper.h"
    33 #include "mythuitext.h"
    34 #include "mythuibutton.h"
    3515#include "mythuibuttonlist.h"
     16#include "mythcorecontext.h"
     17#include "mythsystemevent.h"
    3618#include "mythuistatetype.h"
    37 #include "mythdialogbox.h"
     19#include "mythuicheckbox.h"
    3820#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"
    3927#include "mythuiimage.h"
    40 #include "mythuicheckbox.h"
    41 #include "mythuiprogressbar.h"
    42 
     28#include "programinfo.h"
     29#include "oldsettings.h"
     30#include "mythuitext.h"
    4331#include "remoteutil.h"
    4432
    4533//  Mythfrontend
    4634#include "playbackboxlistitem.h"
    4735#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"
    4843
    4944#define LOC      QString("PlaybackBox: ")
    5045#define LOC_WARN QString("PlaybackBox Warning: ")
     
    447442PlaybackBox::~PlaybackBox(void)
    448443{
    449444    gCoreContext->removeListener(this);
     445    PreviewGeneratorQueue::RemoveListener(this);
    450446
    451447    for (uint i = 0; i < sizeof(m_artImage) / sizeof(MythUIImage*); i++)
    452448    {
     
    517513void PlaybackBox::Load(void)
    518514{
    519515    m_programInfoCache.WaitForLoadToComplete();
     516    PreviewGeneratorQueue::AddListener(this);
    520517}
    521518
    522519void PlaybackBox::Init()
     
    38033800            // asPendingDelete, we need to put them back now..
    38043801            ScheduleUpdateUIList();
    38053802        }
    3806         else if (message == "PREVIEW_READY" && me->ExtraDataCount() == 2)
     3803        else if (message == "PREVIEW_READY" && me->ExtraDataCount() >= 2)
    38073804        {
     3805            VERBOSE(VB_IMPORTANT, "PBB got PREVIEW_READY");
    38083806            HandlePreviewEvent(me->ExtraData(0), me->ExtraData(1));
    38093807        }
    38103808        else if (message == "AVAILABILITY" && me->ExtraDataCount() == 8)
  • programs/mythfrontend/playbackboxhelper.h

     
    1717class QObject;
    1818class QTimer;
    1919
    20 class PreviewGenState
    21 {
    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 
    3920typedef enum CheckAvailabilityType {
    4021    kCheckForCache,
    4122    kCheckForMenuAction,
     
    8364    uint64_t GetFreeSpaceTotalMB(void) const;
    8465    uint64_t GetFreeSpaceUsedMB(void) const;
    8566
    86   private slots:
    87     void previewThreadDone(const QString &fn, bool &success);
    88     void previewReady(const ProgramInfo *pginfo);
    89 
    9067  private:
    9168    void UpdateFreeSpace(void);
    9269
    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 
    10170  private:
    10271    QObject            *m_listener;
    10372    PBHEventHandler    *m_eventHandler;
     
    10776    uint64_t            m_freeSpaceTotalMB;
    10877    uint64_t            m_freeSpaceUsedMB;
    10978
    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 
    12079    // Artwork Variables //////////////////////////////////////////////////////
    12180    QHash<QString, QString>    m_artworkFilenameCache;
    12281};
  • programs/mythfrontend/playbackboxhelper.cpp

     
    77#include <QFileInfo>
    88#include <QDir>
    99
     10#include "previewgeneratorqueue.h"
    1011#include "playbackboxhelper.h"
    11 #include "previewgenerator.h"
    1212#include "mythcorecontext.h"
    1313#include "tvremoteutil.h"
    1414#include "storagegroup.h"
     
    287287            if (asAvailable != CheckAvailability(list))
    288288                return true;
    289289
    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);
    299292
    300293            return true;
    301294        }
     
    458451
    459452//////////////////////////////////////////////////////////////////////
    460453
    461 const uint PreviewGenState::maxAttempts     = 5;
    462 const uint PreviewGenState::minBlockSeconds = 60;
    463 
    464454PlaybackBoxHelper::PlaybackBoxHelper(QObject *listener) :
    465455    m_listener(listener), m_eventHandler(NULL),
    466456    // 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)
    470458{
    471     m_previewGeneratorMode = PreviewGenerator::kRemote;
    472 
    473     int idealThreads = QThread::idealThreadCount();
    474     if (idealThreads >= 1)
    475         m_previewGeneratorMaxThreads = idealThreads * 2;
    476 
    477459    start();
    478460}
    479461
     
    482464    exit();
    483465    wait();
    484466
    485     // disconnect preview generators
    486     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 
    494467    // delete the event handler
    495468    delete m_eventHandler;
    496469    m_eventHandler = NULL;
     
    622595
    623596void PlaybackBoxHelper::GetPreviewImage(const ProgramInfo &pginfo)
    624597{
     598    if (pginfo.GetAvailableStatus() == asPendingDelete)
     599        return;
     600
    625601    QStringList extra;
    626602    pginfo.ToStringList(extra);
    627603    MythEvent *e = new MythEvent("GET_PREVIEW", extra);
    628604    QCoreApplication::postEvent(m_eventHandler, e);
    629605}
    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 screen
    639     // and hence higher priority than anything else we may have
    640     // 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 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;
    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) const
    837  *  \brief Returns true if we have already started a
    838  *         PreviewGenerator to create this file.
    839  */
    840 bool PlaybackBoxHelper::IsGeneratingPreview(const QString &xfn, bool really) const
    841 {
    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 have
    860  *         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 have
    871  *         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_preview
    892  *         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

     
    1919#include <QApplication>
    2020#include <QTimer>
    2121
     22#include "previewgeneratorqueue.h"
    2223#include "mythconfig.h"
    2324#include "tv.h"
    2425#include "proglist.h"
     
    14691470
    14701471    BackendConnectionManager bcm;
    14711472
     1473    PreviewGeneratorQueue::CreatePreviewGeneratorQueue(
     1474        PreviewGenerator::kRemote, 50, 60);
     1475
    14721476    int ret = qApp->exec();
    14731477
     1478    PreviewGeneratorQueue::TeardownPreviewGeneratorQueue();
     1479
    14741480    delete sysEventHandler;
    14751481
    14761482    pmanager->DestroyAllPlugins();
  • programs/mythfrontend/playbackbox.h

     
    1111#include <deque>
    1212using namespace std;
    1313
     14// Qt headers
    1415#include <QStringList>
    1516#include <QDateTime>
    1617#include <QObject>
    1718#include <QMutex>
    1819#include <QMap>
    1920
     21// MythTV headers
     22#include "playbackboxhelper.h"
     23#include "programinfocache.h"
     24#include "mythscreentype.h"
     25#include "schedulecommon.h"
    2026#include "jobqueue.h"
    2127#include "tv_play.h"
    2228
    23 #include "mythscreentype.h"
    24 
    25 // mythfrontend
    26 #include "schedulecommon.h"
    27 #include "programinfocache.h"
    28 #include "playbackboxhelper.h"
    29 
    3029class QKeyEvent;
    3130class QEvent;
    3231class QTimer;
  • programs/mythbackend/playbacksock.h

     
    7070    QStringList GetSGFileQuery(QString &host, QString &groupname,
    7171                               QString &filename);
    7272
    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,
    7577                                 bool               time_fmt_sec,
    7678                                 long long          time,
    7779                                 const QString     &outputFile,
  • programs/mythbackend/mainserver.h

     
    2424#undef DeleteFile
    2525#endif
    2626
     27class QTimer;
     28class QUrl;
     29
    2730class ProcessRequestThread;
    28 class QUrl;
    2931class MythServer;
    30 class QTimer;
    3132
    3233class MainServer : public QObject, public MythSocketCBs
    3334{
  • programs/mythbackend/main.cpp

     
    5151#include "programinfo.h"
    5252#include "dbcheck.h"
    5353#include "jobqueue.h"
    54 #include "previewgenerator.h"
    5554#include "mythcommandlineparser.h"
    5655#include "mythsystemevent.h"
    5756
  • programs/mythbackend/playbacksock.cpp

     
    262262    return strlist;
    263263}
    264264
    265 QStringList PlaybackSock::GenPreviewPixmap(const ProgramInfo *pginfo)
     265QStringList PlaybackSock::GenPreviewPixmap(
     266    const QString &token, const ProgramInfo *pginfo)
    266267{
    267     QStringList strlist( QString("QUERY_GENPIXMAP") );
     268    QStringList strlist( QString("QUERY_GENPIXMAP2") );
     269    strlist += token;
    268270    pginfo->ToStringList(strlist);
    269271
    270272    SendReceiveStringList(strlist);
     
    272274    return strlist;
    273275}
    274276
    275 QStringList PlaybackSock::GenPreviewPixmap(const ProgramInfo *pginfo,
     277QStringList PlaybackSock::GenPreviewPixmap(const QString &token,
     278                                           const ProgramInfo *pginfo,
    276279                                           bool               time_fmt_sec,
    277280                                           long long          time,
    278281                                           const QString     &outputFile,
    279282                                           const QSize       &outputSize)
    280283{
    281     QStringList strlist(QString("QUERY_GENPIXMAP"));
     284    QStringList strlist(QString("QUERY_GENPIXMAP2"));
     285    strlist += token;
    282286    pginfo->ToStringList(strlist);
    283287    strlist.push_back(time_fmt_sec ? "s" : "f");
    284288    encodeLongLong(strlist, time);
  • programs/mythbackend/mainserver.cpp

     
    2626#endif // !__linux__
    2727
    2828#include <QCoreApplication>
     29#include <QThreadPool>
    2930#include <QDateTime>
     31#include <QRunnable>
    3032#include <QFile>
    3133#include <QDir>
    3234#include <QThread>
     
    3739#include <QTcpServer>
    3840#include <QTimer>
    3941
     42#include "previewgeneratorqueue.h"
    4043#include "exitcodes.h"
    4144#include "mythcontext.h"
    4245#include "mythverbose.h"
     
    5356#include "scheduledrecording.h"
    5457#include "jobqueue.h"
    5558#include "autoexpire.h"
    56 #include "previewgenerator.h"
    5759#include "storagegroup.h"
    5860#include "compat.h"
    5961#include "RingBuffer.h"
     
    6264#include "tv.h"
    6365#include "mythcorecontext.h"
    6466#include "mythcoreutil.h"
     67#include "util.h"
    6568#include "mythdirs.h"
    6669#include "mythdownloadmanager.h"
    6770
    68 
    6971/** Milliseconds to wait for an existing thread from
    7072 *  process request thread pool.
    7173 */
     
    184186{
    185187    AutoExpire::Update(true);
    186188
     189    PreviewGeneratorQueue::CreatePreviewGeneratorQueue(
     190        PreviewGenerator::kLocalAndRemote, ~0, 0);
     191    PreviewGeneratorQueue::AddListener(this);
     192
    187193    for (int i = 0; i < PRT_STARTUP_THREAD_COUNT; i++)
    188194    {
    189195        ProcessRequestThread *prt = new ProcessRequestThread(this);
     
    237243
    238244MainServer::~MainServer()
    239245{
     246    PreviewGeneratorQueue::RemoveListener(this);
     247    PreviewGeneratorQueue::TeardownPreviewGeneratorQueue();
     248
    240249    if (mythserver)
    241250    {
    242251        mythserver->disconnect();
     
    546555        else
    547556            HandleFileTransferQuery(listline, tokens, pbs);
    548557    }
    549     else if (command == "QUERY_GENPIXMAP")
     558    else if (command == "QUERY_GENPIXMAP2")
    550559    {
    551560        HandleGenPreviewPixmap(listline, pbs);
    552561    }
     
    734743    {
    735744        MythEvent *me = (MythEvent *)e;
    736745
     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
    737789        if (me->Message().left(11) == "AUTO_EXPIRE")
    738790        {
    739791            QStringList tokens = me->Message()
     
    48724924{
    48734925    MythSocket *pbssock = pbs->getSocket();
    48744926
     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
    48754936    bool      time_fmt_sec   = true;
    48764937    long long time           = -1;
    48774938    QString   outputfile;
     
    48794940    int       height         = -1;
    48804941    bool      has_extra_data = false;
    48814942
    4882     QStringList::const_iterator it = slist.begin() + 1;
     4943    QString token = slist[1];
     4944    QStringList::const_iterator it = slist.begin() + 2;
    48834945    QStringList::const_iterator end = slist.end();
    48844946    ProgramInfo pginfo(it, end);
    48854947    bool ok = pginfo.HasPathname();
     
    49324994            if (has_extra_data)
    49334995            {
    49344996                outputlist = slave->GenPreviewPixmap(
    4935                     &pginfo, time_fmt_sec, time, outputfile, outputsize);
     4997                    token, &pginfo, time_fmt_sec, time, outputfile, outputsize);
    49364998            }
    49374999            else
    49385000            {
    4939                 outputlist = slave->GenPreviewPixmap(&pginfo);
     5001                outputlist = slave->GenPreviewPixmap(token, &pginfo);
    49405002            }
    49415003
    49425004            slave->DownRef();
     
    49595021        return;
    49605022    }
    49615023
    4962     PreviewGenerator *previewgen = new PreviewGenerator(&pginfo);
    49635024    if (has_extra_data)
    49645025    {
    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);
    49685028    }
    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     }
    49795029    else
    49805030    {
    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);
    49855032    }
     5033
     5034    QStringList outputlist("OK");
     5035    if (!outputfile.isEmpty())
     5036        outputlist += outputfile;
     5037    SendResponse(pbssock, outputlist);
    49865038}
    49875039
    49885040void MainServer::HandlePixmapLastModified(QStringList &slist, PlaybackSock *pbs)