Ticket #7195: 7195-gen2-v9.patch

File 7195-gen2-v9.patch, 110.5 KB (added by danielk, 15 years ago)
  • mythplugins/mythweb/modules/tv/classes/Program.php

     
    456456 * Generate a new preview pixmap for this recording.
    457457/**/
    458458    public function generate_pixmap() {
    459         $ret = MythBackend::find()->sendCommand(array('QUERY_GENPIXMAP', $this->backend_row()));
    460         if ($ret == 'BAD') {
     459        $ret = MythBackend::find()->sendCommand(array('QUERY_GENPIXMAP2', "do_not_care", $this->backend_row()));
     460        if ($ret == 'ERROR') {
    461461            return 0;
    462462        }
    463463        return 1;
  • mythplugins/mythweb/classes/MythBackend.php

     
    1515
    1616// MYTH_PROTO_VERSION is defined in libmyth in mythtv/libs/libmyth/mythcontext.h
    1717// and should be the current MythTV protocol version.
    18     static $protocol_version        = '60';
     18    static $protocol_version        = '61';
    1919
    2020// The character string used by the backend to separate records
    2121    static $backend_separator       = '[]:[]';
  • mythtv/libs/libmythtv/jobqueue.cpp

     
    21692169        if (program_info->IsLocal())
    21702170        {
    21712171            PreviewGenerator *pg = new PreviewGenerator(
    2172                 program_info, PreviewGenerator::kLocal);
     2172                program_info, QString(), PreviewGenerator::kLocal);
    21732173            pg->Run();
    21742174            pg->deleteLater();
    21752175        }
  • mythtv/libs/libmythtv/libmythtv.pro

     
    156156HEADERS += scheduledrecording.h
    157157HEADERS += signalmonitorvalue.h     signalmonitorlistener.h
    158158HEADERS += livetvchain.h            playgroup.h
    159 HEADERS += channelsettings.h        previewgenerator.h
     159HEADERS += channelsettings.h
     160HEADERS += previewgenerator.h       previewgeneratorqueue.h
    160161HEADERS += transporteditor.h        listingsources.h
    161162HEADERS += myth_imgconvert.h
    162163HEADERS += channelgroup.h           channelgroupsettings.h
     
    179180SOURCES += scheduledrecording.cpp
    180181SOURCES += signalmonitorvalue.cpp
    181182SOURCES += livetvchain.cpp          playgroup.cpp
    182 SOURCES += channelsettings.cpp      previewgenerator.cpp
     183SOURCES += channelsettings.cpp
     184SOURCES += previewgenerator.cpp     previewgeneratorqueue.cpp
    183185SOURCES += transporteditor.cpp
    184186SOURCES += channelgroup.cpp         channelgroupsettings.cpp
    185187SOURCES += myth_imgconvert.cpp
  • mythtv/libs/libmythtv/tvremoteutil.h

     
    4545MPUBLIC vector<InputInfo> RemoteRequestFreeInputList(
    4646    uint cardid, const vector<uint> &excluded_cardids);
    4747MPUBLIC InputInfo RemoteRequestBusyInputID(uint cardid);
    48 MPUBLIC void RemoteGeneratePreviewPixmap(ProgramInfo *pginfo);
    4948MPUBLIC bool RemoteIsBusy(uint cardid, TunedInputInfo &busy_input);
    5049
    5150MPUBLIC bool RemoteGetRecordingStatus(
  • mythtv/libs/libmythtv/tvremoteutil.cpp

     
    264264    return blank;
    265265}
    266266
    267 void RemoteGeneratePreviewPixmap(ProgramInfo *pginfo)
    268 {
    269     QStringList strlist( "QUERY_GENPIXMAP" );
    270     pginfo->ToStringList(strlist);
    271 
    272     gCoreContext->SendReceiveStringList(strlist);
    273 }
    274 
    275267bool RemoteIsBusy(uint cardid, TunedInputInfo &busy_input)
    276268{
    277269    //VERBOSE(VB_IMPORTANT, QString("RemoteIsBusy(%1) %2")
  • mythtv/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),
     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};
     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 &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                   const QDateTime   &dt);
     80
     81  private:
     82    static PreviewGeneratorQueue *s_pgq;
     83    QSet<QObject*> m_listeners;
     84
     85    mutable QMutex         m_lock;
     86    PreviewGenerator::Mode m_mode;
     87    PreviewMap             m_previewMap;
     88    QMap<QString,QString>  m_tokenToKeyMap;
     89    QStringList            m_queue;
     90    uint                   m_running;
     91    uint                   m_maxThreads;
     92    uint                   m_maxAttempts;
     93    uint                   m_minBlockSeconds;
     94};
     95
     96#endif // _PREVIEW_GENERATOR_QUEUE_H_
  • mythtv/libs/libmythtv/previewgenerator.cpp

     
    22#include <cmath>
    33
    44// POSIX headers
     5#include <sys/types.h> // for utime
    56#include <sys/time.h>
    67#include <fcntl.h>
     8#include <utime.h>     // for utime
    79
    810// Qt headers
     11#include <QCoreApplication>
     12#include <QTemporaryFile>
    913#include <QFileInfo>
     14#include <QMetaType>
     15#include <QThread>
    1016#include <QImage>
    11 #include <QMetaType>
     17#include <QDir>
    1218#include <QUrl>
    13 #include <QDir>
    1419
    1520// MythTV headers
    1621#include "mythconfig.h"
     
    2732#include "playercontext.h"
    2833#include "mythdirs.h"
    2934#include "mythverbose.h"
     35#include "remoteutil.h"
    3036
    3137#define LOC QString("Preview: ")
    3238#define LOC_ERR QString("Preview Error: ")
     
    3743 *
    3844 *   The usage is simple: First, pass a ProgramInfo whose pathname points
    3945 *   to a local or remote recording to the constructor. Then call either
    40  *   Start(void) or Run(void) to generate the preview.
     46 *   start(void) or Run(void) to generate the preview.
    4147 *
    42  *   Start(void) will create a thread that processes the request,
    43  *   creating a sockets the the backend if the recording is not local.
     48 *   start(void) will create a thread that processes the request.
    4449 *
    45  *   Run(void) will process the request in the current thread, and it
    46  *   uses the MythContext's server and event sockets if the recording
    47  *   is not local.
     50 *   Run(void) will block until the preview completes.
    4851 *
    49  *   The PreviewGenerator will send Qt signals when the preview is ready
    50  *   and when the preview thread finishes running if Start(void) was called.
     52 *   The PreviewGenerator will send a PREVIEW_SUCCESS or a
     53 *   PREVIEW_FAILED event when the preview completes or fails.
    5154 */
    5255
    5356/**
     
    6467 *                    if the file is local.
    6568 */
    6669PreviewGenerator::PreviewGenerator(const ProgramInfo *pginfo,
     70                                   const QString     &_token,
    6771                                   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()),
    7074      timeInSeconds(true),  captureTime(-1),  outFileName(QString::null),
    71       outSize(0,0)
     75      outSize(0,0), token(_token), gotReply(false), pixmapOk(false)
    7276{
    7377}
    7478
     
    8488
    8589void PreviewGenerator::TeardownAll(void)
    8690{
    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;
    10693}
    10794
    10895void PreviewGenerator::deleteLater()
     
    114101void PreviewGenerator::AttachSignals(QObject *obj)
    115102{
    116103    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;
    125105}
    126106
    127 /** \fn PreviewGenerator::disconnectSafe(void)
    128  *  \brief disconnects signals while holding previewLock, ensuring that
    129  *         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 
    148107/** \fn PreviewGenerator::RunReal(void)
    149108 *  \brief This call creates a preview without starting a new thread.
    150109 */
    151110bool PreviewGenerator::RunReal(void)
    152111{
     112    QString msg;
     113    QTime tm = QTime::currentTime();
    153114    bool ok = false;
    154115    bool is_local = IsLocal();
    155     if (is_local && (mode && kLocal) && LocalPreviewRun())
     116
     117    if (!is_local && !!(mode & kRemote))
    156118    {
     119        VERBOSE(VB_IMPORTANT, LOC_ERR +
     120                QString("Run() file not local: '%1'")
     121                .arg(pathname));
     122    }
     123    else if (!(mode & kLocal) && !(mode & kRemote))
     124    {
     125        VERBOSE(VB_IMPORTANT, LOC_ERR +
     126                QString("Run() Preview of '%1' failed "
     127                        "because mode was invalid 0x%2")
     128                .arg(pathname).arg((int)mode,0,16));
     129    }
     130    else if (is_local && !!(mode & kLocal) && LocalPreviewRun())
     131    {
    157132        ok = true;
     133        msg = QString("Generated on %1 in %2 seconds, starting at %3")
     134            .arg(gCoreContext->GetHostName())
     135            .arg(tm.elapsed()*0.001)
     136            .arg(tm.toString(Qt::ISODate));
    158137    }
    159     else if (mode & kRemote)
     138    else if (!!(mode & kRemote))
    160139    {
    161         if (is_local)
     140        if (is_local && (mode & kLocal))
    162141        {
    163142            VERBOSE(VB_IMPORTANT, LOC_WARN + "Failed to save preview."
    164143                    "\n\t\t\tYou may need to check user and group ownership on"
     
    166145                    "\n\t\t\tAttempting to regenerate preview on backend.\n");
    167146        }
    168147        ok = RemotePreviewRun();
     148        if (ok)
     149        {
     150            msg = QString("Generated remotely in %1 seconds, starting at %2")
     151                .arg(tm.elapsed()*0.001)
     152                .arg(tm.toString(Qt::ISODate));
     153        }
     154        else
     155        {
     156            msg = "Remote preview failed";
     157        }
    169158    }
    170159    else
    171160    {
    172         VERBOSE(VB_IMPORTANT, LOC_ERR + QString("Run() file not local: '%1'")
    173                 .arg(pathname));
     161        msg = "Could not access recording";
    174162    }
    175163
     164    QMutexLocker locker(&previewLock);
     165    if (listener)
     166    {
     167        QString output_fn = outFileName.isEmpty() ?
     168            (programInfo.GetPathname()+".png") : outFileName;
     169
     170        QDateTime dt;
     171        if (ok)
     172        {
     173            QFileInfo fi(output_fn);
     174            if (fi.exists())
     175                dt = fi.lastModified();
     176        }
     177
     178        QString message = (ok) ? "PREVIEW_SUCCESS" : "PREVIEW_FAILED";
     179        QStringList list;
     180        list.push_back(programInfo.MakeUniqueKey());
     181        list.push_back(output_fn);
     182        list.push_back(msg);
     183        list.push_back(dt.isValid()?dt.toString(Qt::ISODate):"");
     184        list.push_back(token);
     185        QCoreApplication::postEvent(listener, new MythEvent(message, list));
     186    }
     187
    176188    return ok;
    177189}
    178190
    179191bool PreviewGenerator::Run(void)
    180192{
     193    QString msg;
     194    QDateTime dtm = QDateTime::currentDateTime();
     195    QTime tm = QTime::currentTime();
    181196    bool ok = false;
    182197    QString command = GetInstallPrefix() + "/bin/mythpreviewgen";
    183     bool local_ok = (IsLocal() && (mode & kLocal) &&
     198    bool local_ok = (IsLocal() && !!(mode & kLocal) &&
    184199                     QFileInfo(command).isExecutable());
    185200    if (!local_ok)
    186201    {
    187         if (mode & kRemote)
     202        if (!!(mode & kRemote))
    188203        {
    189204            ok = RemotePreviewRun();
     205            if (ok)
     206            {
     207                msg =
     208                    QString("Generated remotely in %1 seconds, starting at %2")
     209                    .arg(tm.elapsed()*0.001)
     210                    .arg(tm.toString(Qt::ISODate));
     211            }
    190212        }
    191213        else
    192214        {
    193215            VERBOSE(VB_IMPORTANT, LOC_ERR +
    194216                    QString("Run() can not generate preview locally for: '%1'")
    195217                    .arg(pathname));
     218            msg = "Failed, local preview requested for remote file.";
    196219        }
    197220    }
    198221    else
     
    215238        if (!outFileName.isEmpty())
    216239            command += QString("--outfile \"%1\" ").arg(outFileName);
    217240
     241        command += " > /dev/null";
     242
    218243        int ret = myth_system(command, MYTH_SYSTEM_DONT_BLOCK_LIRC |
    219244                                       MYTH_SYSTEM_DONT_BLOCK_JOYSTICK_MENU |
    220245                                       MYTH_SYSTEM_DONT_BLOCK_PARENT);
    221246        if (ret)
    222247        {
    223             VERBOSE(VB_IMPORTANT, LOC_ERR + "Encountered problems running " +
    224                     QString("'%1'").arg(command));
     248            msg = QString("Encountered problems running '%1'").arg(command);
     249            VERBOSE(VB_IMPORTANT, LOC_ERR + msg);
    225250        }
    226251        else
    227252        {
     
    240265            QFileInfo fi(outname);
    241266            ok = (fi.exists() && fi.isReadable() && fi.size());
    242267            if (ok)
     268            {
    243269                VERBOSE(VB_PLAYBACK, LOC + "Preview process ran ok.");
     270                msg = QString("Generated on %1 in %2 seconds, starting at %3")
     271                    .arg(gCoreContext->GetHostName())
     272                    .arg(tm.elapsed()*0.001)
     273                    .arg(tm.toString(Qt::ISODate));
     274            }
    244275            else
    245276            {
    246277                VERBOSE(VB_IMPORTANT, LOC_ERR + "Preview process not ok." +
     
    251282                VERBOSE(VB_IMPORTANT, LOC_ERR +
    252283                        QString("Despite command '%1' returning success")
    253284                        .arg(command));
     285                msg = QString("Failed to read preview image despite "
     286                              "preview process returning success.");
    254287            }
    255288        }
    256289    }
    257290
     291    QMutexLocker locker(&previewLock);
     292
     293    // Backdate file to start of preview time in case a bookmark was made
     294    // while we were generating the preview.
     295    QString output_fn = outFileName.isEmpty() ?
     296        (programInfo.GetPathname()+".png") : outFileName;
     297
     298    QDateTime dt;
    258299    if (ok)
    259300    {
    260         QMutexLocker locker(&previewLock);
    261         emit previewReady(&programInfo);
     301        QFileInfo fi(output_fn);
     302        if (fi.exists())
     303            dt = fi.lastModified();
    262304    }
    263305
     306    QString message = (ok) ? "PREVIEW_SUCCESS" : "PREVIEW_FAILED";
     307    if (listener)
     308    {
     309        QStringList list;
     310        list.push_back(programInfo.MakeUniqueKey());
     311        list.push_back(outFileName.isEmpty() ?
     312                       (programInfo.GetPathname()+".png") : outFileName);
     313        list.push_back(msg);
     314        list.push_back(dt.isValid()?dt.toString(Qt::ISODate):"");
     315        list.push_back(token);
     316        QCoreApplication::postEvent(listener, new MythEvent(message, list));
     317    }
     318
    264319    return ok;
    265320}
    266321
    267 void *PreviewGenerator::PreviewRun(void *param)
     322void PreviewGenerator::run(void)
    268323{
    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;
     324    setPriority(QThread::LowPriority);
     325    Run();
     326    connect(this, SIGNAL(finished()),
     327            this, SLOT(deleteLater()));
    277328}
    278329
    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 
    290330bool PreviewGenerator::RemotePreviewRun(void)
    291331{
    292     QStringList strlist( "QUERY_GENPIXMAP" );
     332    QStringList strlist( "QUERY_GENPIXMAP2" );
     333    if (token.isEmpty())
     334    {
     335        token = QString("%1:%2")
     336            .arg(programInfo.MakeUniqueKey()).arg(rand());
     337    }
     338    strlist.push_back(token);
    293339    programInfo.ToStringList(strlist);
    294340    strlist.push_back(timeInSeconds ? "s" : "f");
    295341    encodeLongLong(strlist, captureTime);
     
    305351    strlist.push_back(QString::number(outSize.width()));
    306352    strlist.push_back(QString::number(outSize.height()));
    307353
    308     bool ok = false;
     354    gCoreContext->addListener(this);
     355    pixmapOk = false;
    309356
    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 
     357    bool ok = gCoreContext->SendReceiveStringList(strlist);
    331358    if (!ok || strlist.empty() || (strlist[0] != "OK"))
    332359    {
    333360        if (!ok)
     
    340367            VERBOSE(VB_IMPORTANT, LOC_ERR +
    341368                    "Remote Preview failed, reason given: " <<strlist[1]);
    342369        }
    343         else
    344         {
    345             VERBOSE(VB_IMPORTANT, LOC_ERR +
    346                     "Remote Preview failed due to an unknown error.");
    347         }
     370
     371        gCoreContext->removeListener(this);
     372
    348373        return false;
    349374    }
    350375
     376    QMutexLocker locker(&previewLock);
     377
     378    // wait up to 30 seconds for the preview to complete
     379    if (!gotReply)
     380        previewWaitCondition.wait(&previewLock, 30 * 1000);
     381
     382    if (!gotReply)
     383        VERBOSE(VB_IMPORTANT, LOC + "RemotePreviewRun() -- no reply..");
     384
     385    gCoreContext->removeListener(this);
     386
     387    return pixmapOk;
     388}
     389
     390bool PreviewGenerator::event(QEvent *e)
     391{
     392    if (e->type() != (QEvent::Type) MythEvent::MythEventMessage)
     393        return QObject::event(e);
     394
     395    MythEvent *me = (MythEvent*)e;
     396    if (me->Message() != "GENERATED_PIXMAP" || me->ExtraDataCount() < 3)
     397        return QObject::event(e);
     398           
     399    bool ok = me->ExtraData(0) == "OK";
     400    bool ours = false;
     401    uint i = ok ? 4 : 3;
     402    for (; i < (uint) me->ExtraDataCount() && !ours; i++)
     403        ours |= me->ExtraData(i) == token;
     404    if (!ours)
     405        return false;
     406
     407    QString pginfokey = me->ExtraData(1);
     408
     409    QMutexLocker locker(&previewLock);
     410    gotReply = true;
     411    pixmapOk = ok;
     412    if (!ok)
     413    {
     414        VERBOSE(VB_IMPORTANT, LOC_ERR + pginfokey + ": " + me->ExtraData(2));
     415        previewWaitCondition.wakeAll();
     416        return true;
     417    }
     418
     419    if (me->ExtraDataCount() < 5)
     420    {
     421        pixmapOk = false;
     422        previewWaitCondition.wakeAll();
     423        return true; // could only happen with very broken client...
     424    }
     425
     426    QDateTime datetime = QDateTime::fromString(me->ExtraData(3), Qt::ISODate);
     427    if (!datetime.isValid())
     428    {
     429        pixmapOk = false;
     430        VERBOSE(VB_IMPORTANT, LOC_ERR + pginfokey + "Got invalid date");
     431        previewWaitCondition.wakeAll();
     432        return false;
     433    }
     434
     435    size_t     length     = me->ExtraData(4).toULongLong();
     436    quint16    checksum16 = me->ExtraData(5).toUInt();
     437    QByteArray data       = QByteArray::fromBase64(me->ExtraData(6).toAscii());
     438    if ((size_t) data.size() < length)
     439    {   // (note data.size() may be up to 3
     440        //  bytes longer after decoding
     441        VERBOSE(VB_IMPORTANT, LOC_ERR +
     442                QString("Preview size check failed %1 < %2")
     443                .arg(data.size()).arg(length));
     444        data.clear();
     445    }
     446    data.resize(length);
     447
     448    if (checksum16 != qChecksum(data.constData(), data.size()))
     449    {
     450        VERBOSE(VB_IMPORTANT, LOC_ERR + "Preview checksum failed");
     451        data.clear();
     452    }
     453
     454    pixmapOk = (data.isEmpty()) ? false : SaveOutFile(data, datetime);
     455
     456    previewWaitCondition.wakeAll();
     457
     458    return true;
     459}
     460
     461bool PreviewGenerator::SaveOutFile(const QByteArray &data, const QDateTime &dt)
     462{
    351463    if (outFileName.isEmpty())
    352464    {
    353         QString remotecachedirname = QString("%1/remotecache").arg(GetConfDir());
     465        QString remotecachedirname =
     466            QString("%1/remotecache").arg(GetConfDir());
    354467        QDir remotecachedir(remotecachedirname);
    355468
    356469        if (!remotecachedir.exists())
     
    368481        outFileName = QString("%1/%2").arg(remotecachedirname).arg(filename);
    369482    }
    370483
    371     // find file, copy/move to output file name & location...
    372 
    373     QString url = QString::null;
    374     QString fn = QFileInfo(outFileName).fileName();
    375     QByteArray data;
    376     ok = false;
    377 
    378     QStringList fileNames;
    379     fileNames.push_back(
    380         CreateAccessibleFilename(programInfo.GetPathname(), fn));
    381     fileNames.push_back(
    382         CreateAccessibleFilename(programInfo.GetPathname(), ""));
    383 
    384     QStringList::const_iterator it = fileNames.begin();
    385     for ( ; it != fileNames.end() && (!ok || data.isEmpty()); ++it)
     484    QFile file(outFileName);
     485    bool ok = file.open(QIODevice::Unbuffered|QIODevice::WriteOnly);
     486    if (!ok)
    386487    {
    387         data.resize(0);
    388         url = *it;
    389         RemoteFile *rf = new RemoteFile(url, false, false, 0);
    390         ok = rf->SaveAs(data);
    391         delete rf;
     488        VERBOSE(VB_IMPORTANT, LOC_ERR + QString("Failed to open: '%1'")
     489                .arg(outFileName));
    392490    }
    393491
    394     if (ok && data.size())
     492    off_t offset = 0;
     493    size_t remaining = data.size();
     494    uint failure_cnt = 0;
     495    while ((remaining > 0) && (failure_cnt < 5))
    395496    {
    396         QFile file(outFileName);
    397         ok = file.open(QIODevice::Unbuffered|QIODevice::WriteOnly);
    398         if (!ok)
     497        ssize_t written = file.write(data.data() + offset, remaining);
     498        if (written < 0)
    399499        {
    400             VERBOSE(VB_IMPORTANT, QString("Failed to open: '%1'")
    401                     .arg(outFileName));
     500            failure_cnt++;
     501            usleep(50000);
     502            continue;
    402503        }
    403504
    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         }
     505        failure_cnt  = 0;
     506        offset      += written;
     507        remaining   -= written;
    426508    }
    427509
    428     return ok && data.size();
    429 }
    430 
    431 void PreviewGenerator::RemotePreviewTeardown(void)
    432 {
    433     if (serverSock)
     510    if (ok && !remaining)
    434511    {
    435         serverSock->DownRef();
    436         serverSock = NULL;
     512        file.close();
     513        struct utimbuf times;
     514        times.actime = times.modtime = dt.toTime_t();
     515        utime(outFileName.toLocal8Bit().constData(), &times);
     516        VERBOSE(VB_FILE, LOC + QString("Saved: '%1'").arg(outFileName));
    437517    }
     518    else
     519    {
     520        file.remove();
     521    }
     522
     523    return ok;
    438524}
    439525
    440526bool PreviewGenerator::SavePreview(QString filename,
     
    476562    QImage small_img = img.scaled((int) ppw, (int) pph,
    477563        Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
    478564
    479     QByteArray fname = filename.toAscii();
    480     if (small_img.save(fname.constData(), "PNG"))
     565    QTemporaryFile f(QFileInfo(filename).absoluteFilePath()+".XXXXXX");
     566    f.setAutoRemove(false);
     567    if (f.open() && small_img.save(&f, "PNG"))
    481568    {
    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;
     569        // Let anybody update it
     570        makeFileAccessible(f.fileName().toLocal8Bit().constData());
     571        QFile of(filename);
     572        of.remove();
     573        if (f.rename(filename))
     574        {
     575            VERBOSE(VB_PLAYBACK, LOC +
     576                    QString("Saved preview '%0' %1x%2")
     577                    .arg(filename).arg((int) ppw).arg((int) pph));
     578            return true;
     579        }
     580        f.remove();
    489581    }
    490582
    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());
    499 
    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?
    508583    return false;
    509584}
    510585
     
    515590    float aspect = 0;
    516591    int   width, height, sz;
    517592    long long captime = captureTime;
     593
     594    QDateTime dt = QDateTime::currentDateTime();
     595
     596    if (captime > 0)
     597        VERBOSE(VB_IMPORTANT, "Preview from time spec");
     598
    518599    if (captime < 0)
    519600    {
     601        captime = programInfo.QueryBookmark();
     602        if (captime > 0)
     603            timeInSeconds = false;
     604        else
     605            captime = -1;
     606    }
     607
     608    if (captime < 0)
     609    {
    520610        timeInSeconds = true;
    521611        int startEarly = 0;
    522612        int programDuration = 0;
     
    559649
    560650    bool ok = SavePreview(outname, data, width, height, aspect, dw, dh);
    561651
     652    if (ok)
     653    {
     654        // Backdate file to start of preview time in case a bookmark was made
     655        // while we were generating the preview.
     656        struct utimbuf times;
     657        times.actime = times.modtime = dt.toTime_t();
     658        utime(outname.toLocal8Bit().constData(), &times);
     659    }
     660
    562661    delete[] data;
    563662
    564663    programInfo.MarkAsInUse(false, kPreviewGeneratorInUseID);
     
    604703    if (tmppathname.left(4) == "dvd:")
    605704        tmppathname = tmppathname.section(":", 1, 1);
    606705
     706    if (!QFileInfo(tmppathname).isReadable())
     707        return false;
     708
     709    tmppathname = outFileName.isEmpty() ? tmppathname : outFileName;
    607710    QString pathdir = QFileInfo(tmppathname).path();
    608711
    609     return (QFileInfo(tmppathname).isReadable() &&
    610             QFileInfo(pathdir).isWritable());
     712    if (!QFileInfo(pathdir).isWritable())
     713    {
     714        VERBOSE(VB_IMPORTANT, LOC_WARN +
     715                QString("Output path '%1' is not writeable")
     716                .arg(pathdir));
     717        return false;
     718    }
     719
     720    return true;
    611721}
    612722
    613723/**
     
    640750    (void) video_height;
    641751    char *retbuf = NULL;
    642752    bufferlen = 0;
    643 #ifdef USING_FRONTEND
     753
    644754    if (!MSqlQuery::testDBConnection())
    645755    {
    646756        VERBOSE(VB_IMPORTANT, LOC_ERR + "Previewer could not connect to DB.");
     
    686796
    687797    delete ctx;
    688798
    689 #else // USING_FRONTEND
    690     QString msg = "Backend compiled without USING_FRONTEND !!!!";
    691     VERBOSE(VB_IMPORTANT, LOC_ERR + msg);
    692 #endif // USING_FRONTEND
    693 
    694799    if (retbuf)
    695800    {
    696801        VERBOSE(VB_GENERAL, LOC +
  • mythtv/libs/libmythtv/tv_play.cpp

     
    5757#include "mythsystemevent.h"
    5858#include "videometadatautil.h"
    5959#include "mythdialogbox.h"
     60#include "mythdirs.h"
    6061
    6162#if ! HAVE_ROUND
    6263#define round(x) ((int) ((x) + 0.5))
     
    1147011471 */
    1147111472bool TV::ScreenShot(PlayerContext *ctx, long long frameNumber)
    1147211473{
     11474    QDir d;
     11475    QString confdir = GetConfDir();
     11476    if (!d.mkpath(confdir))
     11477    {
     11478        QString msg = tr("Screen Shot") + " " + tr("Error");
     11479        SetOSDMessage(ctx, msg);
     11480        return false;
     11481    }
     11482
    1147311483    ctx->LockPlayingInfo(__FILE__, __LINE__);
    1147411484    if (!ctx->playingInfo)
    1147511485    {
     
    1147911489        return false;
    1148011490    }
    1148111491
    11482     // TODO FIXME .mythtv isn't guaranteed to exist, and may
    11483     // very well belong to another frontend.
    1148411492    QString outFile =
    11485         QString("%1/.mythtv/%2_%3_%4.png")
    11486         .arg(QDir::homePath()).arg(ctx->playingInfo->GetChanID())
     11493        QString("%1/%2_%3_%4.png")
     11494        .arg(confdir).arg(ctx->playingInfo->GetChanID())
    1148711495        .arg(ctx->playingInfo->GetRecordingStartTime(MythDate))
    1148811496        .arg(frameNumber);
    1148911497
    1149011498    PreviewGenerator *previewgen = new PreviewGenerator(
    11491         ctx->playingInfo, PreviewGenerator::kLocalAndRemote);
     11499        ctx->playingInfo, QString(), PreviewGenerator::kLocalAndRemote);
    1149211500    ctx->UnlockPlayingInfo(__FILE__, __LINE__);
    1149311501
    1149411502    previewgen->SetPreviewTimeAsFrameNumber(frameNumber);
  • mythtv/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->deleteLater();
     58    }
     59}
     60
     61void 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
     89void 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
     98void 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
     107bool 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 datetime  = me->ExtraData(3);
     149        QString token     = me->ExtraData(4);
     150
     151        {
     152            QMutexLocker locker(&m_lock);
     153            QMap<QString,QString>::iterator kit = m_tokenToKeyMap.find(token);
     154            if (kit == m_tokenToKeyMap.end())
     155            {
     156                VERBOSE(VB_IMPORTANT, LOC_ERR +
     157                        QString("Failed to find token %1 in map.").arg(token));
     158                return true;
     159            }
     160            PreviewMap::iterator it = m_previewMap.find(*kit);
     161            if (it == m_previewMap.end())
     162            {
     163                VERBOSE(VB_IMPORTANT, LOC_ERR +
     164                        QString("Failed to find key %1 in map.").arg(*kit));
     165                return true;
     166            }
     167
     168            (*it).gen           = NULL;
     169            (*it).genStarted    = false;
     170            if (me->Message() == "PREVIEW_SUCCESS")
     171            {
     172                (*it).attempts      = 0;
     173                (*it).lastBlockTime = 0;
     174                (*it).blockRetryUntil = QDateTime();
     175            }
     176            else
     177            {
     178                (*it).lastBlockTime =
     179                    max(m_minBlockSeconds, (*it).lastBlockTime * 2);
     180                (*it).blockRetryUntil =
     181                    QDateTime::currentDateTime().addSecs((*it).lastBlockTime);
     182            }
     183
     184            QStringList list;
     185            list.push_back(pginfokey);
     186            list.push_back(filename);
     187            list.push_back(msg);
     188            list.push_back(datetime);
     189            QSet<QString>::const_iterator tit = (*it).tokens.begin();
     190            for (; tit != (*it).tokens.end(); ++tit)
     191            {
     192                kit = m_tokenToKeyMap.find(*tit);
     193                if (kit != m_tokenToKeyMap.end())
     194                    m_tokenToKeyMap.erase(kit);
     195                list.push_back(*tit);
     196            }
     197
     198            QSet<QObject*>::iterator sit = m_listeners.begin();
     199            for (; sit != m_listeners.end(); ++sit)
     200            {
     201                MythEvent *e = new MythEvent(me->Message(), list);
     202                QCoreApplication::postEvent(*sit, e);
     203            }
     204            (*it).tokens.clear();
     205
     206            m_running--;
     207        }
     208
     209        UpdatePreviewGeneratorThreads();
     210
     211        return true;
     212    }
     213    return false;
     214}
     215
     216void PreviewGeneratorQueue::SendEvent(
     217    const ProgramInfo &pginfo,
     218    const QString &eventname,
     219    const QString &fn, const QString &token, const QString &msg,
     220    const QDateTime &dt)
     221{
     222    QStringList list;
     223    list.push_back(pginfo.MakeUniqueKey());
     224    list.push_back(fn);
     225    list.push_back(msg);
     226    list.push_back(dt.toString(Qt::ISODate));
     227    list.push_back(token);
     228
     229    QMutexLocker locker(&m_lock);
     230    QSet<QObject*>::iterator it = m_listeners.begin();
     231    for (; it != m_listeners.end(); ++it)
     232    {
     233        MythEvent *e = new MythEvent(eventname, list);
     234        QCoreApplication::postEvent(*it, e);
     235    }
     236}
     237
     238QString PreviewGeneratorQueue::GeneratePreviewImage(
     239    ProgramInfo &pginfo,
     240    const QSize &size,
     241    const QString &outputfile,
     242    long long time, bool in_seconds,
     243    QString token)
     244{
     245    QString key = QString("%1_%2x%3_%4%5")
     246        .arg(pginfo.GetBasename()).arg(size.width()).arg(size.height())
     247        .arg(time).arg(in_seconds?"s":"f");
     248
     249    if (pginfo.GetAvailableStatus() == asPendingDelete)
     250    {
     251        SendEvent(pginfo, "PREVIEW_FAILED", key, token,
     252                  "Pending Delete", QDateTime());
     253        return QString();
     254    }
     255
     256    QString filename = (outputfile.isEmpty()) ?
     257        pginfo.GetPathname() + ".png" : outputfile;
     258    QString ret_file = filename;
     259    QString ret;
     260
     261    bool is_special = !outputfile.isEmpty() || time >= 0 ||
     262        size.width() || size.height();
     263
     264    bool needs_gen = true;
     265    if (!is_special)
     266    {
     267        QDateTime previewLastModified;
     268        bool streaming = filename.left(1) != "/";
     269        bool locally_accessible = false;
     270        bool bookmark_updated = false;
     271
     272        QDateTime bookmark_ts = pginfo.QueryBookmarkTimeStamp();
     273        QDateTime cmp_ts = bookmark_ts.isValid() ?
     274            bookmark_ts : pginfo.GetLastModifiedTime();
     275
     276        if (streaming)
     277        {
     278            ret_file = QString("%1/remotecache/%2")
     279                .arg(GetConfDir()).arg(filename.section('/', -1));
     280
     281            QFileInfo finfo(ret_file);
     282            if (finfo.isReadable() && finfo.lastModified() >= cmp_ts)
     283            {
     284                // This is just an optimization to avoid
     285                // hitting the backend if our cached copy
     286                // is newer than the bookmark, or if we have
     287                // a preview and do not update it when the
     288                // bookmark changes.
     289                previewLastModified = finfo.lastModified();
     290            }
     291            else if (!IsGeneratingPreview(key))
     292            {
     293                previewLastModified =
     294                    RemoteGetPreviewIfModified(pginfo, ret_file);
     295            }
     296        }
     297        else
     298        {
     299            QFileInfo fi(filename);
     300            if ((locally_accessible = fi.isReadable()))
     301                previewLastModified = fi.lastModified();
     302        }
     303
     304        bookmark_updated =
     305            (!previewLastModified.isValid() || (previewLastModified <= cmp_ts));
     306
     307        if (bookmark_updated && bookmark_ts.isValid() &&
     308            previewLastModified.isValid())
     309        {
     310            ClearPreviewGeneratorAttempts(key);
     311        }
     312
     313        bool preview_exists = previewLastModified.isValid();
     314
     315        if (0)
     316        {
     317            QString alttext = (bookmark_ts.isValid()) ? QString() :
     318                QString("\n\t\t\tcmp_ts:               %1")
     319                .arg(cmp_ts.toString(Qt::ISODate));
     320            VERBOSE(VB_IMPORTANT, QString(
     321                        "previewLastModified:  %1\n\t\t\t"
     322                        "bookmark_ts:          %2%3\n\t\t\t"
     323                        "pginfo.lastmodified:  %4")
     324                    .arg(previewLastModified.toString(Qt::ISODate))
     325                    .arg(bookmark_ts.toString(Qt::ISODate))
     326                    .arg(alttext)
     327                    .arg(pginfo.GetLastModifiedTime(ISODate)) +
     328                    QString("Title: %1\n\t\t\t")
     329                    .arg(pginfo.toString(ProgramInfo::kTitleSubtitle)) +
     330                    QString("File  '%1' \n\t\t\tCache '%2'")
     331                    .arg(filename).arg(ret_file) +
     332                    QString("\n\t\t\tPreview Exists: %1, "
     333                            "Bookmark Updated: %2, "
     334                            "Need Preview: %3")
     335                    .arg(preview_exists).arg(bookmark_updated)
     336                    .arg((bookmark_updated || !preview_exists)));
     337        }
     338
     339        needs_gen = bookmark_updated || !preview_exists;
     340
     341        if (!needs_gen)
     342        {
     343            if (locally_accessible)
     344                ret = filename;
     345            else if (preview_exists && QFileInfo(ret_file).isReadable())
     346                ret = ret_file;
     347        }
     348    }
     349
     350    if (needs_gen && !IsGeneratingPreview(key))
     351    {
     352        uint attempts = IncPreviewGeneratorAttempts(key);
     353        if (attempts < m_maxAttempts)
     354        {
     355            VERBOSE(VB_PLAYBACK, LOC +
     356                    QString("Requesting preview for '%1'")
     357                    .arg(key));
     358            PreviewGenerator *pg = new PreviewGenerator(
     359                &pginfo, token, m_mode);
     360            if (!outputfile.isEmpty() || time >= 0 ||
     361                size.width() || size.height())
     362            {
     363                pg->SetPreviewTime(time, in_seconds);
     364                pg->SetOutputFilename(outputfile);
     365                pg->SetOutputSize(size);
     366            }
     367
     368            SetPreviewGenerator(key, pg);
     369
     370            VERBOSE(VB_PLAYBACK, LOC +
     371                    QString("Requested preview for '%1'").arg(key));
     372        }
     373        else if (attempts >= m_maxAttempts)
     374        {
     375            VERBOSE(VB_IMPORTANT, LOC_ERR +
     376                    QString("Attempted to generate preview for '%1' "
     377                            "%2 times; >= max(%3)")
     378                    .arg(key).arg(attempts).arg(m_maxAttempts));
     379        }
     380    }
     381    else if (needs_gen)
     382    {
     383        VERBOSE(VB_PLAYBACK, LOC +
     384                "Not requesting preview as it "
     385                "is already being generated");
     386        IncPreviewGeneratorPriority(key, token);
     387    }
     388
     389    UpdatePreviewGeneratorThreads();
     390
     391    if (!ret.isEmpty())
     392    {
     393        QString msg = "On Disk";
     394        QDateTime dt = QFileInfo(ret).lastModified();
     395        SendEvent(pginfo, "PREVIEW_SUCCESS", ret, token, msg, dt);
     396    }
     397    else
     398    {
     399        uint queue_depth, token_cnt;
     400        GetInfo(key, queue_depth, token_cnt);
     401        QString msg = QString("Queue depth %1, our tokens %2")
     402            .arg(queue_depth).arg(token_cnt);
     403        SendEvent(pginfo, "PREVIEW_QUEUED", ret, token, msg, QDateTime());
     404    }
     405
     406    return ret;
     407}
     408
     409void PreviewGeneratorQueue::GetInfo(
     410    const QString &key, uint &queue_depth, uint &token_cnt)
     411{
     412    QMutexLocker locker(&m_lock);
     413    queue_depth = m_queue.size();
     414    PreviewMap::iterator pit = m_previewMap.find(key);
     415    token_cnt = (pit == m_previewMap.end()) ? 0 : (*pit).tokens.size();
     416}
     417
     418void PreviewGeneratorQueue::IncPreviewGeneratorPriority(
     419    const QString &key, QString token)
     420{
     421    QMutexLocker locker(&m_lock);
     422    m_queue.removeAll(key);
     423
     424    PreviewMap::iterator pit = m_previewMap.find(key);
     425    if (pit == m_previewMap.end())
     426        return;
     427
     428    if ((*pit).gen && !(*pit).genStarted)
     429        m_queue.push_back(key);
     430
     431    if (!token.isEmpty())
     432    {
     433        m_tokenToKeyMap[token] = key;
     434        (*pit).tokens.insert(token);
     435    }
     436}
     437
     438void PreviewGeneratorQueue::UpdatePreviewGeneratorThreads(void)
     439{
     440    QMutexLocker locker(&m_lock);
     441    QStringList &q = m_queue;
     442    if (!q.empty() && (m_running < m_maxThreads))
     443    {
     444        QString fn = q.back();
     445        q.pop_back();
     446        PreviewMap::iterator it = m_previewMap.find(fn);
     447        if (it != m_previewMap.end() && (*it).gen && !(*it).genStarted)
     448        {
     449            m_running++;
     450            (*it).gen->start();
     451            (*it).genStarted = true;
     452        }
     453    }
     454}
     455
     456/** \brief Sets the PreviewGenerator for a specific file.
     457 *  \return true iff call succeeded.
     458 */
     459void PreviewGeneratorQueue::SetPreviewGenerator(
     460    const QString &key, PreviewGenerator *g)
     461{
     462    {
     463        QMutexLocker locker(&m_lock);
     464        m_tokenToKeyMap[g->GetToken()] = key;
     465        PreviewGenState &state = m_previewMap[key];
     466        if (state.gen)
     467        {
     468            if (!g->GetToken().isEmpty())
     469                state.tokens.insert(g->GetToken());
     470            g->deleteLater();
     471        }
     472        else
     473        {
     474            g->AttachSignals(this);
     475            state.gen = g;
     476            state.genStarted = false;
     477            if (!g->GetToken().isEmpty())
     478                state.tokens.insert(g->GetToken());
     479        }
     480    }
     481
     482    IncPreviewGeneratorPriority(key, "");
     483}
     484
     485/** \brief Returns true if we have already started a
     486 *         PreviewGenerator to create this file.
     487 */
     488bool PreviewGeneratorQueue::IsGeneratingPreview(const QString &key) const
     489{
     490    PreviewMap::const_iterator it;
     491    QMutexLocker locker(&m_lock);
     492
     493    if ((it = m_previewMap.find(key)) == m_previewMap.end())
     494        return false;
     495
     496    if ((*it).blockRetryUntil.isValid())
     497        return QDateTime::currentDateTime() < (*it).blockRetryUntil;
     498
     499    return (*it).gen;
     500}
     501
     502/** \fn PreviewGeneratorQueue::IncPreviewGeneratorAttempts(const QString&)
     503 *  \brief Increments and returns number of times we have
     504 *         started a PreviewGenerator to create this file.
     505 */
     506uint PreviewGeneratorQueue::IncPreviewGeneratorAttempts(const QString &key)
     507{
     508    QMutexLocker locker(&m_lock);
     509    return m_previewMap[key].attempts++;
     510}
     511
     512/** \fn PreviewGeneratorQueue::ClearPreviewGeneratorAttempts(const QString&)
     513 *  \brief Clears the number of times we have
     514 *         started a PreviewGenerator to create this file.
     515 */
     516void PreviewGeneratorQueue::ClearPreviewGeneratorAttempts(const QString &key)
     517{
     518    QMutexLocker locker(&m_lock);
     519    m_previewMap[key].attempts = 0;
     520    m_previewMap[key].lastBlockTime = 0;
     521    m_previewMap[key].blockRetryUntil =
     522        QDateTime::currentDateTime().addSecs(-60);
     523}
  • mythtv/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"
     
    2526#include "recordingrule.h"
    2627#include "eitscanner.h"
    2728#include "RingBuffer.h"
    28 #include "previewgenerator.h"
    2929#include "storagegroup.h"
    3030#include "remoteutil.h"
    3131#include "tvremoteutil.h"
     
    11431143        if (!killFile)
    11441144        {
    11451145            if (curRecording->IsLocal())
    1146             {
    1147                 (new PreviewGenerator(
    1148                     curRecording, PreviewGenerator::kLocal))->Start();
    1149             }
     1146                PreviewGeneratorQueue::GetPreviewImage(*curRecording, "");
    11501147
    11511148            if (!tvchain)
    11521149            {
     
    45284525            if (!oldinfo->IsLocal())
    45294526                oldinfo->SetPathname(oldinfo->GetPlaybackURL(false,true));
    45304527            if (oldinfo->IsLocal())
    4531             {
    4532                 (new PreviewGenerator(
    4533                     oldinfo, PreviewGenerator::kLocal))->Start();
    4534             }
     4528                PreviewGeneratorQueue::GetPreviewImage(*oldinfo, "");
    45354529        }
    45364530        delete oldinfo;
    45374531    }
  • mythtv/libs/libmythtv/previewgenerator.h

     
    22#ifndef PREVIEW_GENERATOR_H_
    33#define PREVIEW_GENERATOR_H_
    44
    5 #include <pthread.h>
    6 
     5#include <QWaitCondition>
     6#include <QDateTime>
    77#include <QString>
     8#include <QThread>
    89#include <QMutex>
    910#include <QSize>
     11#include <QMap>
     12#include <QSet>
    1013
    1114#include "programinfo.h"
    1215#include "util.h"
    1316
     17class PreviewGenerator;
     18class QByteArray;
    1419class MythSocket;
     20class QObject;
     21class QEvent;
    1522
    16 class MPUBLIC PreviewGenerator : public QObject
     23typedef QMap<QString,QDateTime> FileTimeStampMap;
     24
     25class MPUBLIC PreviewGenerator : public QThread
    1726{
    1827    friend int preview_helper(const QString &chanid,
    1928                              const QString &starttime,
     
    3645    } Mode;
    3746
    3847  public:
    39     PreviewGenerator(const ProgramInfo *pginfo, Mode mode = kLocal);
     48    PreviewGenerator(const ProgramInfo *pginfo,
     49                     const QString     &token,
     50                     Mode               mode = kLocal);
    4051
    4152    void SetPreviewTime(long long time, bool in_seconds)
    4253        { captureTime = time; timeInSeconds = in_seconds; }
     
    4758    void SetOutputFilename(const QString&);
    4859    void SetOutputSize(const QSize &size) { outSize = size; }
    4960
    50     void Start(void);
     61    QString GetToken(void) const { return token; }
     62
     63    void run(void); // QThread
    5164    bool Run(void);
    5265
    5366    void AttachSignals(QObject*);
    54     void disconnectSafe(void);
    5567
    56   signals:
    57     void previewThreadDone(const QString&, bool&);
    58     void previewReady(const ProgramInfo*);
    59 
    6068  public slots:
    6169    void deleteLater();
    6270
     
    6472    virtual ~PreviewGenerator();
    6573    void TeardownAll(void);
    6674
    67     bool RemotePreviewSetup(void);
    6875    bool RemotePreviewRun(void);
    69     void RemotePreviewTeardown(void);
    70 
    7176    bool LocalPreviewRun(void);
    7277    bool IsLocal(void) const;
    7378
    7479    bool RunReal(void);
    7580
    76     static void *PreviewRun(void*);
    77 
    7881    static char *GetScreenGrab(const ProgramInfo &pginfo,
    7982                               const QString     &filename,
    8083                               long long          seektime,
     
    9396    static QString CreateAccessibleFilename(
    9497        const QString &pathname, const QString &outFileName);
    9598
     99    virtual bool event(QEvent *e); // QObject
     100    bool SaveOutFile(const QByteArray &data, const QDateTime &dt);
     101
    96102  protected:
     103    QWaitCondition     previewWaitCondition;
    97104    QMutex             previewLock;
    98     pthread_t          previewThread;
    99105    ProgramInfo        programInfo;
    100106
    101107    Mode               mode;
    102     bool               isConnected;
    103     bool               createSockets;
    104     MythSocket        *serverSock;
     108    QObject           *listener;
    105109    QString            pathname;
    106110
    107111    /// tells us whether to use time as seconds or frame number
     
    110114    long long          captureTime;
    111115    QString            outFileName;
    112116    QSize              outSize;
     117
     118    QString            token;
     119    bool               gotReply;
     120    bool               pixmapOk;
    113121};
    114122
    115123#endif // PREVIEW_GENERATOR_H_
  • mythtv/libs/libmythui/mythuiimage.cpp

     
    7373  public:
    7474    ImageLoadThread(MythUIImage *parent, const QString &basefile,
    7575                    const QString &filename, int number,
    76                     QSize forceSize) :
     76                    QSize forceSize, ImageCacheMode mode) :
    7777        m_parent(parent), m_basefile(basefile),
    7878        m_filename(filename), m_number(number),
    79         m_ForceSize(forceSize)
     79        m_ForceSize(forceSize), m_cacheMode(mode)
    8080    {
    8181        m_basefile.detach();
    8282        m_filename.detach();
     
    9595
    9696        if (imageReader.supportsAnimation())
    9797        {
    98             m_parent->LoadAnimatedImage(imageReader, m_filename, m_ForceSize);
     98            m_parent->LoadAnimatedImage(
     99                imageReader, m_filename, m_ForceSize, m_cacheMode);
    99100        }
    100101        else
    101102        {
    102             MythImage *image =
    103                 m_parent->LoadImage(imageReader, m_filename, m_ForceSize);
    104 
     103            MythImage *image = m_parent->LoadImage(
     104                imageReader, m_filename, m_ForceSize, m_cacheMode);
    105105            ImageLoadEvent *le = new ImageLoadEvent(m_parent, image, m_basefile,
    106106                                                    m_filename, m_number);
    107107            QCoreApplication::postEvent(m_parent, le);
     
    114114    QString      m_filename;
    115115    int          m_number;
    116116    QSize        m_ForceSize;
     117    ImageCacheMode m_cacheMode;
    117118};
    118119
    119120/////////////////////////////////////////////////////////////////
     
    542543/**
    543544 *  \brief Load the image(s), wraps LoadImage()
    544545 */
    545 bool MythUIImage::Load(bool allowLoadInBackground)
     546bool MythUIImage::Load(bool allowLoadInBackground, bool forceStat)
    546547{
    547548    d->m_UpdateLock.lockForRead();
    548549
     
    621622    }
    622623
    623624    QString imagelabel;
    624     ImageCacheMode cacheMode = kCacheCheckMemoryOnly;
    625625
    626626    int j = 0;
    627627    for (int i = m_LowNum; i <= m_HighNum && !m_animatedImage; i++)
     
    633633
    634634        // Only load in the background if allowed and the image is
    635635        // not already in our mem cache
     636        ImageCacheMode cacheMode = kCacheCheckMemoryOnly;
    636637        if (m_gradient)
    637638            cacheMode = kCacheIgnoreDisk;
    638         else
    639             cacheMode = kCacheCheckMemoryOnly;
     639        else if (forceStat)
     640            cacheMode = (ImageCacheMode)
     641                ((int)kCacheCheckMemoryOnly | (int)kCacheForceStat);
     642       
     643        ImageCacheMode cacheMode2 = (!forceStat) ? kCacheNormal :
     644            (ImageCacheMode) ((int)kCacheNormal | (int)kCacheForceStat);
    640645
     646
    641647        if ((allowLoadInBackground) &&
    642648            (!GetMythUI()->LoadCacheImage(filename, imagelabel, cacheMode)) &&
    643649            (!getenv("DISABLETHREADEDMYTHUIIMAGE")))
    644650        {
    645651            VERBOSE(VB_GUI|VB_FILE|VB_EXTRA, LOC + QString(
    646652                        "Load(), spawning thread to load '%1'").arg(filename));
    647             ImageLoadThread *bImgThread =
    648                 new ImageLoadThread(this, bFilename, filename, i,
    649                                     bForceSize);
     653            ImageLoadThread *bImgThread = new ImageLoadThread(
     654                this, bFilename, filename, i, bForceSize, cacheMode2);
    650655            GetMythUI()->GetImageThreadPool()->start(bImgThread);
    651656        }
    652657        else
     
    665670
    666671            if (imageReader.supportsAnimation())
    667672            {
    668                 LoadAnimatedImage(imageReader, filename, bForceSize);
     673                LoadAnimatedImage(
     674                    imageReader, filename, bForceSize, cacheMode2);
    669675            }
    670676            else
    671677            {
    672                 MythImage *image = LoadImage(imageReader, filename, bForceSize);
     678                MythImage *image = LoadImage(
     679                    imageReader, filename, bForceSize, cacheMode2);
    673680                if (image)
    674681                {
    675682                    if (bForceSize.isNull())
     
    701708/**
    702709*  \brief Load an image
    703710*/
    704 MythImage *MythUIImage::LoadImage(MythImageReader &imageReader,
    705                                   const QString &imFile, QSize bForceSize)
     711MythImage *MythUIImage::LoadImage(
     712    MythImageReader &imageReader, const QString &imFile,
     713    QSize bForceSize, int cacheMode)
    706714{
    707715    QString filename = imFile;
    708716
     
    754762    imagelabel = GenImageLabel(filename, w, h);
    755763
    756764    if (!imageReader.supportsAnimation())
    757         image = GetMythUI()->LoadCacheImage(filename, imagelabel);
     765    {
     766        image = GetMythUI()->LoadCacheImage(
     767            filename, imagelabel, (ImageCacheMode) cacheMode);
     768    }
    758769
    759770    if (image)
    760771    {
     
    882893/**
    883894*  \brief Load an animated image
    884895*/
    885 bool MythUIImage::LoadAnimatedImage(MythImageReader &imageReader,
    886                                     const QString &imFile, QSize bForceSize)
     896bool MythUIImage::LoadAnimatedImage(
     897    MythImageReader &imageReader, const QString &imFile,
     898    QSize bForceSize, int cacheMode)
    887899{
    888900    bool result = false;
    889901    m_loadingImagesLock.lock();
     
    930942    {
    931943        frameFilename = filename.arg(imageCount);
    932944        imageLabel = GenImageLabel(frameFilename, w, h);
    933         MythImage *im = LoadImage(imageReader, frameFilename, bForceSize);
     945        MythImage *im = LoadImage(
     946            imageReader, frameFilename,
     947            bForceSize, (ImageCacheMode) cacheMode);
    934948
    935949        if (!im)
    936950            break;
  • mythtv/libs/libmythui/mythuihelper.h

     
    2323typedef enum ImageCacheMode
    2424{
    2525    kCacheNormal          = 0x0,
    26     kCacheIgnoreDisk,
    27     kCacheCheckMemoryOnly
     26    kCacheIgnoreDisk      = 0x1,
     27    kCacheCheckMemoryOnly = 0x2,
     28    kCacheForceStat       = 0x4,
    2829} ImageCacheMode;
    2930
    3031struct MPUBLIC MythUIMenuCallbacks
  • mythtv/libs/libmythui/mythuihelper.cpp

     
    13931393    if (srcfile.isEmpty() || label.isEmpty())
    13941394        return NULL;
    13951395
    1396     // Some screens include certain images dozens or even hundreds of
    1397     // times.  Even if the image is in the cache, there is still a
    1398     // stat system call on the original file to see if it has changed.
    1399     // This code relaxes the original-file check so that the check
    1400     // isn't repeated if it was already done within kImageCacheTimeout
    1401     // seconds.
    1402     const uint kImageCacheTimeout = 5;
    1403     uint now = QDateTime::currentDateTime().toTime_t();
    1404     if (d->imageCache.contains(label) &&
    1405         d->CacheTrack[label] + kImageCacheTimeout > now)
     1396    if (!(kCacheForceStat & cacheMode))
    14061397    {
    1407         return d->imageCache[label];
     1398        // Some screens include certain images dozens or even hundreds of
     1399        // times.  Even if the image is in the cache, there is still a
     1400        // stat system call on the original file to see if it has changed.
     1401        // This code relaxes the original-file check so that the check
     1402        // isn't repeated if it was already done within kImageCacheTimeout
     1403        // seconds.
     1404        const uint kImageCacheTimeout = 5;
     1405        uint now = QDateTime::currentDateTime().toTime_t();
     1406        if (d->imageCache.contains(label) &&
     1407            d->CacheTrack[label] + kImageCacheTimeout > now)
     1408        {
     1409            return d->imageCache[label];
     1410        }
    14081411    }
    14091412
    14101413    QString cachefilepath = GetThemeCacheDir() + '/' + label;
     
    14121415
    14131416    MythImage *ret = NULL;
    14141417
    1415     if ((cacheMode == kCacheIgnoreDisk) || fi.exists())
     1418    if (!!(cacheMode & kCacheIgnoreDisk) || fi.exists())
    14161419    {
    14171420        // Now compare the time on the source versus our cached copy
    1418         if (cacheMode != kCacheIgnoreDisk)
     1421        if (!(cacheMode & kCacheIgnoreDisk))
    14191422            FindThemeFile(srcfile);
    14201423
    14211424        QDateTime srcLastModified;
     
    14331436        else if (original.exists())
    14341437            srcLastModified = original.lastModified();
    14351438
    1436         if ((cacheMode == kCacheIgnoreDisk) ||
     1439        if (!!(cacheMode & kCacheIgnoreDisk) ||
    14371440            (fi.lastModified() > srcLastModified))
    14381441        {
    14391442            // Check Memory Cache
    14401443            ret = GetImageFromCache(label);
    1441             if (!ret && (cacheMode == kCacheNormal))
     1444            if (!ret && (!!(kCacheNormal & cacheMode)))
    14421445            {
    14431446                // Load file from disk cache to memory cache
    14441447                ret = GetMythPainter()->GetFormatImage();
  • mythtv/libs/libmythui/mythuiimage.h

     
    5151    void SetDelays(QVector<int> delays);
    5252
    5353    void Reset(void);
    54     bool Load(bool allowLoadInBackground = true);
     54    bool Load(bool allowLoadInBackground = true, bool forceStat = false);
    5555
    5656    bool IsGradient(void) const { return m_gradient; }
    5757
     
    6666    void Init(void);
    6767    void Clear(void);
    6868    MythImage* LoadImage(MythImageReader &imageReader, const QString &imFile,
    69                          QSize bForceSize);
     69                         QSize bForceSize, int cacheMode);
    7070    bool LoadAnimatedImage(MythImageReader &imageReader, const QString &imFile,
    71                            QSize bForceSize);
     71                           QSize bForceSize, int mode);
    7272    void customEvent(QEvent *event);
    7373
    7474    virtual bool ParseElement(
  • mythtv/libs/libmyth/remoteutil.h

     
    5353MPUBLIC void RemoteSendMessage(const QString &message);
    5454MPUBLIC void RemoteSendEvent(const MythEvent &event);
    5555MPUBLIC vector<uint> RemoteRequestFreeRecorderList(void);
    56 MPUBLIC void RemoteGeneratePreviewPixmap(const ProgramInfo *pginfo);
    5756MPUBLIC QDateTime RemoteGetPreviewLastModified(const ProgramInfo *pginfo);
    5857MPUBLIC QDateTime RemoteGetPreviewIfModified(
    5958    const ProgramInfo &pginfo, const QString &cachefile);
  • mythtv/libs/libmyth/remoteutil.cpp

     
    278278    gCoreContext->SendReceiveStringList(strlist);
    279279}
    280280
    281 void RemoteGeneratePreviewPixmap(const ProgramInfo *pginfo)
    282 {
    283     QStringList strlist( "QUERY_GENPIXMAP" );
    284     pginfo->ToStringList(strlist);
    285 
    286     gCoreContext->SendReceiveStringList(strlist);
    287 }
    288    
    289281QDateTime RemoteGetPreviewLastModified(const ProgramInfo *pginfo)
    290282{
    291283    QDateTime retdatetime;
     
    354346        return retdatetime;
    355347    }
    356348
    357     size_t  length     = strlist[1].toLongLong();
     349    size_t  length     = strlist[1].toULongLong();
    358350    quint16 checksum16 = strlist[2].toUInt();
    359351    QByteArray data = QByteArray::fromBase64(strlist[3].toAscii());
    360352    if ((size_t) data.size() < length)
     
    364356                .arg(data.size()).arg(length));
    365357        return QDateTime();
    366358    }
     359    data.resize(length);
    367360
    368361    if (checksum16 != qChecksum(data.constData(), data.size()))
    369362    {
  • mythtv/libs/libmythdb/mythversion.h

     
    1111/// Update this whenever the plug-in API changes.
    1212/// Including changes in the libmythdb, libmyth, libmythtv, libmythav* and
    1313/// libmythui class methods used by plug-ins.
    14 #define MYTH_BINARY_VERSION "0.23.20100903-1"
     14#define MYTH_BINARY_VERSION "0.23.20100903-2"
    1515
    1616/** \brief Increment this whenever the MythTV network protocol changes.
    1717 *
     
    3030 *       mythtv/bindings/python/MythTV/static.py (version number)
    3131 *       mythtv/bindings/python/MythTV/mythproto.py (layout)
    3232 */
    33 #define MYTH_PROTO_VERSION "60"
     33#define MYTH_PROTO_VERSION "61"
    3434
    3535MPUBLIC const char *GetMythSourceVersion();
    3636
  • mythtv/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"
    20 #include "mythplayer.h"
     11// MythTV
     12#include "previewgeneratorqueue.h"
     13#include "mythuiprogressbar.h"
     14#include "mythuibuttonlist.h"
     15#include "mythcorecontext.h"
     16#include "mythsystemevent.h"
     17#include "mythuistatetype.h"
     18#include "mythuicheckbox.h"
     19#include "mythuitextedit.h"
     20#include "mythdialogbox.h"
    2121#include "recordinginfo.h"
    22 #include "playgroup.h"
    23 #include "mythsystemevent.h"
    24 
    25 // libmyth
    26 #include "mythcorecontext.h"
    27 #include "util.h"
     22#include "mythuihelper.h"
    2823#include "storagegroup.h"
     24#include "mythuibutton.h"
     25#include "mythverbose.h"
     26#include "mythuiimage.h"
    2927#include "programinfo.h"
    30 
    31 // libmythui
    32 #include "mythuihelper.h"
     28#include "oldsettings.h"
     29#include "mythplayer.h"
    3330#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 
    4331#include "remoteutil.h"
     32#include "mythdbcon.h"
     33#include "playgroup.h"
     34#include "mythdirs.h"
     35#include "mythdb.h"
     36#include "util.h"
     37#include "tv.h"
    4438
    4539//  Mythfrontend
    4640#include "playbackboxlistitem.h"
    4741#include "customedit.h"
     42#include "proglist.h"
    4843
    4944#define LOC      QString("PlaybackBox: ")
    5045#define LOC_WARN QString("PlaybackBox Warning: ")
     
    444439PlaybackBox::~PlaybackBox(void)
    445440{
    446441    gCoreContext->removeListener(this);
     442    PreviewGeneratorQueue::RemoveListener(this);
    447443
    448444    for (uint i = 0; i < sizeof(m_artImage) / sizeof(MythUIImage*); i++)
    449445    {
     
    514510void PlaybackBox::Load(void)
    515511{
    516512    m_programInfoCache.WaitForLoadToComplete();
     513    PreviewGeneratorQueue::AddListener(this);
    517514}
    518515
    519516void PlaybackBox::Init()
     
    787784
    788785    QString oldimgfile = item->GetImage("preview");
    789786    if (oldimgfile.isEmpty() || force_preview_reload)
    790         m_helper.GetPreviewImage(*pginfo);
     787        m_preview_tokens.insert(m_helper.GetPreviewImage(*pginfo));
     788    MythUIButtonList *p = m_recordingList; //item->parent();
     789    int top = p->GetTopItemPos();
     790    VERBOSE(VB_IMPORTANT,
     791            QString("top: %1, visible: %2, count %3, cur %4")
     792            .arg(p->GetTopItemPos())
     793            .arg(p->GetVisibleCount())
     794            .arg(p->GetCount())
     795            .arg(p->GetCurrentPos()));
     796    for (int i = top; i>=0 && i < top+p->GetVisibleCount();)
     797    {
     798        MythUIButtonListItem *cur = p->GetItemAt(i);
     799        VERBOSE(VB_IMPORTANT, QString("Checking %1 ptr 0x%2")
     800                .arg(i).arg((intptr_t)cur,0,16));
     801        if (cur != item)
     802        {
     803            ProgramInfo *curpi = qVariantValue<ProgramInfo *>(cur->GetData());
     804            if (curpi && cur->GetImage("preview").isEmpty())
     805                m_preview_tokens.insert(m_helper.GetPreviewImage(*curpi));
     806        }
     807        i = (i + 1) % p->GetCount();
     808        if (i==top)
     809            break;
     810    }
     811    if (oldimgfile.isEmpty() || force_preview_reload)
     812        m_preview_tokens.insert(m_helper.GetPreviewImage(*pginfo));
    791813
    792814    if ((GetFocusWidget() == m_recordingList) && is_sel)
    793815    {
     
    811833        if (m_previewImage)
    812834        {
    813835            m_previewImage->SetFilename(oldimgfile);
    814             m_previewImage->Load();
     836            m_previewImage->Load(true, true);
    815837        }
    816838
    817839        // Handle artwork
     
    853875 *  a preview image UI item in the theme that it's filename property
    854876 *  gets updated as well.
    855877 */
    856 void PlaybackBox::HandlePreviewEvent(
    857     const QString &piKey, const QString &previewFile)
     878void PlaybackBox::HandlePreviewEvent(const QStringList &list)
    858879{
     880    if (list.size() < 5)
     881    {
     882        VERBOSE(VB_IMPORTANT, "HandlePreviewEvent() -- too few args");
     883        for (uint i = 0; i < (uint) list.size(); i++)
     884        {
     885            VERBOSE(VB_IMPORTANT, QString("%1: %2")
     886                    .arg(i).arg(list[i]));
     887        }
     888        return;
     889    }
     890
     891    const QString piKey       = list[0];
     892    const QString previewFile = list[1];
     893    const QString message     = list[2];
     894
     895    VERBOSE(VB_IMPORTANT, "HandlePreviewEvent()");
     896
     897    bool found = false;
     898    for (uint i = 4; i < (uint) list.size(); i++)
     899    {
     900        QString token = list[i];
     901        QSet<QString>::iterator it = m_preview_tokens.find(token);
     902        if (it != m_preview_tokens.end())
     903        {
     904            found = true;
     905            m_preview_tokens.erase(it);
     906        }
     907    }
     908
     909    if (!found)
     910    {
     911        QString tokens("\n\t\t\ttokens: ");
     912        for (uint i = 4; i < (uint) list.size(); i++)
     913            tokens += list[i] + ", ";
     914        VERBOSE(VB_IMPORTANT, LOC +
     915                "Ignoring PREVIEW_SUCCESS, no matcing token" + tokens);
     916        return;
     917    }
     918
    859919    if (previewFile.isEmpty())
     920    {
     921        VERBOSE(VB_IMPORTANT, LOC_ERR +
     922                "Ignoring PREVIEW_SUCCESS, no preview file.");
    860923        return;
     924    }
    861925
    862926    ProgramInfo *info = m_programInfoCache.GetProgramInfo(piKey);
    863927    MythUIButtonListItem *item = NULL;
     
    865929    if (info)
    866930        item = m_recordingList->GetItemByData(qVariantFromValue(info));
    867931
     932    if (!item)
     933    {
     934        VERBOSE(VB_IMPORTANT, LOC_ERR +
     935                "Ignoring PREVIEW_SUCCESS, item no longer on screen.");
     936    }
     937
    868938    if (item)
    869939    {
     940        VERBOSE(VB_GUI, LOC +
     941                QString("Loading preview %1,\n\t\t\tmsg %2")
     942                .arg(previewFile).arg(message));
     943
    870944        item->SetImage(previewFile, "preview", true);
    871945
    872946        if ((GetFocusWidget() == m_recordingList) &&
     
    874948            m_previewImage)
    875949        {
    876950            m_previewImage->SetFilename(previewFile);
    877             m_previewImage->Load();
     951            m_previewImage->Load(true, true);
    878952        }
    879953    }
    880954}
     
    22492323        QCoreApplication::postEvent(
    22502324            this, new MythEvent("PLAY_PLAYLIST"));
    22512325    }
     2326    else
     2327    {
     2328        // User may have saved or deleted a bookmark,
     2329        // requiring update of preview..
     2330        ProgramInfo *pginfo = m_programInfoCache.GetProgramInfo(
     2331            tvrec.GetChanID(), tvrec.GetRecordingStartTime());
     2332        if (pginfo)
     2333            UpdateUIListItem(pginfo, true);
     2334    }
    22522335
    22532336    return playCompleted;
    22542337}
     
    37933876            // asPendingDelete, we need to put them back now..
    37943877            ScheduleUpdateUIList();
    37953878        }
    3796         else if (message == "PREVIEW_READY" && me->ExtraDataCount() == 2)
     3879        else if (message == "PREVIEW_SUCCESS")
    37973880        {
    3798             HandlePreviewEvent(me->ExtraData(0), me->ExtraData(1));
     3881            HandlePreviewEvent(me->ExtraDataList());
    37993882        }
     3883        else if (message == "PREVIEW_FAILED" && me->ExtraDataCount() >= 5)
     3884        {
     3885            for (uint i = 4; i < (uint) me->ExtraDataCount(); i++)
     3886            {
     3887                QString token = me->ExtraData(i);
     3888                QSet<QString>::iterator it = m_preview_tokens.find(token);
     3889                if (it != m_preview_tokens.end())
     3890                    m_preview_tokens.erase(it);
     3891            }
     3892        }
    38003893        else if (message == "AVAILABILITY" && me->ExtraDataCount() == 8)
    38013894        {
    38023895            const uint kMaxUIWaitTime = 100; // ms
     
    40134106    {
    40144107        ProgramInfo *dst = FindProgramInUILists(evinfo);
    40154108        if (dst)
    4016             UpdateUIListItem(dst, false);
     4109            UpdateUIListItem(dst, true);
    40174110        return;
    40184111    }
    40194112
  • mythtv/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,
     
    7253    void UndeleteRecording(uint chanid, const QDateTime &recstartts);
    7354    void CheckAvailability(const ProgramInfo&,
    7455                           CheckAvailabilityType cat = kCheckForCache);
    75     void GetPreviewImage(const ProgramInfo&);
     56    QString GetPreviewImage(const ProgramInfo&);
    7657
    7758    QString LocateArtwork(const QString &seriesid, const QString &title,
    7859                          ArtworkType, const QString &host,
     
    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};
  • mythtv/programs/mythfrontend/playbackboxlistitem.cpp

     
    99    pbbox(parent), needs_update(true)
    1010{
    1111}
    12 
     12/*
    1313void PlaybackBoxListItem::SetToRealButton(
    1414    MythUIStateType *button, bool selected)
    1515{
     
    2020    }
    2121    MythUIButtonListItem::SetToRealButton(button, selected);
    2222}
     23*/
  • mythtv/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"
     
    277277        }
    278278        else if (me->Message() == "GET_PREVIEW")
    279279        {
    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());
    281284            if (!evinfo.HasPathname())
    282285                return true;
    283286
    284             QStringList list;
     287            list.clear();
    285288            evinfo.ToStringList(list);
    286289            list += QString::number(kCheckForCache);
    287290            if (asAvailable != CheckAvailability(list))
    288291                return true;
    289292
    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);
    299295
    300296            return true;
    301297        }
     
    458454
    459455//////////////////////////////////////////////////////////////////////
    460456
    461 const uint PreviewGenState::maxAttempts     = 5;
    462 const uint PreviewGenState::minBlockSeconds = 60;
    463 
    464457PlaybackBoxHelper::PlaybackBoxHelper(QObject *listener) :
    465458    m_listener(listener), m_eventHandler(NULL),
    466459    // 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)
    470461{
    471     m_previewGeneratorMode = PreviewGenerator::kRemote;
    472 
    473     int idealThreads = QThread::idealThreadCount();
    474     if (idealThreads >= 1)
    475         m_previewGeneratorMaxThreads = idealThreads * 2;
    476 
    477462    start();
    478463}
    479464
     
    482467    exit();
    483468    wait();
    484469
    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 
    494470    // delete the event handler
    495471    delete m_eventHandler;
    496472    m_eventHandler = NULL;
     
    620596    return QString();
    621597}
    622598
    623 void PlaybackBoxHelper::GetPreviewImage(const ProgramInfo &pginfo)
     599QString PlaybackBoxHelper::GetPreviewImage(const ProgramInfo &pginfo)
    624600{
    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 {
    633601    if (pginfo.GetAvailableStatus() == asPendingDelete)
    634602        return QString();
    635603
    636     QString filename = pginfo.GetPathname() + ".png";
     604    QString token = QString("%1:%2")
     605        .arg(pginfo.MakeUniqueKey()).arg(rand());
    637606
    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);
     607    QStringList extra(token);
     608    pginfo.ToStringList(extra);
     609    MythEvent *e = new MythEvent("GET_PREVIEW", extra);
     610    QCoreApplication::postEvent(m_eventHandler, e);
    642611
    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;
    759613}
    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 }
  • mythtv/programs/mythfrontend/playbackboxlistitem.h

     
    1414  public:
    1515    PlaybackBoxListItem(PlaybackBox *parent, MythUIButtonList *lbtype, ProgramInfo *pi);
    1616
    17     virtual void SetToRealButton(MythUIStateType *button, bool selected);
     17//    virtual void SetToRealButton(MythUIStateType *button, bool selected);
    1818
    1919  private:
    2020    PlaybackBox *pbbox;
  • mythtv/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"
     
    14581459
    14591460    BackendConnectionManager bcm;
    14601461
     1462    PreviewGeneratorQueue::CreatePreviewGeneratorQueue(
     1463        PreviewGenerator::kRemote, 50, 60);
     1464
    14611465    int ret = qApp->exec();
    14621466
     1467    PreviewGeneratorQueue::TeardownPreviewGeneratorQueue();
     1468
    14631469    delete sysEventHandler;
    14641470
    14651471    pmanager->DestroyAllPlugins();
  • mythtv/programs/mythfrontend/playbackbox.h

     
    1616#include <QObject>
    1717#include <QMutex>
    1818#include <QMap>
     19#include <QSet>
    1920
    2021#include "jobqueue.h"
    2122#include "tv_play.h"
     
    315316        ProgramInfo *ProgramInfo_pointer_from_FindProgramInUILists,
    316317        bool force_preview_reload);
    317318    void UpdateUIListItem(MythUIButtonListItem *item, bool is_sel,
    318                           bool force_preview_reload = false);
     319                          bool force_preview_reload = true);
    319320
    320     void HandlePreviewEvent(const QString &piKey, const QString &previewFile);
     321    void HandlePreviewEvent(const QStringList &list);
    321322    void HandleRecordingRemoveEvent(uint chanid, const QDateTime &recstartts);
    322323    void HandleRecordingAddEvent(const ProgramInfo &evinfo);
    323324    void HandleUpdateProgramInfoEvent(const ProgramInfo &evinfo);
     
    442443    QStringList         m_player_selected_new_show;
    443444    /// Main helper thread
    444445    PlaybackBoxHelper   m_helper;
     446    /// Outstanding preview image requests
     447    QSet<QString>       m_preview_tokens;
    445448};
    446449
    447450class GroupSelector : public MythScreenType
  • mythtv/programs/mythfrontend/networkcontrol.cpp

     
    14031403
    14041404        frameNumber = results[7].toLongLong();
    14051405
    1406         PreviewGenerator *previewgen = new PreviewGenerator(&pginfo);
     1406        PreviewGenerator *previewgen = new PreviewGenerator(
     1407            &pginfo, QString(), PreviewGenerator::kLocal);
    14071408        previewgen->SetPreviewTimeAsFrameNumber(frameNumber);
    14081409        previewgen->SetOutputFilename(outFile);
    14091410        previewgen->SetOutputSize(QSize(width, height));
  • mythtv/programs/mythbackend/mythxml.cpp

     
    11491149            return;
    11501150
    11511151        PreviewGenerator *previewgen = new PreviewGenerator(
    1152             &pginfo, PreviewGenerator::kLocal);
     1152            &pginfo, QString(), PreviewGenerator::kLocal);
    11531153        previewgen->SetPreviewTimeAsSeconds(nSecsIn);
    11541154        previewgen->SetOutputFilename(sPreviewFileName);
    11551155        bool ok = previewgen->Run();
  • mythtv/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,
  • mythtv/programs/mythbackend/mainserver.h

     
    11#ifndef MAINSERVER_H_
    22#define MAINSERVER_H_
    33
    4 #include <QMap>
    5 #include <QMutex>
    64#include <QReadWriteLock>
    75#include <QEvent>
     6#include <QMutex>
     7#include <QHash>
     8#include <QMap>
    89
    910#include <vector>
    1011using namespace std;
     
    249250
    250251    int m_exitCode;
    251252
     253    typedef QHash<QString,QString> RequestedBy;
     254    RequestedBy                m_previewRequestedBy;
     255
    252256    static const uint kMasterServerReconnectTimeout;
    253257};
    254258
  • mythtv/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
  • mythtv/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);
  • mythtv/programs/mythbackend/mainserver.cpp

     
    3737#include <QTcpServer>
    3838#include <QTimer>
    3939
     40#include "previewgeneratorqueue.h"
    4041#include "exitcodes.h"
    4142#include "mythcontext.h"
    4243#include "mythverbose.h"
     
    5354#include "scheduledrecording.h"
    5455#include "jobqueue.h"
    5556#include "autoexpire.h"
    56 #include "previewgenerator.h"
    5757#include "storagegroup.h"
    5858#include "compat.h"
    5959#include "RingBuffer.h"
     
    184184{
    185185    AutoExpire::Update(true);
    186186
     187    PreviewGeneratorQueue::CreatePreviewGeneratorQueue(
     188        PreviewGenerator::kLocalAndRemote, ~0, 0);
     189    PreviewGeneratorQueue::AddListener(this);
     190
    187191    for (int i = 0; i < PRT_STARTUP_THREAD_COUNT; i++)
    188192    {
    189193        ProcessRequestThread *prt = new ProcessRequestThread(this);
     
    237241
    238242MainServer::~MainServer()
    239243{
     244    PreviewGeneratorQueue::RemoveListener(this);
     245    PreviewGeneratorQueue::TeardownPreviewGeneratorQueue();
     246
    240247    if (mythserver)
    241248    {
    242249        mythserver->disconnect();
     
    546553        else
    547554            HandleFileTransferQuery(listline, tokens, pbs);
    548555    }
    549     else if (command == "QUERY_GENPIXMAP")
     556    else if (command == "QUERY_GENPIXMAP2")
    550557    {
    551558        HandleGenPreviewPixmap(listline, pbs);
    552559    }
     
    729736void MainServer::customEvent(QEvent *e)
    730737{
    731738    QStringList broadcast;
     739    QSet<QString> receivers;
    732740
    733741    if ((MythEvent::Type)(e->type()) == MythEvent::MythEventMessage)
    734742    {
    735743        MythEvent *me = (MythEvent *)e;
    736744
     745        QString message = me->Message();
     746        QString error;
     747        if ((message == "PREVIEW_SUCCESS" || message == "PREVIEW_QUEUED") &&
     748            me->ExtraDataCount() >= 5)
     749        {
     750            bool ok = true;
     751            QString pginfokey = me->ExtraData(0); // pginfo->MakeUniqueKey()
     752            QString filename  = me->ExtraData(1); // outFileName
     753            QString msg       = me->ExtraData(2);
     754            QString datetime  = me->ExtraData(3);
     755
     756            if (message == "PREVIEW_QUEUED")
     757            {
     758                VERBOSE(VB_PLAYBACK, QString("Preview Queued: '%1' '%2'")
     759                        .arg(pginfokey).arg(filename));
     760                return;
     761            }
     762
     763            QFile file(filename);
     764            ok = ok && file.open(QIODevice::ReadOnly);
     765
     766            if (ok)
     767            {
     768                QByteArray data = file.readAll();
     769                QStringList extra("OK");
     770                extra.push_back(pginfokey);
     771                extra.push_back(msg);
     772                extra.push_back(datetime);
     773                extra.push_back(QString::number(data.size()));
     774                extra.push_back(
     775                    QString::number(qChecksum(data.constData(), data.size())));
     776                extra.push_back(QString(data.toBase64()));
     777
     778                for (uint i = 4 ; i < (uint) me->ExtraDataCount(); i++)
     779                {
     780                    QString token = me->ExtraData(i);
     781                    extra.push_back(token);
     782                    RequestedBy::iterator it = m_previewRequestedBy.find(token);
     783                    if (it != m_previewRequestedBy.end())
     784                    {
     785                        receivers.insert(*it);
     786                        m_previewRequestedBy.erase(it);
     787                    }
     788                }
     789
     790                if (receivers.empty())
     791                {
     792                    VERBOSE(VB_IMPORTANT, LOC_ERR +
     793                            "PREVIEW_SUCCESS but no receivers.");
     794                    return;
     795                }
     796
     797                broadcast.push_back("BACKEND_MESSAGE");
     798                broadcast.push_back("GENERATED_PIXMAP");
     799                broadcast += extra;
     800            }
     801            else
     802            {
     803                message = "PREVIEW_FAILED";
     804                error = QString("Failed to read '%1'").arg(filename);
     805                VERBOSE(VB_IMPORTANT, LOC_ERR + error);
     806            }
     807        }
     808
     809        if (message == "PREVIEW_FAILED" && me->ExtraDataCount() >= 5)
     810        {
     811            QString pginfokey = me->ExtraData(0); // pginfo->MakeUniqueKey()
     812            QString msg       = me->ExtraData(2);
     813
     814            QStringList extra("ERROR");
     815            extra.push_back(pginfokey);
     816            extra.push_back(msg);
     817            for (uint i = 4 ; i < (uint) me->ExtraDataCount(); i++)
     818            {
     819                QString token = me->ExtraData(i);
     820                extra.push_back(token);
     821                RequestedBy::iterator it = m_previewRequestedBy.find(token);
     822                if (it != m_previewRequestedBy.end())
     823                {
     824                    receivers.insert(*it);
     825                    m_previewRequestedBy.erase(it);
     826                }
     827            }
     828
     829            if (receivers.empty())
     830            {
     831                VERBOSE(VB_IMPORTANT, LOC_ERR +
     832                        "PREVIEW_FAILED but no receivers.");
     833                return;
     834            }
     835
     836            broadcast.push_back("BACKEND_MESSAGE");
     837            broadcast.push_back("GENERATED_PIXMAP");
     838            broadcast += extra;
     839        }
     840
    737841        if (me->Message().left(11) == "AUTO_EXPIRE")
    738842        {
    739843            QStringList tokens = me->Message()
     
    9451049            me = &mod_me;
    9461050        }
    9471051
    948         broadcast = QStringList( "BACKEND_MESSAGE" );
    949         broadcast << me->Message();
    950         broadcast += me->ExtraDataList();
     1052        if (broadcast.empty())
     1053        {
     1054            broadcast.push_back("BACKEND_MESSAGE");
     1055            broadcast.push_back(me->Message());
     1056            broadcast += me->ExtraDataList();
     1057        }
    9511058    }
    9521059
    9531060    if (!broadcast.empty())
     
    9731080            sendGlobal = true;
    9741081        }
    9751082
    976         vector<PlaybackSock*> sentSet;
     1083        QSet<PlaybackSock*> sentSet;
    9771084
    9781085        bool isSystemEvent = broadcast[1].startsWith("SYSTEM_EVENT ");
    9791086        QStringList sentSetSystemEvent(gCoreContext->GetHostName());
     
    9831090        {
    9841091            PlaybackSock *pbs = *iter;
    9851092
    986             vector<PlaybackSock*>::const_iterator it =
    987                 find(sentSet.begin(), sentSet.end(), pbs);
    988             if (it != sentSet.end() || pbs->IsDisconnected())
     1093            if (sentSet.contains(pbs) || pbs->IsDisconnected())
    9891094                continue;
    9901095
    991             sentSet.push_back(pbs);
     1096            if (!receivers.empty() && !receivers.contains(pbs->getHostname()))
     1097                continue;
    9921098
     1099            sentSet.insert(pbs);
     1100
    9931101            bool reallysendit = false;
    9941102
    9951103            if (broadcast[1] == "CLEAR_SETTINGS_CACHE")
     
    49135021{
    49145022    MythSocket *pbssock = pbs->getSocket();
    49155023
     5024    if (slist.size() < 3)
     5025    {
     5026        VERBOSE(VB_IMPORTANT, LOC_ERR + "Too few params in pixmap request");
     5027        QStringList outputlist("ERROR");
     5028        outputlist += "TOO_FEW_PARAMS";
     5029        SendResponse(pbssock, outputlist);
     5030        return;
     5031    }
     5032
    49165033    bool      time_fmt_sec   = true;
    49175034    long long time           = -1;
    49185035    QString   outputfile;
     
    49205037    int       height         = -1;
    49215038    bool      has_extra_data = false;
    49225039
    4923     QStringList::const_iterator it = slist.begin() + 1;
     5040    QString token = slist[1];
     5041    if (token.isEmpty())
     5042    {
     5043        VERBOSE(VB_IMPORTANT, LOC_ERR + "Failed to parse pixmap request. "
     5044                "Token absent");
     5045        QStringList outputlist("ERROR");
     5046        outputlist += "TOKEN_ABSENT";
     5047        SendResponse(pbssock, outputlist);
     5048        return;
     5049    }
     5050
     5051    QStringList::const_iterator it = slist.begin() + 2;
    49245052    QStringList::const_iterator end = slist.end();
    49255053    ProgramInfo pginfo(it, end);
    49265054    bool ok = pginfo.HasPathname();
    49275055    if (!ok)
    49285056    {
    4929         VERBOSE(VB_IMPORTANT, "MainServer: Failed to parse pixmap request.");
     5057        VERBOSE(VB_IMPORTANT, LOC_ERR + "Failed to parse pixmap request. "
     5058                "ProgramInfo missing pathname");
    49305059        QStringList outputlist("BAD");
    4931         outputlist += "ERROR_INVALID_REQUEST";
     5060        outputlist += "NO_PATHNAME";
    49325061        SendResponse(pbssock, outputlist);
     5062        return;
    49335063    }
     5064    if (token.toLower() == "do_not_care")
     5065    {
     5066        token = QString("%1:%2")
     5067            .arg(pginfo.MakeUniqueKey()).arg(rand());
     5068    }
    49345069    if (it != slist.end())
    49355070        (time_fmt_sec = ((*it).toLower() == "s")), it++;
    49365071    if (it != slist.end())
     
    49615096
    49625097    pginfo.SetPathname(GetPlaybackURL(&pginfo));
    49635098
     5099    m_previewRequestedBy[token] = pbs->getHostname();
     5100
    49645101    if ((ismaster) &&
    49655102        (pginfo.GetHostname() != gCoreContext->GetHostName()) &&
    49665103        (!masterBackendOverride || !pginfo.IsLocal()))
     
    49695106
    49705107        if (slave)
    49715108        {
    4972             QStringList outputlist("OK");
     5109            QStringList outputlist;
    49735110            if (has_extra_data)
    49745111            {
    49755112                outputlist = slave->GenPreviewPixmap(
    4976                     &pginfo, time_fmt_sec, time, outputfile, outputsize);
     5113                    token, &pginfo, time_fmt_sec, time, outputfile, outputsize);
    49775114            }
    49785115            else
    49795116            {
    4980                 outputlist = slave->GenPreviewPixmap(&pginfo);
     5117                outputlist = slave->GenPreviewPixmap(token, &pginfo);
    49815118            }
    49825119
    49835120            slave->DownRef();
     5121
     5122            if (outputlist.empty() || outputlist[0] != "OK")
     5123                m_previewRequestedBy.remove(token);
     5124
    49845125            SendResponse(pbssock, outputlist);
    49855126            return;
    49865127        }
     
    49925133
    49935134    if (!pginfo.IsLocal())
    49945135    {
    4995         VERBOSE(VB_IMPORTANT, "MainServer: HandleGenPreviewPixmap: Unable to "
     5136        VERBOSE(VB_IMPORTANT, LOC_ERR + "HandleGenPreviewPixmap: Unable to "
    49965137                "find file locally, unable to make preview image.");
    4997         QStringList outputlist( "BAD" );
    4998         outputlist += "ERROR_NOFILE";
     5138        QStringList outputlist( "ERROR" );
     5139        outputlist += "FILE_INACCESSIBLE";
    49995140        SendResponse(pbssock, outputlist);
     5141        m_previewRequestedBy.remove(token);
    50005142        return;
    50015143    }
    50025144
    5003     PreviewGenerator *previewgen = new PreviewGenerator(&pginfo);
    50045145    if (has_extra_data)
    50055146    {
    5006         previewgen->SetOutputSize(outputsize);
    5007         previewgen->SetOutputFilename(outputfile);
    5008         previewgen->SetPreviewTime(time, time_fmt_sec);
     5147        PreviewGeneratorQueue::GetPreviewImage(
     5148            pginfo, outputsize, outputfile, time, time_fmt_sec, token);
    50095149    }
    5010     ok = previewgen->Run();
    5011     previewgen->deleteLater();
    5012 
    5013     if (ok)
    5014     {
    5015         QStringList outputlist("OK");
    5016         if (!outputfile.isEmpty())
    5017             outputlist += outputfile;
    5018         SendResponse(pbssock, outputlist);
    5019     }
    50205150    else
    50215151    {
    5022         VERBOSE(VB_IMPORTANT, "MainServer: Failed to make preview image.");
    5023         QStringList outputlist( "BAD" );
    5024         outputlist += "ERROR_UNKNOWN";
    5025         SendResponse(pbssock, outputlist);
     5152        PreviewGeneratorQueue::GetPreviewImage(pginfo, token);
    50265153    }
     5154
     5155    QStringList outputlist("OK");
     5156    if (!outputfile.isEmpty())
     5157        outputlist += outputfile;
     5158    SendResponse(pbssock, outputlist);
    50275159}
    50285160
    50295161void MainServer::HandlePixmapLastModified(QStringList &slist, PlaybackSock *pbs)
  • mythtv/programs/mythpreviewgen/main.cpp

     
    135135    }
    136136
    137137    PreviewGenerator *previewgen = new PreviewGenerator(
    138         pginfo, PreviewGenerator::kLocal);
     138        pginfo, QString(), PreviewGenerator::kLocal);
    139139
    140140    if (previewFrameNumber >= 0)
    141141        previewgen->SetPreviewTimeAsFrameNumber(previewFrameNumber);
  • mythtv/bindings/python/MythTV/static.py

     
    99MVSCHEMA_VERSION = 1036
    1010NVSCHEMA_VERSION = 1007
    1111MUSICSCHEMA_VERSION = 1017
    12 PROTO_VERSION = '60'
     12PROTO_VERSION = '61'
    1313BACKEND_SEP = '[]:[]'
    1414
    1515class MARKUP( object ):
  • mythtv/bindings/perl/MythTV.pm

     
    106106# Note: as of July 21, 2010, this is actually a string, to account for proto
    107107# versions of the form "58a".  This will get used if protocol versions are
    108108# changed on a fixes branch ongoing.
    109     our $PROTO_VERSION = "60";
     109    our $PROTO_VERSION = "61";
    110110
    111111# NUMPROGRAMLINES is defined in mythtv/libs/libmythtv/programinfo.h and is
    112112# the number of items in a ProgramInfo QStringList group used by
  • mythtv/bindings/perl/MythTV/Recording.pm

     
    448448    sub generate_pixmap {
    449449        my $self = shift;
    450450        my $ret = $self->{'_mythtv'}->backend_command(join($MythTV::BACKEND_SEP,
    451                                                            'QUERY_GENPIXMAP',
     451                                                           'QUERY_GENPIXMAP2',
     452                                                           "do_not_care",
    452453                                                           $self->to_string())
    453454                                                     );
    454         if ($ret eq 'BAD') {
     455        if ($ret eq 'ERROR') {
    455456            print STDERR "Unknown error generating pixmap for $self->{'chanid'}:$self->{'starttime'}\n";
    456457            return 0;
    457458        }