Revert previous commit, was incorrect
[amarok.git] / src / lastfm.cpp
blobe21119471a91799d99d98f9121be0ea14455aeda
1 /***************************************************************************
2 * copyright : (C) 2006 Chris Muehlhaeuser <chris@chris.de> *
3 * : (C) 2006 Seb Ruiz <ruiz@kde.org> *
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 "Process.h"
27 #include "ContextStatusBar.h" //showError()
29 #include <q3http.h>
30 #include <Q3ValueList>
31 #include <QByteArray>
32 #include <QDomDocument>
33 #include <QDomElement>
34 #include <QDomNode>
35 #include <QLabel>
36 #include <QRegExp>
38 #include <KAction>
39 #include <KApplication>
40 #include <KCodecs> //md5sum
41 #include <KHBox>
42 #include <KIO/Job>
43 #include <KLineEdit>
44 #include <KMessageBox>
45 #include <KProtocolManager>
46 #include <KShortcut>
47 #include <KUrl>
48 #include <KVBox>
50 #include <time.h>
51 #include <unistd.h>
53 using namespace LastFm;
55 ///////////////////////////////////////////////////////////////////////////////
56 // CLASS AmarokHttp
57 // AmarokHttp is a hack written so that lastfm code could easily use something proxy aware.
58 // DO NOT use this class for anything else, use KIO directly instead.
59 ////////////////////////////////////////////////////////////////////////////////
60 AmarokHttp::AmarokHttp ( const QString& hostname, quint16 port,
61 QObject* parent )
62 : QObject( parent ),
63 m_hostname( hostname ),
64 m_port( port )
67 int
68 AmarokHttp::get ( const QString & path )
70 QString uri = QString( "http://%1:%2/%3" )
71 .arg( m_hostname )
72 .arg( m_port )
73 .arg( path );
75 m_done = false;
76 m_error = Q3Http::NoError;
77 m_state = Q3Http::Connecting;
78 KIO::TransferJob *job = KIO::get(uri, KIO::Reload, KIO::HideProgressInfo);
79 connect(job, SIGNAL(data(KIO::Job*, const QByteArray&)),
80 this, SLOT(slotData(KIO::Job*, const QByteArray&)));
81 connect(job, SIGNAL(result(KJob*)),
82 this, SLOT(slotResult(KJob*)));
84 return 0;
87 Q3Http::State
88 AmarokHttp::state() const
90 return m_state;
93 QByteArray
94 AmarokHttp::readAll ()
96 return m_result;
99 Q3Http::Error
100 AmarokHttp::error()
102 return m_error;
105 void
106 AmarokHttp::slotData(KIO::Job*, const QByteArray& data)
108 if( data.size() == 0 ) {
109 return;
111 else if ( m_result.size() == 0 ) {
112 m_result = data;
114 else {
115 m_result.resize( m_result.size() + data.size() );
116 memcpy( m_result.end(), data.data(), data.size() );
120 void
121 AmarokHttp::slotResult(KJob* job)
123 bool err = job->error();
124 if( err || m_error != Q3Http::NoError ) {
125 m_error = Q3Http::UnknownError;
127 else {
128 m_error = Q3Http::NoError;
130 m_done = true;
131 m_state = Q3Http::Unconnected;
132 emit( requestFinished( 0, err ) );
137 ///////////////////////////////////////////////////////////////////////////////
138 // CLASS Controller
139 ////////////////////////////////////////////////////////////////////////////////
141 Controller *Controller::s_instance = 0;
143 Controller::Controller()
144 : QObject( EngineController::instance() )
145 , m_service( 0 )
147 setObjectName( "lastfmController" );
148 KActionCollection* ac = Amarok::actionCollection();
149 KAction *action = new KAction( KIcon( Amarok::icon( "remove" ) ), i18n( "Ban" ), ac );
150 connect( action, SIGNAL( triggered( bool ) ), this, SLOT( ban() ) );
151 action->setShortcut( QKeySequence( Qt::CTRL | Qt::Key_B ) );
152 m_actionList.append( action );
154 action = new KAction( KIcon( Amarok::icon( "love" ) ), i18n( "Love" ), ac );
155 connect( action, SIGNAL( triggered( bool ) ), this, SLOT( love() ) );
156 action->setShortcut( QKeySequence( Qt::CTRL | Qt::Key_L ) );
157 m_actionList.append( action );
159 action = new KAction( KIcon( Amarok::icon( "next" ) ), i18n( "Skip" ), ac );
160 connect( action, SIGNAL( triggered( bool ) ), this, SLOT( skip() ) );
161 action->setShortcut( QKeySequence( Qt::CTRL | Qt::Key_K ) );
162 m_actionList.append( action );
163 setActionsEnabled( false );
167 Controller*
168 Controller::instance()
170 if( !s_instance ) s_instance = new Controller();
171 return s_instance;
175 KUrl
176 Controller::getNewProxy( QString genreUrl )
178 DEBUG_BLOCK
180 m_genreUrl = genreUrl;
182 if ( m_service ) playbackStopped();
184 m_service = new WebService( this );
186 if( checkCredentials() )
188 QString user = AmarokConfig::scrobblerUsername();
189 QString pass = AmarokConfig::scrobblerPassword();
191 if( !user.isEmpty() && !pass.isEmpty() &&
192 m_service->handshake( user, pass ) )
194 bool ok = m_service->changeStation( m_genreUrl );
195 if( ok ) // else playbackStopped()
197 if( !AmarokConfig::submitPlayedSongs() )
198 m_service->enableScrobbling( false );
199 setActionsEnabled( true );
200 return KUrl( m_service->proxyUrl() );
205 // Some kind of failure happened, so crap out
206 playbackStopped();
207 return KUrl();
211 void
212 Controller::playbackStopped() //SLOT
214 setActionsEnabled( false );
216 delete m_service;
217 m_service = 0;
221 bool
222 Controller::checkCredentials() //static
224 if( AmarokConfig::scrobblerUsername().isEmpty() || AmarokConfig::scrobblerPassword().isEmpty() )
226 LoginDialog dialog( 0 );
227 dialog.setCaption( "last.fm" );
228 return dialog.exec() == QDialog::Accepted;
230 return true;
234 QString
235 Controller::createCustomStation() //static
237 QString token;
238 CustomStationDialog dialog( 0 );
240 if( dialog.exec() == QDialog::Accepted )
242 token = dialog.text();
245 return token;
249 void
250 Controller::ban()
252 if( m_service )
253 m_service->ban();
257 void
258 Controller::love()
260 if( m_service )
261 m_service->love();
265 void
266 Controller::skip()
268 if( m_service )
269 m_service->skip();
273 void
274 Controller::setActionsEnabled( bool enable )
275 { //pausing last.fm streams doesn't do anything good
276 Amarok::actionCollection()->action( "play_pause" )->setEnabled( !enable );
277 Amarok::actionCollection()->action( "pause" )->setEnabled( !enable );
279 KAction* action;
280 for( action = m_actionList.first(); action; action = m_actionList.next() )
281 action->setEnabled( enable );
284 /// return a translatable description of the station we are connected to
285 QString
286 Controller::stationDescription( QString url )
288 if( url.isEmpty() && instance() && instance()->isPlaying() )
289 url = instance()->getService()->currentStation();
291 if( url.isEmpty() ) return QString();
293 QStringList elements = url.split( '/' );
295 /// TAG RADIOS
296 // eg: lastfm://globaltag/rock
297 if ( elements[1] == "globaltags" )
298 return i18n( "Global Tag Radio: %1", elements[2] );
300 /// ARTIST RADIOS
301 if ( elements[1] == "artist" )
303 // eg: lastfm://artist/Queen/similarartists
304 if ( elements[3] == "similarartists" )
305 return i18n( "Similar Artists to %1", elements[2] );
307 if ( elements[3] == "fans" )
308 return i18n( "Artist Fan Radio: %1", elements[2] );
311 /// CUSTOM STATION
312 if ( elements[1] == "artistnames" )
314 // eg: lastfm://artistnames/genesis,pink floyd,queen
316 // turn "genesis,pink floyd,queen" into "Genesis, Pink Floyd, Queen"
317 QString artists = elements[2];
318 artists.replace( ",", ", " );
319 const QStringList words = QString( artists ).remove( "," ).split( " " );
320 foreach( const QString &word, words ) {
321 QString capitalized = word;
322 capitalized.replace( 0, 1, word[0].toUpper() );
323 artists.replace( word, capitalized );
326 return i18n( "Custom Station: %1", artists );
329 /// USER RADIOS
330 else if ( elements[1] == "user" )
332 // eg: lastfm://user/sebr/neighbours
333 if ( elements[3] == "neighbours" )
334 return i18n( "%1's Neighbor Radio", elements[2] );
336 // eg: lastfm://user/sebr/personal
337 if ( elements[3] == "personal" )
338 return i18n( "%1's Personal Radio", elements[2] );
340 // eg: lastfm://user/sebr/loved
341 if ( elements[3] == "loved" )
342 return i18n( "%1's Loved Radio", elements[2] );
344 // eg: lastfm://user/sebr/recommended/100 : 100 is number for how obscure the music should be
345 if ( elements[3] == "recommended" )
346 return i18n( "%1's Recommended Radio", elements[2] );
349 /// GROUP RADIOS
350 //eg: lastfm://group/Amarok%20users
351 else if ( elements[1] == "group" )
352 return i18n( "Group Radio: %1", elements[2] );
354 /// TRACK RADIOS
355 else if ( elements[1] == "play" )
357 if ( elements[2] == "tracks" )
358 return i18n( "Track Radio" );
359 else if ( elements[2] == "artists" )
360 return i18n( "Artist Radio" );
362 //kaput!
363 return url;
368 ////////////////////////////////////////////////////////////////////////////////
369 // CLASS WebService
370 ////////////////////////////////////////////////////////////////////////////////
372 WebService::WebService( QObject* parent )
373 : QObject( parent )
374 , m_server( 0 )
376 setObjectName( "lastfmParent" );
377 debug() << "Initialising Web Service";
381 WebService::~WebService()
383 DEBUG_BLOCK
385 delete m_server;
389 void
390 WebService::readProxy() //SLOT
392 QString line;
394 while( m_server->readln( line ) != -1 ) {
395 debug() << line;
397 if( line == "AMAROK_PROXY: SYNC" )
398 requestMetaData();
403 bool
404 WebService::handshake( const QString& username, const QString& password )
406 DEBUG_BLOCK
408 m_username = username;
409 m_password = password;
411 AmarokHttp http( "ws.audioscrobbler.com", 80 );
413 const QString path =
414 QString( "/radio/handshake.php?version=%1&platform=%2&username=%3&passwordmd5=%4&debug=%5" )
415 .arg( APP_VERSION ) //Muesli-approved: Amarok version, and Amarok-as-platform
416 .arg( QString("Amarok") )
417 .arg( QString( Q3Url( username ).encodedPathAndQuery() ) )
418 .arg( KMD5( m_password.toUtf8() ).hexDigest().data() )
419 .arg( "0" );
421 http.get( path );
424 kapp->processEvents();
425 while( http.state() != Q3Http::Unconnected );
427 if ( http.error() != Q3Http::NoError )
428 return false;
430 const QString result( http.readAll() );
432 debug() << "result: " << result;
434 m_session = parameter( "session", result );
435 m_baseHost = parameter( "base_url", result );
436 m_basePath = parameter( "base_path", result );
437 m_subscriber = parameter( "subscriber", result ) == "1";
438 m_streamUrl = Q3Url( parameter( "stream_url", result ) );
439 // bool banned = parameter( "banned", result ) == "1";
441 if ( m_session.toLower() == "failed" ) {
442 Amarok::ContextStatusBar::instance()->longMessage( i18n(
443 "Amarok failed to establish a session with last.fm. <br>"
444 "Check if your last.fm user and password are correctly set."
445 ) );
446 return false;
449 Amarok::config( "Scrobbler" ).writeEntry( "Subscriber", m_subscriber );
451 // Find free port
452 MyServerSocket* socket = new MyServerSocket();
453 const int port = socket->port();
454 debug() << "Proxy server using port: " << port;
455 delete socket;
457 m_proxyUrl = QString( "http://localhost:%1/lastfm.mp3" ).arg( port );
459 m_server = new ProcIO();
460 m_server->setOutputChannelMode( ProcIO::MergedChannels );
461 *m_server << "amarok_proxy.rb";
462 *m_server << "--lastfm";
463 *m_server << QString::number( port );
464 *m_server << m_streamUrl.toString();
465 *m_server << AmarokConfig::soundSystem();
466 *m_server << Amarok::proxyForUrl( m_streamUrl.toString() );
468 m_server->start();
469 QString line;
470 while( true ) {
471 if( m_server->error() != QProcess::UnknownError ) return false;
473 kapp->processEvents();
474 m_server->readln( line );
475 if( line == "AMAROK_PROXY: startup" ) break;
478 connect( m_server, SIGNAL( readReady( ProcIO* ) ), this, SLOT( readProxy() ) );
479 connect( m_server, SIGNAL( finished( int ) ), Controller::instance(), SLOT( playbackStopped() ) );
481 return true;
485 bool
486 WebService::changeStation( QString url )
488 debug() << "Changing station:" << url;
490 AmarokHttp http( m_baseHost, 80 );
492 http.get( QString( m_basePath + "/adjust.php?session=%1&url=%2&debug=0" )
493 .arg( m_session )
494 .arg( url ) );
497 kapp->processEvents();
498 while( http.state() != Q3Http::Unconnected );
500 if ( http.error() != Q3Http::NoError )
502 showError( E_OTHER ); // default error
503 return false;
506 const QString result( http.readAll() );
507 const int errCode = parameter( "error", result ).toInt();
509 if ( errCode )
511 showError( errCode );
512 return false;
515 const QString _url = parameter( "url", result );
516 if ( _url.startsWith( "lastfm://" ) )
518 m_station = _url; // parse it in stationDescription
519 emit stationChanged( _url, m_station );
521 else
522 emit stationChanged( _url, QString() );
524 return true;
527 void
528 WebService::requestMetaData() //SLOT
530 AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this );
531 foreach( QObject *observer, m_metaDataObservers )
532 connect( http, SIGNAL( requestFinished( int, bool ) ), observer, SLOT( metaDataFinished( int, bool ) ) );
534 http->get( QString( m_basePath + "/np.php?session=%1&debug=%2" )
535 .arg( m_session )
536 .arg( "0" ) );
540 void
541 WebService::metaDataFinished( int /*id*/, bool error ) //SLOT
543 DEBUG_BLOCK
545 AmarokHttp* http = (AmarokHttp*) sender();
546 http->deleteLater();
547 if( error ) return;
549 const QString result( http->readAll() );
550 debug() << result;
552 int errCode = parameter( "error", result ).toInt();
553 if ( errCode > 0 ) {
554 debug() << "Metadata failed with error code: " << errCode;
555 showError( errCode );
556 return;
559 m_metaBundle.setArtist( parameter( "artist", result ) );
560 m_metaBundle.setAlbum ( parameter( "album", result ) );
561 m_metaBundle.setTitle ( parameter( "track", result ) );
562 m_metaBundle.setUrl ( KUrl( Controller::instance()->getGenreUrl() ) );
563 m_metaBundle.setLength( parameter( "trackduration", result ).toInt() );
565 Bundle lastFmStuff;
566 QString imageUrl = parameter( "albumcover_medium", result );
568 if( imageUrl == "http://static.last.fm/coverart/" ||
569 imageUrl == "http://static.last.fm/depth/catalogue/no_album_large.gif" )
570 imageUrl.clear();
572 lastFmStuff.setImageUrl ( CollectionDB::instance()->notAvailCover( true ) );
573 lastFmStuff.setArtistUrl( parameter( "artist_url", result ) );
574 lastFmStuff.setAlbumUrl ( parameter( "album_url", result ) );
575 lastFmStuff.setTitleUrl ( parameter( "track_url", result ) );
576 // bool discovery = parameter( "discovery", result ) != "-1";
578 m_metaBundle.setLastFmBundle( lastFmStuff );
580 const KUrl u( imageUrl );
581 if( !u.isValid() ) {
582 debug() << "imageUrl empty or invalid.";
583 emit metaDataResult( m_metaBundle );
584 return;
587 KIO::Job* job = KIO::storedGet( u, KIO::Reload, KIO::HideProgressInfo );
588 connect( job, SIGNAL( result( KIO::Job* ) ), this, SLOT( fetchImageFinished( KIO::Job* ) ) );
592 void
593 WebService::fetchImageFinished( KIO::Job* job ) //SLOT
595 DEBUG_BLOCK
597 if( job->error() == 0 ) {
598 const QString path = Amarok::saveLocation() + "lastfm_image.png";
599 const int size = AmarokConfig::coverPreviewSize();
601 QImage img = QImage::fromData( static_cast<KIO::StoredTransferJob*>( job )->data() );
602 img.scaled( size, size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation ).save( path, "PNG" );
604 m_metaBundle.lastFmBundle()->setImageUrl( CollectionDB::makeShadowedImage( path, false ) );
606 emit metaDataResult( m_metaBundle );
610 void
611 WebService::enableScrobbling( bool enabled ) //SLOT
613 if ( enabled )
614 debug() << "Enabling Scrobbling!";
615 else
616 debug() << "Disabling Scrobbling!";
618 AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this );
619 connect( http, SIGNAL( requestFinished( int, bool ) ), this, SLOT( enableScrobblingFinished( int, bool ) ) );
621 http->get( QString( m_basePath + "/control.php?session=%1&command=%2&debug=%3" )
622 .arg( m_session )
623 .arg( enabled ? QString( "rtp" ) : QString( "nortp" ) )
624 .arg( "0" ) );
628 void
629 WebService::enableScrobblingFinished( int /*id*/, bool error ) //SLOT
631 AmarokHttp* http = (AmarokHttp*) sender();
632 http->deleteLater();
633 if ( error ) return;
635 emit enableScrobblingDone();
639 void
640 WebService::love() //SLOT
642 AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this );
643 connect( http, SIGNAL( requestFinished( int, bool ) ), this, SLOT( loveFinished( int, bool ) ) );
645 http->get( QString( m_basePath + "/control.php?session=%1&command=love&debug=%2" )
646 .arg( m_session )
647 .arg( "0" ) );
648 Amarok::ContextStatusBar::instance()->shortMessage( i18nc("love, as in affection", "Loving song...") );
652 void
653 WebService::skip() //SLOT
655 AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this );
656 connect( http, SIGNAL( requestFinished( int, bool ) ), this, SLOT( skipFinished( int, bool ) ) );
658 http->get( QString( m_basePath + "/control.php?session=%1&command=skip&debug=%2" )
659 .arg( m_session )
660 .arg( "0" ) );
661 Amarok::ContextStatusBar::instance()->shortMessage( i18n("Skipping song...") );
665 void
666 WebService::ban() //SLOT
668 AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this );
669 connect( http, SIGNAL( requestFinished( int, bool ) ), this, SLOT( banFinished( int, bool ) ) );
671 http->get( QString( m_basePath + "/control.php?session=%1&command=ban&debug=%2" )
672 .arg( m_session )
673 .arg( "0" ) );
674 Amarok::ContextStatusBar::instance()->shortMessage( i18nc("Ban, as in dislike", "Banning song...") );
678 void
679 WebService::loveFinished( int /*id*/, bool error ) //SLOT
681 DEBUG_BLOCK
683 AmarokHttp* http = (AmarokHttp*) sender();
684 http->deleteLater();
685 if( error ) return;
687 emit loveDone();
691 void
692 WebService::skipFinished( int /*id*/, bool error ) //SLOT
694 DEBUG_BLOCK
696 AmarokHttp* http = (AmarokHttp*) sender();
697 http->deleteLater();
698 if( error ) return;
700 EngineController::engine()->flushBuffer();
701 emit skipDone();
705 void
706 WebService::banFinished( int /*id*/, bool error ) //SLOT
708 DEBUG_BLOCK
710 AmarokHttp* http = (AmarokHttp*) sender();
711 http->deleteLater();
712 if( error ) return;
714 EngineController::engine()->flushBuffer();
715 emit banDone();
716 emit skipDone();
720 void
721 WebService::friends( QString username )
723 if ( username.isEmpty() )
724 username = m_username;
726 AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this );
727 connect( http, SIGNAL( requestFinished( bool ) ), this, SLOT( friendsFinished( bool ) ) );
729 http->get( QString( "/1.0/user/%1/friends.xml" )
730 .arg( QString( Q3Url( username ).encodedPathAndQuery() ) ) );
734 void
735 WebService::friendsFinished( int /*id*/, bool error ) //SLOT
737 AmarokHttp* http = (AmarokHttp*) sender();
738 http->deleteLater();
739 if( error ) return;
741 QDomDocument document;
742 document.setContent( http->readAll() );
744 if ( document.elementsByTagName( "friends" ).length() == 0 )
746 emit friendsResult( QString( "" ), QStringList() );
747 return;
750 QStringList friends;
751 QString user = document.elementsByTagName( "friends" ).item( 0 ).attributes().namedItem( "user" ).nodeValue();
752 QDomNodeList values = document.elementsByTagName( "user" );
753 for ( int i = 0; i < values.count(); i++ )
755 friends << values.item( i ).attributes().namedItem( "username" ).nodeValue();
758 emit friendsResult( user, friends );
762 void
763 WebService::neighbours( QString username )
765 if ( username.isEmpty() )
766 username = m_username;
768 AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this );
769 connect( http, SIGNAL( requestFinished( bool ) ), this, SLOT( neighboursFinished( bool ) ) );
771 http->get( QString( "/1.0/user/%1/neighbours.xml" )
772 .arg( QString( Q3Url( username ).encodedPathAndQuery() ) ) );
776 void
777 WebService::neighboursFinished( int /*id*/, bool error ) //SLOT
779 AmarokHttp* http = (AmarokHttp*) sender();
780 http->deleteLater();
781 if( error ) return;
783 QDomDocument document;
784 document.setContent( http->readAll() );
786 if ( document.elementsByTagName( "neighbours" ).length() == 0 )
788 emit friendsResult( QString( "" ), QStringList() );
789 return;
792 QStringList neighbours;
793 QString user = document.elementsByTagName( "neighbours" ).item( 0 ).attributes().namedItem( "user" ).nodeValue();
794 QDomNodeList values = document.elementsByTagName( "user" );
795 for ( int i = 0; i < values.count(); i++ )
797 neighbours << values.item( i ).attributes().namedItem( "username" ).nodeValue();
800 emit neighboursResult( user, neighbours );
804 void
805 WebService::userTags( QString username )
807 if ( username.isEmpty() )
808 username = m_username;
810 AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this );
811 connect( http, SIGNAL( requestFinished( bool ) ), this, SLOT( userTagsFinished( bool ) ) );
813 http->get( QString( "/1.0/user/%1/tags.xml?debug=%2" )
814 .arg( QString( Q3Url( username ).encodedPathAndQuery() ) ) );
818 void
819 WebService::userTagsFinished( int /*id*/, bool error ) //SLOT
821 AmarokHttp* http = (AmarokHttp*) sender();
822 http->deleteLater();
823 if( error ) return;
825 QDomDocument document;
826 document.setContent( http->readAll() );
828 if ( document.elementsByTagName( "toptags" ).length() == 0 )
830 emit userTagsResult( QString(), QStringList() );
831 return;
834 QStringList tags;
835 QDomNodeList values = document.elementsByTagName( "tag" );
836 QString user = document.elementsByTagName( "toptags" ).item( 0 ).attributes().namedItem( "user" ).nodeValue();
837 for ( int i = 0; i < values.count(); i++ )
839 QDomNode item = values.item( i ).namedItem( "name" );
840 tags << item.toElement().text();
842 emit userTagsResult( user, tags );
846 void
847 WebService::recentTracks( QString username )
849 if ( username.isEmpty() )
850 username = m_username;
852 AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this );
853 connect( http, SIGNAL( requestFinished( bool ) ), this, SLOT( recentTracksFinished( bool ) ) );
855 http->get( QString( "/1.0/user/%1/recenttracks.xml" )
856 .arg( QString( Q3Url( username ).encodedPathAndQuery() ) ) );
860 void
861 WebService::recentTracksFinished( int /*id*/, bool error ) //SLOT
863 AmarokHttp* http = (AmarokHttp*) sender();
864 http->deleteLater();
865 if( error ) return;
867 Q3ValueList< QPair<QString, QString> > songs;
868 QDomDocument document;
869 document.setContent( http->readAll() );
871 if ( document.elementsByTagName( "recenttracks" ).length() == 0 )
873 emit recentTracksResult( QString(), songs );
874 return;
877 QDomNodeList values = document.elementsByTagName( "track" );
878 QString user = document.elementsByTagName( "recenttracks" ).item( 0 ).attributes().namedItem( "user" ).nodeValue();
879 for ( int i = 0; i < values.count(); i++ )
881 QPair<QString, QString> song;
882 song.first = values.item( i ).namedItem( "artist" ).toElement().text();
883 song.second = values.item( i ).namedItem( "name" ).toElement().text();
885 songs << song;
887 emit recentTracksResult( user, songs );
891 void
892 WebService::recommend( int type, QString username, QString artist, QString token )
894 QString modeToken = "";
895 switch ( type )
897 case 0:
898 modeToken = QString( "artist_name=%1" ).arg( QString( Q3Url( artist ).encodedPathAndQuery() ) );
899 break;
901 case 1:
902 modeToken = QString( "album_artist=%1&album_name=%2" )
903 .arg( QString( Q3Url( artist ).encodedPathAndQuery() ) )
904 .arg( QString( Q3Url( token ).encodedPathAndQuery() ) );
905 break;
907 case 2:
908 modeToken = QString( "track_artist=%1&track_name=%2" )
909 .arg( QString( Q3Url( artist ).encodedPathAndQuery() ) )
910 .arg( QString( Q3Url( token ).encodedPathAndQuery() ) );
911 break;
914 Q3Http *http = new Q3Http( "wsdev.audioscrobbler.com", 80, this );
915 connect( http, SIGNAL( requestFinished( bool ) ), this, SLOT( recommendFinished( bool ) ) );
917 uint currentTime = QDateTime::currentDateTime().toUTC().toTime_t();
918 QString challenge = QString::number( currentTime );
920 QByteArray md5pass = KMD5( KMD5( m_password.toUtf8() ).hexDigest().append( QString::number( currentTime ).toLocal8Bit() ) ).hexDigest();
922 QString atoken = QString( "user=%1&auth=%2&nonce=%3recipient=%4" )
923 .arg( QString( Q3Url( currentUsername() ).encodedPathAndQuery() ) )
924 .arg( QString( Q3Url( md5pass ).encodedPathAndQuery() ) )
925 .arg( QString( Q3Url( challenge ).encodedPathAndQuery() ) )
926 .arg( QString( Q3Url( username ).encodedPathAndQuery() ) );
928 Q3HttpRequestHeader header( "POST", "/1.0/rw/recommend.php?" + atoken.toUtf8() );
929 header.setValue( "Host", "wsdev.audioscrobbler.com" );
930 header.setContentType( "application/x-www-form-urlencoded" );
931 http->request( header, modeToken.toUtf8() );
935 void
936 WebService::recommendFinished( int /*id*/, bool /*error*/ ) //SLOT
938 AmarokHttp* http = (AmarokHttp*) sender();
939 http->deleteLater();
941 debug() << "Recommendation:" << http->readAll();
945 QString
946 WebService::parameter( const QString keyName, const QString data ) const
948 QStringList list = data.split( '\n' );
950 for ( int i = 0; i < list.size(); i++ )
952 QStringList values = list[i].split( '=' );
953 if ( values[0] == keyName )
955 values.removeAt( 0 );
956 return QString::fromUtf8( values.join( "=" ).toAscii() );
960 return QString( "" );
964 QStringList
965 WebService::parameterArray( const QString keyName, const QString data ) const
967 QStringList result;
968 QStringList list = data.split( '\n' );
970 for ( int i = 0; i < list.size(); i++ )
972 QStringList values = list[i].split( '=' );
973 if ( values[0].startsWith( keyName ) )
975 values.removeAt( 0 );
976 result.append( QString::fromUtf8( values.join( "=" ).toAscii() ) );
980 return result;
984 QStringList
985 WebService::parameterKeys( const QString keyName, const QString data ) const
987 QStringList result;
988 QStringList list = data.split( '\n' );
990 for ( int i = 0; i < list.size(); i++ )
992 QStringList values = list[i].split( '=' );
993 if ( values[0].startsWith( keyName ) )
995 values = values[0].split( '[' );
996 values = values[1].split( ']' );
997 result.append( values[0] );
1002 return result;
1005 void
1006 WebService::showError( int code, QString message )
1008 switch ( code )
1010 case E_NOCONTENT:
1011 message = i18n( "There is not enough content to play this station." );
1012 break;
1013 case E_NOMEMBERS:
1014 message = i18n( "This group does not have enough members for radio." );
1015 break;
1016 case E_NOFANS:
1017 message = i18n( "This artist does not have enough fans for radio." );
1018 break;
1019 case E_NOAVAIL:
1020 message = i18n( "This item is not available for streaming." );
1021 break;
1022 case E_NOSUBSCRIBER:
1023 message = i18n( "This feature is only available to last.fm subscribers." );
1024 break;
1025 case E_NONEIGHBOURS:
1026 message = i18n( "There are not enough neighbors for this radio." );
1027 break;
1028 case E_NOSTOPPED:
1029 message = i18n( "This stream has stopped. Please try another station." );
1030 break;
1031 default:
1032 if( message.isEmpty() )
1033 message = i18n( "Failed to play this last.fm stream." );
1036 Amarok::ContextStatusBar::instance()->longMessage( message, KDE::StatusBar::Sorry );
1039 ////////////////////////////////////////////////////////////////////////////////
1040 // CLASS LastFm::Bundle
1041 ////////////////////////////////////////////////////////////////////////////////
1043 Bundle::Bundle( const Bundle& lhs )
1044 : m_imageUrl( lhs.m_imageUrl )
1045 , m_albumUrl( lhs.m_albumUrl )
1046 , m_artistUrl( lhs.m_artistUrl )
1047 , m_titleUrl( lhs.m_titleUrl )
1050 ////////////////////////////////////////////////////////////////////////////////
1051 // CLASS LastFm::LoginDialog
1052 ////////////////////////////////////////////////////////////////////////////////
1053 LoginDialog::LoginDialog( QWidget *parent )
1054 : KDialog( parent )
1056 setModal( true );
1057 setButtons( Ok | Cancel );
1059 //makeGridMainWidget( 1, Qt::Horizontal );
1060 KVBox* vbox = new KVBox( this );
1061 setMainWidget( vbox );
1062 new QLabel( i18n( "To use last.fm with Amarok, you need a last.fm profile." ), vbox );
1064 //makeGridMainWidget( 2, Qt::Horizontal );
1065 KHBox* hbox = new KHBox( vbox );
1066 QLabel *nameLabel = new QLabel( i18n("&Username:"), hbox );
1067 m_userLineEdit = new KLineEdit( hbox );
1068 nameLabel->setBuddy( m_userLineEdit );
1070 QLabel *passLabel = new QLabel( i18n("&Password:"), hbox );
1071 m_passLineEdit = new KLineEdit( hbox );
1072 m_passLineEdit->setEchoMode( QLineEdit::Password );
1073 passLabel->setBuddy( m_passLineEdit );
1075 m_userLineEdit->setFocus();
1079 void LoginDialog::slotButtonClicked( ButtonCode button )
1081 if ( button == Ok ) {
1082 AmarokConfig::setScrobblerUsername( m_userLineEdit->text() );
1083 AmarokConfig::setScrobblerPassword( m_passLineEdit->text() );
1086 KDialog::slotButtonClicked( button );
1090 ////////////////////////////////////////////////////////////////////////////////
1091 // CLASS LastFm::CustomStationDialog
1092 ////////////////////////////////////////////////////////////////////////////////
1093 CustomStationDialog::CustomStationDialog( QWidget *parent )
1094 : KDialog( parent )
1096 setCaption( i18n( "Create Custom Station" ) );
1097 setModal( true );
1098 setButtons( Ok | Cancel );
1101 KVBox *vbox = new KVBox( this );
1102 setMainWidget( vbox );
1105 new QLabel( i18n( "Enter the name of a band or artist you like:" ), mainWidget() );
1107 m_edit = new KLineEdit( mainWidget() );
1108 m_edit->setFocus();
1112 QString
1113 CustomStationDialog::text() const
1115 return m_edit->text();
1119 #include "lastfm.moc"