File indexing completed on 2025-01-19 04:24:14

0001 /****************************************************************************************
0002  * Copyright (c) 2009 Simon Esneault <simon.esneault@gmail.com>                         *
0003  * Copyright (c) 2010 Daniel Faust <hessijames@gmail.com>                               *
0004  *                                                                                      *
0005  * This program is free software; you can redistribute it and/or modify it under        *
0006  * the terms of the GNU General Public License as published by the Free Software        *
0007  * Foundation; either version 2 of the License, or (at your option) any later           *
0008  * version.                                                                             *
0009  *                                                                                      *
0010  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0011  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0012  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0013  *                                                                                      *
0014  * You should have received a copy of the GNU General Public License along with         *
0015  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0016  ****************************************************************************************/
0017 
0018 #define DEBUG_PREFIX "LabelsEngine"
0019 
0020 #include "LabelsEngine.h"
0021 
0022 #include "EngineController.h"
0023 #include "context/ContextObserver.h"
0024 #include "context/ContextView.h"
0025 #include "core/collections/Collection.h"
0026 #include "core/collections/QueryMaker.h"
0027 #include "core/meta/Meta.h"
0028 #include "core/support/Debug.h"
0029 #include "core-impl/collections/support/CollectionManager.h"
0030 
0031 #include <KIO/Job>
0032 #include <KLocalizedString>
0033 
0034 #include <QDomDocument>
0035 
0036 using namespace Context;
0037 
0038 LabelsEngine::LabelsEngine( QObject *parent, const QList<QVariant> &args )
0039         : DataEngine( parent )
0040         , ContextObserver( ContextView::self() )
0041 {
0042     Q_UNUSED( args )
0043     m_sources << "lastfm" ;
0044 
0045     m_timeoutTimer.setInterval( 10000 );
0046     m_timeoutTimer.setSingleShot( true );
0047     connect( &m_timeoutTimer, SIGNAL(timeout()), this, SLOT(timeout()) );
0048 
0049     EngineController *engine = The::engineController();
0050 
0051     connect( engine, SIGNAL(trackChanged(Meta::TrackPtr)),
0052              this, SLOT(update()) );
0053     connect( engine, SIGNAL(trackMetadataChanged(Meta::TrackPtr)),
0054              this, SLOT(update()) );
0055 }
0056 
0057 LabelsEngine::~LabelsEngine()
0058 {
0059     DEBUG_BLOCK
0060 }
0061 
0062 QStringList 
0063 LabelsEngine::sources() const
0064 {
0065     return m_sources;
0066 }
0067 
0068 bool 
0069 LabelsEngine::sourceRequestEvent( const QString &name )
0070 {
0071     DEBUG_BLOCK
0072 
0073     Collections::Collection *coll = CollectionManager::instance()->primaryCollection();
0074     if( coll )
0075     {
0076         Collections::QueryMaker *qm = coll->queryMaker();
0077         qm->setAutoDelete( true );
0078         qm->setQueryType( Collections::QueryMaker::Label );
0079         m_allLabels.clear();
0080 
0081         connect( qm, SIGNAL(newResultReady(Meta::LabelList)),
0082                 SLOT(resultReady(Meta::LabelList)), Qt::QueuedConnection );
0083         connect( qm, SIGNAL(queryDone()), SLOT(dataQueryDone()) );
0084 
0085         qm->run();
0086     }
0087 
0088     update( name == "reload" );
0089 
0090     return true;
0091 }
0092 
0093 void
0094 LabelsEngine::resultReady( const Meta::LabelList &labels )
0095 {
0096     foreach( const Meta::LabelPtr &label, labels )
0097     {
0098         if( !label->name().isEmpty() )
0099             m_allLabels << label->name();
0100     }
0101 }
0102 
0103 void
0104 LabelsEngine::dataQueryDone()
0105 {
0106     DEBUG_BLOCK
0107     QVariant varAll;
0108     varAll.setValue< QStringList >( m_allLabels );
0109     setData( "labels", "all", varAll );
0110 }
0111 
0112 void
0113 LabelsEngine::update( bool reload )
0114 {
0115     Meta::TrackPtr track = The::engineController()->currentTrack();
0116 
0117     if( !track )
0118     {
0119         removeAllData( "labels" );
0120         m_artist.clear();
0121         m_title.clear();
0122         m_album.clear();
0123         m_userLabels.clear();
0124         m_webLabels.clear();
0125         setData( "labels", "state", "stopped" );
0126         return;
0127     }
0128 
0129     const QString title = track->name();
0130     Meta::ArtistPtr artist = track->artist();
0131     if( !artist )
0132     {
0133         setData( "labels", "message", i18n( "No labels found on Last.fm" ) );
0134         debug() << "track has no artist, returning";
0135         return;
0136     }
0137 
0138     QStringList userLabels;
0139 
0140     foreach( const Meta::LabelPtr &label, track->labels() )
0141         userLabels += label->name();
0142 
0143     userLabels.sort();
0144     m_userLabels.sort();
0145 
0146     // check what changed
0147     if( !reload && artist->name() == m_artist && title == m_title && userLabels == m_userLabels )
0148     {
0149         // nothing important changed
0150         return;
0151     }
0152     else if( !reload && artist->name() == m_artist && title == m_title )
0153     {
0154         // only the labels changed - no download necessary
0155         debug() << "only the labels changed - no download necessary";
0156         QVariant varUser;
0157         varUser.setValue< QStringList >( userLabels );
0158         setData( "labels", "user", varUser );
0159         m_userLabels = userLabels;
0160         return;
0161     }
0162 
0163     removeAllData( "labels" );
0164     setData( "labels", "state", "started" );
0165 
0166     m_artist = artist->name();
0167     m_title = title;
0168     if( track->album() )
0169         m_album = track->album()->name();
0170     else
0171         m_album.clear();
0172     
0173     m_userLabels = userLabels;
0174     m_webLabels.clear();
0175 
0176     QVariant varUser;
0177     varUser.setValue< QStringList >( m_userLabels );
0178     setData( "labels", "user", varUser );
0179 
0180     m_try = 0;
0181     fetchLastFm();
0182 }
0183 
0184 void
0185 LabelsEngine::fetchLastFm()
0186 {
0187     QStringList separators;
0188     QString currentArtist;
0189     QString currentTitle;
0190 
0191     if( m_title.isEmpty() || m_artist.isEmpty() )
0192     {
0193         // stop timeout timer
0194         m_timeoutTimer.stop();
0195         setData( "labels", "message", i18n( "No labels found on Last.fm" ) );
0196         debug() << "current track is invalid, returning";
0197         return;
0198     }
0199     
0200     if( m_try == 0 )
0201     {
0202         currentArtist = m_artist;
0203         currentTitle = m_title;
0204         m_timeoutTimer.start();
0205     }
0206     else if( m_try == 1 )
0207     {
0208         currentArtist = m_artist;
0209         currentTitle = m_title;
0210         separators.clear();
0211         separators << " (" << " [" << " - " << " featuring " << " feat. " << " feat " << " ft. " << " ft " << "/";
0212         foreach( const QString &separator, separators )
0213         {
0214             if( m_title.contains(separator,Qt::CaseInsensitive) )
0215             {
0216                 currentTitle = m_title.left( m_title.indexOf(separator,0,Qt::CaseInsensitive) );
0217                 break;
0218             }
0219         }
0220         if ( currentTitle == m_title )
0221         {
0222             debug() << "try 2: title is the same, retrying";
0223             m_try++;
0224             fetchLastFm();
0225             return;
0226         }
0227     }
0228     else if( m_try == 2 )
0229     {
0230         currentArtist = m_artist;
0231         currentTitle = m_title;
0232         separators.clear();
0233         separators << " vs. " << " vs " << " featuring " << " feat. " << " feat " << " ft. " << " ft " << ", " << " and " << " & " << "/";
0234         foreach( const QString &separator, separators )
0235         {
0236             if( m_artist.contains(separator,Qt::CaseInsensitive) )
0237             {
0238                 currentArtist = m_artist.left( m_artist.indexOf(separator,0,Qt::CaseInsensitive) );
0239                 break;
0240             }
0241         }
0242         separators.clear();
0243         separators << " (" << " [" << " - " << " featuring " << " feat. " << " feat " << " ft. " << " ft " << "/";
0244         foreach( const QString &separator, separators )
0245         {
0246             if( m_title.contains(separator,Qt::CaseInsensitive) )
0247             {
0248                 currentTitle = m_title.left( m_title.indexOf(separator,0,Qt::CaseInsensitive) );
0249                 break;
0250             }
0251         }
0252         if( currentArtist == m_artist ) // the title got modified the same way as on the last try
0253         {
0254             // stop timeout timer
0255             m_timeoutTimer.stop();
0256             setData( "labels", "message", i18n( "No labels found on Last.fm" ) );
0257             debug() << "try 3: artist and title are the same, returning";
0258             return;
0259         }
0260     }
0261     else
0262     {
0263         // shouldn't happen
0264         // stop timeout timer
0265         m_timeoutTimer.stop();
0266         setData( "labels", "message", i18n( "No labels found on Last.fm" ) );
0267         debug() << "try > 2, returning";
0268         return;
0269     }
0270 
0271     if( !currentArtist.isEmpty() && !currentTitle.isEmpty() )
0272     {
0273         setData( "labels", "message", "fetching");
0274         // send the artist and title actually used for searching labels
0275         setData( "labels", "artist", currentArtist );
0276         setData( "labels", "title", currentTitle );
0277         setData( "labels", "album", m_album );
0278 
0279         // Query lastfm
0280         QUrl lastFmUrl;
0281         lastFmUrl.setScheme( "http" );
0282         lastFmUrl.setHost( "ws.audioscrobbler.com" );
0283         lastFmUrl.setPath( "/2.0/" );
0284         lastFmUrl.addQueryItem( "method", "track.gettoptags" );
0285         lastFmUrl.addQueryItem( "api_key", "402d3ca8e9bc9d3cf9b85e1202944ca5" );
0286         lastFmUrl.addQueryItem( "artist", currentArtist.toLocal8Bit() );
0287         lastFmUrl.addQueryItem( "track", currentTitle.toLocal8Bit() );
0288         m_lastFmUrl = lastFmUrl;
0289         
0290         QNetworkRequest req( lastFmUrl );
0291 //         req.setAttribute( QNetworkRequest::ConnectionEncryptedAttribute, QNetworkRequest::AlwaysNetwork );
0292         The::networkAccessManager()->get( req );
0293         The::networkAccessManager()->getData( lastFmUrl, this,
0294             SLOT(resultLastFm(QUrl,QByteArray,NetworkAccessManagerProxy::Error)) );
0295     }
0296     else
0297     {
0298         // stop timeout timer
0299         m_timeoutTimer.stop();
0300         setData( "labels", "message", i18n( "No labels found on Last.fm" ) );
0301         debug() << "artist or track empty";
0302     }
0303 }
0304 
0305 void LabelsEngine::resultLastFm( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e )
0306 {
0307     DEBUG_BLOCK;
0308 
0309     if( m_lastFmUrl != url )
0310     {
0311         debug() << "urls not matching, returning";
0312         return;
0313     }
0314 
0315     if( e.code != QNetworkReply::NoError )
0316     {
0317         // stop timeout timer
0318         m_timeoutTimer.stop();
0319         setData( "labels", "message", i18n( "Unable to retrieve from Last.fm" ) );
0320         debug() << "Unable to retrieve last.fm information: " << e.description;
0321         return;
0322     }
0323 
0324     QDomDocument xmlDoc;
0325     xmlDoc.setContent( data );
0326     const QDomElement topElement = xmlDoc.elementsByTagName("toptags").at(0).toElement();
0327     const QDomNodeList xmlNodeList = topElement.elementsByTagName( "tag" );
0328 
0329     for( uint i = 0; i < xmlNodeList.length(); i++ )
0330     {
0331         // Get all the information
0332         const QDomElement nd = xmlNodeList.at( i ).toElement();
0333         const QDomElement nameElement = nd.elementsByTagName("name").at(0).toElement();
0334         const QString name = nameElement.text().toLower();
0335         const QDomElement countElement = nd.elementsByTagName("count").at(0).toElement();
0336         const int count = countElement.text().toInt();
0337         m_webLabels.insert( name, count );
0338     }
0339 
0340     if( m_webLabels.isEmpty() )
0341     {
0342         if( m_try < 2 )
0343         {
0344             m_try++;
0345             fetchLastFm();
0346         }
0347         else
0348         {
0349             // stop timeout timer
0350             m_timeoutTimer.stop();
0351             setData( "labels", "message", i18n( "No labels found on Last.fm" ) );
0352         }
0353     }
0354     else
0355     {
0356         // remove previous message
0357         removeData( "labels", "message" );
0358         // stop timeout timer
0359         m_timeoutTimer.stop();
0360 
0361         QVariant varWeb;
0362         varWeb.setValue< QMap< QString, QVariant > > ( m_webLabels );
0363         setData( "labels", "web", varWeb );
0364     }
0365 }
0366 
0367 void LabelsEngine::timeout()
0368 {
0369     setData( "labels", "message", i18n( "No connection to Last.fm" ) );
0370 }
0371 
0372 
0373