mpviewplugins/mpdetailsviewplugin/src/mpquerymanager.cpp
author hgs
Tue, 24 Aug 2010 03:36:14 -0500
changeset 51 560ce2306a17
parent 47 4cc1412daed0
permissions -rw-r--r--
201033

/*
* 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: Music Player Query Manager.
*
*/

#include "mpquerymanager.h"
#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkDiskCache>
#include <QNetworkProxyFactory>
#include <qmobilityglobal.h>
#include <QNetworkSession>
#include <QDomElement>
#include <QList>
#include <QUrl>
#include <QSslError>
#include <XQSysInfo>
#include <QSignalMapper>
#include <QSettings>

#include "mptrace.h"

const int KRecommendationCount = 2;

MpQueryManager::MpQueryManager()
    : mManager(0),
      mAlbumArtDownloader(0),
      mDefaultRecommendationAlbumArt("qtg_large_album_art"),
      mRequestType(NoRequest),
      mRecommendationCount(0)                               
{
    TX_ENTRY
    mManager = new QNetworkAccessManager( this );      
    // A second intance is necessary to reduce complexity.
    // Otherwise, we would have to shoot async events when we want to receive inspire me items' album art
    // and that may not always work.
    mAlbumArtDownloader = new QNetworkAccessManager( this ); 

    mDownloadSignalMapper = new QSignalMapper(this);
    TX_EXIT
}

MpQueryManager::~MpQueryManager()
{
    TX_ENTRY
    reset();
    if ( mManager ) {
       mManager->deleteLater();
    }
    if ( mAlbumArtDownloader ) {
       mAlbumArtDownloader->deleteLater();
    }
    delete mDownloadSignalMapper;
    TX_EXIT
}


void MpQueryManager::clearNetworkReplies()
{
    mRequestType = NoRequest; 
    disconnect( mManager, SIGNAL( finished( QNetworkReply * ) ), this, SLOT( retrieveInformationFinished( QNetworkReply * ) ) );     
    TX_ENTRY_ARGS( "Reply count = " << mReplys.count() );    
    for ( int i = 0; i < mReplys.count(); ++i ) {
        QNetworkReply *reply = mReplys.at( i );
        disconnect( reply, SIGNAL( error( QNetworkReply::NetworkError ) ), this, SLOT( retrieveInformationNetworkError( QNetworkReply::NetworkError ) ) );        
        if ( reply != NULL ) {
            reply->close();
            reply->deleteLater();
            reply = NULL;
        }
    }
    mReplys.clear();
    TX_EXIT
}

    
void MpQueryManager::queryInspireMeItems(QString artist,QString album,QString title)
{
    TX_ENTRY    
    mArtist=artist;
    mAlbum=album;
    mTitle=title;
    // start querying inspire me items
    QString queryRecommendation("http://api.music.ovi.com/1.0/" + mMusicStore + "/releases/recommend/?");
    constructRequest( queryRecommendation );
    // TODO: Tokens change per new ovi api release. 
	//       Need to figure out a way to get them updated on the fly
    queryRecommendation.append("&Token=03574704-e3d1-4466-9691-e0b34c7abfff");

    TX_LOG_ARGS( queryRecommendation );
    retrieveInformation( queryRecommendation );
    mRequestType = InspireMeItemsMetadataRequest;
    TX_EXIT
}

void MpQueryManager::clearRecommendations()
{
    TX_ENTRY
    mRecommendationCount = 0;
    mDownloadedAlbumArts = 0;
    mAlbumArtsReadyCount = 0;
    mRecommendationSongs.clear();
    mRecommendationArtists.clear();
    mRecommendationAlbumArtsLink.clear();
    mRecommendationAlbumArtsMap.clear();
    TX_EXIT    
}



/*!
 Returns the Local Music Store(if available) to be used while querying "Inspire Me" Items
 */
void MpQueryManager::queryLocalMusicStore()
{
    TX_ENTRY
    QString imsi,mcc;

    XQSysInfo sysInfo( this );
    imsi = sysInfo.imsi();
    mcc= imsi.left(3);
    TX_LOG_ARGS( "mcc : " << mcc );
    
    QString queryLocalMusicStore("http://api.music.cq1.brislabs.com/1.0/?mcc=" + mcc + "&token=06543e34-0261-40a4-a03a-9e09fe110c1f");
    TX_LOG_ARGS( "queryLocalMusicStore : " << queryLocalMusicStore );
    retrieveInformation( queryLocalMusicStore );
    mRequestType = LocalStoreRequest;
    TX_EXIT
}


