videocollection/videocollectionwrapper/src/videothumbnaildata_p.cpp
author hgs
Thu, 01 Apr 2010 22:38:49 +0300
changeset 30 4f111d64a341
child 34 bbb98528c666
permissions -rw-r--r--
201005

/*
* Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:  VideoThumbnailDataPrivate class implementation
*
*/

// INCLUDE FILES
#include <QApplication>
#include <QPixmap>
#include <QTimer>
#include <mpxmediageneraldefs.h>
#include <thumbnailmanager_qt.h>

#include "videothumbnaildata_p.h"
#include "videocollectionwrapper.h"
#include "videosortfilterproxymodel.h"

// Maximum thumbnails kept in memory.
const int THUMBNAIL_CACHE_SIZE = 60;
// Maximum of thumbnail fetches done at one background fetch round.
const int THUMBNAIL_BACKGROUND_FETCH_AMOUNT = 20;
// Milliseconds for the background fetch timer.
const int THUMBNAIL_BACKGROUND_TIMEOUT = 100;
// Maximum simultaneous thumbnail fetches.
const int THUMBNAIL_MAX_SIMULTANEOUS_FETCHES = THUMBNAIL_BACKGROUND_FETCH_AMOUNT * 10;
// Milliseconds while thumbnail ready events are gathered before they 
// are signaled.
const int THUMBNAIL_READY_SIGNAL_TIMEOUT = 50;
// Priority for background thumbnail fetches.
const int BACKGROUND_FETCH_PRIORITY = 3000;

/**
 * global qHash function required fo creating hash values for TMPXItemId -keys
 */
inline uint qHash(TMPXItemId key) 
{ 
    QPair<uint, uint> keyPair(key.iId1, key.iId2); 

    return qHash(keyPair);
}

// ================= MEMBER FUNCTIONS =======================
//

// -----------------------------------------------------------------------------
// VideoThumbnailDataPrivate::VideoThumbnailDataPrivate()
// -----------------------------------------------------------------------------
//
VideoThumbnailDataPrivate::VideoThumbnailDataPrivate() :
    mThumbnailManager(0),
    mDefaultTnVideo(0),
    mDefaultTnCategory(0),
    mCollectionWrapper(VideoCollectionWrapper::instance()),
    mModel(0),
    mCurrentFetchIndex(0),
    mCurrentBackgroundFetchCount(0),
    mBgFetchTimer(0),
    mTbnReportTimer(0),
    mSignalsConnected(false),
    mBackgroundFetchingEnabled(true)
{
    initialize();
}

// -----------------------------------------------------------------------------
// VideoThumbnailDataPrivate::~VideoThumbnailDataPrivate()
// -----------------------------------------------------------------------------
//
VideoThumbnailDataPrivate::~VideoThumbnailDataPrivate()
{
    cleanup();
}

// -----------------------------------------------------------------------------
// VideoThumbnailDataPrivate::initialize()
// -----------------------------------------------------------------------------
//
int VideoThumbnailDataPrivate::initialize()
{
    mThumbnailData.setMaxCost(THUMBNAIL_CACHE_SIZE);
    
    if(!mCollectionWrapper)
    {
        return -1;
    }   
    
    if(!mThumbnailManager)
    {
        mThumbnailManager = new ThumbnailManager();
        mThumbnailManager->setThumbnailSize( ThumbnailManager::ThumbnailMedium );
        mThumbnailManager->setQualityPreference( ThumbnailManager::OptimizeForPerformance );
    }

    mModel = mCollectionWrapper->getModel();
    if(!mModel)
    {
        cleanup();
        return -1;
    }

    if(!mBgFetchTimer)
    {
        mBgFetchTimer = new QTimer();
    }
    
    if(!mTbnReportTimer)
    {
        mTbnReportTimer = new QTimer();
    }

    if(connectSignals() < 0)
    {
        cleanup();
        return -1;
    }
    
    return 0;
}

// -----------------------------------------------------------------------------
// VideoThumbnailDataPrivate::cleanup()
// -----------------------------------------------------------------------------
//
void VideoThumbnailDataPrivate::cleanup()
{
    if(mCollectionWrapper)
    {
        mCollectionWrapper->decreaseReferenceCount();
        mCollectionWrapper = 0;
    }
    
    disconnectSignals();

    freeThumbnailData();
    
    if(mTbnReportTimer)
    {
        mTbnReportTimer->stop();
        delete mTbnReportTimer;
        mTbnReportTimer = 0;
    }
    
    if(mBgFetchTimer)
    {
        mBgFetchTimer->stop();
        delete mBgFetchTimer;
        mBgFetchTimer = 0;
    }

    delete mThumbnailManager;
    mThumbnailManager = 0;
}

