Ticket #7195: 7191-gen2-v2.patch

File 7191-gen2-v2.patch, 77.8 KB (added by danielk, 15 years ago)

updated patch

  • libs/libmythtv/libmythtv.pro

     
    153153HEADERS += scheduledrecording.h
    154154HEADERS += signalmonitorvalue.h     signalmonitorlistener.h
    155155HEADERS += livetvchain.h            playgroup.h
    156 HEADERS += channelsettings.h        previewgenerator.h
     156HEADERS += channelsettings.h
     157HEADERS += previewgenerator.h       previewgeneratorqueue.h
    157158HEADERS += transporteditor.h        listingsources.h
    158159HEADERS += myth_imgconvert.h
    159160HEADERS += channelgroup.h           channelgroupsettings.h
     
    177178SOURCES += scheduledrecording.cpp
    178179SOURCES += signalmonitorvalue.cpp
    179180SOURCES += livetvchain.cpp          playgroup.cpp
    180 SOURCES += channelsettings.cpp      previewgenerator.cpp
     181SOURCES += channelsettings.cpp
     182SOURCES += previewgenerator.cpp     previewgeneratorqueue.cpp
    181183SOURCES += transporteditor.cpp
    182184SOURCES += channelgroup.cpp         channelgroupsettings.cpp
    183185SOURCES += myth_imgconvert.cpp
  • libs/libmythtv/previewgeneratorqueue.h

     
     1// -*- Mode: c++ -*-
     2#ifndef _PREVIEW_GENERATOR_QUEUE_H_
     3#define _PREVIEW_GENERATOR_QUEUE_H_
     4
     5#include <QStringList>
     6#include <QDateTime>
     7#include <QThread>
     8#include <QMutex>
     9#include <QMap>
     10#include <QSet>
     11
     12#include "previewgenerator.h"
     13#include "mythexp.h"
     14
     15class ProgramInfo;
     16class QSize;
     17
     18class PreviewGenState
     19{
     20  public:
     21    PreviewGenState() :
     22        gen(NULL), genStarted(false),
     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

     
    11// C headers
    22#include <cmath>
     3#include <cassert>
    34
    45// POSIX headers
    56#include <sys/time.h>
    67#include <fcntl.h>
    78
    89// Qt headers
     10#include <QCoreApplication>
     11#include <QTemporaryFile>
    912#include <QFileInfo>
     13#include <QMetaType>
     14#include <QThread>
    1015#include <QImage>
    11 #include <QMetaType>
     16#include <QDir>
    1217#include <QUrl>
    13 #include <QDir>
    1418
    1519// MythTV headers
    1620#include "mythconfig.h"
     
    2731#include "playercontext.h"
    2832#include "mythdirs.h"
    2933#include "mythverbose.h"
     34#include "remoteutil.h"
    3035
    3136#define LOC QString("Preview: ")
    3237#define LOC_ERR QString("Preview Error: ")
     
    3742 *
    3843 *   The usage is simple: First, pass a ProgramInfo whose pathname points
    3944 *   to a local or remote recording to the constructor. Then call either
    40  *   Start(void) or Run(void) to generate the preview.
     45 *   start(void) or Run(void) to generate the preview.
    4146 *
    42  *   Start(void) will create a thread that processes the request,
     47 *   start(void) will create a thread that processes the request,
    4348 *   creating a sockets the the backend if the recording is not local.
    4449 *
    4550 *   Run(void) will process the request in the current thread, and it
     
    4752 *   is not local.
    4853 *
    4954 *   The PreviewGenerator will send Qt signals when the preview is ready
    50  *   and when the preview thread finishes running if Start(void) was called.
     55 *   and when the preview thread finishes running if start(void) was called.
    5156 */
    5257
    5358/**
     
    6570 */
    6671PreviewGenerator::PreviewGenerator(const ProgramInfo *pginfo,
    6772                                   PreviewGenerator::Mode _mode)
    68     : programInfo(*pginfo), mode(_mode), isConnected(false),
    69       createSockets(false), serverSock(NULL), pathname(pginfo->GetPathname()),
     73    : programInfo(*pginfo), mode(_mode), listener(NULL),
     74      pathname(pginfo->GetPathname()),
    7075      timeInSeconds(true),  captureTime(-1),  outFileName(QString::null),
    71       outSize(0,0)
     76      outSize(0,0), gotReply(false), pixmapOk(false)
    7277{
    7378}
    7479
     
    8489
    8590void PreviewGenerator::TeardownAll(void)
    8691{
    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();
     92    previewWaitCondition.wakeAll();
     93    listener = NULL;
    10694}
    10795
    10896void PreviewGenerator::deleteLater()
     
    114102void PreviewGenerator::AttachSignals(QObject *obj)
    115103{
    116104    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;
     105    listener = obj;
    125106}
    126107
    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 
    148108/** \fn PreviewGenerator::RunReal(void)
    149109 *  \brief This call creates a preview without starting a new thread.
    150110 */
    151111bool PreviewGenerator::RunReal(void)
    152112{
     113    QString msg;
     114    QTime tm = QTime::currentTime();
    153115    bool ok = false;
    154116    bool is_local = IsLocal();
    155117    if (is_local && (mode && kLocal) && LocalPreviewRun())
    156118    {
    157119        ok = true;
     120        msg = QString("Generated on %1 in %2 seconds, starting at %3")
     121            .arg(gCoreContext->GetHostName())
     122            .arg(tm.elapsed()*0.001)
     123            .arg(tm.toString(Qt::ISODate));
    158124    }
    159125    else if (mode & kRemote)
    160126    {
     
    166132                    "\n\t\t\tAttempting to regenerate preview on backend.\n");
    167133        }
    168134        ok = RemotePreviewRun();
     135        if (ok)
     136        {
     137            msg = QString("Generated remotely in %1 seconds, starting at %2")
     138                .arg(tm.elapsed()*0.001)
     139                .arg(tm.toString(Qt::ISODate));
     140        }
    169141    }
    170142    else
    171143    {
    172144        VERBOSE(VB_IMPORTANT, LOC_ERR + QString("Run() file not local: '%1'")
    173145                .arg(pathname));
     146        msg = "Could not access recording";
    174147    }
    175148
     149    QMutexLocker locker(&previewLock);
     150    if (listener)
     151    {
     152        QString message = (ok) ? "PREVIEW_SUCCESS" : "PREVIEW_FAILED";
     153        assert(!token.isEmpty());
     154        QStringList list(token);
     155        list.push_back(programInfo.MakeUniqueKey());
     156        list.push_back(outFileName.isEmpty() ?
     157                       (programInfo.GetPathname()+".png") : outFileName);
     158        list.push_back(msg);
     159        QCoreApplication::postEvent(listener, new MythEvent(message, list));
     160    }
     161
    176162    return ok;
    177163}
    178164
    179165bool PreviewGenerator::Run(void)
    180166{
     167    VERBOSE(VB_IMPORTANT, LOC + "Run() -- begin");
     168
     169    QString msg;
     170    QTime tm = QTime::currentTime();
    181171    bool ok = false;
    182172    QString command = GetInstallPrefix() + "/bin/mythpreviewgen";
    183173    bool local_ok = (IsLocal() && (mode & kLocal) &&
     
    187177        if (mode & kRemote)
    188178        {
    189179            ok = RemotePreviewRun();
     180            if (ok)
     181            {
     182                msg =
     183                    QString("Generated remotely in %1 seconds, starting at %2")
     184                    .arg(tm.elapsed()*0.001)
     185                    .arg(tm.toString(Qt::ISODate));
     186            }
    190187        }
    191188        else
    192189        {
    193190            VERBOSE(VB_IMPORTANT, LOC_ERR +
    194191                    QString("Run() can not generate preview locally for: '%1'")
    195192                    .arg(pathname));
     193            msg = "Failed, local preview requested for remote file.";
    196194        }
    197195    }
    198196    else
     
    215213        if (!outFileName.isEmpty())
    216214            command += QString("--outfile \"%1\" ").arg(outFileName);
    217215
     216        command += " > /dev/null";
     217
    218218        int ret = myth_system(command, MYTH_SYSTEM_DONT_BLOCK_LIRC |
    219219                                       MYTH_SYSTEM_DONT_BLOCK_JOYSTICK_MENU |
    220220                                       MYTH_SYSTEM_DONT_BLOCK_PARENT);
    221221        if (ret)
    222222        {
    223             VERBOSE(VB_IMPORTANT, LOC_ERR + "Encountered problems running " +
    224                     QString("'%1'").arg(command));
     223            msg = QString("Encountered problems running '%1'").arg(command);
     224            VERBOSE(VB_IMPORTANT, LOC_ERR + msg);
    225225        }
    226226        else
    227227        {
     
    240240            QFileInfo fi(outname);
    241241            ok = (fi.exists() && fi.isReadable() && fi.size());
    242242            if (ok)
     243            {
    243244                VERBOSE(VB_PLAYBACK, LOC + "Preview process ran ok.");
     245                msg = QString("Generated on %1 in %2 seconds, starting at %3")
     246                    .arg(gCoreContext->GetHostName())
     247                    .arg(tm.elapsed()*0.001)
     248                    .arg(tm.toString(Qt::ISODate));
     249            }
    244250            else
    245251            {
    246252                VERBOSE(VB_IMPORTANT, LOC_ERR + "Preview process not ok." +
     
    251257                VERBOSE(VB_IMPORTANT, LOC_ERR +
    252258                        QString("Despite command '%1' returning success")
    253259                        .arg(command));
     260                msg = QString("Failed to read preview image despite "
     261                              "preview process returning success.");
    254262            }
    255263        }
    256264    }
    257265
    258     if (ok)
     266    VERBOSE(VB_IMPORTANT, LOC + "Run() -- waiting on lock");
     267    QMutexLocker locker(&previewLock);
     268    VERBOSE(VB_IMPORTANT, LOC + "Run() -- got lock");
     269
     270    QString message = (ok) ? "PREVIEW_SUCCESS" : "PREVIEW_FAILED";
     271    if (listener)
    259272    {
    260         QMutexLocker locker(&previewLock);
    261         emit previewReady(&programInfo);
     273        assert(!token.isEmpty());
     274        QStringList list(token);
     275        list.push_back(programInfo.MakeUniqueKey());
     276        list.push_back(outFileName.isEmpty() ?
     277                       (programInfo.GetPathname()+".png") : outFileName);
     278        if (!msg.isEmpty())
     279            list.push_back(msg);
     280        QCoreApplication::postEvent(listener, new MythEvent(message, list));
    262281    }
     282    VERBOSE(VB_IMPORTANT, LOC + "Run() -- end");
    263283
    264284    return ok;
    265285}
    266286
    267 void *PreviewGenerator::PreviewRun(void *param)
     287void PreviewGenerator::run(void)
    268288{
    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;
     289    VERBOSE(VB_IMPORTANT, LOC + "run() -- begin");
     290    setPriority(QThread::LowPriority);
     291    Run();
     292    connect(this, SIGNAL(finished()),
     293            this, SLOT(deleteLater()));
     294    VERBOSE(VB_IMPORTANT, LOC + "run() -- end");
    277295}
    278296
    279 bool PreviewGenerator::RemotePreviewSetup(void)
     297bool PreviewGenerator::RemotePreviewRun(void)
    280298{
    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);
     299    VERBOSE(VB_IMPORTANT, LOC + "RemotePreviewRun() -- begin");
    285300
    286     serverSock = gCoreContext->ConnectCommandSocket(server, port, ann);
    287     return serverSock;
    288 }
    289 
    290 bool PreviewGenerator::RemotePreviewRun(void)
    291 {
    292     QStringList strlist( "QUERY_GENPIXMAP" );
     301    QStringList strlist( "QUERY_GENPIXMAP2" );
     302    if (token.isEmpty())
     303    {
     304        token = QString("%1:%2")
     305            .arg(programInfo.MakeUniqueKey()).arg(rand());
     306    }
     307    strlist.push_back(token);
    293308    programInfo.ToStringList(strlist);
    294309    strlist.push_back(timeInSeconds ? "s" : "f");
    295310    encodeLongLong(strlist, captureTime);
     
    305320    strlist.push_back(QString::number(outSize.width()));
    306321    strlist.push_back(QString::number(outSize.height()));
    307322
    308     bool ok = false;
     323    gCoreContext->addListener(this);
     324    pixmapOk = false;
    309325
    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 
     326    bool ok = gCoreContext->SendReceiveStringList(strlist);
    331327    if (!ok || strlist.empty() || (strlist[0] != "OK"))
    332328    {
    333329        if (!ok)
     
    340336            VERBOSE(VB_IMPORTANT, LOC_ERR +
    341337                    "Remote Preview failed, reason given: " <<strlist[1]);
    342338        }
    343         else
     339
     340        gCoreContext->removeListener(this);
     341
     342        return false;
     343    }
     344
     345    VERBOSE(VB_IMPORTANT, LOC + "RemotePreviewRun() -- waiting on lock");
     346    QMutexLocker locker(&previewLock);
     347    VERBOSE(VB_IMPORTANT, LOC + "RemotePreviewRun() -- got lock");
     348
     349    // wait up to 30 seconds for the preview to complete
     350    if (!gotReply)
     351        previewWaitCondition.wait(&previewLock, 30 * 1000);
     352
     353    if (!gotReply)
     354        VERBOSE(VB_IMPORTANT, LOC + "RemotePreviewRun() -- no reply..");
     355
     356    gCoreContext->removeListener(this);
     357
     358    VERBOSE(VB_IMPORTANT, LOC + "RemotePreviewRun() -- end");
     359
     360    return pixmapOk;
     361}
     362
     363bool PreviewGenerator::event(QEvent *e)
     364{
     365    if (e->type() == (QEvent::Type) MythEvent::MythEventMessage)
     366    {
     367        MythEvent *me = (MythEvent*)e;
     368        if (me->Message() == "GENERATED_PIXMAP" && me->ExtraDataCount() >= 2 &&
     369            me->ExtraData(0) == token)
    344370        {
    345             VERBOSE(VB_IMPORTANT, LOC_ERR +
    346                     "Remote Preview failed due to an unknown error.");
     371            VERBOSE(VB_IMPORTANT, LOC + "got GENERATED_PIXMAP event\n\n\n");
     372            QMutexLocker locker(&previewLock);
     373            gotReply = true;
     374            pixmapOk = me->ExtraData(1) == "OK";
     375            if (pixmapOk)
     376            {
     377                QByteArray data;
     378                if (me->ExtraDataCount() >= 4)
     379                {
     380                    size_t length = me->ExtraData(2).toULongLong();
     381                    quint16 checksum16 = me->ExtraData(3).toUInt();
     382                    data = QByteArray::fromBase64(me->ExtraData(4).toAscii());
     383                    if ((size_t) data.size() < length)
     384                    { // (note data.size() may be up to 3
     385                      //  bytes longer after decoding
     386                        VERBOSE(VB_IMPORTANT, LOC_ERR +
     387                                QString("Preview size check failed %1 < %2")
     388                                .arg(data.size()).arg(length));
     389                        data.clear();
     390                    }
     391                    data.resize(length);
     392
     393                    if (checksum16 != qChecksum(data.constData(), data.size()))
     394                    {
     395                        VERBOSE(VB_IMPORTANT, LOC_ERR +
     396                                "Preview checksum failed");
     397                        data.clear();
     398                    }
     399                }
     400                pixmapOk = (data.isEmpty()) ? false : SaveOutFile(data);
     401            }
     402
     403            previewWaitCondition.wakeAll();
     404
     405            return true;
    347406        }
    348         return false;
    349407    }
     408    return QObject::event(e);
     409}
    350410
     411bool PreviewGenerator::SaveOutFile(QByteArray &data)
     412{
    351413    if (outFileName.isEmpty())
    352414    {
    353         QString remotecachedirname = QString("%1/remotecache").arg(GetConfDir());
     415        QString remotecachedirname =
     416            QString("%1/remotecache").arg(GetConfDir());
    354417        QDir remotecachedir(remotecachedirname);
    355418
    356419        if (!remotecachedir.exists())
     
    368431        outFileName = QString("%1/%2").arg(remotecachedirname).arg(filename);
    369432    }
    370433
    371     // find file, copy/move to output file name & location...
     434    bool ok = true;
     435    if (data.isEmpty())
     436    {
     437        // find file, copy/move to output file name & location...
     438        QString url = QString::null;
     439        QString fn = QFileInfo(outFileName).fileName();
     440        ok = false;
    372441
    373     QString url = QString::null;
    374     QString fn = QFileInfo(outFileName).fileName();
    375     QByteArray data;
    376     ok = false;
     442        QStringList fileNames;
     443        fileNames.push_back(
     444            CreateAccessibleFilename(programInfo.GetPathname(), fn));
     445        fileNames.push_back(
     446            CreateAccessibleFilename(programInfo.GetPathname(), ""));
    377447
    378     QStringList fileNames;
    379     fileNames.push_back(
    380         CreateAccessibleFilename(programInfo.GetPathname(), fn));
    381     fileNames.push_back(
    382         CreateAccessibleFilename(programInfo.GetPathname(), ""));
     448        QStringList::const_iterator it = fileNames.begin();
     449        for ( ; it != fileNames.end() && (!ok || data.isEmpty()); ++it)
     450        {
     451            data.resize(0);
     452            url = *it;
     453            RemoteFile *rf = new RemoteFile(url, false, false, 0);
     454            ok = rf->SaveAs(data);
     455            delete rf;
     456        }
     457    }
    383458
    384     QStringList::const_iterator it = fileNames.begin();
    385     for ( ; it != fileNames.end() && (!ok || data.isEmpty()); ++it)
     459    if (!ok)
     460        return false;
     461
     462    QFile file(outFileName);
     463    ok = file.open(QIODevice::Unbuffered|QIODevice::WriteOnly);
     464    if (!ok)
    386465    {
    387         data.resize(0);
    388         url = *it;
    389         RemoteFile *rf = new RemoteFile(url, false, false, 0);
    390         ok = rf->SaveAs(data);
    391         delete rf;
     466        VERBOSE(VB_IMPORTANT, LOC_ERR + QString("Failed to open: '%1'")
     467                .arg(outFileName));
    392468    }
    393469
    394     if (ok && data.size())
     470    off_t offset = 0;
     471    size_t remaining = data.size();
     472    uint failure_cnt = 0;
     473    while ((remaining > 0) && (failure_cnt < 5))
    395474    {
    396         QFile file(outFileName);
    397         ok = file.open(QIODevice::Unbuffered|QIODevice::WriteOnly);
    398         if (!ok)
     475        ssize_t written = file.write(data.data() + offset, remaining);
     476        if (written < 0)
    399477        {
    400             VERBOSE(VB_IMPORTANT, QString("Failed to open: '%1'")
    401                     .arg(outFileName));
     478            failure_cnt++;
     479            usleep(50000);
     480            continue;
    402481        }
    403482
    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         }
     483        failure_cnt  = 0;
     484        offset      += written;
     485        remaining   -= written;
    426486    }
    427 
    428     return ok && data.size();
    429 }
    430 
    431 void PreviewGenerator::RemotePreviewTeardown(void)
    432 {
    433     if (serverSock)
     487    if (ok && !remaining)
    434488    {
    435         serverSock->DownRef();
    436         serverSock = NULL;
     489        VERBOSE(VB_PLAYBACK, LOC + QString("Saved: '%1'").arg(outFileName));
    437490    }
     491    else
     492    {
     493        file.remove();
     494    }
     495
     496    return ok;
    438497}
    439498
    440499bool PreviewGenerator::SavePreview(QString filename,
     
    476535    QImage small_img = img.scaled((int) ppw, (int) pph,
    477536        Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
    478537
    479     QByteArray fname = filename.toAscii();
    480     if (small_img.save(fname.constData(), "PNG"))
     538    QTemporaryFile f(QFileInfo(filename).absoluteFilePath()+".XXXXXX");
     539    f.setAutoRemove(false);
     540    if (f.open() && small_img.save(&f, "PNG"))
    481541    {
    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;
     542        // Let anybody update it
     543        makeFileAccessible(f.fileName().toLocal8Bit().constData());
     544        QFile of(filename);
     545        of.remove();
     546        if (f.rename(filename))
     547        {
     548            VERBOSE(VB_PLAYBACK, LOC +
     549                    QString("Saved preview '%0' %1x%2")
     550                    .arg(filename).arg((int) ppw).arg((int) pph));
     551            return true;
     552        }
     553        f.remove();
    489554    }
    490555
    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());
     556    VERBOSE(VB_IMPORTANT, LOC_ERR +
     557            QString("Failed to save preview '%0' %1x%2")
     558            .arg(filename).arg((int) ppw).arg((int) pph));
    499559
    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?
    508560    return false;
    509561}
    510562
  • 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
     61#include <cassert>
     62void PreviewGeneratorQueue::GetPreviewImage(
     63    const ProgramInfo &pginfo,
     64    const QSize &outputsize,
     65    const QString &outputfile,
     66    long long time, bool in_seconds,
     67    QString token)
     68{
     69    assert(!token.isEmpty());
     70
     71    if (!s_pgq)
     72        return;
     73
     74    if (pginfo.GetPathname().isEmpty() ||
     75        pginfo.GetBasename() == pginfo.GetPathname())
     76    {
     77        return;
     78    }
     79
     80    QStringList extra;
     81    pginfo.ToStringList(extra);
     82    extra += token;
     83    extra += QString::number(outputsize.width());
     84    extra += QString::number(outputsize.height());
     85    extra += outputfile;
     86    extra += QString::number(time);
     87    extra += (in_seconds ? "1" : "0");
     88    MythEvent *e = new MythEvent("GET_PREVIEW", extra);
     89    QCoreApplication::postEvent(s_pgq, e);
     90}
     91
     92void PreviewGeneratorQueue::AddListener(QObject *listener)
     93{
     94    if (!s_pgq)
     95        return;
     96
     97    QMutexLocker locker(&s_pgq->m_lock);
     98    s_pgq->m_listeners.insert(listener);
     99}
     100
     101void PreviewGeneratorQueue::RemoveListener(QObject *listener)
     102{
     103    if (!s_pgq)
     104        return;
     105
     106    QMutexLocker locker(&s_pgq->m_lock);
     107    s_pgq->m_listeners.remove(listener);
     108}
     109
     110bool PreviewGeneratorQueue::event(QEvent *e)
     111{
     112    if (e->type() != (QEvent::Type) MythEvent::MythEventMessage)
     113        return QObject::event(e);
     114
     115    MythEvent *me = (MythEvent*)e;
     116    if (me->Message() == "GET_PREVIEW")
     117    {
     118        const QStringList list = me->ExtraDataList();
     119        QStringList::const_iterator it = list.begin();
     120        ProgramInfo evinfo(it, list.end());
     121        QString token;
     122        QSize outputsize;
     123        QString outputfile;
     124        long long time;
     125        bool time_fmt_sec;
     126        if (it != list.end())
     127            token = (*it++);
     128        if (it != list.end())
     129            outputsize.setWidth((*it++).toInt());
     130        if (it != list.end())
     131            outputsize.setHeight((*it++).toInt());
     132        if (it != list.end())
     133            outputfile = (*it++);
     134        if (it != list.end())
     135            time = (*it++).toLongLong();
     136        QString fn;
     137        if (it != list.end())
     138        {
     139            time_fmt_sec = (*it++).toInt() != 0;
     140            fn = GeneratePreviewImage(evinfo, outputsize, outputfile,
     141                                      time, time_fmt_sec, token);
     142        }
     143        return true;
     144    }
     145    else if (me->Message() == "PREVIEW_SUCCESS" ||
     146             me->Message() == "PREVIEW_FAILED")
     147    {
     148        QString token     = me->ExtraData(0);
     149        QString pginfokey = me->ExtraData(1); // pginfo->MakeUniqueKey()
     150        QString filename  = me->ExtraData(2); // outFileName
     151        QString msg;
     152        if (me->ExtraDataCount() >= 4)
     153            msg = me->ExtraData(3);
     154
     155        VERBOSE(VB_PLAYBACK, LOC + QString("Preview '%1' processed")
     156                .arg(token));
     157
     158        {
     159        QMutexLocker locker(&m_lock);
     160        QMap<QString,QString>::iterator kit = m_tokenToKeyMap.find(token);
     161        if (kit == m_tokenToKeyMap.end())
     162        {
     163            VERBOSE(VB_IMPORTANT, LOC_ERR +
     164                    QString("Failed to find token %1 in map.").arg(token));
     165            return true;
     166        }
     167        PreviewMap::iterator it = m_previewMap.find(*kit);
     168        if (it == m_previewMap.end())
     169        {
     170            VERBOSE(VB_IMPORTANT, LOC_ERR +
     171                    QString("Failed to find key %1 in map.").arg(*kit));
     172            return true;
     173        }
     174
     175        (*it).gen           = NULL;
     176        (*it).genStarted    = false;
     177        if (me->Message() == "PREVIEW_SUCCESS")
     178        {
     179            (*it).attempts      = 0;
     180            (*it).lastBlockTime = 0;
     181            (*it).blockRetryUntil = QDateTime();
     182        }
     183        else
     184        {
     185            (*it).lastBlockTime =
     186                max(m_minBlockSeconds, (*it).lastBlockTime * 2);
     187            (*it).blockRetryUntil =
     188                QDateTime::currentDateTime().addSecs((*it).lastBlockTime);
     189        }
     190
     191        QStringList list;
     192        list.push_back(pginfokey);
     193        list.push_back(filename);
     194        list.push_back("");
     195        if (!msg.isEmpty())
     196            list.push_back(msg);
     197
     198        QSet<QString>::const_iterator tit = (*it).tokens.begin();
     199        for (; tit != (*it).tokens.end(); ++tit)
     200        {
     201            list[2] = (*tit);
     202            QSet<QObject*>::iterator sit = m_listeners.begin();
     203            for (; sit != m_listeners.end(); ++sit)
     204            {
     205                MythEvent *e = new MythEvent(me->Message(), list);
     206                QCoreApplication::postEvent(*sit, e);
     207            }
     208        }
     209        (*it).tokens.clear();
     210
     211        m_running--;
     212        }
     213
     214        UpdatePreviewGeneratorThreads();
     215
     216        return true;
     217    }
     218    return false;
     219}
     220
     221void PreviewGeneratorQueue::SendEvent(
     222    const ProgramInfo &pginfo,
     223    const QString &eventname,
     224    const QString &fn, const QString &token, const QString &msg)
     225{
     226    VERBOSE(VB_IMPORTANT, QString("SendEvent(%1, %2, %3, %4)")
     227            .arg(pginfo.toString(ProgramInfo::kTitleSubtitle))
     228            .arg(eventname).arg(msg).arg(token));
     229
     230    QStringList list;
     231    list.push_back(pginfo.MakeUniqueKey());
     232    list.push_back(fn);
     233    list.push_back(token);
     234    list.push_back(msg);
     235
     236    QMutexLocker locker(&m_lock);
     237    QSet<QObject*>::iterator it = m_listeners.begin();
     238    for (; it != m_listeners.end(); ++it)
     239    {
     240        MythEvent *e = new MythEvent(eventname, list);
     241        QCoreApplication::postEvent(*it, e);
     242    }
     243}
     244
     245QString PreviewGeneratorQueue::GeneratePreviewImage(
     246    ProgramInfo &pginfo,
     247    const QSize &size,
     248    const QString &outputfile,
     249    long long time, bool in_seconds,
     250    QString token)
     251{
     252    QString key = QString("%1_%2x%3_%4%5")
     253        .arg(pginfo.GetBasename()).arg(size.width()).arg(size.height())
     254        .arg(time).arg(in_seconds?"s":"f");
     255
     256    if (pginfo.GetAvailableStatus() == asPendingDelete)
     257    {
     258        SendEvent(pginfo, "PREVIEW_FAILED", key, token, "Pending Delete");
     259        return QString();
     260    }
     261
     262    QString filename = (outputfile.isEmpty()) ?
     263        pginfo.GetPathname() + ".png" : outputfile;
     264    QString ret_file = filename;
     265    QString ret;
     266
     267    bool is_special = !outputfile.isEmpty() || time >= 0 ||
     268        size.width() || size.height();
     269
     270    bool needs_gen = true;
     271    if (!is_special)
     272    {
     273        QDateTime previewLastModified;
     274        bool streaming = filename.left(1) != "/";
     275        bool locally_accessible = false;
     276        bool bookmark_updated = false;
     277
     278        QDateTime bookmark_ts = pginfo.QueryBookmarkTimeStamp();
     279        QDateTime cmp_ts = bookmark_ts.isValid() ?
     280            bookmark_ts : pginfo.GetLastModifiedTime();
     281
     282        if (streaming)
     283        {
     284            ret_file = QString("%1/remotecache/%2")
     285                .arg(GetConfDir()).arg(filename.section('/', -1));
     286
     287            QFileInfo finfo(ret_file);
     288            if (finfo.isReadable() && finfo.lastModified() >= cmp_ts)
     289            {
     290                // This is just an optimization to avoid
     291                // hitting the backend if our cached copy
     292                // is newer than the bookmark, or if we have
     293                // a preview and do not update it when the
     294                // bookmark changes.
     295                previewLastModified = finfo.lastModified();
     296            }
     297            else if (!IsGeneratingPreview(key))
     298            {
     299                previewLastModified =
     300                    RemoteGetPreviewIfModified(pginfo, ret_file);
     301            }
     302        }
     303        else
     304        {
     305            QFileInfo fi(filename);
     306            if ((locally_accessible = fi.isReadable()))
     307                previewLastModified = fi.lastModified();
     308        }
     309
     310        bookmark_updated =
     311            (!previewLastModified.isValid() || (previewLastModified < cmp_ts));
     312
     313        if (bookmark_updated && bookmark_ts.isValid() &&
     314            previewLastModified.isValid())
     315        {
     316            ClearPreviewGeneratorAttempts(key);
     317        }
     318
     319        if (0)
     320        {
     321            VERBOSE(VB_IMPORTANT, QString(
     322                        "previewLastModified:  %1\n\t\t\t"
     323                        "bookmark_ts:          %2\n\t\t\t"
     324                        "pginfo.lastmodified: %3")
     325                    .arg(previewLastModified.toString(Qt::ISODate))
     326                    .arg(bookmark_ts.toString(Qt::ISODate))
     327                    .arg(pginfo.GetLastModifiedTime(ISODate)));
     328        }
     329
     330        bool preview_exists = previewLastModified.isValid();
     331
     332        if (0)
     333        {
     334            VERBOSE(VB_IMPORTANT,
     335                    QString("Title: %1\n\t\t\t")
     336                    .arg(pginfo.toString(ProgramInfo::kTitleSubtitle)) +
     337                    QString("File  '%1' \n\t\t\tCache '%2'")
     338                    .arg(filename).arg(ret_file) +
     339                    QString("\n\t\t\tPreview Exists: %1, "
     340                            "Bookmark Updated: %2, "
     341                            "Need Preview: %3")
     342                    .arg(preview_exists).arg(bookmark_updated)
     343                    .arg((bookmark_updated || !preview_exists)));
     344        }
     345
     346        needs_gen = bookmark_updated || !preview_exists;
     347
     348        if (!needs_gen)
     349        {
     350            if (locally_accessible)
     351                ret = filename;
     352            else if (preview_exists && QFileInfo(ret_file).isReadable())
     353                ret = ret_file;
     354        }
     355    }
     356
     357    if (needs_gen && !IsGeneratingPreview(key))
     358    {
     359        uint attempts = IncPreviewGeneratorAttempts(key);
     360        if (attempts < m_maxAttempts)
     361        {
     362            VERBOSE(VB_PLAYBACK, LOC +
     363                    QString("Requesting preview for '%1'")
     364                    .arg(key));
     365            PreviewGenerator *pg = new PreviewGenerator(&pginfo, m_mode);
     366            if (!outputfile.isEmpty() || time >= 0 ||
     367                size.width() || size.height())
     368            {
     369                pg->SetPreviewTime(time, in_seconds);
     370                pg->SetOutputFilename(outputfile);
     371                pg->SetOutputSize(size);
     372            }
     373            pg->token = token;
     374
     375            SetPreviewGenerator(key, pg);
     376
     377            VERBOSE(VB_PLAYBACK, LOC +
     378                    QString("Requested preview for '%1'").arg(key));
     379        }
     380        else if (attempts >= m_maxAttempts)
     381        {
     382            VERBOSE(VB_IMPORTANT, LOC_ERR +
     383                    QString("Attempted to generate preview for '%1' "
     384                            "%2 times; >= max(%3)")
     385                    .arg(key).arg(attempts).arg(m_maxAttempts));
     386        }
     387    }
     388    else if (needs_gen)
     389    {
     390        VERBOSE(VB_PLAYBACK, LOC +
     391                "Not requesting preview as it "
     392                "is already being generated");
     393        IncPreviewGeneratorPriority(key, token);
     394    }
     395
     396    UpdatePreviewGeneratorThreads();
     397
     398    if (!ret.isEmpty())
     399    {
     400        QString msg = "On Disk";
     401        SendEvent(pginfo, "PREVIEW_SUCCESS", ret, token, msg);
     402    }
     403    else
     404    {
     405        uint queue_depth, token_cnt;
     406        GetInfo(key, queue_depth, token_cnt);
     407        QString msg = QString("Queue depth %1, our tokens %2")
     408            .arg(queue_depth).arg(token_cnt);
     409        SendEvent(pginfo, "PREVIEW_QUEUED", ret, token, msg);
     410    }
     411
     412    return ret;
     413}
     414
     415void PreviewGeneratorQueue::GetInfo(
     416    const QString &key, uint &queue_depth, uint &token_cnt)
     417{
     418    QMutexLocker locker(&m_lock);
     419    queue_depth = m_queue.size();
     420    PreviewMap::iterator pit = m_previewMap.find(key);
     421    token_cnt = (pit == m_previewMap.end()) ? 0 : (*pit).tokens.size();
     422}
     423
     424void PreviewGeneratorQueue::IncPreviewGeneratorPriority(
     425    const QString &key, QString token)
     426{
     427    QMutexLocker locker(&m_lock);
     428    m_queue.removeAll(key);
     429
     430    PreviewMap::iterator pit = m_previewMap.find(key);
     431    if (pit == m_previewMap.end())
     432        return;
     433
     434    if ((*pit).gen && !(*pit).genStarted)
     435        m_queue.push_back(key);
     436
     437    if (!token.isEmpty())
     438    {
     439        m_tokenToKeyMap[token] = key;
     440        (*pit).tokens.insert(token);
     441    }
     442}
     443
     444void PreviewGeneratorQueue::UpdatePreviewGeneratorThreads(void)
     445{
     446    QMutexLocker locker(&m_lock);
     447    QStringList &q = m_queue;
     448    if (!q.empty() && (m_running < m_maxThreads))
     449    {
     450        QString fn = q.back();
     451        q.pop_back();
     452        PreviewMap::iterator it = m_previewMap.find(fn);
     453        if (it != m_previewMap.end() && (*it).gen && !(*it).genStarted)
     454        {
     455            m_running++;
     456            VERBOSE(VB_IMPORTANT, LOC + "Starting pg thread");
     457            (*it).gen->start();
     458            (*it).genStarted = true;
     459        }
     460    }
     461}
     462
     463/** \brief Sets the PreviewGenerator for a specific file.
     464 *  \return true iff call succeeded.
     465 */
     466void PreviewGeneratorQueue::SetPreviewGenerator(
     467    const QString &key, PreviewGenerator *g)
     468{
     469    {
     470        QMutexLocker locker(&m_lock);
     471        m_tokenToKeyMap[g->token] = key;
     472        PreviewGenState &state = m_previewMap[key];
     473        if (state.gen)
     474        {
     475            if (!g->token.isEmpty())
     476                state.tokens.insert(g->token);
     477            g->deleteLater();
     478        }
     479        else
     480        {
     481            g->AttachSignals(this);
     482            state.gen = g;
     483            state.genStarted = false;
     484            if (!g->token.isEmpty())
     485                state.tokens.insert(g->token);
     486        }
     487    }
     488
     489    IncPreviewGeneratorPriority(key, "");
     490}
     491
     492/** \brief Returns true if we have already started a
     493 *         PreviewGenerator to create this file.
     494 */
     495bool PreviewGeneratorQueue::IsGeneratingPreview(const QString &key) const
     496{
     497    PreviewMap::const_iterator it;
     498    QMutexLocker locker(&m_lock);
     499
     500    if ((it = m_previewMap.find(key)) == m_previewMap.end())
     501        return false;
     502
     503    if ((*it).blockRetryUntil.isValid())
     504        return QDateTime::currentDateTime() < (*it).blockRetryUntil;
     505
     506    return (*it).gen;
     507}
     508
     509/** \fn PreviewGeneratorQueue::IncPreviewGeneratorAttempts(const QString&)
     510 *  \brief Increments and returns number of times we have
     511 *         started a PreviewGenerator to create this file.
     512 */
     513uint PreviewGeneratorQueue::IncPreviewGeneratorAttempts(const QString &key)
     514{
     515    QMutexLocker locker(&m_lock);
     516    return m_previewMap[key].attempts++;
     517}
     518
     519/** \fn PreviewGeneratorQueue::ClearPreviewGeneratorAttempts(const QString&)
     520 *  \brief Clears the number of times we have
     521 *         started a PreviewGenerator to create this file.
     522 */
     523void PreviewGeneratorQueue::ClearPreviewGeneratorAttempts(const QString &key)
     524{
     525    QMutexLocker locker(&m_lock);
     526    m_previewMap[key].attempts = 0;
     527    m_previewMap[key].lastBlockTime = 0;
     528    m_previewMap[key].blockRetryUntil =
     529        QDateTime::currentDateTime().addSecs(-60);
     530}
  • 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            {
     
    44714468            if (!oldinfo->IsLocal())
    44724469                oldinfo->SetPathname(oldinfo->GetPlaybackURL(false,true));
    44734470            if (oldinfo->IsLocal())
    4474             {
    4475                 (new PreviewGenerator(
    4476                     oldinfo, PreviewGenerator::kLocal))->Start();
    4477             }
     4471                PreviewGeneratorQueue::GetPreviewImage(*oldinfo, "");
    44784472        }
    44794473        delete oldinfo;
    44804474    }
  • 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    {
  • 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    {
     
    864861 *  gets updated as well.
    865862 */
    866863void PlaybackBox::HandlePreviewEvent(
    867     const QString &piKey, const QString &previewFile)
     864    const QString &piKey, const QString &previewFile, const QString &token)
    868865{
     866    QSet<QString>::iterator it = m_preview_tokens.find(token);
     867    if (it == m_preview_tokens.end())
     868        return;
     869    m_preview_tokens.erase(it);
     870
    869871    if (previewFile.isEmpty())
    870872        return;
    871873
     
    38033805            // asPendingDelete, we need to put them back now..
    38043806            ScheduleUpdateUIList();
    38053807        }
    3806         else if (message == "PREVIEW_READY" && me->ExtraDataCount() == 2)
     3808        else if (message == "PREVIEW_SUCCESS" && me->ExtraDataCount() >= 3)
    38073809        {
    3808             HandlePreviewEvent(me->ExtraData(0), me->ExtraData(1));
     3810            HandlePreviewEvent(
     3811                me->ExtraData(0), me->ExtraData(1), me->ExtraData(2));
    38093812        }
     3813        else if (message == "PREVIEW_FAILED" && me->ExtraDataCount() >= 3)
     3814        {
     3815            QString token = me->ExtraData(2);
     3816            QSet<QString>::iterator it = m_preview_tokens.find(token);
     3817            if (it == m_preview_tokens.end())
     3818                return;
     3819            m_preview_tokens.erase(it);
     3820        }
    38103821        else if (message == "AVAILABILITY" && me->ExtraDataCount() == 8)
    38113822        {
    38123823            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(
     322        const QString &piKey, const QString &previewFile, const QString &token);
    321323    void HandleRecordingRemoveEvent(uint chanid, const QDateTime &recstartts);
    322324    void HandleRecordingAddEvent(const ProgramInfo &evinfo);
    323325    void HandleUpdateProgramInfoEvent(const ProgramInfo &evinfo);
     
    443445    QStringList         m_player_selected_new_show;
    444446    /// Main helper thread
    445447    PlaybackBoxHelper   m_helper;
     448    /// Outstanding preview image requests
     449    QSet<QString>       m_preview_tokens;
    446450};
    447451
    448452class 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/main.cpp

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

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

     
    2626#endif // !__linux__
    2727
    2828#include <QCoreApplication>
     29#include <QThreadPool>
    2930#include <QDateTime>
     31#include <QRunnable>
    3032#include <QFile>
    3133#include <QDir>
    3234#include <QThread>
     
    3739#include <QTcpServer>
    3840#include <QTimer>
    3941
     42#include "previewgeneratorqueue.h"
    4043#include "exitcodes.h"
    4144#include "mythcontext.h"
    4245#include "mythverbose.h"
     
    5356#include "scheduledrecording.h"
    5457#include "jobqueue.h"
    5558#include "autoexpire.h"
    56 #include "previewgenerator.h"
    5759#include "storagegroup.h"
    5860#include "compat.h"
    5961#include "RingBuffer.h"
     
    6567#include "mythdirs.h"
    6668#include "mythdownloadmanager.h"
    6769
    68 
    6970/** Milliseconds to wait for an existing thread from
    7071 *  process request thread pool.
    7172 */
     
    184185{
    185186    AutoExpire::Update(true);
    186187
     188    PreviewGeneratorQueue::CreatePreviewGeneratorQueue(
     189        PreviewGenerator::kLocalAndRemote, ~0, 0);
     190    PreviewGeneratorQueue::AddListener(this);
     191
    187192    for (int i = 0; i < PRT_STARTUP_THREAD_COUNT; i++)
    188193    {
    189194        ProcessRequestThread *prt = new ProcessRequestThread(this);
     
    237242
    238243MainServer::~MainServer()
    239244{
     245    PreviewGeneratorQueue::RemoveListener(this);
     246    PreviewGeneratorQueue::TeardownPreviewGeneratorQueue();
     247
    240248    if (mythserver)
    241249    {
    242250        mythserver->disconnect();
     
    546554        else
    547555            HandleFileTransferQuery(listline, tokens, pbs);
    548556    }
    549     else if (command == "QUERY_GENPIXMAP")
     557    else if (command == "QUERY_GENPIXMAP2")
    550558    {
    551559        HandleGenPreviewPixmap(listline, pbs);
    552560    }
     
    734742    {
    735743        MythEvent *me = (MythEvent *)e;
    736744
     745        QString message = me->Message();
     746        QString error;
     747        if (message == "PREVIEW_SUCCESS" || message == "PREVIEW_QUEUED")
     748        {
     749            bool ok = false;
     750            QString filename;
     751            QString pginfokey;
     752            QString token;
     753            if (me->ExtraDataCount() >= 3)
     754            {
     755                ok = true;
     756                pginfokey = me->ExtraData(0);
     757                filename  = me->ExtraData(1);
     758                token     = me->ExtraData(2);
     759            }
     760
     761            if (message == "PREVIEW_QUEUED")
     762            {
     763                VERBOSE(VB_IMPORTANT, QString("Preview Queued: '%1' '%2' '%3'")
     764                        .arg(pginfokey).arg(filename).arg(token));
     765                return;
     766            }
     767
     768            QFile file(filename);
     769            ok = ok && file.open(QIODevice::ReadOnly);
     770
     771            if (ok)
     772            {
     773                VERBOSE(VB_IMPORTANT, QString("Preview Success: '%1' '%2' '%3'")
     774                        .arg(pginfokey).arg(filename).arg(token));
     775
     776                QByteArray data = file.readAll();
     777                QStringList extra(token);
     778                extra += "OK";
     779                extra += QString::number(data.size());
     780                extra += QString::number(
     781                    qChecksum(data.constData(), data.size()));
     782                extra += QString(data.toBase64());
     783                MythEvent me("GENERATED_PIXMAP", extra);   
     784                gCoreContext->dispatch(me);
     785                return;
     786            }
     787            else
     788            {
     789                message = "PREVIEW_FAILED";
     790                error = QString("Failed to read '%1'")
     791                    .arg(filename);
     792                VERBOSE(VB_IMPORTANT, LOC_ERR + error);
     793            }
     794        }
     795
     796        if (message == "PREVIEW_FAILED" && me->ExtraDataCount() >= 3)
     797        {
     798            QString pginfokey = me->ExtraData(0);
     799            QString filename  = me->ExtraData(1);
     800            QString token     = me->ExtraData(2);
     801            QString msg = me->ExtraDataCount() >= 4 ? me->ExtraData(3) : error;
     802            QStringList extra(token);
     803            extra.push_back("ERROR");
     804            extra.push_back(msg);
     805
     806            VERBOSE(VB_IMPORTANT, QString("Preview Failed: '%1' '%2' '%3'"
     807                                          "\n\t\t\tMsg: %4")
     808                    .arg(pginfokey).arg(filename).arg(token)
     809                    .arg(msg));
     810
     811            MythEvent me("GENERATED_PIXMAP", extra);
     812            gCoreContext->dispatch(me);
     813            return;
     814        }
     815
    737816        if (me->Message().left(11) == "AUTO_EXPIRE")
    738817        {
    739818            QStringList tokens = me->Message()
     
    48764955{
    48774956    MythSocket *pbssock = pbs->getSocket();
    48784957
     4958    if (slist.size() < 3)
     4959    {
     4960        VERBOSE(VB_IMPORTANT, LOC_ERR + "Too few params in pixmap request");
     4961        QStringList outputlist("ERROR");
     4962        outputlist += "TOO_FEW_PARAMS";
     4963        SendResponse(pbssock, outputlist);
     4964        return;
     4965    }
     4966
    48794967    bool      time_fmt_sec   = true;
    48804968    long long time           = -1;
    48814969    QString   outputfile;
     
    48834971    int       height         = -1;
    48844972    bool      has_extra_data = false;
    48854973
    4886     QStringList::const_iterator it = slist.begin() + 1;
     4974    QString token = slist[1];
     4975    if (token.isEmpty())
     4976    {
     4977        VERBOSE(VB_IMPORTANT, LOC_ERR + "Failed to parse pixmap request. "
     4978                "Token absent");
     4979        QStringList outputlist("ERROR");
     4980        outputlist += "TOKEN_ABSENT";
     4981        SendResponse(pbssock, outputlist);
     4982    }   
     4983
     4984    QStringList::const_iterator it = slist.begin() + 2;
    48874985    QStringList::const_iterator end = slist.end();
    48884986    ProgramInfo pginfo(it, end);
    48894987    bool ok = pginfo.HasPathname();
    48904988    if (!ok)
    48914989    {
    4892         VERBOSE(VB_IMPORTANT, "MainServer: Failed to parse pixmap request.");
     4990        VERBOSE(VB_IMPORTANT, LOC_ERR + "Failed to parse pixmap request. "
     4991                "ProgramInfo missing pathname");
    48934992        QStringList outputlist("BAD");
    4894         outputlist += "ERROR_INVALID_REQUEST";
     4993        outputlist += "NO_PATHNAME";
    48954994        SendResponse(pbssock, outputlist);
    48964995    }
    48974996    if (it != slist.end())
     
    49365035            if (has_extra_data)
    49375036            {
    49385037                outputlist = slave->GenPreviewPixmap(
    4939                     &pginfo, time_fmt_sec, time, outputfile, outputsize);
     5038                    token, &pginfo, time_fmt_sec, time, outputfile, outputsize);
    49405039            }
    49415040            else
    49425041            {
    4943                 outputlist = slave->GenPreviewPixmap(&pginfo);
     5042                outputlist = slave->GenPreviewPixmap(token, &pginfo);
    49445043            }
    49455044
    49465045            slave->DownRef();
     
    49555054
    49565055    if (!pginfo.IsLocal())
    49575056    {
    4958         VERBOSE(VB_IMPORTANT, "MainServer: HandleGenPreviewPixmap: Unable to "
     5057        VERBOSE(VB_IMPORTANT, LOC_ERR + "HandleGenPreviewPixmap: Unable to "
    49595058                "find file locally, unable to make preview image.");
    4960         QStringList outputlist( "BAD" );
    4961         outputlist += "ERROR_NOFILE";
     5059        QStringList outputlist( "ERROR" );
     5060        outputlist += "FILE_INACCESSIBLE";
    49625061        SendResponse(pbssock, outputlist);
    49635062        return;
    49645063    }
    49655064
    4966     PreviewGenerator *previewgen = new PreviewGenerator(&pginfo);
    49675065    if (has_extra_data)
    49685066    {
    4969         previewgen->SetOutputSize(outputsize);
    4970         previewgen->SetOutputFilename(outputfile);
    4971         previewgen->SetPreviewTime(time, time_fmt_sec);
     5067        PreviewGeneratorQueue::GetPreviewImage(
     5068            pginfo, outputsize, outputfile, time, time_fmt_sec, token);
    49725069    }
    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     }
    49835070    else
    49845071    {
    4985         VERBOSE(VB_IMPORTANT, "MainServer: Failed to make preview image.");
    4986         QStringList outputlist( "BAD" );
    4987         outputlist += "ERROR_UNKNOWN";
    4988         SendResponse(pbssock, outputlist);
     5072        PreviewGeneratorQueue::GetPreviewImage(pginfo, token);
    49895073    }
     5074
     5075    QStringList outputlist("OK");
     5076    if (!outputfile.isEmpty())
     5077        outputlist += outputfile;
     5078    SendResponse(pbssock, outputlist);
    49905079}
    49915080
    49925081void MainServer::HandlePixmapLastModified(QStringList &slist, PlaybackSock *pbs)