/*************************************************************************** * copyright : (C) 2006 Chris Muehlhaeuser * * : (C) 2006 Seb Ruiz * * : (C) 2006 Ian Monroe * * : (C) 2006 Mark Kretschmann * **************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #define DEBUG_PREFIX "LastFm" #include "amarok.h" //APP_VERSION, actioncollection #include "amarokconfig.h" //last.fm username and passwd #include "collectiondb.h" #include "debug.h" #include "enginecontroller.h" #include "lastfm.h" #include "statusbar.h" //showError() #include #include #include #include #include #include #include #include //md5sum #include #include #include #include #include #include #include #include using namespace LastFm; /////////////////////////////////////////////////////////////////////////////// // CLASS AmarokHttp // AmarokHttp is a hack written so that lastfm code could easily use something proxy aware. // DO NOT use this class for anything else, use KIO directly instead. //////////////////////////////////////////////////////////////////////////////// AmarokHttp::AmarokHttp ( const TQString& hostname, TQ_UINT16 port, TQObject* parent ) : TQObject( parent ), m_hostname( hostname ), m_port( port ) {} int AmarokHttp::get ( const TQString & path ) { TQString uri = TQString( "http://%1:%2/%3" ) .tqarg( m_hostname ) .tqarg( m_port ) .tqarg( path ); m_done = false; m_error = TQHttp::NoError; m_state = TQHttp::Connecting; KIO::TransferJob *job = KIO::get(uri, true, false); connect(job, TQT_SIGNAL(data(KIO::Job*, const TQByteArray&)), this, TQT_SLOT(slotData(KIO::Job*, const TQByteArray&))); connect(job, TQT_SIGNAL(result(KIO::Job*)), this, TQT_SLOT(slotResult(KIO::Job*))); return 0; } TQHttp::State AmarokHttp::state() const { return m_state; } TQByteArray AmarokHttp::readAll () { return m_result; } TQHttp::Error AmarokHttp::error() { return m_error; } void AmarokHttp::slotData(KIO::Job*, const TQByteArray& data) { if( data.size() == 0 ) { return; } else if ( m_result.size() == 0 ) { m_result = data; } else if ( m_result.tqresize( m_result.size() + data.size() ) ) { memcpy( m_result.end(), data.data(), data.size() ); } } void AmarokHttp::slotResult(KIO::Job* job) { bool err = job->error(); if( err || m_error != TQHttp::NoError ) { m_error = TQHttp::UnknownError; } else { m_error = TQHttp::NoError; } m_done = true; m_state = TQHttp::Unconnected; emit( requestFinished( 0, err ) ); } /////////////////////////////////////////////////////////////////////////////// // CLASS Controller //////////////////////////////////////////////////////////////////////////////// Controller *Controller::s_instance = 0; Controller::Controller() : TQObject( EngineController::instance(), "lastfmController" ) , m_service( 0 ) { KActionCollection* ac = Amarok::actionCollection(); m_actionList.append( new KAction( i18n( "Ban" ), Amarok::icon( "remove" ), KKey( TQt::CTRL | TQt::Key_B ), this, TQT_SLOT( ban() ), ac, "ban" ) ); m_actionList.append( new KAction( i18n( "Love" ), Amarok::icon( "love" ), KKey( TQt::CTRL | TQt::Key_L ), this, TQT_SLOT( love() ), ac, "love" ) ); m_actionList.append( new KAction( i18n( "Skip" ), Amarok::icon( "next" ), KKey( TQt::CTRL | TQt::Key_K ), this, TQT_SLOT( skip() ), ac, "skip" ) ); setActionsEnabled( false ); } Controller* Controller::instance() { if( !s_instance ) s_instance = new Controller(); return s_instance; } KURL Controller::getNewProxy( TQString genreUrl, bool useProxy ) { DEBUG_BLOCK m_genreUrl = genreUrl; if ( m_service ) playbackStopped(); WebService* service; // m_service might have already been reset until changeStation() and/or handshare() // calls return service = m_service = new WebService( this, useProxy ); if( checkCredentials() ) { TQString user = AmarokConfig::scrobblerUsername(); TQString pass = AmarokConfig::scrobblerPassword(); if( !user.isEmpty() && !pass.isEmpty() && service->handshake( user, pass ) ) { bool ok = service->changeStation( m_genreUrl ); if( ok ) // else playbackStopped() { if( !AmarokConfig::submitPlayedSongs() ) m_service->enableScrobbling( false ); setActionsEnabled( true ); return KURL( m_service->proxyUrl() ); } } if (service->wasCanceled()) { // It was canceled before (during kapp->processEvents() loop) delete service; return KURL("lastfm://"); // construct invalid url } } // Some kind of failure happened, so crap out playbackStopped(); return KURL(); } int Controller::changeStation( TQString url ) { if (isPlaying()) { WebService* service = getService(); if (service->changeStation( url )) { return 1; // success } else if (service->wasCanceled()) { delete service; return -1; // canceled } else { return 0; // failed } } else { return 0; // impossible, failed } } void Controller::playbackStopped() //SLOT { setActionsEnabled( false ); if (m_service) { if (m_service->cancel()) delete m_service;; m_service = 0; } } bool Controller::checkCredentials() //static { if( AmarokConfig::scrobblerUsername().isEmpty() || AmarokConfig::scrobblerPassword().isEmpty() ) { LoginDialog dialog( 0 ); dialog.setCaption( "last.fm" ); return dialog.exec() == TQDialog::Accepted; } return true; } TQString Controller::createCustomStation() //static { TQString token; CustomStationDialog dialog( 0 ); if( dialog.exec() == TQDialog::Accepted ) { token = dialog.text(); } return token; } void Controller::ban() { if( m_service ) m_service->ban(); } void Controller::love() { if( m_service ) m_service->love(); } void Controller::skip() { if( m_service ) m_service->skip(); } void Controller::setActionsEnabled( bool enable ) { //pausing last.fm streams doesn't do anything good Amarok::actionCollection()->action( "play_pause" )->setEnabled( !enable ); Amarok::actionCollection()->action( "pause" )->setEnabled( !enable ); KAction* action; for( action = m_actionList.first(); action; action = m_actionList.next() ) action->setEnabled( enable ); } /// return a translatable description of the station we are connected to TQString Controller::stationDescription( TQString url ) { if( url.isEmpty() && instance() && instance()->isPlaying() ) url = instance()->getService()->currentStation(); if( url.isEmpty() ) return TQString(); TQStringList elements = TQStringList::split( "/", url ); /// TAG RADIOS // eg: lastfm://globaltag/rock if ( elements[1] == "globaltags" ) return i18n( "Global Tag Radio: %1" ).tqarg( elements[2] ); /// ARTIST RADIOS if ( elements[1] == "artist" ) { // eg: lastfm://artist/Queen/similarartists if ( elements[3] == "similarartists" ) return i18n( "Similar Artists to %1" ).tqarg( elements[2] ); if ( elements[3] == "fans" ) return i18n( "Artist Fan Radio: %1" ).tqarg( elements[2] ); } /// CUSTOM STATION if ( elements[1] == "artistnames" ) { // eg: lastfm://artistnames/genesis,pink floyd,queen // turn "genesis,pink floyd,queen" into "Genesis, Pink Floyd, Queen" TQString artists = elements[2]; artists.replace( ",", ", " ); const TQStringList words = TQStringList::split( " ", TQString( artists ).remove( "," ) ); foreach( words ) { TQString capitalized = *it; capitalized.replace( 0, 1, (*it)[0].upper() ); artists.replace( *it, capitalized ); } return i18n( "Custom Station: %1" ).tqarg( artists ); } /// USER RADIOS else if ( elements[1] == "user" ) { // eg: lastfm://user/sebr/neighbours if ( elements[3] == "neighbours" ) return i18n( "%1's Neighbor Radio" ).tqarg( elements[2] ); // eg: lastfm://user/sebr/personal if ( elements[3] == "personal" ) return i18n( "%1's Personal Radio" ).tqarg( elements[2] ); // eg: lastfm://user/sebr/loved if ( elements[3] == "loved" ) return i18n( "%1's Loved Radio" ).tqarg( elements[2] ); // eg: lastfm://user/sebr/recommended/100 : 100 is number for how obscure the music should be if ( elements[3] == "recommended" ) return i18n( "%1's Recommended Radio" ).tqarg( elements[2] ); } /// GROUP RADIOS //eg: lastfm://group/Amarok%20users else if ( elements[1] == "group" ) return i18n( "Group Radio: %1" ).tqarg( elements[2] ); /// TRACK RADIOS else if ( elements[1] == "play" ) { if ( elements[2] == "tracks" ) return i18n( "Track Radio" ); else if ( elements[2] == "artists" ) return i18n( "Artist Radio" ); } //kaput! return url; } //////////////////////////////////////////////////////////////////////////////// // CLASS WebService //////////////////////////////////////////////////////////////////////////////// WebService::WebService( TQObject* parent, bool useProxy ) : TQObject( parent, "lastfmParent" ) , m_useProxy( useProxy ) , m_deletionUnsafe( false ) , m_wasCanceled( false ) { debug() << "Initialising Web Service" << endl; } WebService::~WebService() { DEBUG_BLOCK } bool WebService::cancel() { m_wasCanceled = true; return !m_deletionUnsafe; } void WebService::readProxy() //SLOT { TQString line; while( m_server->readln( line ) != -1 ) { debug() << line << endl; if( line == "AMAROK_PROXY: SYNC" ) requestMetaData(); } } bool WebService::handshake( const TQString& username, const TQString& password ) { DEBUG_BLOCK m_username = username; m_password = password; AmarokHttp http( "ws.audioscrobbler.com", 80 ); const TQString path = TQString( "/radio/handshake.php?version=%1&platform=%2&username=%3&passwordmd5=%4&debug=%5" ) .tqarg( APP_VERSION ) //Muesli-approved: Amarok version, and Amarok-as-platform .tqarg( TQString("Amarok") ) .tqarg( TQString( TQUrl( username ).encodedPathAndQuery() ) ) .tqarg( KMD5( m_password.utf8() ).hexDigest().data() ) .tqarg( "0" ); http.get( path ); // We don't know what might happen within processEvents() loop. // Therefore this service instance must be protected from deletion. m_deletionUnsafe = true; do kapp->processEvents(); while( http.state() != TQHttp::Unconnected ); m_deletionUnsafe = false; if (this->wasCanceled()) return false; if ( http.error() != TQHttp::NoError ) return false; const TQString result( TQDeepCopy( http.readAll() ) ); debug() << "result: " << result << endl; m_session = parameter( "session", result ); m_baseHost = parameter( "base_url", result ); m_basePath = parameter( "base_path", result ); m_subscriber = parameter( "subscriber", result ) == "1"; m_streamUrl = TQUrl( parameter( "stream_url", result ) ); // bool banned = parameter( "banned", result ) == "1"; if ( m_session.lower() == "failed" ) { Amarok::StatusBar::instance()->longMessage( i18n( "Amarok failed to establish a session with last.fm.
" "Check if your last.fm user and password are correctly set." ) ); return false; } Amarok::config( "Scrobbler" )->writeEntry( "Subscriber", m_subscriber ); if( m_useProxy ) { // Find free port MyServerSocket* socket = new MyServerSocket(); const int port = socket->port(); debug() << "Proxy server using port: " << port << endl; delete socket; m_proxyUrl = TQString( "http://localhost:%1/lastfm.mp3" ).tqarg( port ); m_server = new Amarok::ProcIO(); m_server->setComm( KProcess::Communication( KProcess::AllOutput ) ); *m_server << "amarok_proxy.rb"; *m_server << "--lastfm"; *m_server << TQString::number( port ); *m_server << m_streamUrl.toString(); *m_server << AmarokConfig::soundSystem(); *m_server << Amarok::proxyForUrl( m_streamUrl.toString() ); if( !m_server->start( KProcIO::NotifyOnExit, true ) ) { error() << "Failed to start amarok_proxy.rb" << endl; return false; } TQString line; m_deletionUnsafe = true; while( true ) { kapp->processEvents(); m_server->readln( line ); if( line == "AMAROK_PROXY: startup" ) break; } m_deletionUnsafe = false; if (this->wasCanceled()) return false; connect( m_server, TQT_SIGNAL( readReady( KProcIO* ) ), this, TQT_SLOT( readProxy() ) ); connect( m_server, TQT_SIGNAL( processExited( KProcess* ) ), Controller::instance(), TQT_SLOT( playbackStopped() ) ); } else m_proxyUrl = m_streamUrl.toString(); return true; } bool WebService::changeStation( TQString url ) { debug() << "Changing station:" << url << endl; AmarokHttp http( m_baseHost, 80 ); http.get( TQString( m_basePath + "/adjust.php?session=%1&url=%2&debug=0" ) .tqarg( m_session ) .tqarg( url ) ); m_deletionUnsafe = true; do kapp->processEvents(); while( http.state() != TQHttp::Unconnected ); m_deletionUnsafe = false; if (this->wasCanceled()) return false; if ( http.error() != TQHttp::NoError ) { showError( E_OTHER ); // default error return false; } const TQString result( TQDeepCopy( http.readAll() ) ); const int errCode = parameter( "error", result ).toInt(); if ( errCode ) { showError( errCode ); return false; } const TQString _url = parameter( "url", result ); if ( _url.startsWith( "lastfm://" ) ) { m_station = _url; // parse it in stationDescription emit stationChanged( _url, m_station ); } else emit stationChanged( _url, TQString() ); return true; } void WebService::requestMetaData() //SLOT { AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this ); connect( http, TQT_SIGNAL( requestFinished( int, bool ) ), this, TQT_SLOT( metaDataFinished( int, bool ) ) ); http->get( TQString( m_basePath + "/np.php?session=%1&debug=%2" ) .tqarg( m_session ) .tqarg( "0" ) ); } void WebService::metaDataFinished( int /*id*/, bool error ) //SLOT { DEBUG_BLOCK AmarokHttp* http = (AmarokHttp*) sender(); http->deleteLater(); if( error ) return; const TQString result( http->readAll() ); debug() << result << endl; int errCode = parameter( "error", result ).toInt(); if ( errCode > 0 ) { debug() << "Metadata failed with error code: " << errCode << endl; showError( errCode ); return; } m_metaBundle.setArtist( parameter( "artist", result ) ); m_metaBundle.setAlbum ( parameter( "album", result ) ); m_metaBundle.setTitle ( parameter( "track", result ) ); m_metaBundle.setUrl ( KURL( Controller::instance()->getGenreUrl() ) ); m_metaBundle.setLength( parameter( "trackduration", result ).toInt() ); Bundle lastFmStuff; TQString imageUrl = parameter( "albumcover_medium", result ); if( imageUrl == "http://static.last.fm/coverart/" || imageUrl == "http://static.last.fm/depth/catalogue/no_album_large.gif" ) imageUrl = TQString(); lastFmStuff.setImageUrl ( CollectionDB::instance()->notAvailCover( true ) ); lastFmStuff.setArtistUrl( parameter( "artist_url", result ) ); lastFmStuff.setAlbumUrl ( parameter( "album_url", result ) ); lastFmStuff.setTitleUrl ( parameter( "track_url", result ) ); // bool discovery = parameter( "discovery", result ) != "-1"; m_metaBundle.setLastFmBundle( lastFmStuff ); const KURL u( imageUrl ); if( !u.isValid() ) { debug() << "imageUrl empty or invalid." << endl; emit metaDataResult( m_metaBundle ); return; } KIO::Job* job = KIO::storedGet( u, true, false ); connect( job, TQT_SIGNAL( result( KIO::Job* ) ), this, TQT_SLOT( fetchImageFinished( KIO::Job* ) ) ); } void WebService::fetchImageFinished( KIO::Job* job ) //SLOT { DEBUG_BLOCK if( job->error() == 0 ) { const TQString path = Amarok::saveLocation() + "lastfm_image.png"; const int size = AmarokConfig::coverPreviewSize(); TQImage img( static_cast( job )->data() ); img.smoothScale( size, size ).save( path, "PNG" ); m_metaBundle.lastFmBundle()->setImageUrl( CollectionDB::makeShadowedImage( path, false ) ); } emit metaDataResult( m_metaBundle ); } void WebService::enableScrobbling( bool enabled ) //SLOT { if ( enabled ) debug() << "Enabling Scrobbling!" << endl; else debug() << "Disabling Scrobbling!" << endl; AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this ); connect( http, TQT_SIGNAL( requestFinished( int, bool ) ), this, TQT_SLOT( enableScrobblingFinished( int, bool ) ) ); http->get( TQString( m_basePath + "/control.php?session=%1&command=%2&debug=%3" ) .tqarg( m_session ) .tqarg( enabled ? TQString( "rtp" ) : TQString( "nortp" ) ) .tqarg( "0" ) ); } void WebService::enableScrobblingFinished( int /*id*/, bool error ) //SLOT { AmarokHttp* http = (AmarokHttp*) sender(); http->deleteLater(); if ( error ) return; emit enableScrobblingDone(); } void WebService::love() //SLOT { AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this ); connect( http, TQT_SIGNAL( requestFinished( int, bool ) ), this, TQT_SLOT( loveFinished( int, bool ) ) ); http->get( TQString( m_basePath + "/control.php?session=%1&command=love&debug=%2" ) .tqarg( m_session ) .tqarg( "0" ) ); Amarok::StatusBar::instance()->shortMessage( i18n("love, as in affection", "Loving song...") ); } void WebService::skip() //SLOT { AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this ); connect( http, TQT_SIGNAL( requestFinished( int, bool ) ), this, TQT_SLOT( skipFinished( int, bool ) ) ); http->get( TQString( m_basePath + "/control.php?session=%1&command=skip&debug=%2" ) .tqarg( m_session ) .tqarg( "0" ) ); Amarok::StatusBar::instance()->shortMessage( i18n("Skipping song...") ); } void WebService::ban() //SLOT { AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this ); connect( http, TQT_SIGNAL( requestFinished( int, bool ) ), this, TQT_SLOT( banFinished( int, bool ) ) ); http->get( TQString( m_basePath + "/control.php?session=%1&command=ban&debug=%2" ) .tqarg( m_session ) .tqarg( "0" ) ); Amarok::StatusBar::instance()->shortMessage( i18n("Ban, as in dislike", "Banning song...") ); } void WebService::loveFinished( int /*id*/, bool error ) //SLOT { DEBUG_BLOCK AmarokHttp* http = (AmarokHttp*) sender(); http->deleteLater(); if( error ) return; emit loveDone(); } void WebService::skipFinished( int /*id*/, bool error ) //SLOT { DEBUG_BLOCK AmarokHttp* http = (AmarokHttp*) sender(); http->deleteLater(); if( error ) return; EngineController::engine()->flushBuffer(); emit skipDone(); } void WebService::banFinished( int /*id*/, bool error ) //SLOT { DEBUG_BLOCK AmarokHttp* http = (AmarokHttp*) sender(); http->deleteLater(); if( error ) return; EngineController::engine()->flushBuffer(); emit banDone(); emit skipDone(); } void WebService::friends( TQString username ) { if ( username.isEmpty() ) username = m_username; AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this ); connect( http, TQT_SIGNAL( requestFinished( bool ) ), this, TQT_SLOT( friendsFinished( bool ) ) ); http->get( TQString( "/1.0/user/%1/friends.xml" ) .tqarg( TQString( TQUrl( username ).encodedPathAndQuery() ) ) ); } void WebService::friendsFinished( int /*id*/, bool error ) //SLOT { AmarokHttp* http = (AmarokHttp*) sender(); http->deleteLater(); if( error ) return; TQDomDocument document; document.setContent( http->readAll() ); if ( document.elementsByTagName( "friends" ).length() == 0 ) { emit friendsResult( TQString( "" ), TQStringList() ); return; } TQStringList friends; TQString user = document.elementsByTagName( "friends" ).item( 0 ).attributes().namedItem( "user" ).nodeValue(); TQDomNodeList values = document.elementsByTagName( "user" ); for ( uint i = 0; i < values.count(); i++ ) { friends << values.item( i ).attributes().namedItem( "username" ).nodeValue(); } emit friendsResult( user, friends ); } void WebService::neighbours( TQString username ) { if ( username.isEmpty() ) username = m_username; AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this ); connect( http, TQT_SIGNAL( requestFinished( bool ) ), this, TQT_SLOT( neighboursFinished( bool ) ) ); http->get( TQString( "/1.0/user/%1/neighbours.xml" ) .tqarg( TQString( TQUrl( username ).encodedPathAndQuery() ) ) ); } void WebService::neighboursFinished( int /*id*/, bool error ) //SLOT { AmarokHttp* http = (AmarokHttp*) sender(); http->deleteLater(); if( error ) return; TQDomDocument document; document.setContent( http->readAll() ); if ( document.elementsByTagName( "neighbours" ).length() == 0 ) { emit friendsResult( TQString( "" ), TQStringList() ); return; } TQStringList neighbours; TQString user = document.elementsByTagName( "neighbours" ).item( 0 ).attributes().namedItem( "user" ).nodeValue(); TQDomNodeList values = document.elementsByTagName( "user" ); for ( uint i = 0; i < values.count(); i++ ) { neighbours << values.item( i ).attributes().namedItem( "username" ).nodeValue(); } emit neighboursResult( user, neighbours ); } void WebService::userTags( TQString username ) { if ( username.isEmpty() ) username = m_username; AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this ); connect( http, TQT_SIGNAL( requestFinished( bool ) ), this, TQT_SLOT( userTagsFinished( bool ) ) ); http->get( TQString( "/1.0/user/%1/tags.xml?debug=%2" ) .tqarg( TQString( TQUrl( username ).encodedPathAndQuery() ) ) ); } void WebService::userTagsFinished( int /*id*/, bool error ) //SLOT { AmarokHttp* http = (AmarokHttp*) sender(); http->deleteLater(); if( error ) return; TQDomDocument document; document.setContent( http->readAll() ); if ( document.elementsByTagName( "toptags" ).length() == 0 ) { emit userTagsResult( TQString(), TQStringList() ); return; } TQStringList tags; TQDomNodeList values = document.elementsByTagName( "tag" ); TQString user = document.elementsByTagName( "toptags" ).item( 0 ).attributes().namedItem( "user" ).nodeValue(); for ( uint i = 0; i < values.count(); i++ ) { TQDomNode item = values.item( i ).namedItem( "name" ); tags << item.toElement().text(); } emit userTagsResult( user, tags ); } void WebService::recentTracks( TQString username ) { if ( username.isEmpty() ) username = m_username; AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this ); connect( http, TQT_SIGNAL( requestFinished( bool ) ), this, TQT_SLOT( recentTracksFinished( bool ) ) ); http->get( TQString( "/1.0/user/%1/recenttracks.xml" ) .tqarg( TQString( TQUrl( username ).encodedPathAndQuery() ) ) ); } void WebService::recentTracksFinished( int /*id*/, bool error ) //SLOT { AmarokHttp* http = (AmarokHttp*) sender(); http->deleteLater(); if( error ) return; TQValueList< TQPair > songs; TQDomDocument document; document.setContent( http->readAll() ); if ( document.elementsByTagName( "recenttracks" ).length() == 0 ) { emit recentTracksResult( TQString(), songs ); return; } TQDomNodeList values = document.elementsByTagName( "track" ); TQString user = document.elementsByTagName( "recenttracks" ).item( 0 ).attributes().namedItem( "user" ).nodeValue(); for ( uint i = 0; i < values.count(); i++ ) { TQPair song; song.first = values.item( i ).namedItem( "artist" ).toElement().text(); song.second = values.item( i ).namedItem( "name" ).toElement().text(); songs << song; } emit recentTracksResult( user, songs ); } void WebService::recommend( int type, TQString username, TQString artist, TQString token ) { TQString modeToken = ""; switch ( type ) { case 0: modeToken = TQString( "artist_name=%1" ).tqarg( TQString( TQUrl( artist ).encodedPathAndQuery() ) ); break; case 1: modeToken = TQString( "album_artist=%1&album_name=%2" ) .tqarg( TQString( TQUrl( artist ).encodedPathAndQuery() ) ) .tqarg( TQString( TQUrl( token ).encodedPathAndQuery() ) ); break; case 2: modeToken = TQString( "track_artist=%1&track_name=%2" ) .tqarg( TQString( TQUrl( artist ).encodedPathAndQuery() ) ) .tqarg( TQString( TQUrl( token ).encodedPathAndQuery() ) ); break; } TQHttp *http = new TQHttp( "wsdev.audioscrobbler.com", 80, this ); connect( http, TQT_SIGNAL( requestFinished( bool ) ), this, TQT_SLOT( recommendFinished( bool ) ) ); uint currentTime = TQDateTime::currentDateTime( Qt::UTC ).toTime_t(); TQString challenge = TQString::number( currentTime ); TQCString md5pass = KMD5( KMD5( m_password.utf8() ).hexDigest() + currentTime ).hexDigest(); token = TQString( "user=%1&auth=%2&nonce=%3recipient=%4" ) .tqarg( TQString( TQUrl( currentUsername() ).encodedPathAndQuery() ) ) .tqarg( TQString( TQUrl( md5pass ).encodedPathAndQuery() ) ) .tqarg( TQString( TQUrl( challenge ).encodedPathAndQuery() ) ) .tqarg( TQString( TQUrl( username ).encodedPathAndQuery() ) ); TQHttpRequestHeader header( "POST", "/1.0/rw/recommend.php?" + token.utf8() ); header.setValue( "Host", "wsdev.audioscrobbler.com" ); header.setContentType( "application/x-www-form-urlencoded" ); http->request( header, modeToken.utf8() ); } void WebService::recommendFinished( int /*id*/, bool /*error*/ ) //SLOT { AmarokHttp* http = (AmarokHttp*) sender(); http->deleteLater(); debug() << "Recommendation:" << http->readAll() << endl; } TQString WebService::parameter( const TQString keyName, const TQString data ) const { TQStringList list = TQStringList::split( '\n', data ); for ( uint i = 0; i < list.size(); i++ ) { TQStringList values = TQStringList::split( '=', list[i] ); if ( values[0] == keyName ) { values.remove( values.at(0) ); return TQString::fromUtf8( values.join( "=" ).ascii() ); } } return TQString( "" ); } TQStringList WebService::parameterArray( const TQString keyName, const TQString data ) const { TQStringList result; TQStringList list = TQStringList::split( '\n', data ); for ( uint i = 0; i < list.size(); i++ ) { TQStringList values = TQStringList::split( '=', list[i] ); if ( values[0].startsWith( keyName ) ) { values.remove( values.at(0) ); result.append( TQString::fromUtf8( values.join( "=" ).ascii() ) ); } } return result; } TQStringList WebService::parameterKeys( const TQString keyName, const TQString data ) const { TQStringList result; TQStringList list = TQStringList::split( '\n', data ); for ( uint i = 0; i < list.size(); i++ ) { TQStringList values = TQStringList::split( '=', list[i] ); if ( values[0].startsWith( keyName ) ) { values = TQStringList::split( '[', values[0] ); values = TQStringList::split( ']', values[1] ); result.append( values[0] ); } } return result; } void WebService::showError( int code, TQString message ) { switch ( code ) { case E_NOCONTENT: message = i18n( "There is not enough content to play this station." ); break; case E_NOMEMBERS: message = i18n( "This group does not have enough members for radio." ); break; case E_NOFANS: message = i18n( "This artist does not have enough fans for radio." ); break; case E_NOAVAIL: message = i18n( "This item is not available for streaming." ); break; case E_NOSUBSCRIBER: message = i18n( "This feature is only available to last.fm subscribers." ); break; case E_NONEIGHBOURS: message = i18n( "There are not enough neighbors for this radio." ); break; case E_NOSTOPPED: message = i18n( "This stream has stopped. Please try another station." ); break; default: if( message.isEmpty() ) message = i18n( "Failed to play this last.fm stream." ); } Amarok::StatusBar::instance()->longMessage( message, KDE::StatusBar::Sorry ); } //////////////////////////////////////////////////////////////////////////////// // CLASS LastFm::Bundle //////////////////////////////////////////////////////////////////////////////// Bundle::Bundle( const Bundle& lhs ) : m_imageUrl( lhs.m_imageUrl ) , m_albumUrl( lhs.m_albumUrl ) , m_artistUrl( lhs.m_artistUrl ) , m_titleUrl( lhs.m_titleUrl ) {} void Bundle::detach() { m_imageUrl = TQDeepCopy(m_imageUrl); m_albumUrl = TQDeepCopy(m_albumUrl); m_artistUrl = TQDeepCopy(m_artistUrl); m_titleUrl = TQDeepCopy(m_titleUrl); } //////////////////////////////////////////////////////////////////////////////// // CLASS LastFm::LoginDialog //////////////////////////////////////////////////////////////////////////////// LoginDialog::LoginDialog( TQWidget *parent ) : KDialogBase( parent, "LastfmLogin", true, TQString(), Ok|Cancel) { makeGridMainWidget( 1, Qt::Horizontal ); new TQLabel( i18n( "To use last.fm with Amarok, you need a last.fm profile." ), mainWidget() ); makeGridMainWidget( 2, Qt::Horizontal ); TQLabel *nameLabel = new TQLabel( i18n("&Username:"), mainWidget() ); m_userLineEdit = new KLineEdit( mainWidget() ); nameLabel->setBuddy( m_userLineEdit ); TQLabel *passLabel = new TQLabel( i18n("&Password:"), mainWidget() ); m_passLineEdit = new KLineEdit( mainWidget() ); m_passLineEdit->setEchoMode( TQLineEdit::Password ); passLabel->setBuddy( m_passLineEdit ); m_userLineEdit->setFocus(); } void LoginDialog::slotOk() { AmarokConfig::setScrobblerUsername( m_userLineEdit->text() ); AmarokConfig::setScrobblerPassword( m_passLineEdit->text() ); KDialogBase::slotOk(); } //////////////////////////////////////////////////////////////////////////////// // CLASS LastFm::CustomStationDialog //////////////////////////////////////////////////////////////////////////////// CustomStationDialog::CustomStationDialog( TQWidget *parent ) : KDialogBase( parent, "LastfmCustomStation", true, i18n( "Create Custom Station" ) , Ok|Cancel) { makeVBoxMainWidget(); new TQLabel( i18n( "Enter the name of a band or artist you like:" ), mainWidget() ); m_edit = new KLineEdit( mainWidget(), "CustomStationEdit" ); m_edit->setFocus(); } TQString CustomStationDialog::text() const { return m_edit->text(); } #include "lastfm.moc"