// -----------------------------------------------------------------------------
// VideoThumbnailDataPrivate::disconnectSignals()
// -----------------------------------------------------------------------------
//
void VideoThumbnailDataPrivate::disconnectSignals()
{
    if(mSignalsConnected)
    {
        disconnect(mThumbnailManager, SIGNAL(thumbnailReady( QPixmap , void * , int , int )),
                    this, SLOT(thumbnailReadySlot( QPixmap , void * , int , int )));
        disconnect(mModel, SIGNAL(layoutChanged()), this, SLOT(layoutChangedSlot()));
        disconnect(mModel, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
                    this, SLOT(rowsInsertedSlot(const QModelIndex &, int, int)));
        disconnect(mBgFetchTimer, SIGNAL(timeout()), this, SLOT(doBackgroundFetching()));
        disconnect(mTbnReportTimer, SIGNAL(timeout()), this, SLOT(reportThumbnailsReadySlot()));
    }
    mSignalsConnected = false;
}

// -----------------------------------------------------------------------------
// VideoThumbnailDataPrivate::connectSignals()
// -----------------------------------------------------------------------------
//
int VideoThumbnailDataPrivate::connectSignals()
{
    if(!mSignalsConnected)
    {
        if(!connect(mThumbnailManager, SIGNAL(thumbnailReady( QPixmap , void * , int , int )),
                    this, SLOT(thumbnailReadySlot( QPixmap , void * , int , int ))) ||
           !connect(mModel, SIGNAL(layoutChanged()), this, SLOT(layoutChangedSlot())) ||
           !connect(mModel, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
                    this, SLOT(rowsInsertedSlot(const QModelIndex &, int, int))) ||
           !connect(mBgFetchTimer, SIGNAL(timeout()), this, SLOT(doBackgroundFetching())) ||
           !connect(mTbnReportTimer, SIGNAL(timeout()), this, SLOT(reportThumbnailsReadySlot())))
        {
            return -1;
        }
        
        QApplication *app = qApp;
        if(!connect(app, SIGNAL(aboutToQuit()), this, SLOT(aboutToQuitSlot())))
        {
            return -1;
        }
        
        mSignalsConnected = true;
    }
    return 0;
}

// -----------------------------------------------------------------------------
// VideoThumbnailDataPrivate::getThumbnail()
// -----------------------------------------------------------------------------
//
const QIcon* VideoThumbnailDataPrivate::getThumbnail(TMPXItemId mediaId)
{
    const QIcon *thumbnail = mThumbnailData[mediaId];
    if(!thumbnail)
    {
        return defaultThumbnail(mediaId);
    }
    return thumbnail;
}

// -----------------------------------------------------------------------------
// VideoThumbnailDataPrivate::startFetchingThumbnails()
// -----------------------------------------------------------------------------
//
int VideoThumbnailDataPrivate::startFetchingThumbnails(const QList<QModelIndex> &indexes, int priority)
{
    if(!mModel || !mThumbnailManager)
    {
        return -1;
    }
    if(indexes.count() == 0)
    {
        return 0;
    }
    
    // Now we fetch the requested thumbnails with higher priority than any of the current fetches.
    // TODO: Better would be to cancel the current fetches but it causes crashes in 10.1.   
    
    int fetchCountBefore = mFetchList.count();
    
    // Fetch the thumbnails in ascending priority.

    int startPriority = fetchCountBefore + priority + indexes.count();
    
    for(int i = 0; i < indexes.count(); i++)
    {
        startFetchingThumbnail(mModel->getMediaIdAtIndex(indexes[i]), startPriority-i);
    }
    
    return mFetchList.count() - fetchCountBefore;
}