int MpQueryManager::recommendationsCount() const
{
    TX_LOG_ARGS ("count: " << mRecommendationSongs.count());
    return mRecommendationSongs.count();
}

QString MpQueryManager::recommendedSong(int index) const
{
    QString result;
    if( (0 <= index) && (index < mRecommendationSongs.count())) {
        result = mRecommendationSongs.at(index);
    }
    TX_LOG_ARGS ("recommendedSong: " << result);    
    return result; 
}

/*!
 Return recommendation artists
 */
QString MpQueryManager::recommendedArtist(int index) const
{
    QString result;
    if( (0 <= index) && (index < mRecommendationArtists.count())) {
        result = mRecommendationArtists.at(index);
    }
    TX_LOG_ARGS ("recommendedArtist: " << result);    
    return result;
}

/*!
 Slot to call when getting response
 */
void MpQueryManager::retrieveInformationFinished( QNetworkReply* reply )
{
    TX_ENTRY
    QString errorStr;
    int errorLine;
    int errorColumn;
    bool parsingSuccess;
        
    if ( reply->error() != QNetworkReply::NoError ) {
		TX_LOG_ARGS("reply->error(): " << reply->error());
        signalError();
        return;
    }
        
    parsingSuccess = mDomDocument.setContent( reply, true, &errorStr, &errorLine, &errorColumn );
    if ( !parsingSuccess ) {
		TX_LOG_ARGS("Parsing Received Content Failed");
        signalError();
        return;
    }

    handleParsedXML();  //CodeScanner throws a warning mis-interpreting the trailing 'L' to be a leaving function.
    
    mReplys.removeAll(reply); // remove it so that we wont process it again
    reply->deleteLater(); // make sure reply is deleted, as we longer care about it
    
    TX_EXIT
}

void MpQueryManager::signalError()
{
    TX_ENTRY; 
    switch(mRequestType) {
        case InspireMeItemsMetadataRequest:
            TX_LOG_ARGS("emit inspireMeItemsRetrievalError");
            emit inspireMeItemsRetrievalError();
            break;
        case InspireMeItemsAlbumArtRequest:
            TX_LOG_ARGS("Warning: InspireMeItemsAlbumArtRequestError, will keep using the default AA icon");            
            break;    
        case LocalStoreRequest:
            TX_LOG_ARGS("emit localMusicStoreRetrievalError");            
            emit localMusicStoreRetrievalError();
            break;
        case NoRequest:   
        default:
            TX_LOG_ARGS("Warning!! Possible uninitialized mRequestType");            
            break;                                 
    }
    TX_EXIT    
}

/*!
 Slot to call when there is network error
 */
void MpQueryManager::retrieveInformationNetworkError( QNetworkReply::NetworkError error )
{
    TX_ENTRY_ARGS( "Network error for retrieving Information" << error);    

	Q_UNUSED(error)

    disconnect( mManager, SIGNAL( finished( QNetworkReply * ) ), this, SLOT( retrieveInformationFinished( QNetworkReply * ) ) );     
    signalError();
    TX_EXIT
}

/*!
 Slot to call when there is ssl error
 */
void MpQueryManager::retrieveInformationSslErrors( const QList<QSslError> &/*error*/ )
{
    TX_ENTRY_ARGS( "SSL error for retrieving Information" );
    disconnect( mManager, SIGNAL( finished( QNetworkReply * ) ), this, SLOT( retrieveInformationFinished( QNetworkReply * ) ) );     
    signalError();    
    TX_EXIT
}

/*!
 Slot to call when downloading finished
 */
