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 + -
显示快捷键?