File indexing completed on 2024-05-19 04:50:17
0001 /**************************************************************************************** 0002 * Copyright (c) 2007 Shane King <kde@dontletsstart.com> * 0003 * Copyright (c) 2008 Leo Franchi <lfranchi@kde.org> * 0004 * Copyright (c) 2012 Matěj Laitl <matej@laitlcz> * 0005 * Copyright (c) 2013 Vedant Agarwala <vedant.kota@gmail.com> * 0006 * * 0007 * This program is free software; you can redistribute it and/or modify it under * 0008 * the terms of the GNU General Public License as published by the Free Software * 0009 * Foundation; either version 2 of the License, or (at your option) any later * 0010 * version. * 0011 * * 0012 * This program is distributed in the hope that it will be useful, but WITHOUT ANY * 0013 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * 0014 * PARTICULAR PURPOSE. See the GNU General Public License for more details. * 0015 * * 0016 * You should have received a copy of the GNU General Public License along with * 0017 * this program. If not, see <http://www.gnu.org/licenses/>. * 0018 ****************************************************************************************/ 0019 0020 #define DEBUG_PREFIX "lastfm" 0021 0022 #include "ScrobblerAdapter.h" 0023 0024 #include "MainWindow.h" 0025 #include "core/collections/Collection.h" 0026 #include "core/logger/Logger.h" 0027 #include "core/meta/Meta.h" 0028 #include "core/meta/support/MetaConstants.h" 0029 #include "core/support/Components.h" 0030 #include "core/support/Debug.h" 0031 0032 #include <KLocalizedString> 0033 0034 #include <QNetworkReply> 0035 0036 #include <misc.h> 0037 0038 ScrobblerAdapter::ScrobblerAdapter( const QString &clientId, const LastFmServiceConfigPtr &config ) 0039 : m_scrobbler( clientId ) 0040 , m_config( config ) 0041 { 0042 // work around a bug in liblastfm -- -it doesn't create its config dir, so when it 0043 // tries to write the track cache, it fails silently. Last check: liblastfm 1.0.! 0044 QList<QDir> dirs; 0045 dirs << lastfm::dir::runtimeData() << lastfm::dir::cache() << lastfm::dir::logs(); 0046 foreach( const QDir &dir, dirs ) 0047 { 0048 if( !dir.exists() ) 0049 { 0050 debug() << "creating" << dir.absolutePath() << "directory for liblastfm"; 0051 dir.mkpath( "." ); 0052 } 0053 } 0054 0055 connect( The::mainWindow(), &MainWindow::loveTrack, 0056 this, &ScrobblerAdapter::loveTrack ); 0057 connect( The::mainWindow(), &MainWindow::banTrack, 0058 this, &ScrobblerAdapter::banTrack ); 0059 0060 connect( &m_scrobbler, &lastfm::Audioscrobbler::scrobblesSubmitted, 0061 this, &ScrobblerAdapter::slotScrobblesSubmitted ); 0062 connect( &m_scrobbler, &lastfm::Audioscrobbler::nowPlayingError, 0063 this, &ScrobblerAdapter::slotNowPlayingError ); 0064 } 0065 0066 ScrobblerAdapter::~ScrobblerAdapter() 0067 { 0068 } 0069 0070 QString 0071 ScrobblerAdapter::prettyName() const 0072 { 0073 return i18n( "Last.fm" ); 0074 } 0075 0076 StatSyncing::ScrobblingService::ScrobbleError 0077 ScrobblerAdapter::scrobble( const Meta::TrackPtr &track, double playedFraction, 0078 const QDateTime &time ) 0079 { 0080 Q_ASSERT( track ); 0081 if( isToBeSkipped( track ) ) 0082 { 0083 debug() << "scrobble(): refusing track" << track->prettyUrl() 0084 << "- contains label:" << m_config->filteredLabel() << "which is marked to be skipped"; 0085 return SkippedByUser; 0086 } 0087 if( track->length() * qMin( 1.0, playedFraction ) < 30 * 1000 ) 0088 { 0089 debug() << "scrobble(): refusing track" << track->prettyUrl() << "- played time (" 0090 << track->length() / 1000 << "*" << playedFraction << "s) shorter than 30 s"; 0091 return TooShort; 0092 } 0093 int playcount = qRound( playedFraction ); 0094 if( playcount <= 0 ) 0095 { 0096 debug() << "scrobble(): refusing track" << track->prettyUrl() << "- played " 0097 << "fraction (" << playedFraction * 100 << "%) less than 50 %"; 0098 return TooShort; 0099 } 0100 0101 lastfm::MutableTrack lfmTrack; 0102 copyTrackMetadata( lfmTrack, track ); 0103 // since liblastfm >= 1.0.3 it interprets following extra property: 0104 lfmTrack.setExtra( "playCount", QString::number( playcount ) ); 0105 lfmTrack.setTimeStamp( time.isValid() ? time : QDateTime::currentDateTime() ); 0106 debug() << "scrobble: " << lfmTrack.artist() << "-" << lfmTrack.album() << "-" 0107 << lfmTrack.title() << "source:" << lfmTrack.source() << "duration:" 0108 << lfmTrack.duration(); 0109 m_scrobbler.cache( lfmTrack ); 0110 m_scrobbler.submit(); // since liblastfm 1.0.7, submit() is not called automatically upon cache() 0111 switch( lfmTrack.scrobbleStatus() ) 0112 { 0113 case lastfm::Track::Cached: 0114 case lastfm::Track::Submitted: 0115 return NoError; 0116 case lastfm::Track::Null: 0117 case lastfm::Track::Error: 0118 break; 0119 } 0120 return BadMetadata; 0121 } 0122 0123 void 0124 ScrobblerAdapter::updateNowPlaying( const Meta::TrackPtr &track ) 0125 { 0126 lastfm::MutableTrack lfmTrack; 0127 if( track ) 0128 { 0129 if( isToBeSkipped( track ) ) 0130 { 0131 debug() << "updateNowPlaying(): refusing track" << track->prettyUrl() 0132 << "- contains label:" << m_config->filteredLabel() << "which is marked to be skipped"; 0133 return; 0134 } 0135 copyTrackMetadata( lfmTrack, track ); 0136 debug() << "nowPlaying: " << lfmTrack.artist() << "-" << lfmTrack.album() << "-" 0137 << lfmTrack.title() << "source:" << lfmTrack.source() << "duration:" 0138 << lfmTrack.duration(); 0139 m_scrobbler.nowPlaying( lfmTrack ); 0140 } 0141 else 0142 { 0143 debug() << "removeNowPlaying"; 0144 QNetworkReply *reply = lfmTrack.removeNowPlaying(); // works even with empty lfmTrack 0145 connect( reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater ); // don't leak 0146 } 0147 } 0148 0149 void 0150 ScrobblerAdapter::loveTrack( const Meta::TrackPtr &track ) // slot 0151 { 0152 if( !track ) 0153 return; 0154 0155 lastfm::MutableTrack trackInfo; 0156 copyTrackMetadata( trackInfo, track ); 0157 trackInfo.love(); 0158 Amarok::Logger::shortMessage( i18nc( "As in Last.fm", "Loved Track: %1", track->prettyName() ) ); 0159 } 0160 0161 void 0162 ScrobblerAdapter::banTrack( const Meta::TrackPtr &track ) // slot 0163 { 0164 if( !track ) 0165 return; 0166 0167 lastfm::MutableTrack trackInfo; 0168 copyTrackMetadata( trackInfo, track ); 0169 trackInfo.ban(); 0170 Amarok::Logger::shortMessage( i18nc( "As in Last.fm", "Banned Track: %1", track->prettyName() ) ); 0171 } 0172 0173 void 0174 ScrobblerAdapter::slotScrobblesSubmitted( const QList<lastfm::Track> &tracks ) 0175 { 0176 foreach( const lastfm::Track &track, tracks ) 0177 { 0178 switch( track.scrobbleStatus() ) 0179 { 0180 case lastfm::Track::Null: 0181 warning() << "slotScrobblesSubmitted(): track" << track 0182 << "has Null scrobble status, strange"; 0183 break; 0184 case lastfm::Track::Cached: 0185 warning() << "slotScrobblesSubmitted(): track" << track 0186 << "has Cached scrobble status, strange"; 0187 break; 0188 case lastfm::Track::Submitted: 0189 if( track.corrected() && m_config->announceCorrections() ) 0190 announceTrackCorrections( track ); 0191 break; 0192 case lastfm::Track::Error: 0193 warning() << "slotScrobblesSubmitted(): error scrobbling track" << track 0194 << ":" << track.scrobbleErrorText(); 0195 break; 0196 } 0197 } 0198 } 0199 0200 void 0201 ScrobblerAdapter::slotNowPlayingError( int code, const QString &message ) 0202 { 0203 Q_UNUSED( code ) 0204 warning() << "error updating Now Playing status:" << message; 0205 } 0206 0207 void 0208 ScrobblerAdapter::copyTrackMetadata( lastfm::MutableTrack &to, const Meta::TrackPtr &track ) 0209 { 0210 to.setTitle( track->name() ); 0211 0212 QString artistOrComposer; 0213 Meta::ComposerPtr composer = track->composer(); 0214 if( m_config->scrobbleComposer() && composer ) 0215 artistOrComposer = composer->name(); 0216 Meta::ArtistPtr artist = track->artist(); 0217 if( artistOrComposer.isEmpty() && artist ) 0218 artistOrComposer = artist->name(); 0219 to.setArtist( artistOrComposer ); 0220 0221 Meta::AlbumPtr album = track->album(); 0222 Meta::ArtistPtr albumArtist; 0223 if( album ) 0224 { 0225 to.setAlbum( album->name() ); 0226 albumArtist = album->hasAlbumArtist() ? album->albumArtist() : Meta::ArtistPtr(); 0227 } 0228 if( albumArtist ) 0229 to.setAlbumArtist( albumArtist->name() ); 0230 0231 to.setDuration( track->length() / 1000 ); 0232 if( track->trackNumber() >= 0 ) 0233 to.setTrackNumber( track->trackNumber() ); 0234 0235 lastfm::Track::Source source = lastfm::Track::Player; 0236 if( track->type() == "stream/lastfm" ) 0237 source = lastfm::Track::LastFmRadio; 0238 else if( track->type().startsWith( "stream" ) ) 0239 source = lastfm::Track::NonPersonalisedBroadcast; 0240 else if( track->collection() && track->collection()->collectionId() != "localCollection" ) 0241 source = lastfm::Track::MediaDevice; 0242 to.setSource( source ); 0243 } 0244 0245 static QString 0246 printCorrected( qint64 field, const QString &original, const QString &corrected ) 0247 { 0248 if( corrected.isEmpty() || original == corrected ) 0249 return QString(); 0250 return i18nc( "%1 is field name such as Album Name; %2 is the original value; %3 is " 0251 "the corrected value", "%1 <b>%2</b> should be corrected to " 0252 "<b>%3</b>", Meta::i18nForField( field ), original, corrected ); 0253 } 0254 0255 static QString 0256 printCorrected( qint64 field, const lastfm::AbstractType &original, const lastfm::AbstractType &corrected ) 0257 { 0258 return printCorrected( field, original.toString(), corrected.toString() ); 0259 } 0260 0261 void 0262 ScrobblerAdapter::announceTrackCorrections( const lastfm::Track &track ) 0263 { 0264 static const lastfm::Track::Corrections orig = lastfm::Track::Original; 0265 static const lastfm::Track::Corrections correct = lastfm::Track::Corrected; 0266 0267 QString trackName = i18nc( "%1 is artist, %2 is title", "%1 - %2", 0268 track.artist().name(), track.title() ); 0269 QStringList lines; 0270 lines << i18n( "Last.fm suggests that some tags of track <b>%1</b> should be " 0271 "corrected:", trackName ); 0272 QString line; 0273 line = printCorrected( Meta::valTitle, track.title( orig ), track.title( correct ) ); 0274 if( !line.isEmpty() ) 0275 lines << line; 0276 line = printCorrected( Meta::valAlbum, track.album( orig ), track.album( correct ) ); 0277 if( !line.isEmpty() ) 0278 lines << line; 0279 line = printCorrected( Meta::valArtist, track.artist( orig ), track.artist( correct ) ); 0280 if( !line.isEmpty() ) 0281 lines << line; 0282 line = printCorrected( Meta::valAlbumArtist, track.albumArtist( orig ), track.albumArtist( correct ) ); 0283 if( !line.isEmpty() ) 0284 lines << line; 0285 Amarok::Logger::longMessage( lines.join( "<br>" ) ); 0286 } 0287 0288 bool 0289 ScrobblerAdapter::isToBeSkipped( const Meta::TrackPtr &track ) const 0290 { 0291 Q_ASSERT( track ); 0292 if( !m_config->filterByLabel() ) 0293 return false; 0294 foreach( const Meta::LabelPtr &label, track->labels() ) 0295 if( label->name() == m_config->filteredLabel() ) 0296 return true; 0297 return false; 0298 }