Ticket #7195: 7191-gen2-v5.patch

File 7191-gen2-v5.patch, 85.1 KB (added by danielk, 14 years ago)
  • libs/libmythtv/libmythtv.pro

     
    147147HEADERS += scheduledrecording.h
    148148HEADERS += signalmonitorvalue.h     signalmonitorlistener.h
    149149HEADERS += livetvchain.h            playgroup.h
    150 HEADERS += channelsettings.h        previewgenerator.h
     150HEADERS += channelsettings.h
     151HEADERS += previewgenerator.h       previewgeneratorqueue.h
    151152HEADERS += transporteditor.h        listingsources.h
    152153HEADERS += myth_imgconvert.h
    153154HEADERS += channelgroup.h           channelgroupsettings.h
     
    171172SOURCES += scheduledrecording.cpp
    172173SOURCES += signalmonitorvalue.cpp
    173174SOURCES += livetvchain.cpp          playgroup.cpp
    174 SOURCES += channelsettings.cpp      previewgenerator.cpp
     175SOURCES += channelsettings.cpp
     176SOURCES += previewgenerator.cpp     previewgeneratorqueue.cpp
    175177SOURCES += transporteditor.cpp
    176178SOURCES += channelgroup.cpp         channelgroupsettings.cpp
    177179SOURCES += myth_imgconvert.cpp
  • libs/libmythtv/previewgeneratorqueue.h

     
     1// -*- Mode: c++ -*-
     2#ifndef _PREVIEW_GENERATOR_QUEUE_H_
     3#define _PREVIEW_GENERATOR_QUEUE_H_
     4
     5#include <QStringList>
     6#include <QDateTime>
     7#include <QThread>
     8#include <QMutex>
     9#include <QMap>
     10#include <QSet>
     11
     12#include "previewgenerator.h"
     13#include "mythexp.h"
     14
     15class ProgramInfo;
     16class QSize;
     17
     18class PreviewGenState
     19{
     20  public:
     21    PreviewGenState() :
     22        gen(NULL), genStarted(false),
     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
     80  private:
     81    static PreviewGeneratorQueue *s_pgq;
     82    QSet<QObject*> m_listeners;
     83
     84    mutable QMutex         m_lock;
     85    PreviewGenerator::Mode m_mode;
     86    PreviewMap             m_previewMap;
     87    QMap<QString,QString>  m_tokenToKeyMap;
     88    QStringList            m_queue;
     89    uint                   m_running;
     90    uint                   m_maxThreads;
     91    uint                   m_maxAttempts;
     92    uint                   m_minBlockSeconds;
     93};
     94
     95#endif // _PREVIEW_GENERATOR_QUEUE_H_
  • libs/libmythtv/previewgenerator.cpp

     
    66#include <fcntl.h>
    77
    88// Qt headers
     9#include <QCoreApplication>
     10#include <QTemporaryFile>
    911#include <QFileInfo>
     12#include <QMetaType>
     13#include <QThread>
    1014#include <QImage>
    11 #include <QMetaType>
     15#include <QDir>
    1216#include <QUrl>
    13 #include <QDir>
    1417
    1518// MythTV headers
    1619#include "mythconfig.h"
     
    2730#include "playercontext.h"
    2831#include "mythdirs.h"
    2932#include "mythverbose.h"
     33#include "remoteutil.h"
    3034
    3135#define LOC QString("Preview: ")
    3236#define LOC_ERR QString("Preview Error: ")
     
    3741 *
    3842 *   The usage is simple: First, pass a ProgramInfo whose pathname points
    3943 *   to a local or remote recording to the constructor. Then call either
    40  *   Start(void) or Run(void) to generate the preview.
     44 *   start(void) or Run(void) to generate the preview.
    4145 *
    42  *   Start(void) will create a thread that processes the request,
     46 *   start(void) will create a thread that processes the request,
    4347 *   creating a sockets the the backend if the recording is not local.
    4448 *
    4549 *   Run(void) will process the request in the current thread, and it
     
    4751 *   is not local.
    4852 *
    4953 *   The PreviewGenerator will send Qt signals when the preview is ready
    50  *   and when the preview thread finishes running if Start(void) was called.
     54 *   and when the preview thread finishes running if start(void) was called.
    5155 */
    5256
    5357/**
     
    6569 */
    6670PreviewGenerator::PreviewGenerator(const ProgramInfo *pginfo,
    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), 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();
    155116    if (is_local && (mode && kLocal) && LocalPreviewRun())
    156117    {
    157118        ok = true;
     119        msg = QString("Generated on %1 in %2 seconds, starting at %3")
     120            .arg(gCoreContext->GetHostName())
     121            .arg(tm.elapsed()*0.001)
     122            .arg(tm.toString(Qt::ISODate));
    158123    }
    159124    else if (mode & kRemote)
    160125    {
     
    166131                    "\n\t\t\tAttempting to regenerate preview on backend.\n");
    167132        }
    168133        ok = RemotePreviewRun();
     134        if (ok)
     135        {
     136            msg = QString("Generated remotely in %1 seconds, starting at %2")
     137                .arg(tm.elapsed()*0.001)
     138                .arg(tm.toString(Qt::ISODate));
     139        }
    169140    }
    170141    else
    171142    {
    172143        VERBOSE(VB_IMPORTANT, LOC_ERR + QString("Run() file not local: '%1'")
    173144                .arg(pathname));
     145        msg = "Could not access recording";
    174146    }
    175147
     148    QMutexLocker locker(&previewLock);
     149    if (listener)
     150    {
     151        QString message = (ok) ? "PREVIEW_SUCCESS" : "PREVIEW_FAILED";
     152        QStringList list;
     153        list.push_back(programInfo.MakeUniqueKey());
     154        list.push_back(outFileName.isEmpty() ?
     155                       (programInfo.GetPathname()+".png") : outFileName);
     156        list.push_back(msg);
     157        list.push_back(token);
     158        QCoreApplication::postEvent(listener, new MythEvent(message, list));
     159    }
     160
    176161    return ok;
    177162}
    178163
    179164bool PreviewGenerator::Run(void)
    180165{
     166    VERBOSE(VB_IMPORTANT, LOC + "Run() -- begin");
     167
     168    QString msg;
     169    QTime tm = QTime::currentTime();
    181170    bool ok = false;
    182171    QString command = GetInstallPrefix() + "/bin/mythpreviewgen";
    183172    bool local_ok = (IsLocal() && (mode & kLocal) &&
     
    187176        if (mode & kRemote)
    188177        {
    189178            ok = RemotePreviewRun();
     179            if (ok)
     180            {
     181                msg =
     182                    QString("Generated remotely in %1 seconds, starting at %2")
     183                    .arg(tm.elapsed()*0.001)
     184                    .arg(tm.toString(Qt::ISODate));
     185            }
    190186        }
    191187        else
    192188        {
    193189            VERBOSE(VB_IMPORTANT, LOC_ERR +
    194190                    QString("Run() can not generate preview locally for: '%1'")
    195191                    .arg(pathname));
     192            msg = "Failed, local preview requested for remote file.";
    196193        }
    197194    }
    198195    else
     
    215212        if (!outFileName.isEmpty())
    216213            command += QString("--outfile \"%1\" ").arg(outFileName);
    217214
     215        command += " >> /tmp/previewgen.txt";
     216
    218217        int ret = myth_system(command, MYTH_SYSTEM_DONT_BLOCK_LIRC |
    219218                                       MYTH_SYSTEM_DONT_BLOCK_JOYSTICK_MENU |
    220219                                       MYTH_SYSTEM_DONT_BLOCK_PARENT);
    221220        if (ret)
    222221        {
    223             VERBOSE(VB_IMPORTANT, LOC_ERR + "Encountered problems running " +
    224                     QString("'%1'").arg(command));
     222            msg = QString("Encountered problems running '%1'").arg(command);
     223            VERBOSE(VB_IMPORTANT, LOC_ERR + msg);
    225224        }
    226225        else
    227226        {
     
    240239            QFileInfo fi(outname);
    241240            ok = (fi.exists() && fi.isReadable() && fi.size());
    242241            if (ok)
     242            {
    243243                VERBOSE(VB_PLAYBACK, LOC + "Preview process ran ok.");
     244                msg = QString("Generated on %1 in %2 seconds, starting at %3")
     245                    .arg(gCoreContext->GetHostName())
     246                    .arg(tm.elapsed()*0.001)
     247                    .arg(tm.toString(Qt::ISODate));
     248            }
    244249            else
    245250            {
    246251                VERBOSE(VB_IMPORTANT, LOC_ERR + "Preview process not ok." +
     
    251256                VERBOSE(VB_IMPORTANT, LOC_ERR +
    252257                        QString("Despite command '%1' returning success")
    253258                        .arg(command));
     259                msg = QString("Failed to read preview image despite "
     260                              "preview process returning success.");
    254261            }
    255262        }
    256263    }
    257264
    258     if (ok)
     265    VERBOSE(VB_IMPORTANT, LOC + "Run() -- waiting on lock");
     266    QMutexLocker locker(&previewLock);
     267    VERBOSE(VB_IMPORTANT, LOC + "Run() -- got lock");
     268
     269    QString message = (ok) ? "PREVIEW_SUCCESS" : "PREVIEW_FAILED";
     270    if (listener)
    259271    {
    260         QMutexLocker locker(&previewLock);
    261         emit previewReady(&programInfo);
     272        QStringList list;
     273        list.push_back(programInfo.MakeUniqueKey());
     274        list.push_back(outFileName.isEmpty() ?
     275                       (programInfo.GetPathname()+".png") : outFileName);
     276        list.push_back(msg);
     277        list.push_back(token);
     278        QCoreApplication::postEvent(listener, new MythEvent(message, list));
    262279    }
     280    VERBOSE(VB_IMPORTANT, LOC + "Run() -- end");
    263281
    264282    return ok;
    265283}
    266284
    267 void *PreviewGenerator::PreviewRun(void *param)
     285void PreviewGenerator::run(void)
    268286{
    269     // Lower scheduling priority, to avoid problems with recordings.
    270     if (setpriority(PRIO_PROCESS, 0, 9))
    271         VERBOSE(VB_IMPORTANT, LOC + "Setting priority failed." + ENO);
    272     PreviewGenerator *gen = (PreviewGenerator*) param;
    273     gen->createSockets = true;
    274     gen->Run();
    275     gen->deleteLater();
    276     return NULL;
     287    VERBOSE(VB_IMPORTANT, LOC + "run() -- begin");
     288    setPriority(QThread::LowPriority);
     289    Run();
     290    connect(this, SIGNAL(finished()),
     291            this, SLOT(deleteLater()));
     292    VERBOSE(VB_IMPORTANT, LOC + "run() -- end");
    277293}
    278294
    279 bool PreviewGenerator::RemotePreviewSetup(void)
     295bool PreviewGenerator::RemotePreviewRun(void)
    280296{
    281     QString server = gCoreContext->GetSetting("MasterServerIP", "localhost");
    282     int     port   = gCoreContext->GetNumSetting("MasterServerPort", 6543);
    283     QString ann    = QString("ANN Monitor %2 %3")
    284         .arg(gCoreContext->GetHostName()).arg(false);
     297    VERBOSE(VB_IMPORTANT, LOC + "RemotePreviewRun() -- begin");
    285298
    286     serverSock = gCoreContext->ConnectCommandSocket(server, port, ann);
    287     return serverSock;
    288 }
    289 
    290 bool PreviewGenerator::RemotePreviewRun(void)
    291 {
    292     QStringList strlist( "QUERY_GENPIXMAP" );
     299    QStringList strlist( "QUERY_GENPIXMAP2" );
     300    if (token.isEmpty())
     301    {
     302        token = QString("%1:%2")
     303            .arg(programInfo.MakeUniqueKey()).arg(rand());
     304    }
     305    strlist.push_back(token);
    293306    programInfo.ToStringList(strlist);
    294307    strlist.push_back(timeInSeconds ? "s" : "f");
    295308    encodeLongLong(strlist, captureTime);
     
    305318    strlist.push_back(QString::number(outSize.width()));
    306319    strlist.push_back(QString::number(outSize.height()));
    307320
    308     bool ok = false;
     321    gCoreContext->addListener(this);
     322    pixmapOk = false;
    309323
    310     if (createSockets)
    311     {
    312         if (!RemotePreviewSetup())
    313         {
    314             VERBOSE(VB_IMPORTANT, LOC_ERR + "Failed to open sockets.");
    315             return false;
    316         }
    317 
    318         if (serverSock)
    319         {
    320             serverSock->writeStringList(strlist);
    321             ok = serverSock->readStringList(strlist, false);
    322         }
    323 
    324         RemotePreviewTeardown();
    325     }
    326     else
    327     {
    328         ok = gCoreContext->SendReceiveStringList(strlist);
    329     }
    330 
     324    bool ok = gCoreContext->SendReceiveStringList(strlist);
    331325    if (!ok || strlist.empty() || (strlist[0] != "OK"))
    332326    {
    333327        if (!ok)
     
    340334            VERBOSE(VB_IMPORTANT, LOC_ERR +
    341335                    "Remote Preview failed, reason given: " <<strlist[1]);
    342336        }
    343         else
     337
     338        gCoreContext->removeListener(this);
     339
     340        return false;
     341    }
     342
     343    VERBOSE(VB_IMPORTANT, LOC + "RemotePreviewRun() -- waiting on lock");
     344    QMutexLocker locker(&previewLock);
     345    VERBOSE(VB_IMPORTANT, LOC + "RemotePreviewRun() -- got lock");
     346
     347    // wait up to 30 seconds for the preview to complete
     348    if (!gotReply)
     349        previewWaitCondition.wait(&previewLock, 30 * 1000);
     350
     351    if (!gotReply)
     352        VERBOSE(VB_IMPORTANT, LOC + "RemotePreviewRun() -- no reply..");
     353
     354    gCoreContext->removeListener(this);
     355
     356    VERBOSE(VB_IMPORTANT, LOC + "RemotePreviewRun() -- end");
     357
     358    return pixmapOk;
     359}
     360
     361bool PreviewGenerator::event(QEvent *e)
     362{
     363    if (e->type() == (QEvent::Type) MythEvent::MythEventMessage)
     364    {
     365        MythEvent *me = (MythEvent*)e;
     366        if (me->Message() == "GENERATED_PIXMAP" && (me->ExtraDataCount() >= 3))
    344367        {
    345             VERBOSE(VB_IMPORTANT, LOC_ERR +
    346                     "Remote Preview failed due to an unknown error.");
     368            bool ok = me->ExtraData(0) == "OK";
     369            bool ours = false;
     370            uint i = ok ? 3 : 2;
     371            for (; i < (uint) me->ExtraDataCount() && !ours; i++)
     372                ours |= me->ExtraData(i) == token;
     373
     374            if (!ours)
     375                return false;
     376
     377            VERBOSE(VB_IMPORTANT, LOC + "GOT OUR GENERATED_PIXMAP EVENT");
     378
     379            QMutexLocker locker(&previewLock);
     380            gotReply = true;
     381            pixmapOk = ok;
     382            if (!pixmapOk)
     383            {
     384                QString msg =  me->ExtraData(1);
     385                if (!msg.isEmpty())
     386                    VERBOSE(VB_IMPORTANT, LOC_ERR + msg);
     387                return true;
     388            }
     389
     390            if (me->ExtraDataCount() < 4)
     391                return true; // could only happen with very broken client...
     392
     393            QByteArray data;
     394            size_t length = me->ExtraData(1).toULongLong();
     395            quint16 checksum16 = me->ExtraData(2).toUInt();
     396            data = QByteArray::fromBase64(me->ExtraData(3).toAscii());
     397            if ((size_t) data.size() < length)
     398            {   // (note data.size() may be up to 3
     399                //  bytes longer after decoding
     400                VERBOSE(VB_IMPORTANT, LOC_ERR +
     401                        QString("Preview size check failed %1 < %2")
     402                        .arg(data.size()).arg(length));
     403                data.clear();
     404            }
     405            data.resize(length);
     406
     407            if (checksum16 != qChecksum(data.constData(), data.size()))
     408            {
     409                VERBOSE(VB_IMPORTANT, LOC_ERR + "Preview checksum failed");
     410                data.clear();
     411            }
     412
     413            pixmapOk = (data.isEmpty()) ? false : SaveOutFile(data);
     414
     415            previewWaitCondition.wakeAll();
     416
     417            return true;
    347418        }
    348         return false;
    349419    }
     420    return QObject::event(e);
     421}
    350422
     423bool PreviewGenerator::SaveOutFile(QByteArray &data)
     424{
    351425    if (outFileName.isEmpty())
    352426    {
    353         QString remotecachedirname = QString("%1/remotecache").arg(GetConfDir());
     427        QString remotecachedirname =
     428            QString("%1/remotecache").arg(GetConfDir());
    354429        QDir remotecachedir(remotecachedirname);
    355430
    356431        if (!remotecachedir.exists())
     
    368443        outFileName = QString("%1/%2").arg(remotecachedirname).arg(filename);
    369444    }
    370445
    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)
     446    QFile file(outFileName);
     447    bool ok = file.open(QIODevice::Unbuffered|QIODevice::WriteOnly);
     448    if (!ok)
    386449    {
    387         data.resize(0);
    388         url = *it;
    389         RemoteFile *rf = new RemoteFile(url, false, false, 0);
    390         ok = rf->SaveAs(data);
    391         delete rf;
     450        VERBOSE(VB_IMPORTANT, LOC_ERR + QString("Failed to open: '%1'")
     451                .arg(outFileName));
    392452    }
    393453
    394     if (ok && data.size())
     454    off_t offset = 0;
     455    size_t remaining = data.size();
     456    uint failure_cnt = 0;
     457    while ((remaining > 0) && (failure_cnt < 5))
    395458    {
    396         QFile file(outFileName);
    397         ok = file.open(QIODevice::Unbuffered|QIODevice::WriteOnly);
    398         if (!ok)
     459        ssize_t written = file.write(data.data() + offset, remaining);
     460        if (written < 0)
    399461        {
    400             VERBOSE(VB_IMPORTANT, QString("Failed to open: '%1'")
    401                     .arg(outFileName));
     462            failure_cnt++;
     463            usleep(50000);
     464            continue;
    402465        }
    403466
    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         }
     467        failure_cnt  = 0;
     468        offset      += written;
     469        remaining   -= written;
    426470    }
    427 
    428     return ok && data.size();
    429 }
    430 
    431 void PreviewGenerator::RemotePreviewTeardown(void)
    432 {
    433     if (serverSock)
     471    if (ok && !remaining)
    434472    {
    435         serverSock->DownRef();
    436         serverSock = NULL;
     473        VERBOSE(VB_PLAYBACK, LOC + QString("Saved: '%1'").arg(outFileName));
    437474    }
     475    else
     476    {
     477        file.remove();
     478    }
     479
     480    return ok;
    438481}
    439482
    440483bool PreviewGenerator::SavePreview(QString filename,
     
    476519    QImage small_img = img.scaled((int) ppw, (int) pph,
    477520        Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
    478521
    479     QByteArray fname = filename.toAscii();
    480     if (small_img.save(fname.constData(), "PNG"))
     522    QTemporaryFile f(QFileInfo(filename).absoluteFilePath()+".XXXXXX");
     523    f.setAutoRemove(false);
     524    if (f.open() && small_img.save(&f, "PNG"))
    481525    {
    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;
     526        // Let anybody update it
     527        makeFileAccessible(f.fileName().toLocal8Bit().constData());
     528        QFile of(filename);
     529        of.remove();
     530        if (f.rename(filename))
     531        {
     532            VERBOSE(VB_PLAYBACK, LOC +
     533                    QString("Saved preview '%0' %1x%2")
     534                    .arg(filename).arg((int) ppw).arg((int) pph));
     535            return true;
     536        }
     537        f.remove();
    489538    }
    490539
    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());
     540    VERBOSE(VB_IMPORTANT, LOC_ERR +
     541            QString("Failed to save preview '%0' %1x%2")
     542            .arg(filename).arg((int) ppw).arg((int) pph));
    499543
    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?
    508544    return false;
    509545}
    510546
     
    515551    float aspect = 0;
    516552    int   width, height, sz;
    517553    long long captime = captureTime;
     554
     555    if (captime > 0)
     556        VERBOSE(VB_IMPORTANT, "Preview from time spec");
     557
    518558    if (captime < 0)
    519559    {
     560        captime = programInfo.QueryBookmark();
     561        if (captime > 0)
     562        {
     563            VERBOSE(VB_IMPORTANT, "Preview from bookmark...");
     564            timeInSeconds = false;
     565        }
     566        else
     567        {
     568            captime = -1;
     569        }
     570    }
     571
     572    if (captime < 0)
     573    {
    520574        timeInSeconds = true;
    521575        int startEarly = 0;
    522576        int programDuration = 0;
     
    544598        if (captime < 0)
    545599            captime = 600;
    546600        captime += preroll;
     601
     602        VERBOSE(VB_IMPORTANT, "Preview from pre-roll...");
    547603    }
    548604
    549605    width = height = sz = 0;
  • libs/libmythtv/recordingrule.h

     
    124124    // different options for override rules
    125125    bool m_isOverride;
    126126   
    127   private:
     127  //private:
    128128    // Populate variables from a ProgramInfo object
    129129    void AssignProgramInfo();
    130130     
  • 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 token     = me->ExtraData(3);
     149
     150        VERBOSE(VB_PLAYBACK, LOC + QString("Preview '%1' processed")
     151                .arg(token));
     152
     153        {
     154            QMutexLocker locker(&m_lock);
     155            QMap<QString,QString>::iterator kit = m_tokenToKeyMap.find(token);
     156            if (kit == m_tokenToKeyMap.end())
     157            {
     158                VERBOSE(VB_IMPORTANT, LOC_ERR +
     159                        QString("Failed to find token %1 in map.").arg(token));
     160                return true;
     161            }
     162            PreviewMap::iterator it = m_previewMap.find(*kit);
     163            if (it == m_previewMap.end())
     164            {
     165                VERBOSE(VB_IMPORTANT, LOC_ERR +
     166                        QString("Failed to find key %1 in map.").arg(*kit));
     167                return true;
     168            }
     169
     170            (*it).gen           = NULL;
     171            (*it).genStarted    = false;
     172            if (me->Message() == "PREVIEW_SUCCESS")
     173            {
     174                (*it).attempts      = 0;
     175                (*it).lastBlockTime = 0;
     176                (*it).blockRetryUntil = QDateTime();
     177            }
     178            else
     179            {
     180                (*it).lastBlockTime =
     181                    max(m_minBlockSeconds, (*it).lastBlockTime * 2);
     182                (*it).blockRetryUntil =
     183                    QDateTime::currentDateTime().addSecs((*it).lastBlockTime);
     184            }
     185
     186            QStringList list;
     187            list.push_back(pginfokey);
     188            list.push_back(filename);
     189            list.push_back(msg);
     190            QSet<QString>::const_iterator tit = (*it).tokens.begin();
     191            for (; tit != (*it).tokens.end(); ++tit)
     192            {
     193                kit = m_tokenToKeyMap.find(*tit);
     194                if (kit != m_tokenToKeyMap.end())
     195                    m_tokenToKeyMap.erase(kit);
     196                list.push_back(*tit);
     197            }
     198
     199            QSet<QObject*>::iterator sit = m_listeners.begin();
     200            for (; sit != m_listeners.end(); ++sit)
     201            {
     202                MythEvent *e = new MythEvent(me->Message(), list);
     203                QCoreApplication::postEvent(*sit, e);
     204            }
     205            (*it).tokens.clear();
     206
     207            m_running--;
     208        }
     209
     210        UpdatePreviewGeneratorThreads();
     211
     212        return true;
     213    }
     214    return false;
     215}
     216
     217void PreviewGeneratorQueue::SendEvent(
     218    const ProgramInfo &pginfo,
     219    const QString &eventname,
     220    const QString &fn, const QString &token, const QString &msg)
     221{
     222    VERBOSE(VB_IMPORTANT, QString("SendEvent(%1, %2, %3, %4)")
     223            .arg(pginfo.toString(ProgramInfo::kTitleSubtitle))
     224            .arg(eventname).arg(msg).arg(token));
     225
     226    QStringList list;
     227    list.push_back(pginfo.MakeUniqueKey());
     228    list.push_back(fn);
     229    list.push_back(msg);
     230    list.push_back(token);
     231
     232    QMutexLocker locker(&m_lock);
     233    QSet<QObject*>::iterator it = m_listeners.begin();
     234    for (; it != m_listeners.end(); ++it)
     235    {
     236        MythEvent *e = new MythEvent(eventname, list);
     237        QCoreApplication::postEvent(*it, e);
     238    }
     239}
     240
     241QString PreviewGeneratorQueue::GeneratePreviewImage(
     242    ProgramInfo &pginfo,
     243    const QSize &size,
     244    const QString &outputfile,
     245    long long time, bool in_seconds,
     246    QString token)
     247{
     248    QString key = QString("%1_%2x%3_%4%5")
     249        .arg(pginfo.GetBasename()).arg(size.width()).arg(size.height())
     250        .arg(time).arg(in_seconds?"s":"f");
     251
     252    if (pginfo.GetAvailableStatus() == asPendingDelete)
     253    {
     254        SendEvent(pginfo, "PREVIEW_FAILED", key, token, "Pending Delete");
     255        return QString();
     256    }
     257
     258    QString filename = (outputfile.isEmpty()) ?
     259        pginfo.GetPathname() + ".png" : outputfile;
     260    QString ret_file = filename;
     261    QString ret;
     262
     263    bool is_special = !outputfile.isEmpty() || time >= 0 ||
     264        size.width() || size.height();
     265
     266    bool needs_gen = true;
     267    if (!is_special)
     268    {
     269        QDateTime previewLastModified;
     270        bool streaming = filename.left(1) != "/";
     271        bool locally_accessible = false;
     272        bool bookmark_updated = false;
     273
     274        QDateTime bookmark_ts = pginfo.QueryBookmarkTimeStamp();
     275        QDateTime cmp_ts = bookmark_ts.isValid() ?
     276            bookmark_ts : pginfo.GetLastModifiedTime();
     277
     278        if (streaming)
     279        {
     280            ret_file = QString("%1/remotecache/%2")
     281                .arg(GetConfDir()).arg(filename.section('/', -1));
     282
     283            QFileInfo finfo(ret_file);
     284            if (finfo.isReadable() && finfo.lastModified() >= cmp_ts)
     285            {
     286                // This is just an optimization to avoid
     287                // hitting the backend if our cached copy
     288                // is newer than the bookmark, or if we have
     289                // a preview and do not update it when the
     290                // bookmark changes.
     291                previewLastModified = finfo.lastModified();
     292            }
     293            else if (!IsGeneratingPreview(key))
     294            {
     295                previewLastModified =
     296                    RemoteGetPreviewIfModified(pginfo, ret_file);
     297            }
     298        }
     299        else
     300        {
     301            QFileInfo fi(filename);
     302            if ((locally_accessible = fi.isReadable()))
     303                previewLastModified = fi.lastModified();
     304        }
     305
     306        bookmark_updated =
     307            (!previewLastModified.isValid() || (previewLastModified < cmp_ts));
     308
     309        if (bookmark_updated && bookmark_ts.isValid() &&
     310            previewLastModified.isValid())
     311        {
     312            ClearPreviewGeneratorAttempts(key);
     313        }
     314
     315        if (0)
     316        {
     317            VERBOSE(VB_IMPORTANT, QString(
     318                        "previewLastModified:  %1\n\t\t\t"
     319                        "bookmark_ts:          %2\n\t\t\t"
     320                        "pginfo.lastmodified: %3")
     321                    .arg(previewLastModified.toString(Qt::ISODate))
     322                    .arg(bookmark_ts.toString(Qt::ISODate))
     323                    .arg(pginfo.GetLastModifiedTime(ISODate)));
     324        }
     325
     326        bool preview_exists = previewLastModified.isValid();
     327
     328        if (0)
     329        {
     330            VERBOSE(VB_IMPORTANT,
     331                    QString("Title: %1\n\t\t\t")
     332                    .arg(pginfo.toString(ProgramInfo::kTitleSubtitle)) +
     333                    QString("File  '%1' \n\t\t\tCache '%2'")
     334                    .arg(filename).arg(ret_file) +
     335                    QString("\n\t\t\tPreview Exists: %1, "
     336                            "Bookmark Updated: %2, "
     337                            "Need Preview: %3")
     338                    .arg(preview_exists).arg(bookmark_updated)
     339                    .arg((bookmark_updated || !preview_exists)));
     340        }
     341
     342        needs_gen = bookmark_updated || !preview_exists;
     343
     344        if (!needs_gen)
     345        {
     346            if (locally_accessible)
     347                ret = filename;
     348            else if (preview_exists && QFileInfo(ret_file).isReadable())
     349                ret = ret_file;
     350        }
     351    }
     352
     353    if (needs_gen && !IsGeneratingPreview(key))
     354    {
     355        uint attempts = IncPreviewGeneratorAttempts(key);
     356        if (attempts < m_maxAttempts)
     357        {
     358            VERBOSE(VB_PLAYBACK, LOC +
     359                    QString("Requesting preview for '%1'")
     360                    .arg(key));
     361            PreviewGenerator *pg = new PreviewGenerator(&pginfo, m_mode);
     362            if (!outputfile.isEmpty() || time >= 0 ||
     363                size.width() || size.height())
     364            {
     365                pg->SetPreviewTime(time, in_seconds);
     366                pg->SetOutputFilename(outputfile);
     367                pg->SetOutputSize(size);
     368            }
     369            pg->token = token;
     370
     371            SetPreviewGenerator(key, pg);
     372
     373            VERBOSE(VB_PLAYBACK, LOC +
     374                    QString("Requested preview for '%1'").arg(key));
     375        }
     376        else if (attempts >= m_maxAttempts)
     377        {
     378            VERBOSE(VB_IMPORTANT, LOC_ERR +
     379                    QString("Attempted to generate preview for '%1' "
     380                            "%2 times; >= max(%3)")
     381                    .arg(key).arg(attempts).arg(m_maxAttempts));
     382        }
     383    }
     384    else if (needs_gen)
     385    {
     386        VERBOSE(VB_PLAYBACK, LOC +
     387                "Not requesting preview as it "
     388                "is already being generated");
     389        IncPreviewGeneratorPriority(key, token);
     390    }
     391
     392    UpdatePreviewGeneratorThreads();
     393
     394    if (!ret.isEmpty())
     395    {
     396        QString msg = "On Disk";
     397        SendEvent(pginfo, "PREVIEW_SUCCESS", ret, token, msg);
     398    }
     399    else
     400    {
     401        uint queue_depth, token_cnt;
     402        GetInfo(key, queue_depth, token_cnt);
     403        QString msg = QString("Queue depth %1, our tokens %2")
     404            .arg(queue_depth).arg(token_cnt);
     405        SendEvent(pginfo, "PREVIEW_QUEUED", ret, token, msg);
     406    }
     407
     408    return ret;
     409}
     410
     411void PreviewGeneratorQueue::GetInfo(
     412    const QString &key, uint &queue_depth, uint &token_cnt)
     413{
     414    QMutexLocker locker(&m_lock);
     415    queue_depth = m_queue.size();
     416    PreviewMap::iterator pit = m_previewMap.find(key);
     417    token_cnt = (pit == m_previewMap.end()) ? 0 : (*pit).tokens.size();
     418}
     419
     420void PreviewGeneratorQueue::IncPreviewGeneratorPriority(
     421    const QString &key, QString token)
     422{
     423    QMutexLocker locker(&m_lock);
     424    m_queue.removeAll(key);
     425
     426    PreviewMap::iterator pit = m_previewMap.find(key);
     427    if (pit == m_previewMap.end())
     428        return;
     429
     430    if ((*pit).gen && !(*pit).genStarted)
     431        m_queue.push_back(key);
     432
     433    if (!token.isEmpty())
     434    {
     435        m_tokenToKeyMap[token] = key;
     436        (*pit).tokens.insert(token);
     437    }
     438}
     439
     440void PreviewGeneratorQueue::UpdatePreviewGeneratorThreads(void)
     441{
     442    QMutexLocker locker(&m_lock);
     443    QStringList &q = m_queue;
     444    if (!q.empty() && (m_running < m_maxThreads))
     445    {
     446        QString fn = q.back();
     447        q.pop_back();
     448        PreviewMap::iterator it = m_previewMap.find(fn);
     449        if (it != m_previewMap.end() && (*it).gen && !(*it).genStarted)
     450        {
     451            m_running++;
     452            VERBOSE(VB_IMPORTANT, LOC + "Starting pg thread");
     453            (*it).gen->start();
     454            (*it).genStarted = true;
     455        }
     456    }
     457}
     458
     459/** \brief Sets the PreviewGenerator for a specific file.
     460 *  \return true iff call succeeded.
     461 */
     462void PreviewGeneratorQueue::SetPreviewGenerator(
     463    const QString &key, PreviewGenerator *g)
     464{
     465    {
     466        QMutexLocker locker(&m_lock);
     467        m_tokenToKeyMap[g->token] = key;
     468        PreviewGenState &state = m_previewMap[key];
     469        if (state.gen)
     470        {
     471            if (!g->token.isEmpty())
     472                state.tokens.insert(g->token);
     473            g->deleteLater();
     474        }
     475        else
     476        {
     477            g->AttachSignals(this);
     478            state.gen = g;
     479            state.genStarted = false;
     480            if (!g->token.isEmpty())
     481                state.tokens.insert(g->token);
     482        }
     483    }
     484
     485    IncPreviewGeneratorPriority(key, "");
     486}
     487
     488/** \brief Returns true if we have already started a
     489 *         PreviewGenerator to create this file.
     490 */
     491bool PreviewGeneratorQueue::IsGeneratingPreview(const QString &key) const
     492{
     493    PreviewMap::const_iterator it;
     494    QMutexLocker locker(&m_lock);
     495
     496    if ((it = m_previewMap.find(key)) == m_previewMap.end())
     497        return false;
     498
     499    if ((*it).blockRetryUntil.isValid())
     500        return QDateTime::currentDateTime() < (*it).blockRetryUntil;
     501
     502    return (*it).gen;
     503}
     504
     505/** \fn PreviewGeneratorQueue::IncPreviewGeneratorAttempts(const QString&)
     506 *  \brief Increments and returns number of times we have
     507 *         started a PreviewGenerator to create this file.
     508 */
     509uint PreviewGeneratorQueue::IncPreviewGeneratorAttempts(const QString &key)
     510{
     511    QMutexLocker locker(&m_lock);
     512    return m_previewMap[key].attempts++;
     513}
     514
     515/** \fn PreviewGeneratorQueue::ClearPreviewGeneratorAttempts(const QString&)
     516 *  \brief Clears the number of times we have
     517 *         started a PreviewGenerator to create this file.
     518 */
     519void PreviewGeneratorQueue::ClearPreviewGeneratorAttempts(const QString &key)
     520{
     521    QMutexLocker locker(&m_lock);
     522    m_previewMap[key].attempts = 0;
     523    m_previewMap[key].lastBlockTime = 0;
     524    m_previewMap[key].blockRetryUntil =
     525        QDateTime::currentDateTime().addSecs(-60);
     526}
  • libs/libmythtv/tv_rec.cpp

     
    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"
     
    11321132        if (!killFile)
    11331133        {
    11341134            if (curRecording->IsLocal())
    1135             {
    1136                 (new PreviewGenerator(
    1137                     curRecording, PreviewGenerator::kLocal))->Start();
    1138             }
     1135                PreviewGeneratorQueue::GetPreviewImage(*curRecording, "");
    11391136
    11401137            if (!tvchain)
    11411138            {
     
    44834480            if (!oldinfo->IsLocal())
    44844481                oldinfo->SetPathname(oldinfo->GetPlaybackURL(false,true));
    44854482            if (oldinfo->IsLocal())
    4486             {
    4487                 (new PreviewGenerator(
    4488                     oldinfo, PreviewGenerator::kLocal))->Start();
    4489             }
     4483                PreviewGeneratorQueue::GetPreviewImage(*oldinfo, "");
    44904484        }
    44914485        delete oldinfo;
    44924486    }
  • libs/libmythtv/previewgenerator.h

     
    44
    55#include <pthread.h>
    66
     7#include <QWaitCondition>
    78#include <QString>
     9#include <QThread>
    810#include <QMutex>
    911#include <QSize>
     12#include <QSet>
    1013
    1114#include "programinfo.h"
    1215#include "util.h"
    1316
    1417class MythSocket;
     18class PreviewGenerator;
    1519
    16 class MPUBLIC PreviewGenerator : public QObject
     20// TODO Changes to make...
     21// Update docs
     22
     23typedef QMap<QString,QDateTime> FileTimeStampMap;
     24
     25class MPUBLIC PreviewGenerator : public QThread
    1726{
    1827    friend int preview_helper(const QString &chanid,
    1928                              const QString &starttime,
     
    4756    void SetOutputFilename(const QString&);
    4857    void SetOutputSize(const QSize &size) { outSize = size; }
    4958
    50     void Start(void);
     59    void run(void); // QThread
    5160    bool Run(void);
    5261
    5362    void AttachSignals(QObject*);
    54     void disconnectSafe(void);
    5563
    56   signals:
    57     void previewThreadDone(const QString&, bool&);
    58     void previewReady(const ProgramInfo*);
    59 
    6064  public slots:
    6165    void deleteLater();
    6266
     
    6468    virtual ~PreviewGenerator();
    6569    void TeardownAll(void);
    6670
    67     bool RemotePreviewSetup(void);
    6871    bool RemotePreviewRun(void);
    69     void RemotePreviewTeardown(void);
    70 
    7172    bool LocalPreviewRun(void);
    7273    bool IsLocal(void) const;
    7374
    7475    bool RunReal(void);
    7576
    76     static void *PreviewRun(void*);
    77 
    7877    static char *GetScreenGrab(const ProgramInfo &pginfo,
    7978                               const QString     &filename,
    8079                               long long          seektime,
     
    9392    static QString CreateAccessibleFilename(
    9493        const QString &pathname, const QString &outFileName);
    9594
    96   protected:
     95    virtual bool event(QEvent *e); // QObject
     96    bool SaveOutFile(QByteArray &data);
     97
     98//  protected:
     99  public:
     100    QWaitCondition     previewWaitCondition;
    97101    QMutex             previewLock;
    98102    pthread_t          previewThread;
    99103    ProgramInfo        programInfo;
    100104
    101105    Mode               mode;
    102     bool               isConnected;
    103     bool               createSockets;
    104     MythSocket        *serverSock;
     106    QObject           *listener;
    105107    QString            pathname;
    106108
    107109    /// tells us whether to use time as seconds or frame number
     
    110112    long long          captureTime;
    111113    QString            outFileName;
    112114    QSize              outSize;
     115
     116    QString            token;
     117    bool               gotReply;
     118    bool               pixmapOk;
    113119};
    114120
    115121#endif // PREVIEW_GENERATOR_H_
  • libs/libmyth/remoteutil.cpp

     
    357357        return retdatetime;
    358358    }
    359359
    360     size_t  length     = strlist[1].toLongLong();
     360    size_t  length     = strlist[1].toULongLong();
    361361    quint16 checksum16 = strlist[2].toUInt();
    362362    QByteArray data = QByteArray::fromBase64(strlist[3].toAscii());
    363363    if ((size_t) data.size() < length)
     
    367367                .arg(data.size()).arg(length));
    368368        return QDateTime();
    369369    }
     370    data.resize(length);
    370371
    371372    if (checksum16 != qChecksum(data.constData(), data.size()))
    372373    {
  • 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.20100811-2"
     14#define MYTH_BINARY_VERSION "0.23.20100811-3"
    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 "58"
     33#define MYTH_PROTO_VERSION "59"
    3434
    3535MPUBLIC const char *GetMythSourceVersion();
    3636
  • programs/mythfrontend/playbackbox.cpp

     
    88#include <QTimer>
    99#include <QMap>
    1010
    11 // libmythdb
    12 #include "oldsettings.h"
    13 #include "mythdb.h"
    14 #include "mythdbcon.h"
    15 #include "mythverbose.h"
    16 #include "mythdirs.h"
    17 
    18 // libmythtv
    19 #include "tv.h"
     11// MythTV
     12#include "previewgeneratorqueue.h"
     13#include "mythuiprogressbar.h"
    2014#include "mythplayer.h"
     15#include "mythuibuttonlist.h"
     16#include "mythcorecontext.h"
     17#include "mythsystemevent.h"
     18#include "mythuistatetype.h"
     19#include "mythuicheckbox.h"
     20#include "mythuitextedit.h"
     21#include "mythdialogbox.h"
    2122#include "recordinginfo.h"
    22 #include "playgroup.h"
    23 #include "mythsystemevent.h"
    24 
    25 // libmyth
    26 #include "mythcorecontext.h"
    27 #include "util.h"
     23#include "mythuihelper.h"
    2824#include "storagegroup.h"
     25#include "mythuibutton.h"
     26#include "mythverbose.h"
     27#include "mythuiimage.h"
    2928#include "programinfo.h"
    30 
    31 // libmythui
    32 #include "mythuihelper.h"
     29#include "oldsettings.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"
    4432
    4533//  Mythfrontend
    4634#include "playbackboxlistitem.h"
    4735#include "customedit.h"
     36#include "mythdbcon.h"
     37#include "playgroup.h"
     38#include "mythdirs.h"
     39#include "proglist.h"
     40#include "mythdb.h"
     41#include "util.h"
     42#include "tv.h"
    4843
    4944#define LOC      QString("PlaybackBox: ")
    5045#define LOC_WARN QString("PlaybackBox Warning: ")
     
    447442PlaybackBox::~PlaybackBox(void)
    448443{
    449444    gCoreContext->removeListener(this);
     445    PreviewGeneratorQueue::RemoveListener(this);
    450446
    451447    for (uint i = 0; i < sizeof(m_artImage) / sizeof(MythUIImage*); i++)
    452448    {
     
    517513void PlaybackBox::Load(void)
    518514{
    519515    m_programInfoCache.WaitForLoadToComplete();
     516    PreviewGeneratorQueue::AddListener(this);
    520517}
    521518
    522519void PlaybackBox::Init()
     
    797794
    798795    QString oldimgfile = item->GetImage("preview");
    799796    if (oldimgfile.isEmpty() || force_preview_reload)
    800         m_helper.GetPreviewImage(*pginfo);
     797        m_preview_tokens.insert(m_helper.GetPreviewImage(*pginfo));
    801798
    802799    if ((GetFocusWidget() == m_recordingList) && is_sel)
    803800    {
     
    863860 *  a preview image UI item in the theme that it's filename property
    864861 *  gets updated as well.
    865862 */
    866 void PlaybackBox::HandlePreviewEvent(
    867     const QString &piKey, const QString &previewFile)
     863void PlaybackBox::HandlePreviewEvent(const QStringList &list)
    868864{
     865    const QString piKey       = list[0];
     866    const QString previewFile = list[1];
     867    const QString message     = list[2];
     868
     869    bool found = false;
     870    for (uint i = 3; i < list.size(); i++)
     871    {
     872        QString token = list[i];
     873        QSet<QString>::iterator it = m_preview_tokens.find(token);
     874        if (it != m_preview_tokens.end())
     875        {
     876            found = true;
     877            m_preview_tokens.erase(it);
     878        }
     879    }
     880
     881    if (!found)
     882    {
     883        QString tokens("\n\t\t\ttokens: ");
     884        for (uint i = 3; i < list.size(); i++)
     885            tokens += list[i] + ", ";
     886        VERBOSE(VB_IMPORTANT, LOC +
     887                "Ignoring PREVIEW_SUCCESS, no matcing token" + tokens);
     888        return;
     889    }
     890
    869891    if (previewFile.isEmpty())
     892    {
     893        VERBOSE(VB_IMPORTANT, LOC_ERR +
     894                "Ignoring PREVIEW_SUCCESS, no preview file.");
    870895        return;
     896    }
    871897
    872898    ProgramInfo *info = m_programInfoCache.GetProgramInfo(piKey);
    873899    MythUIButtonListItem *item = NULL;
     
    875901    if (info)
    876902        item = m_recordingList->GetItemByData(qVariantFromValue(info));
    877903
     904    if (!item)
     905    {
     906        VERBOSE(VB_IMPORTANT, LOC_ERR +
     907                "Ignoring PREVIEW_SUCCESS, item no longer on screen.");
     908    }
     909
    878910    if (item)
    879911    {
     912        VERBOSE(VB_IMPORTANT, LOC +
     913                QString("Loading preview %1,\n\t\t\tmsg %2")
     914                .arg(previewFile).arg(message));
     915
    880916        item->SetImage(previewFile, "preview", true);
    881917
    882918        if ((GetFocusWidget() == m_recordingList) &&
     
    38033839            // asPendingDelete, we need to put them back now..
    38043840            ScheduleUpdateUIList();
    38053841        }
    3806         else if (message == "PREVIEW_READY" && me->ExtraDataCount() == 2)
     3842        else if (message == "PREVIEW_SUCCESS" && me->ExtraDataCount() >= 3)
    38073843        {
    3808             HandlePreviewEvent(me->ExtraData(0), me->ExtraData(1));
     3844            HandlePreviewEvent(me->ExtraDataList());
    38093845        }
     3846        else if (message == "PREVIEW_FAILED" && me->ExtraDataCount() >= 3)
     3847        {
     3848            for (uint i = 3; i < me->ExtraDataCount(); i++)
     3849            {
     3850                QString token = me->ExtraData(i);
     3851                QSet<QString>::iterator it = m_preview_tokens.find(token);
     3852                if (it != m_preview_tokens.end())
     3853                    m_preview_tokens.erase(it);
     3854            }
     3855        }
    38103856        else if (message == "AVAILABILITY" && me->ExtraDataCount() == 8)
    38113857        {
    38123858            const uint kMaxUIWaitTime = 100; // ms
  • 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};
  • 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 }
  • 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();
  • 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"
     
    317318    void UpdateUIListItem(MythUIButtonListItem *item, bool is_sel,
    318319                          bool force_preview_reload = false);
    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);
     
    443444    QStringList         m_player_selected_new_show;
    444445    /// Main helper thread
    445446    PlaybackBoxHelper   m_helper;
     447    /// Outstanding preview image requests
     448    QSet<QString>       m_preview_tokens;
    446449};
    447450
    448451class GroupSelector : public MythScreenType
  • programs/mythbackend/playbacksock.h

     
    7070    QStringList GetSGFileQuery(QString &host, QString &groupname,
    7171                               QString &filename);
    7272
    73     QStringList GenPreviewPixmap(const ProgramInfo *pginfo);
    74     QStringList GenPreviewPixmap(const ProgramInfo *pginfo,
     73    QStringList GenPreviewPixmap(const QString &token,
     74                                 const ProgramInfo *pginfo);
     75    QStringList GenPreviewPixmap(const QString &token,
     76                                 const ProgramInfo *pginfo,
    7577                                 bool               time_fmt_sec,
    7678                                 long long          time,
    7779                                 const QString     &outputFile,
  • programs/mythbackend/mainserver.h

     
    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
  • programs/mythbackend/main.cpp

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

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

     
    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() >= 4)
     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
     755            if (message == "PREVIEW_QUEUED")
     756            {
     757                VERBOSE(VB_IMPORTANT, QString("Preview Queued: '%1' '%2'")
     758                        .arg(pginfokey).arg(filename));
     759                return;
     760            }
     761
     762            QFile file(filename);
     763            ok = ok && file.open(QIODevice::ReadOnly);
     764
     765            if (ok)
     766            {
     767                VERBOSE(VB_IMPORTANT, QString("Preview Success: '%1' '%2'")
     768                        .arg(pginfokey).arg(filename));
     769
     770                QByteArray data = file.readAll();
     771                QStringList extra("OK");
     772                extra.push_back(QString::number(data.size()));
     773                extra.push_back(
     774                    QString::number(qChecksum(data.constData(), data.size())));
     775                extra.push_back(QString(data.toBase64()));
     776
     777                for (uint i = 3 ; i < (uint) me->ExtraDataCount(); i++)
     778                {
     779                    QString token = me->ExtraData(i);
     780                    extra.push_back(token);
     781                    RequestedBy::iterator it = m_previewRequestedBy.find(token);
     782                    if (it != m_previewRequestedBy.end())
     783                    {
     784                        receivers.insert(*it);
     785                        m_previewRequestedBy.erase(it);
     786                    }
     787                }
     788
     789                if (receivers.empty())
     790                {
     791                    VERBOSE(VB_IMPORTANT, "PREVIEW_SUCCESS but no receivers.");
     792                    return;
     793                }
     794
     795                broadcast.push_back("BACKEND_MESSAGE");
     796                broadcast.push_back("GENERATED_PIXMAP");
     797                broadcast += extra;
     798            }
     799            else
     800            {
     801                message = "PREVIEW_FAILED";
     802                error = QString("Failed to read '%1'").arg(filename);
     803                VERBOSE(VB_IMPORTANT, LOC_ERR + error);
     804            }
     805        }
     806
     807        if (message == "PREVIEW_FAILED" && me->ExtraDataCount() >= 4)
     808        {
     809            QString pginfokey = me->ExtraData(0); // pginfo->MakeUniqueKey()
     810            QString filename  = me->ExtraData(1); // outFileName
     811            QString msg       = me->ExtraData(2);
     812
     813            QStringList extra("ERROR");
     814            extra.push_back(msg);
     815            for (uint i = 3 ; i < (uint) me->ExtraDataCount(); i++)
     816            {
     817                QString token = me->ExtraData(i);
     818                extra.push_back(token);
     819                RequestedBy::iterator it = m_previewRequestedBy.find(token);
     820                if (it != m_previewRequestedBy.end())
     821                {
     822                    receivers.insert(*it);
     823                    m_previewRequestedBy.erase(it);
     824                }
     825            }
     826
     827            if (receivers.empty())
     828            {
     829                VERBOSE(VB_IMPORTANT, "PREVIEW_FAILED but no receivers.");
     830                return;
     831            }
     832
     833            broadcast.push_back("BACKEND_MESSAGE");
     834            broadcast.push_back("GENERATED_PIXMAP");
     835            broadcast += extra;
     836        }
     837
    737838        if (me->Message().left(11) == "AUTO_EXPIRE")
    738839        {
    739840            QStringList tokens = me->Message()
     
    9421043                m_downloadURLs.remove(localFile);
    9431044        }
    9441045
    945         broadcast = QStringList( "BACKEND_MESSAGE" );
    946         broadcast << me->Message();
    947         broadcast += me->ExtraDataList();
     1046        if (broadcast.empty())
     1047        {
     1048            broadcast.push_back("BACKEND_MESSAGE");
     1049            broadcast.push_back(me->Message());
     1050            broadcast += me->ExtraDataList();
     1051        }
    9481052    }
    9491053
    9501054    if (!broadcast.empty())
     
    9701074            sendGlobal = true;
    9711075        }
    9721076
    973         vector<PlaybackSock*> sentSet;
     1077        QSet<PlaybackSock*> sentSet;
    9741078
    9751079        bool isSystemEvent = broadcast[1].startsWith("SYSTEM_EVENT ");
    9761080        QStringList sentSetSystemEvent(gCoreContext->GetHostName());
     
    9801084        {
    9811085            PlaybackSock *pbs = *iter;
    9821086
    983             vector<PlaybackSock*>::const_iterator it =
    984                 find(sentSet.begin(), sentSet.end(), pbs);
    985             if (it != sentSet.end() || pbs->IsDisconnected())
     1087            if (sentSet.contains(pbs))
     1088            {
     1089                VERBOSE(VB_IMPORTANT, LOC +
     1090                        QString("Ignoring playback sock to %1, "
     1091                                "message already sent there.")
     1092                        .arg(pbs->getHostname()));
    9861093                continue;
     1094            }
    9871095
    988             sentSet.push_back(pbs);
     1096            if (pbs->IsDisconnected())
     1097            {
     1098                VERBOSE(VB_IMPORTANT, LOC +
     1099                        QString("Ignoring playback sock to %1, "
     1100                                "disconnected")
     1101                        .arg(pbs->getHostname()));
     1102                continue;
     1103            }
    9891104
     1105            if (!receivers.empty() && !receivers.contains(pbs->getHostname()))
     1106            {
     1107                VERBOSE(VB_IMPORTANT, LOC +
     1108                        QString("Ignoring playback sock to %1, "
     1109                                "not in receivers list (wantsEvents %2)")
     1110                        .arg(pbs->getHostname()).arg(pbs->wantsEvents()));
     1111                continue;
     1112            }
     1113
     1114            if (!receivers.empty())
     1115            {
     1116                VERBOSE(VB_IMPORTANT, LOC +
     1117                        QString("Playback sock to %1, "
     1118                                "on receivers list. (wantsEvents %2)")
     1119                        .arg(pbs->getHostname()).arg(pbs->wantsEvents()));
     1120            }
     1121
     1122            sentSet.insert(pbs);
     1123
    9901124            bool reallysendit = false;
    9911125
    9921126            if (broadcast[1] == "CLEAR_SETTINGS_CACHE")
     
    48765010{
    48775011    MythSocket *pbssock = pbs->getSocket();
    48785012
     5013    if (slist.size() < 3)
     5014    {
     5015        VERBOSE(VB_IMPORTANT, LOC_ERR + "Too few params in pixmap request");
     5016        QStringList outputlist("ERROR");
     5017        outputlist += "TOO_FEW_PARAMS";
     5018        SendResponse(pbssock, outputlist);
     5019        return;
     5020    }
     5021
    48795022    bool      time_fmt_sec   = true;
    48805023    long long time           = -1;
    48815024    QString   outputfile;
     
    48835026    int       height         = -1;
    48845027    bool      has_extra_data = false;
    48855028
    4886     QStringList::const_iterator it = slist.begin() + 1;
     5029    QString token = slist[1];
     5030    if (token.isEmpty())
     5031    {
     5032        VERBOSE(VB_IMPORTANT, LOC_ERR + "Failed to parse pixmap request. "
     5033                "Token absent");
     5034        QStringList outputlist("ERROR");
     5035        outputlist += "TOKEN_ABSENT";
     5036        SendResponse(pbssock, outputlist);
     5037    }   
     5038
     5039    QStringList::const_iterator it = slist.begin() + 2;
    48875040    QStringList::const_iterator end = slist.end();
    48885041    ProgramInfo pginfo(it, end);
    48895042    bool ok = pginfo.HasPathname();
    48905043    if (!ok)
    48915044    {
    4892         VERBOSE(VB_IMPORTANT, "MainServer: Failed to parse pixmap request.");
     5045        VERBOSE(VB_IMPORTANT, LOC_ERR + "Failed to parse pixmap request. "
     5046                "ProgramInfo missing pathname");
    48935047        QStringList outputlist("BAD");
    4894         outputlist += "ERROR_INVALID_REQUEST";
     5048        outputlist += "NO_PATHNAME";
    48955049        SendResponse(pbssock, outputlist);
    48965050    }
    48975051    if (it != slist.end())
     
    49245078
    49255079    pginfo.SetPathname(GetPlaybackURL(&pginfo));
    49265080
     5081    m_previewRequestedBy[token] = pbs->getHostname();
     5082
    49275083    if ((ismaster) &&
    49285084        (pginfo.GetHostname() != gCoreContext->GetHostName()) &&
    49295085        (!masterBackendOverride || !pginfo.IsLocal()))
     
    49325088
    49335089        if (slave)
    49345090        {
    4935             QStringList outputlist("OK");
     5091            QStringList outputlist;
    49365092            if (has_extra_data)
    49375093            {
    49385094                outputlist = slave->GenPreviewPixmap(
    4939                     &pginfo, time_fmt_sec, time, outputfile, outputsize);
     5095                    token, &pginfo, time_fmt_sec, time, outputfile, outputsize);
    49405096            }
    49415097            else
    49425098            {
    4943                 outputlist = slave->GenPreviewPixmap(&pginfo);
     5099                outputlist = slave->GenPreviewPixmap(token, &pginfo);
    49445100            }
    49455101
    49465102            slave->DownRef();
     5103
     5104            if (outputlist.empty() || outputlist[0] != "OK")
     5105                m_previewRequestedBy.remove(token);
     5106
    49475107            SendResponse(pbssock, outputlist);
    49485108            return;
    49495109        }
     
    49555115
    49565116    if (!pginfo.IsLocal())
    49575117    {
    4958         VERBOSE(VB_IMPORTANT, "MainServer: HandleGenPreviewPixmap: Unable to "
     5118        VERBOSE(VB_IMPORTANT, LOC_ERR + "HandleGenPreviewPixmap: Unable to "
    49595119                "find file locally, unable to make preview image.");
    4960         QStringList outputlist( "BAD" );
    4961         outputlist += "ERROR_NOFILE";
     5120        QStringList outputlist( "ERROR" );
     5121        outputlist += "FILE_INACCESSIBLE";
    49625122        SendResponse(pbssock, outputlist);
     5123        m_previewRequestedBy.remove(token);
    49635124        return;
    49645125    }
    49655126
    4966     PreviewGenerator *previewgen = new PreviewGenerator(&pginfo);
    49675127    if (has_extra_data)
    49685128    {
    4969         previewgen->SetOutputSize(outputsize);
    4970         previewgen->SetOutputFilename(outputfile);
    4971         previewgen->SetPreviewTime(time, time_fmt_sec);
     5129        PreviewGeneratorQueue::GetPreviewImage(
     5130            pginfo, outputsize, outputfile, time, time_fmt_sec, token);
    49725131    }
    4973     ok = previewgen->Run();
    4974     previewgen->deleteLater();
    4975 
    4976     if (ok)
    4977     {
    4978         QStringList outputlist("OK");
    4979         if (!outputfile.isEmpty())
    4980             outputlist += outputfile;
    4981         SendResponse(pbssock, outputlist);
    4982     }
    49835132    else
    49845133    {
    4985         VERBOSE(VB_IMPORTANT, "MainServer: Failed to make preview image.");
    4986         QStringList outputlist( "BAD" );
    4987         outputlist += "ERROR_UNKNOWN";
    4988         SendResponse(pbssock, outputlist);
     5134        PreviewGeneratorQueue::GetPreviewImage(pginfo, token);
    49895135    }
     5136
     5137    QStringList outputlist("OK");
     5138    if (!outputfile.isEmpty())
     5139        outputlist += outputfile;
     5140    SendResponse(pbssock, outputlist);
    49905141}
    49915142
    49925143void MainServer::HandlePixmapLastModified(QStringList &slist, PlaybackSock *pbs)