File indexing completed on 2024-06-02 04:52:50
0001 /**************************************************************************************** 0002 * Copyright (c) 2007 Maximilian Kossick <maximilian.kossick@googlemail.com> * 0003 * Copyright (c) 2008 Shane King <kde@dontletsstart.com> * 0004 * Copyright (c) 2008 Leo Franchi <lfranchi@kde.org> * 0005 * * 0006 * This program is free software; you can redistribute it and/or modify it under * 0007 * the terms of the GNU General Public License as published by the Free Software * 0008 * Foundation; either version 2 of the License, or (at your option) any later * 0009 * version. * 0010 * * 0011 * This program is distributed in the hope that it will be useful, but WITHOUT ANY * 0012 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * 0013 * PARTICULAR PURPOSE. See the GNU General Public License for more details. * 0014 * * 0015 * You should have received a copy of the GNU General Public License along with * 0016 * this program. If not, see <http://www.gnu.org/licenses/>. * 0017 ****************************************************************************************/ 0018 0019 #include "LastFmMeta.h" 0020 0021 #include "EngineController.h" 0022 #include "services/lastfm/meta/LastFmMeta_p.h" 0023 #include "services/lastfm/meta/LastFmMultiPlayableCapability.h" 0024 #include "services/lastfm/meta/LastFmStreamInfoCapability.h" 0025 0026 #include <QIcon> 0027 0028 #include <QCoreApplication> 0029 #include <QThread> 0030 0031 using namespace LastFm; 0032 0033 Track::Track( const QString &lastFmUri ) 0034 : QObject() 0035 , Meta::Track() 0036 , d( new Private() ) 0037 { 0038 d->lastFmUri = QUrl( lastFmUri ); 0039 d->t = this; 0040 0041 init(); 0042 } 0043 0044 Track::Track( lastfm::Track track ) 0045 : QObject() 0046 , Meta::Track() 0047 , d( new Private() ) 0048 { 0049 d->t = this; 0050 d->track = track.title(); 0051 d->lastFmTrack = track; 0052 QMap< QString, QString > params; 0053 params[ "method" ] = "track.getInfo"; 0054 params[ "artist" ] = track.artist(); 0055 params[ "track" ] = track.title(); 0056 0057 d->trackFetch = lastfm::ws::post( params ); 0058 0059 connect( d->trackFetch, &QNetworkReply::finished, this, &Track::slotResultReady ); 0060 } 0061 0062 0063 Track::~Track() 0064 { 0065 delete d; 0066 } 0067 0068 void Track::init( int id /* = -1*/ ) 0069 { 0070 if( id != -1 ) 0071 d->lastFmUri = QUrl( "lastfm://play/tracks/" + QString::number( id ) ); 0072 d->length = 0; 0073 0074 d->albumPtr = Meta::AlbumPtr( new LastFmAlbum( d ) ); 0075 d->artistPtr = Meta::ArtistPtr( new LastFmArtist( d ) ); 0076 d->genrePtr = Meta::GenrePtr( new LastFmGenre( d ) ); 0077 d->composerPtr = Meta::ComposerPtr( new LastFmComposer( d ) ); 0078 d->yearPtr = Meta::YearPtr( new LastFmYear( d ) ); 0079 0080 QAction *banAction = new QAction( QIcon::fromTheme( "remove-amarok" ), i18n( "Last.fm: &Ban" ), this ); 0081 banAction->setShortcut( i18n( "Ctrl+B" ) ); 0082 banAction->setStatusTip( i18n( "Ban this track" ) ); 0083 connect( banAction, &QAction::triggered, this, &Track::ban ); 0084 m_trackActions.append( banAction ); 0085 0086 QAction *skipAction = new QAction( QIcon::fromTheme( "media-seek-forward-amarok" ), i18n( "Last.fm: &Skip" ), this ); 0087 skipAction->setShortcut( i18n( "Ctrl+S" ) ); 0088 skipAction->setStatusTip( i18n( "Skip this track" ) ); 0089 connect( skipAction, &QAction::triggered, this, &Track::skipTrack ); 0090 m_trackActions.append( skipAction ); 0091 0092 QThread *mainThread = QCoreApplication::instance()->thread(); 0093 bool foreignThread = QThread::currentThread() != mainThread; 0094 if( foreignThread ) 0095 { 0096 moveToThread( mainThread ); // the actions are children and are moved together with parent 0097 d->moveToThread( mainThread ); 0098 } 0099 } 0100 0101 QString 0102 Track::name() const 0103 { 0104 if( d->track.isEmpty() ) 0105 { 0106 return streamName(); 0107 } 0108 else 0109 { 0110 return d->track; 0111 } 0112 } 0113 0114 QString 0115 Track::sortableName() const 0116 { 0117 // TODO 0118 return name(); 0119 } 0120 0121 QUrl 0122 Track::playableUrl() const 0123 { 0124 return d->lastFmUri; 0125 } 0126 0127 QUrl 0128 Track::internalUrl() const 0129 { 0130 return QUrl( d->trackPath ); 0131 } 0132 0133 QString 0134 Track::prettyUrl() const 0135 { 0136 return d->lastFmUri.toString(); 0137 } 0138 0139 QString 0140 Track::uidUrl() const 0141 { 0142 return d->lastFmUri.toString(); 0143 } 0144 0145 QString 0146 Track::notPlayableReason() const 0147 { 0148 return networkNotPlayableReason(); 0149 } 0150 0151 Meta::AlbumPtr 0152 Track::album() const 0153 { 0154 return d->albumPtr; 0155 } 0156 0157 Meta::ArtistPtr 0158 Track::artist() const 0159 { 0160 return d->artistPtr; 0161 } 0162 0163 Meta::GenrePtr 0164 Track::genre() const 0165 { 0166 return d->genrePtr; 0167 } 0168 0169 Meta::ComposerPtr 0170 Track::composer() const 0171 { 0172 return d->composerPtr; 0173 } 0174 0175 Meta::YearPtr 0176 Track::year() const 0177 { 0178 return d->yearPtr; 0179 } 0180 0181 qreal 0182 Track::bpm() const 0183 { 0184 return -1.0; 0185 } 0186 0187 QString 0188 Track::comment() const 0189 { 0190 return QString(); 0191 } 0192 0193 int 0194 Track::trackNumber() const 0195 { 0196 return 0; 0197 } 0198 0199 int 0200 Track::discNumber() const 0201 { 0202 return 0; 0203 } 0204 0205 qint64 0206 Track::length() const 0207 { 0208 return d->length; 0209 } 0210 0211 int 0212 Track::filesize() const 0213 { 0214 return 0; //stream 0215 } 0216 0217 int 0218 Track::sampleRate() const 0219 { 0220 return 0; //does the engine deliver this? 0221 } 0222 0223 int 0224 Track::bitrate() const 0225 { 0226 return 0; //does the engine deliver this?? 0227 } 0228 0229 QString 0230 Track::type() const 0231 { 0232 return "stream/lastfm"; 0233 } 0234 0235 bool 0236 Track::inCollection() const 0237 { 0238 return false; 0239 } 0240 0241 Collections::Collection* 0242 Track::collection() const 0243 { 0244 return nullptr; 0245 } 0246 0247 void 0248 Track::setTrackInfo( const lastfm::Track &track ) 0249 { 0250 if( !track.isNull() ) 0251 d->setTrackInfo( track ); 0252 } 0253 0254 QString 0255 Track::streamName() const 0256 { 0257 // parse the url to get a name if we don't have a track name (ie we're not playing the station) 0258 // do it as name rather than prettyname so it shows up nice in the playlist. 0259 QStringList elements = d->lastFmUri.toString().split( '/', Qt::SkipEmptyParts ); 0260 if( elements.size() >= 2 && elements[0] == "lastfm:" ) 0261 { 0262 QString customPart = QUrl::fromPercentEncoding( elements[2].toUtf8() ); 0263 0264 if( elements[1] == "globaltags" ) 0265 { 0266 // lastfm://globaltag/<tag> 0267 if( elements.size() >= 3 ) 0268 return i18n( "Global Tag Radio: \"%1\"", customPart ); 0269 } 0270 else if( elements[1] == "usertags" ) 0271 { 0272 // lastfm://usertag/<tag> 0273 if( elements.size() >= 3 ) 0274 return i18n( "User Tag Radio: \"%1\"", customPart ); 0275 } 0276 else if( elements[1] == "artist" ) 0277 { 0278 if( elements.size() >= 4 ) 0279 { 0280 // lastfm://artist/<artist>/similarartists 0281 if( elements[3] == "similarartists" ) 0282 return i18n( "Similar Artists to \"%1\"", customPart ); 0283 0284 // lastfm://artist/<artist>/fans 0285 else if( elements[3] == "fans" ) 0286 return i18n( "Artist Fan Radio: \"%1\"", customPart ); 0287 } 0288 } 0289 else if( elements[1] == "user" ) 0290 { 0291 if( elements.size() >= 4 ) 0292 { 0293 // lastfm://user/<user>/neighbours 0294 if( elements[3] == "neighbours" ) 0295 return i18n( "%1's Neighbor Radio", elements[2] ); 0296 0297 // lastfm://user/<user>/personal 0298 else if( elements[3] == "personal" ) 0299 return i18n( "%1's Personal Radio", elements[2] ); 0300 0301 // lastfm://user/<user>/mix 0302 else if( elements[3] == "mix" ) 0303 return i18n( "%1's Mix Radio", elements[2] ); 0304 0305 // lastfm://user/<user>/recommended 0306 else if( elements.size() < 5 && elements[3] == "recommended" ) 0307 return i18n( "%1's Recommended Radio", elements[2] ); 0308 0309 // lastfm://user/<user>/recommended/<popularity> 0310 else if( elements.size() >= 5 && elements[3] == "recommended" ) 0311 return i18n( "%1's Recommended Radio (Popularity %2)", elements[2], elements[4] ); 0312 } 0313 } 0314 else if( elements[1] == "group" ) 0315 { 0316 // lastfm://group/<group> 0317 if( elements.size() >= 3 ) 0318 return i18n( "Group Radio: %1", elements[2] ); 0319 } 0320 else if( elements[1] == "play" ) 0321 { 0322 if( elements.size() >= 4 ) 0323 { 0324 // lastfm://play/tracks/<track #s> 0325 if ( elements[2] == "tracks" ) 0326 return i18n( "Track Radio" ); 0327 0328 // lastfm://play/artists/<artist #s> 0329 else if ( elements[2] == "artists" ) 0330 return i18n( "Artist Radio" ); 0331 } 0332 } 0333 } 0334 0335 return d->lastFmUri.toString(); 0336 } 0337 0338 void 0339 Track::ban() 0340 { 0341 DEBUG_BLOCK 0342 0343 d->wsReply = lastfm::MutableTrack( d->lastFmTrack ).ban(); 0344 connect( d->wsReply, &QNetworkReply::finished, this, &Track::slotWsReply ); 0345 if( The::engineController()->currentTrack().data() == this ) 0346 emit skipTrack(); 0347 } 0348 0349 void Track::slotResultReady() 0350 { 0351 if( d->trackFetch->error() == QNetworkReply::NoError ) 0352 { 0353 lastfm::XmlQuery lfm; 0354 if( lfm.parse( d->trackFetch->readAll() ) ) 0355 { 0356 QString id = lfm[ "track" ][ "id" ].text(); 0357 QString streamable = lfm[ "track" ][ "streamable" ].text(); 0358 if( streamable.toInt() == 1 ) 0359 init( id.toInt() ); 0360 else 0361 init(); 0362 0363 } 0364 else 0365 { 0366 debug() << "Got exception in parsing from last.fm:" << lfm.parseError().message(); 0367 } 0368 } else 0369 { 0370 init(); 0371 } 0372 d->trackFetch->deleteLater(); 0373 } 0374 0375 0376 void 0377 Track::slotWsReply() 0378 { 0379 if( d->wsReply->error() == QNetworkReply::NoError ) 0380 { 0381 //debug() << "successfully completed WS transaction"; 0382 } else 0383 { 0384 debug() << "ERROR in last.fm ban!" << d->wsReply->error(); 0385 } 0386 } 0387 0388 bool 0389 Track::hasCapabilityInterface( Capabilities::Capability::Type type ) const 0390 { 0391 return type == Capabilities::Capability::MultiPlayable || 0392 type == Capabilities::Capability::SourceInfo || 0393 type == Capabilities::Capability::Actions || 0394 type == Capabilities::Capability::StreamInfo; 0395 } 0396 0397 Capabilities::Capability* 0398 Track::createCapabilityInterface( Capabilities::Capability::Type type ) 0399 { 0400 switch( type ) 0401 { 0402 case Capabilities::Capability::MultiPlayable: 0403 return new LastFmMultiPlayableCapability( this ); 0404 case Capabilities::Capability::SourceInfo: 0405 return new ServiceSourceInfoCapability( this ); 0406 case Capabilities::Capability::Actions: 0407 return new Capabilities::ActionsCapability( m_trackActions ); 0408 case Capabilities::Capability::StreamInfo: 0409 return new LastFmStreamInfoCapability( this ); 0410 default: 0411 return nullptr; 0412 } 0413 } 0414 0415 Meta::StatisticsPtr 0416 Track::statistics() 0417 { 0418 if( d->statsStore ) 0419 return d->statsStore; 0420 return Meta::Track::statistics(); 0421 } 0422 0423 QString LastFm::Track::sourceName() 0424 { 0425 return "Last.fm"; 0426 } 0427 0428 QString LastFm::Track::sourceDescription() 0429 { 0430 return i18n( "Last.fm is cool..." ); 0431 } 0432 0433 QPixmap LastFm::Track::emblem() 0434 { 0435 if ( !d->track.isEmpty() ) 0436 return QPixmap( QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/images/emblem-lastfm.png" ) ); 0437 else 0438 return QPixmap(); 0439 } 0440 0441 QString LastFm::Track::scalableEmblem() 0442 { 0443 if ( !d->track.isEmpty() ) 0444 return QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/images/emblem-lastfm-scalable.svg" ); 0445 else 0446 return QString(); 0447 } 0448 0449 #include "moc_LastFmMeta_p.cpp"