// -----------------------------------------------------------------------------
// VideoThumbnailDataPrivate::startFetchingThumbnail()
// -----------------------------------------------------------------------------
//
int VideoThumbnailDataPrivate::startFetchingThumbnail(TMPXItemId mediaId, int priority)
{
    if(!mModel || !mThumbnailManager)
    {
        return -1;
    }
    if(mFetchList.count() >= THUMBNAIL_MAX_SIMULTANEOUS_FETCHES)
    {
        return -1;
    }

    // Check that it's not fetched before.
    if(mThumbnailData.contains(mediaId))
    {
        return 0;
    }

    QString fileName = mModel->getMediaFilePathForId(mediaId);

    // object containing media id to be passed throught
    // thumbnail generation process.
    TMPXItemId *internal = new TMPXItemId(mediaId.iId1, mediaId.iId2);
    
    int tnId = -1;
    // mThumbnailManager signals into thumbnailReadySlot when thumbnail ready
    if(fileName.length() > 0)
    {
        tnId = mThumbnailManager->getThumbnail(fileName, internal, priority);
    }

    if( tnId != -1 )
    {
        // add to fetching list to indicate we're fetching this tn.
        mFetchList.insert(tnId);
    }
    else
    {
        // tn getting starting failed for some reason
        delete internal;
    }
    return tnId;
}

// -----------------------------------------------------------------------------
// VideoThumbnailDataPrivate::doBackgroundFetching()
// -----------------------------------------------------------------------------
//
void VideoThumbnailDataPrivate::doBackgroundFetching()
{
    if(!mModel)
    {
        return;
    }

    if(mCurrentBackgroundFetchCount >= THUMBNAIL_CACHE_SIZE)
    {
        return;
    }
    
    int maxIndex = mModel->rowCount();
    if(maxIndex == 0)
    {
        return;
    }

    // Delta to UI index where fetch has been done already.  
    int currentDelta = mCurrentBackgroundFetchCount/2;
    
    // How many will be fetched.  
    const int fetchAmount = THUMBNAIL_BACKGROUND_FETCH_AMOUNT/2;

    QList<QModelIndex> indexes;

    // Items before the current fetch index.
    int startIndex = mCurrentFetchIndex-currentDelta-fetchAmount;
    int endIndex = mCurrentFetchIndex-currentDelta;
    getModelIndexes(indexes, startIndex, endIndex);

    // Items after the current fetch index.
    startIndex = mCurrentFetchIndex+currentDelta;
    endIndex = mCurrentFetchIndex+currentDelta+fetchAmount;
    getModelIndexes(indexes, startIndex, endIndex);

    mCurrentBackgroundFetchCount += THUMBNAIL_BACKGROUND_FETCH_AMOUNT;

    int fetchesStarted = startFetchingThumbnails(indexes, BACKGROUND_FETCH_PRIORITY);
    
    // No thumbnails to fetch, start again.
    if(fetchesStarted == 0)
    {
        continueBackgroundFetch();
    }
}

// -----------------------------------------------------------------------------
// VideoThumbnailDataPrivate::getModelIndexes()
// -----------------------------------------------------------------------------
//
void VideoThumbnailDataPrivate::getModelIndexes(QList<QModelIndex> &indexes, int startIndex, int endIndex)
{
    QModelIndex index;
    for(int i = startIndex; i < endIndex; i++)
    {
        if(i >= 0)
        {
            index = mModel->index(i, 0);
            if(index.isValid())
            {
                indexes.append(index);
            }
        }
    }
}

// -----------------------------------------------------------------------------
// VideoThumbnailDataPrivate::thumbnailReadySlot()
// -----------------------------------------------------------------------------
//
void VideoThumbnailDataPrivate::thumbnailReadySlot(QPixmap tnData, void *internal , int id, int error)
{
    // Tn ready, either failed or not it must be removed from the fetch list.
    // It's not stored if it's not found from the list. 
    if(!removeFromFetchList(id))
    {
        if(internal)
            delete internal;
        return;
    }
    
    TMPXItemId mediaId(0, 0);
    if(internal)
    {
        mediaId = *(static_cast<TMPXItemId*>(internal));
        delete internal;
    }
    else
    {
        return;
    }

    if(!error && !tnData.isNull())
    {
        mThumbnailData.insert(mediaId, new QIcon(tnData));
        
        // Gather list of media ids and emit thumbnailReady signals in larger set
        // when timer goes off.
        if(mTbnReportTimer && !mTbnReportTimer->isActive())
        {
            mTbnReportTimer->setSingleShot(true);
            mTbnReportTimer->start(THUMBNAIL_READY_SIGNAL_TIMEOUT);
        }

        // Save the media id for the signal.  
        mReadyThumbnailMediaIds.append(mediaId);
    }
}

// -----------------------------------------------------------------------------
// VideoThumbnailDataPrivate::reportThumbnailsReadySlot()
// -----------------------------------------------------------------------------
//
void VideoThumbnailDataPrivate::reportThumbnailsReadySlot()
{
    emit thumbnailsFetched(mReadyThumbnailMediaIds);
    mReadyThumbnailMediaIds.clear();
}

