File indexing completed on 2024-05-19 04:50:17
0001 /**************************************************************************************** 0002 * Copyright (c) 2012 Matěj Laitl <matej@laitl.cz> * 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 #include "SynchronizationAdapter.h" 0018 0019 #include "MetaValues.h" 0020 #include "core/support/Debug.h" 0021 #include "core/support/SemaphoreReleaser.h" 0022 #include "services/lastfm/SynchronizationTrack.h" 0023 0024 #include <KLocalizedString> 0025 0026 #include <QNetworkReply> 0027 0028 #include <Library.h> 0029 #include <XmlQuery.h> 0030 0031 const int SynchronizationAdapter::s_entriesPerQuery( 200 ); 0032 0033 SynchronizationAdapter::SynchronizationAdapter( const LastFmServiceConfigPtr &config ) 0034 : m_config( config ) 0035 { 0036 connect( this, &SynchronizationAdapter::startArtistSearch, this, &SynchronizationAdapter::slotStartArtistSearch ); 0037 connect( this, &SynchronizationAdapter::startTrackSearch, this, &SynchronizationAdapter::slotStartTrackSearch ); 0038 connect( this, &SynchronizationAdapter::startTagSearch, this, &SynchronizationAdapter::slotStartTagSearch ); 0039 } 0040 0041 SynchronizationAdapter::~SynchronizationAdapter() 0042 { 0043 } 0044 0045 QString 0046 SynchronizationAdapter::id() const 0047 { 0048 return "lastfm"; 0049 } 0050 0051 QString 0052 SynchronizationAdapter::prettyName() const 0053 { 0054 return i18n( "Last.fm" ); 0055 } 0056 0057 QString 0058 SynchronizationAdapter::description() const 0059 { 0060 return i18nc( "description of the Last.fm statistics synchronization provider", 0061 "slows down track matching" ); 0062 } 0063 0064 QIcon 0065 SynchronizationAdapter::icon() const 0066 { 0067 return QIcon::fromTheme( "view-services-lastfm-amarok" ); 0068 } 0069 0070 qint64 0071 SynchronizationAdapter::reliableTrackMetaData() const 0072 { 0073 return Meta::valArtist | Meta::valAlbum | Meta::valTitle; 0074 } 0075 0076 qint64 0077 SynchronizationAdapter::writableTrackStatsData() const 0078 { 0079 bool useRating = m_config->useFancyRatingTags(); 0080 return ( useRating ? Meta::valRating : 0 ) | Meta::valLabel; 0081 } 0082 0083 StatSyncing::Provider::Preference 0084 SynchronizationAdapter::defaultPreference() 0085 { 0086 return StatSyncing::Provider::Never; // don't overload Last.fm servers 0087 } 0088 0089 QSet<QString> 0090 SynchronizationAdapter::artists() 0091 { 0092 DEBUG_BLOCK 0093 Q_ASSERT( m_semaphore.available() == 0 ); 0094 emit startArtistSearch( 1 ); // Last.fm indexes from 1 0095 0096 m_semaphore.acquire(); 0097 QSet<QString> ret = m_artists; 0098 m_artists.clear(); // save memory 0099 debug() << __PRETTY_FUNCTION__ << ret.count() << "artists total"; 0100 return ret; 0101 } 0102 0103 StatSyncing::TrackList 0104 SynchronizationAdapter::artistTracks( const QString &artistName ) 0105 { 0106 /* This method should match track artists case-sensitively, but we don't do it. 0107 * Last.fm webservice returns only the preferred capitalisation in artists(), so no 0108 * duplicates threat us. */ 0109 Q_ASSERT( m_semaphore.available() == 0 ); 0110 emit startTrackSearch( artistName, 1 ); // Last.fm indexes from 1 0111 0112 m_semaphore.acquire(); 0113 debug() << __PRETTY_FUNCTION__ << m_tracks.count() << "tracks from" << artistName 0114 << m_tagQueue.count() << "of them have tags"; 0115 0116 // fetch tags 0117 QMutableListIterator<StatSyncing::TrackPtr> it( m_tagQueue ); 0118 while( it.hasNext() ) 0119 { 0120 StatSyncing::TrackPtr track = it.next(); 0121 emit startTagSearch( track->artist(), track->name() ); 0122 m_semaphore.acquire(); 0123 it.remove(); 0124 } 0125 0126 StatSyncing::TrackList ret = m_tracks; 0127 m_tracks.clear(); // save memory 0128 m_tagQueue.clear(); // paranoia 0129 return ret; 0130 } 0131 0132 void 0133 SynchronizationAdapter::slotStartArtistSearch( int page ) 0134 { 0135 QString user = m_config->username(); 0136 QNetworkReply *reply = lastfm::Library::getArtists( user, s_entriesPerQuery, page ); 0137 connect( reply, &QNetworkReply::finished, this, &SynchronizationAdapter::slotArtistsReceived ); 0138 } 0139 0140 void 0141 SynchronizationAdapter::slotStartTrackSearch( QString artistName, int page ) 0142 { 0143 lastfm::Artist artist( artistName ); 0144 QString user = m_config->username(); 0145 QNetworkReply *reply = lastfm::Library::getTracks( user, artist, s_entriesPerQuery, page ); 0146 connect( reply, &QNetworkReply::finished, this, &SynchronizationAdapter::slotTracksReceived ); 0147 } 0148 0149 void 0150 SynchronizationAdapter::slotStartTagSearch( QString artistName, QString trackName ) 0151 { 0152 lastfm::MutableTrack track; 0153 track.setArtist( artistName ); 0154 track.setTitle( trackName ); 0155 QNetworkReply *reply = track.getTags(); 0156 connect( reply, &QNetworkReply::finished, this, &SynchronizationAdapter::slotTagsReceived ); 0157 } 0158 0159 void 0160 SynchronizationAdapter::slotArtistsReceived() 0161 { 0162 SemaphoreReleaser releaser( &m_semaphore ); 0163 QNetworkReply *reply = qobject_cast<QNetworkReply *>( sender() ); 0164 if( !reply ) 0165 { 0166 warning() << __PRETTY_FUNCTION__ << "cannot cast sender to QNetworkReply. (?)"; 0167 return; 0168 } 0169 reply->deleteLater(); 0170 0171 lastfm::XmlQuery lfm; 0172 if( !lfm.parse( reply->readAll() ) ) 0173 { 0174 warning() << __PRETTY_FUNCTION__ << "Error parsing Last.fm reply:" << lfm.parseError().message(); 0175 return; 0176 } 0177 lastfm::XmlQuery artists = lfm[ "artists" ]; 0178 bool ok = false; 0179 int page = artists.attribute( "page" ).toInt( &ok ); 0180 if( !ok ) 0181 { 0182 warning() << __PRETTY_FUNCTION__ << "cannot read page number"; 0183 return; 0184 } 0185 int totalPages = artists.attribute( "totalPages" ).toInt( &ok ); 0186 if( !ok ) 0187 { 0188 warning() << __PRETTY_FUNCTION__ << "cannot read total number or pages"; 0189 return; 0190 } 0191 debug() << __PRETTY_FUNCTION__ << "page" << page << "of" << totalPages; 0192 0193 // following is based on lastfm::Artist::list(): 0194 foreach( const lastfm::XmlQuery &xq, lfm.children( "artist" ) ) 0195 { 0196 lastfm::Artist artist( xq ); 0197 m_artists.insert( artist.name() ); 0198 } 0199 0200 // Last.fm indexes from 1! 0201 if( page < totalPages ) 0202 { 0203 releaser.dontRelease(); // don't release the semaphore yet 0204 emit startArtistSearch( page + 1 ); 0205 } 0206 } 0207 0208 void 0209 SynchronizationAdapter::slotTracksReceived() 0210 { 0211 SemaphoreReleaser releaser( &m_semaphore ); 0212 QNetworkReply *reply = qobject_cast<QNetworkReply *>( sender() ); 0213 if( !reply ) 0214 { 0215 warning() << __PRETTY_FUNCTION__ << "cannot cast sender to QNetworkReply. (?)"; 0216 return; 0217 } 0218 reply->deleteLater(); 0219 0220 lastfm::XmlQuery lfm; 0221 if( !lfm.parse( reply->readAll() ) ) 0222 { 0223 warning() << __PRETTY_FUNCTION__ << "Error parsing Last.fm reply:" << lfm.parseError().message(); 0224 return; 0225 } 0226 lastfm::XmlQuery tracks = lfm[ "tracks" ]; 0227 bool ok = false; 0228 int page = tracks.attribute( "page" ).toInt( &ok ); 0229 if( !ok ) 0230 { 0231 warning() << __PRETTY_FUNCTION__ << "cannot read page number"; 0232 return; 0233 } 0234 int totalPages = tracks.attribute( "totalPages" ).toInt( &ok ); 0235 if( !ok ) 0236 { 0237 warning() << __PRETTY_FUNCTION__ << "cannot read total number or pages"; 0238 return; 0239 } 0240 QString searchedArtist = tracks.attribute( "artist" ); 0241 if( searchedArtist.isEmpty() ) 0242 { 0243 warning() << __PRETTY_FUNCTION__ << "searchedArtist in Last.fm reply is empty"; 0244 return; 0245 } 0246 0247 // following is based on lastfm::Track::list(): 0248 foreach( const lastfm::XmlQuery &xq, lfm.children( "track" ) ) 0249 { 0250 QString name = xq[ "name" ].text(); 0251 int playCount = xq[ "playcount" ].text().toInt(); 0252 int tagCount = xq[ "tagcount" ].text().toInt(); 0253 QString artist = xq[ "artist" ][ "name" ].text(); 0254 QString album = xq[ "album" ][ "name" ].text(); 0255 0256 bool useRatings = m_config->useFancyRatingTags(); 0257 StatSyncing::TrackPtr track( new SynchronizationTrack( artist, album, name, 0258 playCount, useRatings ) ); 0259 m_tracks.append( track ); 0260 if( tagCount > 0 ) 0261 m_tagQueue.append( track ); 0262 } 0263 0264 // Last.fm indexes from 1! 0265 if( page < totalPages ) 0266 { 0267 releaser.dontRelease(); // don't release the semaphore yet 0268 emit startTrackSearch( searchedArtist, page + 1 ); 0269 } 0270 } 0271 0272 void 0273 SynchronizationAdapter::slotTagsReceived() 0274 { 0275 SemaphoreReleaser releaser( &m_semaphore ); 0276 QNetworkReply *reply = qobject_cast<QNetworkReply *>( sender() ); 0277 if( !reply ) 0278 { 0279 warning() << __PRETTY_FUNCTION__ << "cannot cast sender to QNetworkReply. (?)"; 0280 return; 0281 } 0282 reply->deleteLater(); 0283 0284 lastfm::XmlQuery lfm; 0285 if( !lfm.parse( reply->readAll() ) ) 0286 { 0287 warning() << __PRETTY_FUNCTION__ << "Error parsing Last.fm reply:" << lfm.parseError().message(); 0288 return; 0289 } 0290 QSet<QString> tags; 0291 foreach( const lastfm::XmlQuery &xq, lfm.children( "tag" ) ) 0292 { 0293 tags.insert( xq[ "name" ].text() ); 0294 } 0295 Q_ASSERT( !m_tagQueue.isEmpty() ); 0296 SynchronizationTrack *track = dynamic_cast<SynchronizationTrack *>( m_tagQueue.first().data() ); 0297 Q_ASSERT( track ); 0298 track->parseAndSaveLastFmTags( tags ); 0299 }