File indexing completed on 2025-01-05 04:26:56

0001 /****************************************************************************************
0002  * Copyright (c) 2007 Maximilian Kossick <maximilian.kossick@googlemail.com>            *
0003  * Copyright (c) 2008 Leo Franchi <lfranchi@kde.org>                                    *
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 #ifndef AMAROK_LASTFMMETA_P_H
0019 #define AMAROK_LASTFMMETA_P_H
0020 
0021 #include "core/support/Debug.h"
0022 
0023 #include "amarokconfig.h"
0024 #include "core/meta/Meta.h"
0025 #include "core/support/Amarok.h"
0026 #include "core-impl/support/TagStatisticsStore.h"
0027 
0028 #include <KIO/Job>
0029 
0030 #include <QDir>
0031 #include <QImage>
0032 #include <QList>
0033 #include <QPixmap>
0034 #include <QStandardPaths>
0035 #include <QStringList>
0036 
0037 #include <Track.h>
0038 #include <ws.h>
0039 #include <RadioTuner.h>
0040 #include <XmlQuery.h>
0041 
0042 namespace LastFm
0043 {
0044 
0045 class Track::Private : public QObject
0046 {
0047     Q_OBJECT
0048 
0049     public:
0050         Track *t;
0051         lastfm::Track lastFmTrack; // this is how we love, ban, etc
0052         QUrl trackPath;
0053         QUrl lastFmUri;
0054 
0055         QImage albumArt;
0056         QString artist;
0057         QString album;
0058         QString track;
0059         qint64 length;
0060 
0061         //not sure what these are for but they exist in the LastFmBundle
0062         QString albumUrl;
0063         QString artistUrl;
0064         QString trackUrl;
0065         QString imageUrl;
0066 
0067         Meta::ArtistPtr artistPtr;
0068         Meta::AlbumPtr albumPtr;
0069         Meta::GenrePtr genrePtr;
0070         Meta::ComposerPtr composerPtr;
0071         Meta::YearPtr yearPtr;
0072 
0073         QNetworkReply* trackFetch;
0074         QNetworkReply* wsReply;
0075 
0076         Meta::StatisticsPtr statsStore;
0077         uint currentTrackStartTime;
0078 
0079     public:
0080         Private()
0081             : lastFmUri( QUrl() )
0082             , currentTrackStartTime( 0 )
0083         {
0084             artist = QString ( "Last.fm" );
0085         }
0086 
0087         ~Private() override
0088         {
0089         }
0090 
0091         void notifyObservers();
0092 
0093         void setTrackInfo( const lastfm::Track &trackInfo )
0094         {
0095             DEBUG_BLOCK
0096             bool newTrackInfo = artist != trackInfo.artist() ||
0097                                 album != trackInfo.album() ||
0098                                 track != trackInfo.title();
0099 
0100 
0101             lastFmTrack = trackInfo;
0102             artist = trackInfo.artist();
0103             album = trackInfo.album();
0104             track = trackInfo.title();
0105             length = trackInfo.duration() * 1000;
0106             trackPath = trackInfo.url();
0107 
0108             // need to reset other items
0109             albumUrl = "";
0110             trackUrl = "";
0111             albumArt = QImage();
0112 
0113             if( newTrackInfo )
0114             {
0115                 statsStore = new TagStatisticsStore( t );
0116                 currentTrackStartTime = QDateTime::currentDateTimeUtc().toSecsSinceEpoch();
0117             }
0118 
0119             notifyObservers();
0120 
0121             if( !trackInfo.isNull() )
0122             {
0123                 QMap< QString, QString > params;
0124                 params[ "method" ] = "track.getInfo";
0125                 params[ "artist" ] = artist;
0126                 params[ "track" ] = track;
0127 
0128                 m_userFetch = lastfm::ws::post( params );
0129 
0130                 connect( m_userFetch, SIGNAL( finished() ), SLOT( requestResult() ) );
0131             }
0132         }
0133 
0134     public Q_SLOTS:
0135         void requestResult( )
0136         {
0137             if( !m_userFetch )
0138                 return;
0139             if( m_userFetch->error() == QNetworkReply::NoError )
0140             {
0141                 lastfm::XmlQuery lfm;
0142                 if( lfm.parse( m_userFetch->readAll() ) )
0143                 {
0144                     albumUrl = lfm[ "track" ][ "album" ][ "url" ].text();
0145                     trackUrl = lfm[ "track" ][ "url" ].text();
0146                     artistUrl = lfm[ "track" ][ "artist" ][ "url" ].text();
0147 
0148                     notifyObservers();
0149 
0150                     imageUrl = lfm[ "track" ][ "album" ][ "image size=large" ].text();
0151 
0152                     if( !imageUrl.isEmpty() )
0153                     {
0154                         KIO::Job* job = KIO::storedGet( QUrl( imageUrl ), KIO::Reload, KIO::HideProgressInfo );
0155                         connect( job, SIGNAL( result( KJob* ) ), this, SLOT( fetchImageFinished( KJob* ) ) );
0156                     }
0157                 }
0158                 else
0159                 {
0160                     debug() << "Got exception in parsing from last.fm:" << lfm.parseError().message();
0161                     return;
0162                 }
0163             }
0164 
0165         }
0166 
0167         void fetchImageFinished( KJob* job )
0168         {
0169             if( job->error() == 0 )
0170             {
0171                 const int size = 100;
0172 
0173                 QImage img = QImage::fromData( static_cast<KIO::StoredTransferJob*>( job )->data() );
0174                 if( !img.isNull() )
0175                 {
0176                     img.scaled( size, size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
0177 
0178                     albumArt = img;
0179                 }
0180                 else
0181                     albumArt = QImage();
0182             }
0183             else
0184             {
0185                 //use default image
0186                 albumArt = QImage();
0187             }
0188             notifyObservers();
0189         }
0190 
0191     private:
0192         QNetworkReply* m_userFetch;
0193 
0194 };
0195 
0196 // internal helper classes
0197 
0198 class LastFmArtist : public Meta::Artist
0199 {
0200 public:
0201     explicit LastFmArtist( Track::Private *dptr )
0202         : Meta::Artist()
0203         , d( dptr )
0204     {}
0205 
0206     Meta::TrackList tracks() override
0207     {
0208         return Meta::TrackList();
0209     }
0210 
0211     QString name() const override
0212     {
0213         if( d )
0214             return d->artist;
0215         return QStringLiteral( "Last.fm" );
0216     }
0217 
0218     Track::Private * const d;
0219 
0220     friend class Track::Private;
0221 };
0222 
0223 class LastFmAlbum : public Meta::Album
0224 {
0225 public:
0226     explicit LastFmAlbum( Track::Private *dptr )
0227         : Meta::Album()
0228         , d( dptr )
0229     {}
0230 
0231     bool isCompilation() const override { return false; }
0232     bool hasAlbumArtist() const override { return false; }
0233     Meta::ArtistPtr albumArtist() const override { return Meta::ArtistPtr(); }
0234 
0235     Meta::TrackList tracks() override
0236     {
0237         return Meta::TrackList();
0238     }
0239 
0240     QString name() const override
0241     {
0242         if( d )
0243             return d->album;
0244         return QString();
0245     }
0246 
0247     QImage image( int size ) const override
0248     {
0249         if( !d || d->albumArt.isNull() )
0250         {
0251             //return Meta::Album::image( size, withShadow );
0252             //TODO implement shadow
0253             //TODO improve this
0254 
0255             if ( size <= 1 )
0256                 size = 100;
0257             QString sizeKey = QString::number( size ) + '@';
0258 
0259             QImage image;
0260             QDir cacheCoverDir = QDir( Amarok::saveLocation( "albumcovers/cache/" ) );
0261             if( cacheCoverDir.exists( sizeKey + "lastfm-default-cover.png" ) )
0262                 image = QImage( cacheCoverDir.filePath( sizeKey + "lastfm-default-cover.png" ) );
0263             else
0264             {
0265                 QImage orgImage = QImage( QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/images/lastfm-default-cover.png" ) ); //optimize this!
0266                 //scaled() does not change the original image but returns a scaled copy
0267                 image = orgImage.scaled( size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation );
0268                 image.save( cacheCoverDir.filePath( sizeKey + "lastfm-default-cover.png" ), "PNG" );
0269             }
0270 
0271             return image;
0272         }
0273 
0274 
0275         if( d->albumArt.width() != size && size > 0 )
0276             return d->albumArt.scaled( size, size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
0277         return d->albumArt;
0278     }
0279 
0280     QUrl imageLocation( int size ) override
0281     {
0282         Q_UNUSED( size );
0283         if( d && !d->imageUrl.isEmpty() )
0284             return QUrl( d->imageUrl );
0285         return QUrl();
0286     }
0287 
0288     // return true since we handle our own fetching
0289     bool hasImage( int size = 1 ) const override { Q_UNUSED( size ); return true; }
0290 
0291     Track::Private * const d;
0292 
0293     friend class Track::Private;
0294 };
0295 
0296 class LastFmGenre : public Meta::Genre
0297 {
0298 public:
0299     explicit LastFmGenre( Track::Private *dptr )
0300         : Meta::Genre()
0301         , d( dptr )
0302     {}
0303 
0304     QString name() const override
0305     {
0306         return QString();
0307     }
0308 
0309     Meta::TrackList tracks() override
0310     {
0311         return Meta::TrackList();
0312     }
0313 
0314     Track::Private * const d;
0315 
0316     friend class Track::Private;
0317 };
0318 
0319 class LastFmComposer : public Meta::Composer
0320 {
0321 public:
0322     explicit LastFmComposer( Track::Private *dptr )
0323         : Meta::Composer()
0324         , d( dptr )
0325     {}
0326 
0327     QString name() const override
0328     {
0329         return QString();
0330     }
0331 
0332     Meta::TrackList tracks() override
0333     {
0334         return Meta::TrackList();
0335     }
0336 
0337     Track::Private * const d;
0338 
0339     friend class Track::Private;
0340 };
0341 
0342 class LastFmYear : public Meta::Year
0343 {
0344 public:
0345     explicit LastFmYear( Track::Private *dptr )
0346         : Meta::Year()
0347         , d( dptr )
0348     {}
0349 
0350     QString name() const override
0351     {
0352         return QString();
0353     }
0354 
0355     Meta::TrackList tracks() override
0356     {
0357         return Meta::TrackList();
0358     }
0359 
0360     Track::Private * const d;
0361 
0362     friend class Track::Private;
0363 };
0364 
0365 void
0366 Track::Private::notifyObservers()
0367 {
0368     // TODO: only notify what actually has changed
0369     t->notifyObservers();
0370     static_cast<LastFmAlbum *>( t->album().data() )->notifyObservers();
0371     static_cast<LastFmArtist *>( t->artist().data() )->notifyObservers();
0372 }
0373 
0374 }
0375 
0376 #endif