File indexing completed on 2025-03-09 04:24:06

0001 /****************************************************************************************
0002  * Copyright (c) 2009 Simon Esneault <simon.esneault@gmail.com>                         *
0003  *                                                                                      *
0004  * This program is free software; you can redistribute it and/or modify it under        *
0005  * the terms of the GNU General Public License as published by the Free Software        *
0006  * Foundation; either version 2 of the License, or (at your option) any later           *
0007  * version.                                                                             *
0008  *                                                                                      *
0009  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0010  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0011  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0012  *                                                                                      *
0013  * You should have received a copy of the GNU General Public License along with         *
0014  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0015  ****************************************************************************************/
0016 
0017 #define DEBUG_PREFIX "Photos"
0018 
0019 #include "PhotosEngine.h"
0020 
0021 #include "EngineController.h"
0022 #include "core/support/Amarok.h"
0023 #include "core/support/Debug.h"
0024 
0025 #include <QXmlStreamReader>
0026 #include <QUrlQuery>
0027 
0028 
0029 PhotosEngine::PhotosEngine( QObject* parent )
0030     : QObject( parent )
0031     , m_nbPhotos( 10 )
0032     , m_status( Stopped )
0033 {
0034     DEBUG_BLOCK
0035 
0036     EngineController *controller = The::engineController();
0037     connect( controller, &EngineController::trackMetadataChanged, this, &PhotosEngine::trackChanged );
0038     connect( controller, &EngineController::trackChanged, this, &PhotosEngine::trackChanged );
0039     connect( controller, &EngineController::stopped, this, &PhotosEngine::stopped );
0040 }
0041 
0042 PhotosEngine::~PhotosEngine()
0043 {
0044 }
0045 
0046 void
0047 PhotosEngine::stopped()
0048 {
0049     DEBUG_BLOCK
0050 
0051     setPhotos( QList<PhotoInfo>() );
0052     setStatus( Stopped );
0053     setArtist( QString() );
0054     m_currentTrack.clear();
0055 }
0056 
0057 void
0058 PhotosEngine::trackChanged( const Meta::TrackPtr &track )
0059 {
0060     if( !track )
0061         return;
0062 
0063     update();
0064 }
0065 
0066 int
0067 PhotosEngine::fetchSize() const
0068 {
0069     return m_nbPhotos;
0070 }
0071 
0072 void
0073 PhotosEngine::setFetchSize( int size )
0074 {
0075     m_nbPhotos = size;
0076 }
0077 
0078 QStringList
0079 PhotosEngine::keywords() const
0080 {
0081     return m_keywords;
0082 }
0083 
0084 void
0085 PhotosEngine::setKeywords( const QStringList &keywords )
0086 {
0087     if( m_keywords == keywords )
0088         return;
0089 
0090     m_keywords = keywords;
0091     Q_EMIT keywordsChanged();
0092 }
0093 
0094 void
0095 PhotosEngine::metadataChanged(const Meta::TrackPtr &track )
0096 {
0097     const bool hasChanged = !track->artist() || track->artist()->name() != m_artist;
0098     if ( hasChanged )
0099         update();
0100 }
0101 
0102 void
0103 PhotosEngine::update( bool force )
0104 {
0105     // prevent
0106     Meta::TrackPtr currentTrack = The::engineController()->currentTrack();
0107     if( !currentTrack || !currentTrack->artist() )
0108     {
0109         debug() << "invalid current track";
0110         setPhotos( QList<PhotoInfo>() );
0111         return;
0112     }
0113     else if( !force && currentTrack->artist()->name() == m_artist )
0114     {
0115         debug() << "artist name unchanged";
0116         return;
0117     }
0118     else
0119     {
0120         unsubscribeFrom( m_currentTrack );
0121         m_currentTrack = currentTrack;
0122         subscribeTo( currentTrack );
0123 
0124         if ( !currentTrack )
0125             return;
0126 
0127         // Save artist
0128         setArtist( currentTrack->artist()->name() );
0129         setPhotos( QList<PhotoInfo>() );
0130 
0131         // Show the information
0132         if( !m_artist.isEmpty() )
0133         {
0134             setStatus( Fetching );
0135         }
0136         else
0137         {
0138             setPhotos( QList<PhotoInfo>() );
0139             return;
0140         }
0141 
0142         QStringList tags = m_keywords;
0143         tags << m_artist;
0144         tags.removeDuplicates();
0145 
0146         // Query flickr, order by relevance, 10 max
0147         // Flickr :http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=9c5a288116c34c17ecee37877397fe31&text=ARTIST&per_page=20
0148         QUrl flickrUrl;
0149         QUrlQuery query;
0150         flickrUrl.setScheme( "https" );
0151         flickrUrl.setHost( "api.flickr.com" );
0152         flickrUrl.setPath( "/services/rest/" );
0153         query.addQueryItem( "method", "flickr.photos.search" );
0154         query.addQueryItem( "api_key", Amarok::flickrApiKey() );
0155         query.addQueryItem( "per_page", QString::number( m_nbPhotos ) );
0156         query.addQueryItem( "sort", "date-posted-desc" );
0157         query.addQueryItem( "media", "photos" );
0158         query.addQueryItem( "content_type", QString::number(1) );
0159         query.addQueryItem( "text", tags.join(" ") );
0160         flickrUrl.setQuery( query );
0161         debug() << "Flickr url:" << flickrUrl;
0162 
0163         m_flickrUrls << flickrUrl;
0164         The::networkAccessManager()->getData( flickrUrl, this, &PhotosEngine::resultFlickr );
0165 
0166     }
0167 }
0168 
0169 void
0170 PhotosEngine::resultFlickr(const QUrl &url, const QByteArray &data, const NetworkAccessManagerProxy::Error &e )
0171 {
0172     if( !m_flickrUrls.contains( url ) )
0173         return;
0174 
0175     DEBUG_BLOCK
0176 
0177     m_flickrUrls.remove( url );
0178     if( e.code != QNetworkReply::NoError )
0179     {
0180         setError( e.description );
0181         debug() << "Unable to retrieve Flickr information:" << e.description;
0182         return;
0183     }
0184 
0185     if( data.isNull() )
0186     {
0187         debug() << "Got bad xml!";
0188         return;
0189     }
0190 
0191     setPhotos( QList<PhotoInfo>() );
0192     QXmlStreamReader xml( data );
0193     QList<PhotoInfo> photosInfo = photosListFromXml( xml );
0194     debug() << "got" << photosInfo.size() << "photo info";
0195     setPhotos( photosInfo );
0196     setStatus( Completed );
0197 }
0198 
0199 QList<PhotosEngine::PhotoInfo>
0200 PhotosEngine::photosListFromXml( QXmlStreamReader &xml )
0201 {
0202     QList<PhotoInfo> photoList;
0203     xml.readNextStartElement(); // rsp
0204     if( xml.attributes().value(QLatin1String("stat")) != QLatin1String("ok") )
0205         return photoList;
0206 
0207     xml.readNextStartElement(); // photos
0208     while( xml.readNextStartElement() )
0209     {
0210         if( xml.name() == QLatin1String("photo") )
0211         {
0212             const QXmlStreamAttributes &attr = xml.attributes();
0213             QStringRef id     = attr.value( QLatin1String("id") );
0214             QStringRef farm   = attr.value( QLatin1String("farm") );
0215             QStringRef owner  = attr.value( QLatin1String("owner") );
0216             QStringRef secret = attr.value( QLatin1String("secret") );
0217             QStringRef server = attr.value( QLatin1String("server") );
0218             QStringRef title  = attr.value( QLatin1String("title") );
0219 
0220             QUrl photoUrl;
0221             photoUrl.setScheme( "http" );
0222             photoUrl.setHost( QStringLiteral("farm%1.static.flickr.com").arg( farm.toString() ) );
0223             photoUrl.setPath( QStringLiteral("/%1/%2_%3.jpg").arg( server.toString(), id.toString(), secret.toString() ) );
0224 
0225             QUrl pageUrl;
0226             pageUrl.setScheme( "http" );
0227             pageUrl.setHost( QLatin1String("www.flickr.com") );
0228             pageUrl.setPath( QStringLiteral("/photos/%1/%2").arg( owner.toString(), id.toString() ) );
0229 
0230             PhotoInfo info;
0231             info.title = title.toString();
0232             info.urlpage = pageUrl;
0233             info.urlphoto = photoUrl;
0234             photoList.append( info );
0235         }
0236         xml.skipCurrentElement();
0237     }
0238     return photoList;
0239 }
0240 
0241 void
0242 PhotosEngine::setPhotos( const QList<PhotoInfo> &photos )
0243 {
0244     if( m_photos == photos )
0245         return;
0246 
0247     m_photos = photos;
0248     Q_EMIT photosChanged();
0249 }
0250 
0251 void
0252 PhotosEngine::setStatus( Status status )
0253 {
0254     if( m_status == status )
0255         return;
0256 
0257     m_status = status;
0258     Q_EMIT statusChanged();
0259 }
0260 
0261 void
0262 PhotosEngine::setError( const QString &error )
0263 {
0264     if( m_error == error )
0265         return;
0266 
0267     m_error = error;
0268     Q_EMIT errorChanged();
0269 }
0270 
0271 void
0272 PhotosEngine::setArtist( const QString &artist )
0273 {
0274     if( m_artist == artist )
0275         return;
0276 
0277     m_artist = artist;
0278     Q_EMIT artistChanged();
0279 }
0280 
0281 QList<QUrl>
0282 PhotosEngine::photoUrls() const
0283 {
0284     QList<QUrl> list;
0285     for( const auto &photo : m_photos )
0286     {
0287         list << photo.urlphoto;
0288     }
0289     return list;
0290 }
0291 
0292 QList<QUrl>
0293 PhotosEngine::pageUrls() const
0294 {
0295     QList<QUrl> list;
0296     for( const auto &photo : m_photos )
0297     {
0298         list << photo.urlpage;
0299     }
0300     return list;
0301 }
0302 
0303 QList<QString>
0304 PhotosEngine::photoTitles() const
0305 {
0306     QList<QString> list;
0307     for( const auto &photo : m_photos )
0308     {
0309         list << photo.title;
0310     }
0311     return list;
0312 }