Make playlist items use the full width available to them and adjust correctly when...
[amarok.git] / src / lastfm.cpp
blobdc2fafcdcee98a96ded8bc172277873418a55dc8
1 /***************************************************************************
2 * copyright : (C) 2006 Chris Muehlhaeuser <chris@chris.de> *
3 * : (C) 2006 Seb Ruiz <me@sebruiz.net> *
4 * : (C) 2006 Ian Monroe <ian@monroe.nu> *
5 * : (C) 2006 Mark Kretschmann <markey@web.de> *
6 **************************************************************************/
8 /***************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 ***************************************************************************/
17 #define DEBUG_PREFIX "LastFm"
19 #include "lastfm.h"
21 #include "amarokconfig.h" //last.fm username and passwd
22 #include "amarok.h" //APP_VERSION, actioncollection
23 #include "collectiondb.h"
24 #include "debug.h"
25 #include "enginecontroller.h"
26 #include "statusbar.h" //showError()
28 #include <q3http.h>
29 #include <Q3ValueList>
30 #include <QByteArray>
31 #include <QDomDocument>
32 #include <QDomElement>
33 #include <QDomNode>
34 #include <QLabel>
35 #include <QRegExp>
37 #include <KAction>
38 #include <KApplication>
39 #include <KCodecs> //md5sum
40 #include <KHBox>
41 #include <KIO/Job>
42 #include <KLineEdit>
43 #include <KMessageBox>
44 #include <KProtocolManager>
45 #include <KShortcut>
46 #include <KUrl>
47 #include <KVBox>
49 #include <time.h>
50 #include <unistd.h>
52 using namespace LastFm;
54 ///////////////////////////////////////////////////////////////////////////////
55 // CLASS AmarokHttp
56 // AmarokHttp is a hack written so that lastfm code could easily use something proxy aware.
57 // DO NOT use this class for anything else, use KIO directly instead.
58 ////////////////////////////////////////////////////////////////////////////////
59 AmarokHttp::AmarokHttp ( const QString& hostname, quint16 port,
60 QObject* parent )
61 : QObject( parent ),
62 m_hostname( hostname ),
63 m_port( port )
66 int
67 AmarokHttp::get ( const QString & path )
69 QString uri = QString( "http://%1:%2/%3" )
70 .arg( m_hostname )
71 .arg( m_port )
72 .arg( path );
74 m_done = false;
75 m_error = Q3Http::NoError;
76 m_state = Q3Http::Connecting;
77 KIO::TransferJob *job = KIO::get(uri, true, false);
78 connect(job, SIGNAL(data(KIO::Job*, const QByteArray&)),
79 this, SLOT(slotData(KIO::Job*, const QByteArray&)));
80 connect(job, SIGNAL(result(KJob*)),
81 this, SLOT(slotResult(KJob*)));
83 return 0;
86 Q3Http::State
87 AmarokHttp::state() const
89 return m_state;
92 QByteArray
93 AmarokHttp::readAll ()
95 return m_result;
98 Q3Http::Error
99 AmarokHttp::error()
101 return m_error;
104 void
105 AmarokHttp::slotData(KIO::Job*, const QByteArray& data)
107 if( data.size() == 0 ) {
108 return;
110 else if ( m_result.size() == 0 ) {
111 m_result = data;
113 else {
114 m_result.resize( m_result.size() + data.size() );
115 memcpy( m_result.end(), data.data(), data.size() );
119 void
120 AmarokHttp::slotResult(KJob* job)
122 bool err = job->error();
123 if( err || m_error != Q3Http::NoError ) {
124 m_error = Q3Http::UnknownError;
126 else {
127 m_error = Q3Http::NoError;
129 m_done = true;
130 m_state = Q3Http::Unconnected;
131 emit( requestFinished( 0, err ) );
136 ///////////////////////////////////////////////////////////////////////////////
137 // CLASS Controller
138 ////////////////////////////////////////////////////////////////////////////////
140 Controller *Controller::s_instance = 0;
142 Controller::Controller()
143 : QObject( EngineController::instance() )
144 , m_service( 0 )
146 setObjectName( "lastfmController" );
147 KActionCollection* ac = Amarok::actionCollection();
148 KAction *action = new KAction( KIcon( Amarok::icon( "remove" ) ), i18n( "Ban" ), ac );
149 connect( action, SIGNAL( triggered( bool ) ), this, SLOT( ban() ) );
150 action->setShortcut( QKeySequence( Qt::CTRL | Qt::Key_B ) );
151 m_actionList.append( action );
153 action = new KAction( KIcon( Amarok::icon( "love" ) ), i18n( "Love" ), ac );
154 connect( action, SIGNAL( triggered( bool ) ), this, SLOT( love() ) );
155 action->setShortcut( QKeySequence( Qt::CTRL | Qt::Key_L ) );
156 m_actionList.append( action );
158 action = new KAction( KIcon( Amarok::icon( "next" ) ), i18n( "Skip" ), ac );
159 connect( action, SIGNAL( triggered( bool ) ), this, SLOT( skip() ) );
160 action->setShortcut( QKeySequence( Qt::CTRL | Qt::Key_K ) );
161 m_actionList.append( action );
162 setActionsEnabled( false );
166 Controller*
167 Controller::instance()
169 if( !s_instance ) s_instance = new Controller();
170 return s_instance;
174 KUrl
175 Controller::getNewProxy( QString genreUrl )
177 DEBUG_BLOCK
179 m_genreUrl = genreUrl;
181 if ( m_service ) playbackStopped();
183 m_service = new WebService( this );
185 if( checkCredentials() )
187 QString user = AmarokConfig::scrobblerUsername();
188 QString pass = AmarokConfig::scrobblerPassword();
190 if( !user.isEmpty() && !pass.isEmpty() &&
191 m_service->handshake( user, pass ) )
193 bool ok = m_service->changeStation( m_genreUrl );
194 if( ok ) // else playbackStopped()
196 if( !AmarokConfig::submitPlayedSongs() )
197 m_service->enableScrobbling( false );
198 setActionsEnabled( true );
199 return KUrl( m_service->proxyUrl() );
204 // Some kind of failure happened, so crap out
205 playbackStopped();
206 return KUrl();
210 void
211 Controller::playbackStopped() //SLOT
213 setActionsEnabled( false );
215 delete m_service;
216 m_service = 0;
220 bool
221 Controller::checkCredentials() //static
223 if( AmarokConfig::scrobblerUsername().isEmpty() || AmarokConfig::scrobblerPassword().isEmpty() )
225 LoginDialog dialog( 0 );
226 dialog.setCaption( "last.fm" );
227 return dialog.exec() == QDialog::Accepted;
229 return true;
233 QString
234 Controller::createCustomStation() //static
236 QString token;
237 CustomStationDialog dialog( 0 );
239 if( dialog.exec() == QDialog::Accepted )
241 token = dialog.text();
244 return token;
248 void
249 Controller::ban()
251 if( m_service )
252 m_service->ban();
256 void
257 Controller::love()
259 if( m_service )
260 m_service->love();
264 void
265 Controller::skip()
267 if( m_service )
268 m_service->skip();
272 void
273 Controller::setActionsEnabled( bool enable )
274 { //pausing last.fm streams doesn't do anything good
275 Amarok::actionCollection()->action( "play_pause" )->setEnabled( !enable );
276 Amarok::actionCollection()->action( "pause" )->setEnabled( !enable );
278 KAction* action;
279 for( action = m_actionList.first(); action; action = m_actionList.next() )
280 action->setEnabled( enable );
283 /// return a translatable description of the station we are connected to
284 QString
285 Controller::stationDescription( QString url )
287 if( url.isEmpty() && instance() && instance()->isPlaying() )
288 url = instance()->getService()->currentStation();
290 if( url.isEmpty() ) return QString();
292 QStringList elements = url.split( '/' );
294 /// TAG RADIOS
295 // eg: lastfm://globaltag/rock
296 if ( elements[1] == "globaltags" )
297 return i18n( "Global Tag Radio: %1", elements[2] );
299 /// ARTIST RADIOS
300 if ( elements[1] == "artist" )
302 // eg: lastfm://artist/Queen/similarartists
303 if ( elements[3] == "similarartists" )
304 return i18n( "Similar Artists to %1", elements[2] );
306 if ( elements[3] == "fans" )
307 return i18n( "Artist Fan Radio: %1", elements[2] );
310 /// CUSTOM STATION
311 if ( elements[1] == "artistnames" )
313 // eg: lastfm://artistnames/genesis,pink floyd,queen
315 // turn "genesis,pink floyd,queen" into "Genesis, Pink Floyd, Queen"
316 QString artists = elements[2];
317 artists.replace( ",", ", " );
318 const QStringList words = QString( artists ).remove( "," ).split( " " );
319 oldForeach( words ) {
320 QString capitalized = *it;
321 capitalized.replace( 0, 1, (*it)[0].toUpper() );
322 artists.replace( *it, capitalized );
325 return i18n( "Custom Station: %1", artists );
328 /// USER RADIOS
329 else if ( elements[1] == "user" )
331 // eg: lastfm://user/sebr/neighbours
332 if ( elements[3] == "neighbours" )
333 return i18n( "%1's Neighbor Radio", elements[2] );
335 // eg: lastfm://user/sebr/personal
336 if ( elements[3] == "personal" )
337 return i18n( "%1's Personal Radio", elements[2] );
339 // eg: lastfm://user/sebr/loved
340 if ( elements[3] == "loved" )
341 return i18n( "%1's Loved Radio", elements[2] );
343 // eg: lastfm://user/sebr/recommended/100 : 100 is number for how obscure the music should be
344 if ( elements[3] == "recommended" )
345 return i18n( "%1's Recommended Radio", elements[2] );
348 /// GROUP RADIOS
349 //eg: lastfm://group/Amarok%20users
350 else if ( elements[1] == "group" )
351 return i18n( "Group Radio: %1", elements[2] );
353 /// TRACK RADIOS
354 else if ( elements[1] == "play" )
356 if ( elements[2] == "tracks" )
357 return i18n( "Track Radio" );
358 else if ( elements[2] == "artists" )
359 return i18n( "Artist Radio" );
361 //kaput!
362 return url;
367 ////////////////////////////////////////////////////////////////////////////////
368 // CLASS WebService
369 ////////////////////////////////////////////////////////////////////////////////
371 WebService::WebService( QObject* parent )
372 : QObject( parent )
373 , m_server( 0 )
375 setObjectName( "lastfmParent" );
376 debug() << "Initialising Web Service";
380 WebService::~WebService()
382 DEBUG_BLOCK
384 delete m_server;
388 void
389 WebService::readProxy() //SLOT
391 QString line;
393 while( m_server->readln( line ) != -1 ) {
394 debug() << line;
396 if( line == "AMAROK_PROXY: SYNC" )
397 requestMetaData();
402 bool
403 WebService::handshake( const QString& username, const QString& password )
405 DEBUG_BLOCK
407 m_username = username;
408 m_password = password;
410 AmarokHttp http( "ws.audioscrobbler.com", 80 );
412 const QString path =
413 QString( "/radio/handshake.php?version=%1&platform=%2&username=%3&passwordmd5=%4&debug=%5" )
414 .arg( APP_VERSION ) //Muesli-approved: Amarok version, and Amarok-as-platform
415 .arg( QString("Amarok") )
416 .arg( QString( Q3Url( username ).encodedPathAndQuery() ) )
417 .arg( KMD5( m_password.toUtf8() ).hexDigest().data() )
418 .arg( "0" );
420 http.get( path );
423 kapp->processEvents();
424 while( http.state() != Q3Http::Unconnected );
426 if ( http.error() != Q3Http::NoError )
427 return false;
429 const QString result( http.readAll() );
431 debug() << "result: " << result;
433 m_session = parameter( "session", result );
434 m_baseHost = parameter( "base_url", result );
435 m_basePath = parameter( "base_path", result );
436 m_subscriber = parameter( "subscriber", result ) == "1";
437 m_streamUrl = Q3Url( parameter( "stream_url", result ) );
438 // bool banned = parameter( "banned", result ) == "1";
440 if ( m_session.toLower() == "failed" ) {
441 Amarok::StatusBar::instance()->longMessage( i18n(
442 "Amarok failed to establish a session with last.fm. <br>"
443 "Check if your last.fm user and password are correctly set."
444 ) );
445 return false;
448 Amarok::config( "Scrobbler" ).writeEntry( "Subscriber", m_subscriber );
450 // Find free port
451 MyServerSocket* socket = new MyServerSocket();
452 const int port = socket->port();
453 debug() << "Proxy server using port: " << port;
454 delete socket;
456 m_proxyUrl = QString( "http://localhost:%1/lastfm.mp3" ).arg( port );
458 m_server = new Amarok::ProcIO();
459 m_server->setComm( K3Process::Communication( K3Process::AllOutput ) );
460 *m_server << "amarok_proxy.rb";
461 *m_server << "--lastfm";
462 *m_server << QString::number( port );
463 *m_server << m_streamUrl.toString();
464 *m_server << AmarokConfig::soundSystem();
465 *m_server << Amarok::proxyForUrl( m_streamUrl.toString() );
467 if( !m_server->start( K3ProcIO::NotifyOnExit, true ) ) {
468 error() << "Failed to start amarok_proxy.rb" << endl;
469 return false;
472 QString line;
473 while( true ) {
474 kapp->processEvents();
475 m_server->readln( line );
476 if( line == "AMAROK_PROXY: startup" ) break;
479 connect( m_server, SIGNAL( readReady( K3ProcIO* ) ), this, SLOT( readProxy() ) );
480 connect( m_server, SIGNAL( processExited( K3Process* ) ), Controller::instance(), SLOT( playbackStopped() ) );
482 return true;
486 bool
487 WebService::changeStation( QString url )
489 debug() << "Changing station:" << url;
491 AmarokHttp http( m_baseHost, 80 );
493 http.get( QString( m_basePath + "/adjust.php?session=%1&url=%2&debug=0" )
494 .arg( m_session )
495 .arg( url ) );
498 kapp->processEvents();
499 while( http.state() != Q3Http::Unconnected );
501 if ( http.error() != Q3Http::NoError )
503 showError( E_OTHER ); // default error
504 return false;
507 const QString result( http.readAll() );
508 const int errCode = parameter( "error", result ).toInt();
510 if ( errCode )
512 showError( errCode );
513 return false;
516 const QString _url = parameter( "url", result );
517 if ( _url.startsWith( "lastfm://" ) )
519 m_station = _url; // parse it in stationDescription
520 emit stationChanged( _url, m_station );
522 else
523 emit stationChanged( _url, QString() );
525 return true;
528 void
529 WebService::requestMetaData() //SLOT
531 AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this );
532 foreach( QObject *observer, m_metaDataObservers )
533 connect( http, SIGNAL( requestFinished( int, bool ) ), observer, SLOT( metaDataFinished( int, bool ) ) );
535 http->get( QString( m_basePath + "/np.php?session=%1&debug=%2" )
536 .arg( m_session )
537 .arg( "0" ) );
541 void
542 WebService::metaDataFinished( int /*id*/, bool error ) //SLOT
544 DEBUG_BLOCK
546 AmarokHttp* http = (AmarokHttp*) sender();
547 http->deleteLater();
548 if( error ) return;
550 const QString result( http->readAll() );
551 debug() << result;
553 int errCode = parameter( "error", result ).toInt();
554 if ( errCode > 0 ) {
555 debug() << "Metadata failed with error code: " << errCode;
556 showError( errCode );
557 return;
560 m_metaBundle.setArtist( parameter( "artist", result ) );
561 m_metaBundle.setAlbum ( parameter( "album", result ) );
562 m_metaBundle.setTitle ( parameter( "track", result ) );
563 m_metaBundle.setUrl ( KUrl( Controller::instance()->getGenreUrl() ) );
564 m_metaBundle.setLength( parameter( "trackduration", result ).toInt() );
566 Bundle lastFmStuff;
567 QString imageUrl = parameter( "albumcover_medium", result );
569 if( imageUrl == "http://static.last.fm/coverart/" ||
570 imageUrl == "http://static.last.fm/depth/catalogue/no_album_large.gif" )
571 imageUrl.clear();
573 lastFmStuff.setImageUrl ( CollectionDB::instance()->notAvailCover( true ) );
574 lastFmStuff.setArtistUrl( parameter( "artist_url", result ) );
575 lastFmStuff.setAlbumUrl ( parameter( "album_url", result ) );
576 lastFmStuff.setTitleUrl ( parameter( "track_url", result ) );
577 // bool discovery = parameter( "discovery", result ) != "-1";
579 m_metaBundle.setLastFmBundle( lastFmStuff );
581 const KUrl u( imageUrl );
582 if( !u.isValid() ) {
583 debug() << "imageUrl empty or invalid.";
584 emit metaDataResult( m_metaBundle );
585 return;
588 KIO::Job* job = KIO::storedGet( u, true, false );
589 connect( job, SIGNAL( result( KIO::Job* ) ), this, SLOT( fetchImageFinished( KIO::Job* ) ) );
593 void
594 WebService::fetchImageFinished( KIO::Job* job ) //SLOT
596 DEBUG_BLOCK
598 if( job->error() == 0 ) {
599 const QString path = Amarok::saveLocation() + "lastfm_image.png";
600 const int size = AmarokConfig::coverPreviewSize();
602 QImage img = QImage::fromData( static_cast<KIO::StoredTransferJob*>( job )->data() );
603 img.scaled( size, size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation ).save( path, "PNG" );
605 m_metaBundle.lastFmBundle()->setImageUrl( CollectionDB::makeShadowedImage( path, false ) );
607 emit metaDataResult( m_metaBundle );
611 void
612 WebService::enableScrobbling( bool enabled ) //SLOT
614 if ( enabled )
615 debug() << "Enabling Scrobbling!";
616 else
617 debug() << "Disabling Scrobbling!";
619 AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this );
620 connect( http, SIGNAL( requestFinished( int, bool ) ), this, SLOT( enableScrobblingFinished( int, bool ) ) );
622 http->get( QString( m_basePath + "/control.php?session=%1&command=%2&debug=%3" )
623 .arg( m_session )
624 .arg( enabled ? QString( "rtp" ) : QString( "nortp" ) )
625 .arg( "0" ) );
629 void
630 WebService::enableScrobblingFinished( int /*id*/, bool error ) //SLOT
632 AmarokHttp* http = (AmarokHttp*) sender();
633 http->deleteLater();
634 if ( error ) return;
636 emit enableScrobblingDone();
640 void
641 WebService::love() //SLOT
643 AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this );
644 connect( http, SIGNAL( requestFinished( int, bool ) ), this, SLOT( loveFinished( int, bool ) ) );
646 http->get( QString( m_basePath + "/control.php?session=%1&command=love&debug=%2" )
647 .arg( m_session )
648 .arg( "0" ) );
649 Amarok::StatusBar::instance()->shortMessage( i18nc("love, as in affection", "Loving song...") );
653 void
654 WebService::skip() //SLOT
656 AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this );
657 connect( http, SIGNAL( requestFinished( int, bool ) ), this, SLOT( skipFinished( int, bool ) ) );
659 http->get( QString( m_basePath + "/control.php?session=%1&command=skip&debug=%2" )
660 .arg( m_session )
661 .arg( "0" ) );
662 Amarok::StatusBar::instance()->shortMessage( i18n("Skipping song...") );
666 void
667 WebService::ban() //SLOT
669 AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this );
670 connect( http, SIGNAL( requestFinished( int, bool ) ), this, SLOT( banFinished( int, bool ) ) );
672 http->get( QString( m_basePath + "/control.php?session=%1&command=ban&debug=%2" )
673 .arg( m_session )
674 .arg( "0" ) );
675 Amarok::StatusBar::instance()->shortMessage( i18nc("Ban, as in dislike", "Banning song...") );
679 void
680 WebService::loveFinished( int /*id*/, bool error ) //SLOT
682 DEBUG_BLOCK
684 AmarokHttp* http = (AmarokHttp*) sender();
685 http->deleteLater();
686 if( error ) return;
688 emit loveDone();
692 void
693 WebService::skipFinished( int /*id*/, bool error ) //SLOT
695 DEBUG_BLOCK
697 AmarokHttp* http = (AmarokHttp*) sender();
698 http->deleteLater();
699 if( error ) return;
701 EngineController::engine()->flushBuffer();
702 emit skipDone();
706 void
707 WebService::banFinished( int /*id*/, bool error ) //SLOT
709 DEBUG_BLOCK
711 AmarokHttp* http = (AmarokHttp*) sender();
712 http->deleteLater();
713 if( error ) return;
715 EngineController::engine()->flushBuffer();
716 emit banDone();
717 emit skipDone();
721 void
722 WebService::friends( QString username )
724 if ( username.isEmpty() )
725 username = m_username;
727 AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this );
728 connect( http, SIGNAL( requestFinished( bool ) ), this, SLOT( friendsFinished( bool ) ) );
730 http->get( QString( "/1.0/user/%1/friends.xml" )
731 .arg( QString( Q3Url( username ).encodedPathAndQuery() ) ) );
735 void
736 WebService::friendsFinished( int /*id*/, bool error ) //SLOT
738 AmarokHttp* http = (AmarokHttp*) sender();
739 http->deleteLater();
740 if( error ) return;
742 QDomDocument document;
743 document.setContent( http->readAll() );
745 if ( document.elementsByTagName( "friends" ).length() == 0 )
747 emit friendsResult( QString( "" ), QStringList() );
748 return;
751 QStringList friends;
752 QString user = document.elementsByTagName( "friends" ).item( 0 ).attributes().namedItem( "user" ).nodeValue();
753 QDomNodeList values = document.elementsByTagName( "user" );
754 for ( int i = 0; i < values.count(); i++ )
756 friends << values.item( i ).attributes().namedItem( "username" ).nodeValue();
759 emit friendsResult( user, friends );
763 void
764 WebService::neighbours( QString username )
766 if ( username.isEmpty() )
767 username = m_username;
769 AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this );
770 connect( http, SIGNAL( requestFinished( bool ) ), this, SLOT( neighboursFinished( bool ) ) );
772 http->get( QString( "/1.0/user/%1/neighbours.xml" )
773 .arg( QString( Q3Url( username ).encodedPathAndQuery() ) ) );
777 void
778 WebService::neighboursFinished( int /*id*/, bool error ) //SLOT
780 AmarokHttp* http = (AmarokHttp*) sender();
781 http->deleteLater();
782 if( error ) return;
784 QDomDocument document;
785 document.setContent( http->readAll() );
787 if ( document.elementsByTagName( "neighbours" ).length() == 0 )
789 emit friendsResult( QString( "" ), QStringList() );
790 return;
793 QStringList neighbours;
794 QString user = document.elementsByTagName( "neighbours" ).item( 0 ).attributes().namedItem( "user" ).nodeValue();
795 QDomNodeList values = document.elementsByTagName( "user" );
796 for ( int i = 0; i < values.count(); i++ )
798 neighbours << values.item( i ).attributes().namedItem( "username" ).nodeValue();
801 emit neighboursResult( user, neighbours );
805 void
806 WebService::userTags( QString username )
808 if ( username.isEmpty() )
809 username = m_username;
811 AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this );
812 connect( http, SIGNAL( requestFinished( bool ) ), this, SLOT( userTagsFinished( bool ) ) );
814 http->get( QString( "/1.0/user/%1/tags.xml?debug=%2" )
815 .arg( QString( Q3Url( username ).encodedPathAndQuery() ) ) );
819 void
820 WebService::userTagsFinished( int /*id*/, bool error ) //SLOT
822 AmarokHttp* http = (AmarokHttp*) sender();
823 http->deleteLater();
824 if( error ) return;
826 QDomDocument document;
827 document.setContent( http->readAll() );
829 if ( document.elementsByTagName( "toptags" ).length() == 0 )
831 emit userTagsResult( QString(), QStringList() );
832 return;
835 QStringList tags;
836 QDomNodeList values = document.elementsByTagName( "tag" );
837 QString user = document.elementsByTagName( "toptags" ).item( 0 ).attributes().namedItem( "user" ).nodeValue();
838 for ( int i = 0; i < values.count(); i++ )
840 QDomNode item = values.item( i ).namedItem( "name" );
841 tags << item.toElement().text();
843 emit userTagsResult( user, tags );
847 void
848 WebService::recentTracks( QString username )
850 if ( username.isEmpty() )
851 username = m_username;
853 AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this );
854 connect( http, SIGNAL( requestFinished( bool ) ), this, SLOT( recentTracksFinished( bool ) ) );
856 http->get( QString( "/1.0/user/%1/recenttracks.xml" )
857 .arg( QString( Q3Url( username ).encodedPathAndQuery() ) ) );
861 void
862 WebService::recentTracksFinished( int /*id*/, bool error ) //SLOT
864 AmarokHttp* http = (AmarokHttp*) sender();
865 http->deleteLater();
866 if( error ) return;
868 Q3ValueList< QPair<QString, QString> > songs;
869 QDomDocument document;
870 document.setContent( http->readAll() );
872 if ( document.elementsByTagName( "recenttracks" ).length() == 0 )
874 emit recentTracksResult( QString(), songs );
875 return;
878 QDomNodeList values = document.elementsByTagName( "track" );
879 QString user = document.elementsByTagName( "recenttracks" ).item( 0 ).attributes().namedItem( "user" ).nodeValue();
880 for ( int i = 0; i < values.count(); i++ )
882 QPair<QString, QString> song;
883 song.first = values.item( i ).namedItem( "artist" ).toElement().text();
884 song.second = values.item( i ).namedItem( "name" ).toElement().text();
886 songs << song;
888 emit recentTracksResult( user, songs );
892 void
893 WebService::recommend( int type, QString username, QString artist, QString token )
895 QString modeToken = "";
896 switch ( type )
898 case 0:
899 modeToken = QString( "artist_name=%1" ).arg( QString( Q3Url( artist ).encodedPathAndQuery() ) );
900 break;
902 case 1:
903 modeToken = QString( "album_artist=%1&album_name=%2" )
904 .arg( QString( Q3Url( artist ).encodedPathAndQuery() ) )
905 .arg( QString( Q3Url( token ).encodedPathAndQuery() ) );
906 break;
908 case 2:
909 modeToken = QString( "track_artist=%1&track_name=%2" )
910 .arg( QString( Q3Url( artist ).encodedPathAndQuery() ) )
911 .arg( QString( Q3Url( token ).encodedPathAndQuery() ) );
912 break;
915 Q3Http *http = new Q3Http( "wsdev.audioscrobbler.com", 80, this );
916 connect( http, SIGNAL( requestFinished( bool ) ), this, SLOT( recommendFinished( bool ) ) );
918 uint currentTime = QDateTime::currentDateTime().toUTC().toTime_t();
919 QString challenge = QString::number( currentTime );
921 QByteArray md5pass = KMD5( KMD5( m_password.toUtf8() ).hexDigest().append( QString::number( currentTime ).toLocal8Bit() ) ).hexDigest();
923 QString atoken = QString( "user=%1&auth=%2&nonce=%3recipient=%4" )
924 .arg( QString( Q3Url( currentUsername() ).encodedPathAndQuery() ) )
925 .arg( QString( Q3Url( md5pass ).encodedPathAndQuery() ) )
926 .arg( QString( Q3Url( challenge ).encodedPathAndQuery() ) )
927 .arg( QString( Q3Url( username ).encodedPathAndQuery() ) );
929 Q3HttpRequestHeader header( "POST", "/1.0/rw/recommend.php?" + atoken.toUtf8() );
930 header.setValue( "Host", "wsdev.audioscrobbler.com" );
931 header.setContentType( "application/x-www-form-urlencoded" );
932 http->request( header, modeToken.toUtf8() );
936 void
937 WebService::recommendFinished( int /*id*/, bool /*error*/ ) //SLOT
939 AmarokHttp* http = (AmarokHttp*) sender();
940 http->deleteLater();
942 debug() << "Recommendation:" << http->readAll();
946 QString
947 WebService::parameter( const QString keyName, const QString data ) const
949 QStringList list = data.split( '\n' );
951 for ( int i = 0; i < list.size(); i++ )
953 QStringList values = list[i].split( '=' );
954 if ( values[0] == keyName )
956 values.removeAt( 0 );
957 return QString::fromUtf8( values.join( "=" ).toAscii() );
961 return QString( "" );
965 QStringList
966 WebService::parameterArray( const QString keyName, const QString data ) const
968 QStringList result;
969 QStringList list = data.split( '\n' );
971 for ( int i = 0; i < list.size(); i++ )
973 QStringList values = list[i].split( '=' );
974 if ( values[0].startsWith( keyName ) )
976 values.removeAt( 0 );
977 result.append( QString::fromUtf8( values.join( "=" ).toAscii() ) );
981 return result;
985 QStringList
986 WebService::parameterKeys( const QString keyName, const QString data ) const
988 QStringList result;
989 QStringList list = data.split( '\n' );
991 for ( int i = 0; i < list.size(); i++ )
993 QStringList values = list[i].split( '=' );
994 if ( values[0].startsWith( keyName ) )
996 values = values[0].split( '[' );
997 values = values[1].split( ']' );
998 result.append( values[0] );
1003 return result;
1006 void
1007 WebService::showError( int code, QString message )
1009 switch ( code )
1011 case E_NOCONTENT:
1012 message = i18n( "There is not enough content to play this station." );
1013 break;
1014 case E_NOMEMBERS:
1015 message = i18n( "This group does not have enough members for radio." );
1016 break;
1017 case E_NOFANS:
1018 message = i18n( "This artist does not have enough fans for radio." );
1019 break;
1020 case E_NOAVAIL:
1021 message = i18n( "This item is not available for streaming." );
1022 break;
1023 case E_NOSUBSCRIBER:
1024 message = i18n( "This feature is only available to last.fm subscribers." );
1025 break;
1026 case E_NONEIGHBOURS:
1027 message = i18n( "There are not enough neighbors for this radio." );
1028 break;
1029 case E_NOSTOPPED:
1030 message = i18n( "This stream has stopped. Please try another station." );
1031 break;
1032 default:
1033 if( message.isEmpty() )
1034 message = i18n( "Failed to play this last.fm stream." );
1037 Amarok::StatusBar::instance()->longMessage( message, KDE::StatusBar::Sorry );
1040 ////////////////////////////////////////////////////////////////////////////////
1041 // CLASS LastFm::Bundle
1042 ////////////////////////////////////////////////////////////////////////////////
1044 Bundle::Bundle( const Bundle& lhs )
1045 : m_imageUrl( lhs.m_imageUrl )
1046 , m_albumUrl( lhs.m_albumUrl )
1047 , m_artistUrl( lhs.m_artistUrl )
1048 , m_titleUrl( lhs.m_titleUrl )
1051 ////////////////////////////////////////////////////////////////////////////////
1052 // CLASS LastFm::LoginDialog
1053 ////////////////////////////////////////////////////////////////////////////////
1054 LoginDialog::LoginDialog( QWidget *parent )
1055 : KDialog( parent )
1057 setModal( true );
1058 setButtons( Ok | Cancel );
1060 //makeGridMainWidget( 1, Qt::Horizontal );
1061 KVBox* vbox = new KVBox( this );
1062 setMainWidget( vbox );
1063 new QLabel( i18n( "To use last.fm with Amarok, you need a last.fm profile." ), vbox );
1065 //makeGridMainWidget( 2, Qt::Horizontal );
1066 KHBox* hbox = new KHBox( vbox );
1067 QLabel *nameLabel = new QLabel( i18n("&Username:"), hbox );
1068 m_userLineEdit = new KLineEdit( hbox );
1069 nameLabel->setBuddy( m_userLineEdit );
1071 QLabel *passLabel = new QLabel( i18n("&Password:"), hbox );
1072 m_passLineEdit = new KLineEdit( hbox );
1073 m_passLineEdit->setEchoMode( QLineEdit::Password );
1074 passLabel->setBuddy( m_passLineEdit );
1076 m_userLineEdit->setFocus();
1080 void LoginDialog::slotButtonClicked( ButtonCode button )
1082 if ( button == Ok ) {
1083 AmarokConfig::setScrobblerUsername( m_userLineEdit->text() );
1084 AmarokConfig::setScrobblerPassword( m_passLineEdit->text() );
1087 KDialog::slotButtonClicked( button );
1091 ////////////////////////////////////////////////////////////////////////////////
1092 // CLASS LastFm::CustomStationDialog
1093 ////////////////////////////////////////////////////////////////////////////////
1094 CustomStationDialog::CustomStationDialog( QWidget *parent )
1095 : KDialog( parent )
1097 setCaption( i18n( "Create Custom Station" ) );
1098 setModal( true );
1099 setButtons( Ok | Cancel );
1102 KVBox *vbox = new KVBox( this );
1103 setMainWidget( vbox );
1106 new QLabel( i18n( "Enter the name of a band or artist you like:" ), mainWidget() );
1108 m_edit = new KLineEdit( mainWidget() );
1109 m_edit->setFocus();
1113 QString
1114 CustomStationDialog::text() const
1116 return m_edit->text();
1120 #include "lastfm.moc"