void MpQueryManager::albumArtDownloaded( int index )
{
    TX_ENTRY_ARGS( "mDownloadedAlbumArts = " << mDownloadedAlbumArts << "index = " << index);
    
    QNetworkReply* reply = qobject_cast<QNetworkReply*> ( qobject_cast<QSignalMapper*>( sender() )->mapping( index ) );
    // It seems we get several finished signals for the same reply obj
    // do nothing if we get a second signal
    if( mReplys.indexOf(reply) == -1 ) {
        TX_LOG_ARGS("Warning: QNetworkReply AA request may have been processed in previous call: " << reply );
        return;
    }
       
    if ( reply->error() == QNetworkReply::NoError ) {
        QPixmap albumart;
        bool result = albumart.loadFromData( reply->readAll() );
        if ( result ) {
            mRecommendationAlbumArtsMap.insert( mRecommendationAlbumArtsLink.at( index ), HbIcon( QIcon( albumart ) ) );

        } else {
            mRecommendationAlbumArtsMap.insert( mRecommendationAlbumArtsLink.at( index ), mDefaultRecommendationAlbumArt );        
        }

        ++mDownloadedAlbumArts;
        mReplys.removeAll(reply); // remove it so that we wont process it again
        reply->deleteLater(); // make sure reply is deleted, as we longer care about it
    }
    else {
        TX_LOG_ARGS( "Error: Downloading album art failed! Will keep using the default AA" );
        mRecommendationAlbumArtsMap.insert( mRecommendationAlbumArtsLink.at( index ), mDefaultRecommendationAlbumArt );  
    }
    mDownloadSignalMapper->removeMappings( reply );
    
    if( mDownloadedAlbumArts == mRecommendationCount) {
        // no need to be informed anymore
        mDownloadSignalMapper->disconnect(this);
        emit inspireMeItemAlbumArtReady();    
    }
    
    TX_EXIT
}


/*!
 Get Atom response from Ovi server based on query
 */
void MpQueryManager::retrieveInformation( const QString &urlEncoded )
{
    TX_ENTRY_ARGS( "urlEconded = " << urlEncoded)
    connect( mManager, SIGNAL( finished( QNetworkReply * ) ), this, SLOT( retrieveInformationFinished( QNetworkReply * ) ), Qt::UniqueConnection );
    QNetworkReply *reply = mManager->get( QNetworkRequest( QUrl( urlEncoded ) ) );
    mReplys.append( reply );
    
    connect( reply, SIGNAL( error( QNetworkReply::NetworkError ) ), this, SLOT( retrieveInformationNetworkError( QNetworkReply::NetworkError ) ) );
    connect( reply, SIGNAL( sslErrors( QList<QSslError> ) ), this, SLOT( retrieveInformationSslErrors( QList<QSslError> ) ) );
    TX_EXIT
}


/*!
 Find the most suitable link based on Atom response from Ovi music server
 */
void MpQueryManager::handleParsedXML()
{
    TX_ENTRY
    QDomElement rootElement = mDomDocument.documentElement();
    
    if ( rootElement.attribute( "type" ) == "recommendedTracks" ) {
        TX_LOG_ARGS( "Recommendation response" )
        QDomElement entry = rootElement.firstChildElement( "entry" );
        mRecommendationCount = 0;
        while ( !entry.isNull() && mRecommendationCount < KRecommendationCount ) {
            if ( entry.attribute( "type" ) == "musictrack" ) {
                QDomElement link = entry.firstChildElement( "link" );
                while ( !link.isNull() ) {
                    if ( link.attribute( "title" ) == "albumart100" ) {
                        mRecommendationAlbumArtsLink.append( link.attribute( "href" ) );
                        mRecommendationAlbumArtsMap.insert( link.attribute( "href" ), mDefaultRecommendationAlbumArt );                      
                        break;
                    }
                    else {
                        link = link.nextSiblingElement( "link" );
                    }
                }
                QDomElement metadata = entry.firstChildElement( "metadata" );
                mRecommendationSongs.append( metadata.firstChildElement( "name" ).text() );
                mRecommendationArtists.append( metadata.firstChildElement( "primaryartist" ).text() );
                ++mRecommendationCount;
            }
            entry = entry.nextSiblingElement( "entry" );
        }
 
        emit inspireMeItemsMetadataRetrieved();

        QNetworkReply *reply = 0;
        // we need to channel the retrieved album arts to albumArtDownloaded slot only
        disconnect( mManager, SIGNAL( finished( QNetworkReply * ) ), this, SLOT( retrieveInformationFinished( QNetworkReply * ) ) );        
        for (int i = 0; i < mRecommendationCount; i++ ) {
            TX_LOG_ARGS( "song name: " << mRecommendationSongs.at(i) );
            TX_LOG_ARGS( "Artist name: " << mRecommendationArtists.at(i) );
            TX_LOG_ARGS( "Album art link: " << mRecommendationAlbumArtsLink.at(i) );
            mRequestType = InspireMeItemsAlbumArtRequest;
            if ( mRecommendationAlbumArtsLink.at( i ).contains( "http", Qt::CaseInsensitive ) ) {
                reply = mAlbumArtDownloader->get( QNetworkRequest( QUrl( mRecommendationAlbumArtsLink.at(i) ) ) );
                mReplys.append( reply );
                connect( reply, SIGNAL( finished() ), mDownloadSignalMapper, SLOT( map() ) );
                mDownloadSignalMapper->setMapping( reply, i );

                connect( reply, SIGNAL( error( QNetworkReply::NetworkError ) ), this, SLOT( retrieveInformationNetworkError( QNetworkReply::NetworkError ) ) );
                connect( reply, SIGNAL( sslErrors( QList<QSslError> ) ), this, SLOT( retrieveInformationSslErrors( QList<QSslError> ) ) );
            }
        }
       // we have queried for album arts for inspire me items. Now, time to wait for a response        
        connect( mDownloadSignalMapper, SIGNAL( mapped( int ) ), this, SLOT( albumArtDownloaded( int ) ) );

    }
    else if ( rootElement.attribute( "type" ) == "storeList" ) {
		TX_LOG_ARGS( "Music Store List" )
        QDomElement entry = rootElement.firstChildElement( "workspace" );
        QString previousMusicStore = mMusicStore;
        mMusicStore = entry.attribute( "countryCode" );
        if( !mMusicStore.isEmpty() ) {
            bool musicStoreUpdated = ( previousMusicStore != mMusicStore );
            TX_LOG_ARGS("Music Store" << mMusicStore );
            emit localMusicStoreRetrieved( musicStoreUpdated );
            if( musicStoreUpdated ) {
                QSettings settings;
                TX_LOG_ARGS( "Storing music store value: " << mMusicStore );
                settings.setValue( "LocalMusicStore", QVariant( mMusicStore ) );
            }    
        }
        else {
            emit localMusicStoreRetrievalError();   
        }		
    }
    else {
        TX_LOG_ARGS( "Not supported response" )
    }
    TX_EXIT
}