// -----------------------------------------------------------------------------
// VideoThumbnailDataPrivate::layoutChangedSlot()
// -----------------------------------------------------------------------------
//
void VideoThumbnailDataPrivate::layoutChangedSlot()
{
    startBackgroundFetching(mCurrentFetchIndex);
}

// -----------------------------------------------------------------------------
// VideoThumbnailDataPrivate::rowsInsertedSlot()
// -----------------------------------------------------------------------------
//
void VideoThumbnailDataPrivate::rowsInsertedSlot(const QModelIndex & /* parent */, int /* start */, int /* end */)
{
    startBackgroundFetching(mCurrentFetchIndex);
}

// -----------------------------------------------------------------------------
// VideoThumbnailDataPrivate::defaultThumbnail()
// -----------------------------------------------------------------------------
//
const QIcon* VideoThumbnailDataPrivate::defaultThumbnail(TMPXItemId mediaId)
{
    // Is thumbnail for a video or a category.
    if(mediaId.iId2 == 0)
    {
        if(!mDefaultTnVideo)
            mDefaultTnVideo = new QIcon(":/icons/default_thumbnail_video.svg");
        return mDefaultTnVideo;
    }
    else
    {
        if(!mDefaultTnCategory)
            mDefaultTnCategory = new QIcon(":/icons/default_thumbnail_collection.svg");
        return mDefaultTnCategory;
    }
}

// -----------------------------------------------------------------------------
// VideoThumbnailDataPrivate::removeThumbnail()
// -----------------------------------------------------------------------------
//
bool VideoThumbnailDataPrivate::removeThumbnail(TMPXItemId mediaId)
{
    return mThumbnailData.remove(mediaId);
}

// -----------------------------------------------------------------------------
// VideoThumbnailDataPrivate::enableBackgroundFetching()
// -----------------------------------------------------------------------------
//
void VideoThumbnailDataPrivate::enableBackgroundFetching(bool enable)
{
    mBackgroundFetchingEnabled = enable;
    startBackgroundFetching(0);
}

// -----------------------------------------------------------------------------
// VideoThumbnailDataPrivate::freeThumbnailData()
// -----------------------------------------------------------------------------
//
void VideoThumbnailDataPrivate::freeThumbnailData()
{
    // Stop timers.
    if(mBgFetchTimer)
        mBgFetchTimer->stop();
    
    if(mTbnReportTimer)
        mTbnReportTimer->stop();

    // Clear data.
    mFetchList.clear();
    mReadyThumbnailMediaIds.clear();
    mThumbnailData.clear();
    
    delete mDefaultTnVideo;
    mDefaultTnVideo = 0;
    
    delete mDefaultTnCategory;
    mDefaultTnCategory = 0;
}

// -----------------------------------------------------------------------------
// VideoThumbnailDataPrivate::removeFromFetchList()
// -----------------------------------------------------------------------------
//
bool VideoThumbnailDataPrivate::removeFromFetchList(int tnId)
{
    if(mFetchList.isEmpty())
    {
        return false;
    }
    bool removed = mFetchList.remove(tnId);
    if(mFetchList.isEmpty())
    {
        continueBackgroundFetch();
    }
    return removed;
}

// -----------------------------------------------------------------------------
// VideoThumbnailDataPrivate::startBackgroundFetching()
// -----------------------------------------------------------------------------
//
void VideoThumbnailDataPrivate::startBackgroundFetching(int fetchIndex)
{
    if(!mBackgroundFetchingEnabled)
        return;

    mCurrentFetchIndex = fetchIndex;
    mCurrentBackgroundFetchCount = 0;
    doBackgroundFetching();
}

// -----------------------------------------------------------------------------
// VideoThumbnailDataPrivate::continueBackgroundFetch()
// -----------------------------------------------------------------------------
//
void VideoThumbnailDataPrivate::continueBackgroundFetch()
{
    if(!mBackgroundFetchingEnabled)
        return;

    if(mBgFetchTimer)
    {
        mBgFetchTimer->stop();
        mBgFetchTimer->setSingleShot(true);
        mBgFetchTimer->start(THUMBNAIL_BACKGROUND_TIMEOUT);
    }
}

// -----------------------------------------------------------------------------
// VideoThumbnailDataPrivate::aboutToQuitSlot()
// -----------------------------------------------------------------------------
//
void VideoThumbnailDataPrivate::aboutToQuitSlot()
{
    cleanup();
}

// End of file