You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
amarok/amarok/src/lastfm.cpp

1148 lines
33 KiB

/***************************************************************************
* copyright : (C) 2006 Chris Muehlhaeuser <chris@chris.de> *
* : (C) 2006 Seb Ruiz <me@sebruiz.net> *
* : (C) 2006 Ian Monroe <ian@monroe.nu> *
* : (C) 2006 Mark Kretschmann <markey@web.de> *
**************************************************************************/
/***************************************************************************
* *
* 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 <tqdeepcopy.h>
#include <tqdom.h>
#include <tqhttp.h>
#include <tqlabel.h>
#include <tqregexp.h>
#include <kaction.h>
#include <klineedit.h>
#include <kmdcodec.h> //md5sum
#include <kmessagebox.h>
#include <kio/job.h>
#include <kio/jobclasses.h>
#include <kprotocolmanager.h>
#include <kshortcut.h>
#include <kurl.h>
#include <time.h>
#include <unistd.h>
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<TQString>( 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. <br>"
"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<TQString>( 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<KIO::StoredTransferJob*>( 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<TQString, TQString> > 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<TQString, TQString> 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::tqcurrentDateTime( 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<TQString>(m_imageUrl);
m_albumUrl = TQDeepCopy<TQString>(m_albumUrl);
m_artistUrl = TQDeepCopy<TQString>(m_artistUrl);
m_titleUrl = TQDeepCopy<TQString>(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"