void MpQueryManager::reset()
{
    TX_ENTRY
    mManager->disconnect(this);
    mAlbumArtDownloader->disconnect(this);
    clearNetworkReplies();
    clearRecommendations();
    mRecommendationAlbumArtsMap.clear();
    TX_EXIT
}
    
/*!
 Construct the query for fetching URI & recommendations
 */
void MpQueryManager::constructRequest( QString &uri )
{
    TX_ENTRY_ARGS( "uri =" << uri)
    
    QStringList keys;
    keys << "artist" << "albumtitle" << "tracktitle" << "orderby";
    
    // "relevancy" is the selected sort order
    // sort order types can be relevancy, alltimedownloads, streetreleasedate, sortname, recentdownloads
    QStringList values;
    values << mArtist << mAlbum << mTitle << QString("relevancy");
    TX_LOG_ARGS( "Artist: " << mArtist ); 
    TX_LOG_ARGS( "Album: " << mAlbum );
    TX_LOG_ARGS( "Title: " << mTitle );
    
    uri += keyValues( keys, values );

    QUrl url(uri);
    uri = url.toEncoded();
    TX_EXIT
}

/*!
 Make a key & value pair string for querying
 */
QString MpQueryManager::keyValues( QStringList keys, QStringList values ) const
{
    TX_ENTRY
    QString str;
    if ( keys.length() != values.length() ) {
        TX_LOG_ARGS( "Error: keys length is not equal to values length" ); 
    }
    else {
        for ( int i = 0; i < keys.length(); i++ ) {
            QString tValue = values.at( i );
            if ( 0 != tValue.length() ) {
                str += keys.at( i ) + "=" + values.at( i ) + "&";
            }
        }
    }
    TX_EXIT
    return str.left( str.length() - 1 );
}

bool MpQueryManager::isLocalMusicStore()
{
    if( mMusicStore.isEmpty() ) {
        QSettings settings;
        QVariant settingsvariant = settings.value( "LocalMusicStore", "" );
        mMusicStore = settingsvariant.toString(); 
        TX_LOG_ARGS( "Got local music store from settings:" << mMusicStore );
    }
    TX_LOG_ARGS( "isLocalMusicStore = " << !mMusicStore.isEmpty() )
    return !mMusicStore.isEmpty();
}

HbIcon MpQueryManager::recommendedAlbumArt(int index) const
{
    TX_LOG_ARGS( "index = " << index )
    return mRecommendationAlbumArtsMap.value( mRecommendationAlbumArtsLink.at( index ) );
}