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"