scrobbler.cpp
来自「Amarok是一款在LINUX或其他类UNIX操作系统中运行的音频播放器软件。 」· C++ 代码 · 共 1,186 行 · 第 1/3 页
CPP
1,186 行
// (c) 2004 Christian Muehlhaeuser <chris@chris.de>// (c) 2004 Sami Nieminen <sami.nieminen@iki.fi>// (c) 2006 Shane King <kde@dontletsstart.com>// (c) 2006 Iain Benson <iain@arctos.me.uk>// (c) 2006 Alexandre Oliveira <aleprj@gmail.com>// (c) 2006 Andy Kelk <andy@mopoke.co.uk>// See COPYING file for licensing information.#define DEBUG_PREFIX "Scrobbler"#include "amarok.h"#include "amarokconfig.h"#include "collectiondb.h"#include "config.h"#include "debug.h"#include "enginecontroller.h"#include "playlist.h"#include "scrobbler.h"#include "statusbar.h"#include <unistd.h>#include <qdatetime.h>#include <qdeepcopy.h>#include <kapplication.h>#include <kio/job.h>#include <kio/jobclasses.h>#include <klocale.h>#include <kmdcodec.h>#include <kstandarddirs.h>#include <kurl.h>//some setups require this#undef PROTOCOL_VERSION////////////////////////////////////////////////////////////////////////////////// CLASS Scrobbler////////////////////////////////////////////////////////////////////////////////Scrobbler* Scrobbler::instance(){ static Scrobbler scrobbler; return &scrobbler;}Scrobbler::Scrobbler() : EngineObserver( EngineController::instance() ) , m_similarArtistsJob( 0 ) , m_validForSending( false ) , m_startPos( 0 ) , m_submitter( new ScrobblerSubmitter() ) , m_item( new SubmitItem() ){}Scrobbler::~Scrobbler(){ delete m_item; delete m_submitter;}/** * Queries similar artists from Audioscrobbler. */void Scrobbler::similarArtists( const QString & artist ){ QString safeArtist = QDeepCopy<QString>( artist ); if ( AmarokConfig::retrieveSimilarArtists() ) {// Request looks like this:// http://ws.audioscrobbler.com/1.0/artist/Metallica/similar.xml m_similarArtistsBuffer = QByteArray(); m_artist = artist; m_similarArtistsJob = KIO::get( "http://ws.audioscrobbler.com/1.0/artist/" + safeArtist + "/similar.xml", false, false ); connect( m_similarArtistsJob, SIGNAL( result( KIO::Job* ) ), this, SLOT( audioScrobblerSimilarArtistsResult( KIO::Job* ) ) ); connect( m_similarArtistsJob, SIGNAL( data( KIO::Job*, const QByteArray& ) ), this, SLOT( audioScrobblerSimilarArtistsData( KIO::Job*, const QByteArray& ) ) ); }}/** * Called when the similar artists TransferJob finishes. */void Scrobbler::audioScrobblerSimilarArtistsResult( KIO::Job* job ) //SLOT{ if ( m_similarArtistsJob != job ) return; //not the right job, so let's ignore it if ( job->error() ) { warning() << "KIO error! errno: " << job->error() << endl; return; }// Result looks like this:// <?xml version="1.0" encoding="UTF-8"?>// <similarartists artist="Metallica" streamable="1" picture="http://static.last.fm/proposedimages/sidebar/6/1000024/288059.jpg" mbid="">// <artist>// <name>Iron Maiden</name>// <mbid></mbid>// <match>100</match>// <url>http://www.last.fm/music/Iron+Maiden</url>// <image_small>http://static.last.fm/proposedimages/thumbnail/6/1000107/264195.jpg</image_small>// <image>http://static.last.fm/proposedimages/sidebar/6/1000107/264195.jpg</image>// <streamable>1</streamable>// </artist>// </similarartists> QDomDocument document; if ( !document.setContent( m_similarArtistsBuffer ) ) { debug() << "Couldn't read similar artists response" << endl; return; } QDomNodeList values = document.elementsByTagName( "similarartists" ) .item( 0 ).childNodes(); QStringList suggestions; for ( uint i = 0; i < values.count() && i < 30; i++ ) // limit to top 30 artists suggestions << values.item( i ).namedItem( "name" ).toElement().text(); debug() << "Suggestions retrieved (" << suggestions.count() << ")" << endl; if ( !suggestions.isEmpty() ) emit similarArtistsFetched( m_artist, suggestions ); m_similarArtistsJob = 0;}/** * Called when similar artists data is received for the TransferJob. */void Scrobbler::audioScrobblerSimilarArtistsData( KIO::Job* job, const QByteArray& data ) //SLOT{ if ( m_similarArtistsJob != job ) return; //not the right job, so let's ignore it uint oldSize = m_similarArtistsBuffer.size(); m_similarArtistsBuffer.resize( oldSize + data.size() ); memcpy( m_similarArtistsBuffer.data() + oldSize, data.data(), data.size() );}/** * Called when the signal is received. */void Scrobbler::engineNewMetaData( const MetaBundle& bundle, bool trackChanged ){ //debug() << "engineNewMetaData: " << bundle.artist() << ":" << bundle.album() << ":" << bundle.title() << ":" << trackChanged << endl; if ( !trackChanged ) { debug() << "It's still the same track." << endl; m_item->setArtist( bundle.artist() ); m_item->setAlbum( bundle.album() ); m_item->setTitle( bundle.title() ); return; } //to work around xine bug, we have to explictly prevent submission the first few seconds of a track //http://sourceforge.net/tracker/index.php?func=detail&aid=1401026&group_id=9655&atid=109655 m_timer.stop(); m_timer.start( 10000, true ); m_startPos = 0; // Plugins must not submit tracks played from online radio stations, even // if they appear to be providing correct metadata. if ( !bundle.streamUrl().isEmpty() ) { debug() << "Won't submit: It's a stream." << endl; m_validForSending = false; } else if( bundle.podcastBundle() != NULL ) { debug() << "Won't submit: It's a podcast." << endl; m_validForSending = false; } else { *m_item = SubmitItem( bundle.artist(), bundle.album(), bundle.title(), bundle.length() ); m_validForSending = true; // check length etc later }}/** * Called when cue file detects track change */void Scrobbler::subTrack( long currentPos, long startPos, long endPos ){ //debug() << "subTrack: " << currentPos << ":" << startPos << ":" << endPos << endl; *m_item = SubmitItem( m_item->artist(), m_item->album(), m_item->title(), endPos - startPos ); if ( currentPos <= startPos + 2 ) // only submit if starting from the start of the track (need to allow 2 second difference for rounding/delay) { m_startPos = startPos * 1000; m_validForSending = true; } else { debug() << "Won't submit: Detected cuefile jump to " << currentPos - startPos << " seconds into track." << endl; m_validForSending = false; }}/** * Called when the signal is received. */void Scrobbler::engineTrackPositionChanged( long position, bool userSeek ){ //debug() << "engineTrackPositionChanged: " << position << ":" << userSeek << endl; if ( !m_validForSending ) return; if ( userSeek ) { m_validForSending = false; debug() << "Won't submit: Seek detected." << endl; return; } if ( m_timer.isActive() ) return; // Each track must be submitted to the server when it is 50% or 240 // seconds complete, whichever comes first. if ( position - m_startPos > 240 * 1000 || position - m_startPos > 0.5 * m_item->length() * 1000 ) { if ( m_item->valid() ) m_submitter->submitItem( new SubmitItem( *m_item ) ); else debug() << "Won't submit: No artist, no title, or less than 30 seconds." << endl; m_validForSending = false; }}/** * Applies settings from the config dialog. */void Scrobbler::applySettings(){ m_submitter->configure( AmarokConfig::scrobblerUsername(), AmarokConfig::scrobblerPassword(), AmarokConfig::submitPlayedSongs() );}////////////////////////////////////////////////////////////////////////////////// CLASS SubmitItem////////////////////////////////////////////////////////////////////////////////SubmitItem::SubmitItem( const QString& artist, const QString& album, const QString& title, int length, bool now){ m_artist = artist; m_album = album; m_title = title; m_length = length; m_playStartTime = now ? QDateTime::currentDateTime( Qt::UTC ).toTime_t() : 0;}SubmitItem::SubmitItem( const QDomElement& element ){ m_artist = element.namedItem( "artist" ).toElement().text(); m_album = element.namedItem( "album" ).toElement().text(); m_title = element.namedItem( "title" ).toElement().text(); m_length = element.namedItem( "length" ).toElement().text().toInt(); m_playStartTime = element.namedItem( "playtime" ).toElement().text().toUInt();}SubmitItem::SubmitItem() : m_length( 0 ) , m_playStartTime( 0 ){}bool SubmitItem::operator==( const SubmitItem& item ){ bool result = true; if ( m_artist != item.artist() || m_album != item.album() || m_title != item.title() || m_length != item.length() || m_playStartTime != item.playStartTime() ) { result = false; } return result;}QDomElement SubmitItem::toDomElement( QDomDocument& document ) const{ QDomElement item = document.createElement( "item" ); // TODO: In the future, it might be good to store url too //item.setAttribute("url", item->url().url()); QDomElement artist = document.createElement( "artist" ); QDomText artistText = document.createTextNode( m_artist ); artist.appendChild( artistText ); item.appendChild( artist ); QDomElement album = document.createElement( "album" ); QDomText albumText = document.createTextNode( m_album ); album.appendChild( albumText ); item.appendChild( album ); QDomElement title = document.createElement( "title" ); QDomText titleText = document.createTextNode( m_title ); title.appendChild( titleText ); item.appendChild( title ); QDomElement length = document.createElement( "length" ); QDomText lengthText = document.createTextNode( QString::number( m_length ) ); length.appendChild( lengthText ); item.appendChild( length ); QDomElement playtime = document.createElement( "playtime" ); QDomText playtimeText = document.createTextNode( QString::number( m_playStartTime ) ); playtime.appendChild( playtimeText ); item.appendChild( playtime ); return item;}////////////////////////////////////////////////////////////////////////////////// CLASS SubmitQueue////////////////////////////////////////////////////////////////////////////////int SubmitQueue::compareItems( QPtrCollection::Item item1, QPtrCollection::Item item2 ){ SubmitItem *sItem1 = static_cast<SubmitItem*>( item1 ); SubmitItem *sItem2 = static_cast<SubmitItem*>( item2 ); int result; if ( sItem1 == sItem2 ) { result = 0; } else if ( sItem1->playStartTime() > sItem2->playStartTime() ) { result = 1; } else { result = -1; } return result;}////////////////////////////////////////////////////////////////////////////////// CLASS ScrobblerSubmitter////////////////////////////////////////////////////////////////////////////////QString ScrobblerSubmitter::PROTOCOL_VERSION = "1.1";QString ScrobblerSubmitter::CLIENT_ID = "ark";QString ScrobblerSubmitter::CLIENT_VERSION = "1.4";QString ScrobblerSubmitter::HANDSHAKE_URL = "http://post.audioscrobbler.com/?hs=true";ScrobblerSubmitter::ScrobblerSubmitter() : m_username( 0 ) , m_password( 0 ) , m_submitUrl( 0 ) , m_challenge( 0 ) , m_scrobblerEnabled( false ) , m_holdFakeQueue( false ) , m_inProgress( false ) , m_needHandshake( true ) , m_prevSubmitTime( 0 ) , m_interval( 0 ) , m_backoff( 0 ) , m_lastSubmissionFinishTime( 0 ) , m_fakeQueueLength( 0 ){ connect( &m_timer, SIGNAL(timeout()), this, SLOT(scheduledTimeReached()) );
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?