PageRenderTime 148ms CodeModel.GetById 40ms app.highlight 88ms RepoModel.GetById 13ms app.codeStats 0ms

/src/libtomahawk/infosystem/infoplugins/generic/chartsplugin.cpp

http://github.com/tomahawk-player/tomahawk
C++ | 572 lines | 418 code | 99 blank | 55 comment | 84 complexity | 7502fdc9f7f9642f9f67dae7effa5252 MD5 | raw file
  1/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
  2 *
  3 *   Copyright 2010-2011, Hugo Lindstr??m <hugolm84@gmail.com>
  4 *   Copyright 2011, Leo Franchi <lfranchi@kde.org>
  5 *
  6 *   Tomahawk is free software: you can redistribute it and/or modify
  7 *   it under the terms of the GNU General Public License as published by
  8 *   the Free Software Foundation, either version 3 of the License, or
  9 *   (at your option) any later version.
 10 *
 11 *   Tomahawk is distributed in the hope that it will be useful,
 12 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 13 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 14 *   GNU General Public License for more details.
 15 *
 16 *   You should have received a copy of the GNU General Public License
 17 *   along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
 18 */
 19
 20#include "chartsplugin.h"
 21
 22#include <QtCore/QDir>
 23#include <QtCore/QSettings>
 24#include <QtNetwork/QNetworkConfiguration>
 25#include <QtNetwork/QNetworkReply>
 26
 27#include "album.h"
 28#include "chartsplugin_data_p.h"
 29#include "typedefs.h"
 30#include "audio/audioengine.h"
 31#include "tomahawksettings.h"
 32#include "utils/tomahawkutils.h"
 33#include "utils/logger.h"
 34
 35#define CHART_URL "http://charts.tomahawk-player.org/"
 36//#define CHART_URL "http://localhost:8080/"
 37#include <qjson/parser.h>
 38#include <qjson/serializer.h>
 39
 40using namespace Tomahawk::InfoSystem;
 41
 42
 43ChartsPlugin::ChartsPlugin()
 44    : InfoPlugin()
 45    , m_chartsFetchJobs( 0 )
 46{
 47    /// Add resources here
 48    m_chartResources << "billboard" << "itunes" << "rdio" << "wearehunted" << "ex.fm" << "soundcloudwall.com";
 49    m_supportedGetTypes <<  InfoChart << InfoChartCapabilities;
 50
 51}
 52
 53
 54ChartsPlugin::~ChartsPlugin()
 55{
 56    tDebug( LOGVERBOSE ) << Q_FUNC_INFO;
 57}
 58
 59
 60void
 61ChartsPlugin::dataError( Tomahawk::InfoSystem::InfoRequestData requestData )
 62{
 63    emit info( requestData, QVariant() );
 64    return;
 65}
 66
 67
 68void
 69ChartsPlugin::getInfo( Tomahawk::InfoSystem::InfoRequestData requestData )
 70{
 71    //qDebug() << Q_FUNC_INFO << requestData.caller;
 72    //qDebug() << Q_FUNC_INFO << requestData.customData;
 73
 74    InfoStringHash hash = requestData.input.value< Tomahawk::InfoSystem::InfoStringHash >();
 75    bool foundSource = false;
 76
 77    switch ( requestData.type )
 78    {
 79        case InfoChart:
 80            /// We need something to check if the request is actually ment to go to this plugin
 81            if ( !hash.contains( "chart_source" ) )
 82            {
 83                tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Hash did not contain required param!";
 84                dataError( requestData );
 85                break;
 86            }
 87            else
 88            {
 89                foreach( QString resource, m_chartResources )
 90                {
 91                    if( resource == hash["chart_source"] )
 92                    {
 93                        foundSource = true;
 94                    }
 95                }
 96
 97                if( !foundSource )
 98                {
 99                    dataError( requestData );
100                    break;
101                }
102
103            }
104            fetchChart( requestData );
105            break;
106
107        case InfoChartCapabilities:
108            fetchChartCapabilities( requestData );
109            break;
110        default:
111            dataError( requestData );
112    }
113}
114
115
116void
117ChartsPlugin::fetchChart( Tomahawk::InfoSystem::InfoRequestData requestData )
118{
119
120    if ( !requestData.input.canConvert< Tomahawk::InfoSystem::InfoStringHash >() )
121    {
122        dataError( requestData );
123        return;
124    }
125
126    InfoStringHash hash = requestData.input.value< Tomahawk::InfoSystem::InfoStringHash >();
127    Tomahawk::InfoSystem::InfoStringHash criteria;
128
129    /// Each request needs to contain both a id and source
130    if ( !hash.contains( "chart_id" ) && !hash.contains( "chart_source" ) )
131    {
132        tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Hash did not contain required params!";
133        dataError( requestData );
134        return;
135
136    }
137    /// Set the criterias for current chart
138    criteria["chart_id"] = hash["chart_id"];
139    criteria["chart_source"] = hash["chart_source"];
140
141    emit getCachedInfo( criteria, 86400000, requestData );
142}
143
144void
145ChartsPlugin::fetchChartCapabilities( Tomahawk::InfoSystem::InfoRequestData requestData )
146{
147    if ( !requestData.input.canConvert< Tomahawk::InfoSystem::InfoStringHash >() )
148    {
149        tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Could not convert requestData to InfoStringHash!";
150        dataError( requestData );
151        return;
152    }
153
154    Tomahawk::InfoSystem::InfoStringHash criteria;
155    criteria[ "InfoChartCapabilities" ] = "chartsplugin";
156    emit getCachedInfo( criteria, 604800000, requestData );
157}
158
159void
160ChartsPlugin::notInCacheSlot( QHash<QString, QString> criteria, Tomahawk::InfoSystem::InfoRequestData requestData )
161{
162    switch ( requestData.type )
163    {
164        case InfoChart:
165        {
166            tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "InfoChart not in cache! Fetching...";
167
168            /// Fetch the chart, we need source and id
169            QUrl url = QUrl( QString( CHART_URL "source/%1/chart/%2" ).arg( criteria["chart_source"] ).arg( criteria["chart_id"] ) );
170            tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Getting chart url" << url;
171
172            QNetworkReply* reply = TomahawkUtils::nam()->get( QNetworkRequest( url ) );
173            reply->setProperty( "requestData", QVariant::fromValue< Tomahawk::InfoSystem::InfoRequestData >( requestData ) );
174
175            connect( reply, SIGNAL( finished() ), SLOT( chartReturned() ) );
176            return;
177
178        }
179
180        case InfoChartCapabilities:
181        {
182
183            tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "InfoChartCapabilities not in cache! Fetching...";
184            // we never need to re-fetch
185            if ( !m_allChartsMap.isEmpty() )
186                return;
187
188            /// Then get each chart from resource
189
190            if ( !m_chartResources.isEmpty() && m_allChartsMap.isEmpty() )
191            {
192                tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "InfoChart fetching possible resources";
193                foreach ( QString resource, m_chartResources )
194                {
195                    QUrl url = QUrl( QString( CHART_URL "source/%1" ).arg( resource ) );
196                    QNetworkReply* reply = TomahawkUtils::nam()->get( QNetworkRequest( url ) );
197                    reply->setProperty( "chart_resource", resource);
198
199                    tDebug() << "fetching:" << url;
200                    connect( reply, SIGNAL( finished() ), SLOT( chartTypes() ) );
201
202                    m_chartsFetchJobs++;
203                }
204            }
205
206            if ( m_chartsFetchJobs > 0 )
207            {
208                tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "InfoChartCapabilities still fetching!";
209                m_cachedRequests.append( requestData );
210                return;
211            }
212
213            emit info( requestData, m_allChartsMap );
214            return;
215        }
216
217        default:
218        {
219            tLog() << Q_FUNC_INFO << "Couldn't figure out what to do with this type of request after cache miss";
220            emit info( requestData, QVariant() );
221            return;
222        }
223    }
224}
225
226
227void
228ChartsPlugin::chartTypes()
229{
230    /// Get possible chart type for specificChartsPlugin: InfoChart types returned chart source
231    tDebug( LOGVERBOSE )  << "Got chart type result";
232    QNetworkReply* reply = qobject_cast<QNetworkReply*>( sender() );
233
234
235    if ( reply->error() == QNetworkReply::NoError )
236    {
237        QJson::Parser p;
238        bool ok;
239        const QVariantMap res = p.parse( reply, &ok ).toMap();
240        const QVariantMap chartObjs = res.value( "charts" ).toMap();
241
242        if ( !ok )
243        {
244            tLog() << "Failed to parse resources" << p.errorString() << "On line" << p.errorLine();
245
246            return;
247        }
248
249        /// Got types, append!
250        const QString source = res.value( "source" ).toString();
251
252        // We'll populate charts with the data from the server
253        QVariantMap charts;
254        QString chartName;
255        QStringList defaultChain;
256        if ( source == "wearehunted" || source == "itunes" )
257        {
258            // Some charts can have an extra param, itunes has geo, WAH has emerging/mainstream
259            // Itunes has geographic-area based charts. So we build a breadcrumb of
260            // ITunes - Country - Albums - Top Chart Type
261            //                  - Tracks - Top Chart Type
262            // WeAreHunted has Mainstream/Emerging
263            // WeAreHunted - Type - Artists - Chart Type
264            //                    - Tracks  - Chart Type
265            QHash< QString, QVariantMap > extraType;
266            foreach( const QVariant& chartObj, chartObjs.values() )
267            {
268                if( !chartObj.toMap().isEmpty() )
269                {
270                    const QVariantMap chart = chartObj.toMap();
271                    const QString id = chart.value( "id" ).toString();
272                    const QString geo = chart.value( "geo" ).toString();
273                    QString name = chart.value( "genre" ).toString();
274                    const QString type = chart.value( "type" ).toString();
275                    const bool isDefault = ( chart.contains( "default" ) && chart[ "default" ].toInt() == 1 );
276
277                    QString extra;
278                    if( !geo.isEmpty() )
279                    {
280
281                        if ( !m_cachedCountries.contains( geo ) )
282                        {
283                            QLocale l( QString( "en_%1" ).arg( geo ) );
284                            extra = Tomahawk::CountryUtils::fullCountryFromCode( geo );
285
286                            for ( int i = 1; i < extra.size(); i++ )
287                            {
288                                if ( extra.at( i ).isUpper() )
289                                {
290                                    extra.insert( i, " " );
291                                    i++;
292                                }
293                            }
294                            m_cachedCountries[ geo ] = extra;
295                        }
296                        else
297                            extra = m_cachedCountries[ geo ];
298                    }
299                    else
300                        extra = chart.value( "extra" ).toString();
301
302                    if ( name.isEmpty() ) // not a specific chart, an all chart
303                        name = tr( "Top Overall" );
304
305                    InfoStringHash c;
306                    c[ "id" ] = id;
307                    c[ "label" ] = name;
308                    c[ "type" ] = "album";
309                    if ( isDefault )
310                        c[ "default" ] = "true";
311
312                    QList< Tomahawk::InfoSystem::InfoStringHash > extraTypeData = extraType[ extra ][ type ].value< QList< Tomahawk::InfoSystem::InfoStringHash > >();
313                    extraTypeData.append( c );
314                    extraType[ extra ][ type ] = QVariant::fromValue< QList< Tomahawk::InfoSystem::InfoStringHash > >( extraTypeData );
315
316                    if ( isDefault )
317                    {
318                        defaultChain.clear();
319                        defaultChain.append( extra );
320                        defaultChain.append( type );
321                        defaultChain.append( name );
322                    }
323                }
324                foreach( const QString& c, extraType.keys() )
325                {
326                    charts[ c ] = extraType[ c ];
327//                    tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "extraType has types:" << c;
328                }
329                if( source == "itunes" ){
330                    chartName = "iTunes";
331                }
332
333                if( source == "wearehunted" ){
334                    chartName = "WeAreHunted";
335                }
336            }
337
338        }
339        else
340        {
341            // We'll just build:
342            // [Source] - Album - Chart Type
343            // [Source] - Track - Chart Type
344            QList< InfoStringHash > albumCharts;
345            QList< InfoStringHash > trackCharts;
346            QList< InfoStringHash > artistCharts;
347
348            foreach( const QVariant& chartObj, chartObjs.values() )
349            {
350                if( !chartObj.toMap().isEmpty() ){
351                    const QVariantMap chart = chartObj.toMap();
352                    const QString type = chart.value( "type" ).toString();
353                    const bool isDefault = ( chart.contains( "default" ) && chart[ "default" ].toInt() == 1 );
354
355                    InfoStringHash c;
356                    c[ "id" ] = chart.value( "id" ).toString();
357                    if( chart.value( "genre").isValid() )
358                        c[ "label" ] = chart.value( "genre" ).toString();
359                    else
360                        c[ "label" ] = chart.value( "name" ).toString();
361                    if ( isDefault )
362                        c[ "default" ] = "true";
363
364                    if ( type == "Album" )
365                    {
366                        c[ "type" ] = "album";
367                        albumCharts.append( c );
368                    }
369                    else if ( type == "Track" )
370                    {
371                        c[ "type" ] = "tracks";
372                        trackCharts.append( c );
373
374                    }else if ( type == "Artist" )
375                    {
376                        c[ "type" ] = "artists";
377                        artistCharts.append( c );
378
379                    }
380
381                    if ( isDefault )
382                    {
383                        defaultChain.clear();
384                        defaultChain.append( type + "s" ); //UGLY but it's plural to the user, see below
385                        defaultChain.append( c[ "label" ] );
386                    }
387                }
388                if( !artistCharts.isEmpty() )
389                    charts.insert( tr( "Artists" ), QVariant::fromValue< QList< Tomahawk::InfoSystem::InfoStringHash > >( artistCharts ) );
390                if( !albumCharts.isEmpty() )
391                    charts.insert( tr( "Albums" ), QVariant::fromValue< QList< Tomahawk::InfoSystem::InfoStringHash > >( albumCharts ) );
392                if( !trackCharts.isEmpty() )
393                    charts.insert( tr( "Tracks" ), QVariant::fromValue< QList< Tomahawk::InfoSystem::InfoStringHash > >( trackCharts ) );
394
395                /// @note For displaying purposes, upper the first letter
396                /// @note Remeber to lower it when fetching this!
397                chartName = source;
398                chartName[0] = chartName[0].toUpper();
399            }
400        }
401
402        /// Add the possible charts and its types to breadcrumb
403//         tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "ADDING CHART TYPE TO CHARTS:" << chartName;
404        QVariantMap defaultMap = m_allChartsMap.value( "defaults" ).value< QVariantMap >();
405        defaultMap[ source ] = defaultChain;
406        m_allChartsMap[ "defaults" ] = defaultMap;
407        m_allChartsMap[ "defaultSource" ] = "itunes";
408        m_allChartsMap.insert( chartName , QVariant::fromValue< QVariantMap >( charts ) );
409
410    }
411    else
412    {
413        tLog() << "Error fetching charts:" << reply->errorString();
414    }
415
416    m_chartsFetchJobs--;
417    if ( !m_cachedRequests.isEmpty() && m_chartsFetchJobs == 0 )
418    {
419        foreach ( InfoRequestData request, m_cachedRequests )
420        {
421            emit info( request, m_allChartsMap );
422            // update cache
423            Tomahawk::InfoSystem::InfoStringHash criteria;
424            criteria[ "InfoChartCapabilities" ] = "chartsplugin";
425            emit updateCache( criteria, 604800000, request.type, m_allChartsMap );
426        }
427        m_cachedRequests.clear();
428    }
429
430}
431
432void
433ChartsPlugin::chartReturned()
434{
435
436    /// Chart request returned something! Woho
437    QNetworkReply* reply = qobject_cast<QNetworkReply*>( sender() );
438    QVariantMap returnedData;
439
440    if ( reply->error() == QNetworkReply::NoError )
441    {
442        QJson::Parser p;
443        bool ok;
444        QVariantMap res = p.parse( reply, &ok ).toMap();
445
446        if ( !ok )
447        {
448            tLog() << "Failed to parse json from chart lookup:" << p.errorString() << "On line" << p.errorLine();
449            return;
450        }
451
452        /// SO we have a result, parse it!
453        QVariantList chartResponse = res.value( "list" ).toList();
454        QList< Tomahawk::InfoSystem::InfoStringHash > top_tracks;
455        QList< Tomahawk::InfoSystem::InfoStringHash > top_albums;
456        QStringList top_artists;
457
458        /// Deside what type, we need to handle it differently
459        /// @todo: We allready know the type, append it to breadcrumb hash
460
461        if( res.value( "type" ).toString() == "Album" )
462            setChartType( Album );
463        else if( res.value( "type" ).toString() == "Track" )
464            setChartType( Track );
465        else if( res.value( "type" ).toString() == "Artist" )
466            setChartType( Artist );
467        else
468            setChartType( None );
469
470
471//         qDebug() << "Got chart returned!" << res;
472        foreach ( QVariant chartR, chartResponse )
473        {
474            QString title, artist, album;
475            QVariantMap chartMap = chartR.toMap();
476
477            if ( !chartMap.isEmpty() )
478            {
479
480                title = chartMap.value( "track" ).toString();
481                album = chartMap.value( "album" ).toString();
482                artist = chartMap.value( "artist" ).toString();
483                /// Maybe we can use rank later on, to display something nice
484                /// rank = chartMap.value( "rank" ).toString();
485
486                if ( chartType() == Album )
487                {
488
489                    if ( album.isEmpty() && artist.isEmpty() ) // don't have enough...
490                    {
491                        tDebug( LOGVERBOSE )  << "Didn't get an artist and album name from chart, not enough to build a query on. Aborting" << title << album << artist;
492
493                    }
494                    else
495                    {
496
497                        Tomahawk::InfoSystem::InfoStringHash pair;
498                        pair["artist"] = artist;
499                        pair["album"] = album;
500                        top_albums.append( pair );
501
502                    }
503                }
504
505                else if ( chartType() == Track )
506                {
507
508                    if ( title.isEmpty() && artist.isEmpty() ) // don't have enough...
509                    {
510                        tDebug( LOGVERBOSE )  << "Didn't get an artist and track name from charts, not enough to build a query on. Aborting" << title << artist << album;
511
512                    }
513                    else
514                    {
515
516                        Tomahawk::InfoSystem::InfoStringHash pair;
517                        pair["artist"] = artist;
518                        pair["track"] = title;
519                        top_tracks.append( pair );
520
521                    }
522                }else if( chartType() == Artist )
523                {
524                    if ( artist.isEmpty() ) // don't have enough...
525                    {
526                        tDebug( LOGVERBOSE )  << "Didn't get an artist from charts, not enough to build a query on. Aborting" << artist;
527
528                    }
529                    else
530                    {
531                        top_artists.append( artist );
532                    }
533
534                }
535            }
536        }
537
538        if( chartType() == Artist )
539        {
540            tDebug() << "ChartsPlugin:" << "\tgot " << top_artists.size() << " artists";
541            returnedData[ "artists" ] = QVariant::fromValue< QStringList >( top_artists );
542            returnedData[ "type" ] = "artists";
543        }
544
545        if( chartType() == Track )
546        {
547            tDebug() << "ChartsPlugin:" << "\tgot " << top_tracks.size() << " tracks";
548            returnedData[ "tracks" ] = QVariant::fromValue< QList< Tomahawk::InfoSystem::InfoStringHash > >( top_tracks );
549            returnedData[ "type" ] = "tracks";
550        }
551
552        if( chartType() == Album )
553        {
554            tDebug() << "ChartsPlugin:" << "\tgot " << top_albums.size() << " albums";
555            returnedData[ "albums" ] = QVariant::fromValue< QList< Tomahawk::InfoSystem::InfoStringHash > >( top_albums );
556            returnedData[ "type" ] = "albums";
557        }
558
559        Tomahawk::InfoSystem::InfoRequestData requestData = reply->property( "requestData" ).value< Tomahawk::InfoSystem::InfoRequestData >();
560
561        emit info( requestData, returnedData );
562        // update cache
563        Tomahawk::InfoSystem::InfoStringHash criteria;
564        Tomahawk::InfoSystem::InfoStringHash origData = requestData.input.value< Tomahawk::InfoSystem::InfoStringHash >();
565        criteria[ "chart_id" ] = origData[ "chart_id" ];
566        criteria[ "chart_source" ] = origData[ "chart_source" ];
567        emit updateCache( criteria, 86400000, requestData.type, returnedData );
568    }
569    else
570        tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Network error in fetching chart:" << reply->url().toString();
571
572}