1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-04-07 17:42:35 +02:00

Make a Last FM InfoSystem plugin and use it to handle now playing and scrobbling

This commit is contained in:
Jeff Mitchell 2011-03-26 09:36:31 -04:00
parent 0530faf15c
commit 5c09fd2360
8 changed files with 376 additions and 167 deletions

View File

@ -83,6 +83,9 @@ enum InfoType {
InfoMiscTopHotttness,
InfoMiscTopTerms,
InfoMiscSubmitNowPlaying,
InfoMiscSubmitScrobble,
InfoNoInfo
};

View File

@ -36,6 +36,7 @@ SET( tomahawkSources ${tomahawkSources}
infosystem/infosystem.cpp
infosystem/infoplugins/echonestplugin.cpp
infosystem/infoplugins/lastfmplugin.cpp
infosystem/infoplugins/musixmatchplugin.cpp
web/api_v1.cpp
@ -77,6 +78,7 @@ SET( tomahawkHeaders ${tomahawkHeaders}
sip/SipHandler.h
infosystem/infoplugins/echonestplugin.h
infosystem/infoplugins/lastfmplugin.h
infosystem/infoplugins/musixmatchplugin.h
web/api_v1.h

View File

@ -0,0 +1,251 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
*
* Tomahawk 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 3 of the License, or
* (at your option) any later version.
*
* Tomahawk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#include "lastfmplugin.h"
#include <QDir>
#include <QSettings>
#include <QCryptographicHash>
#include "album.h"
#include "typedefs.h"
#include "audio/audioengine.h"
#include "tomahawksettings.h"
#include "tomahawk/tomahawkapp.h"
#include <lastfm/ws.h>
#include <lastfm/XmlQuery>
using namespace Tomahawk::InfoSystem;
static QString
md5( const QByteArray& src )
{
QByteArray const digest = QCryptographicHash::hash( src, QCryptographicHash::Md5 );
return QString::fromLatin1( digest.toHex() ).rightJustified( 32, '0' );
}
LastFmPlugin::LastFmPlugin( QObject* parent )
: InfoPlugin(parent)
, m_scrobbler( 0 )
, m_authJob( 0 )
{
QSet< InfoType > supportedTypes;
supportedTypes << Tomahawk::InfoSystem::InfoMiscSubmitScrobble << Tomahawk::InfoSystem::InfoMiscSubmitNowPlaying;
qobject_cast< InfoSystem* >(parent)->registerInfoTypes(this, supportedTypes);
/*
Your API Key is 7194b85b6d1f424fe1668173a78c0c4a
Your secret is ba80f1df6d27ae63e9cb1d33ccf2052f
*/
// Flush session key cache
TomahawkSettings::instance()->setLastFmSessionKey( QByteArray() );
lastfm::ws::ApiKey = "7194b85b6d1f424fe1668173a78c0c4a";
lastfm::ws::SharedSecret = "ba80f1df6d27ae63e9cb1d33ccf2052f";
lastfm::ws::Username = TomahawkSettings::instance()->lastFmUsername();
m_pw = TomahawkSettings::instance()->lastFmPassword();
if( TomahawkSettings::instance()->scrobblingEnabled() && !lastfm::ws::Username.isEmpty() )
{
createScrobbler();
}
//HACK work around a bug in liblastfm---it doesn't create its config dir, so when it
// tries to write the track cache, it fails silently. until we have a fixed version, do this
// code taken from Amarok (src/services/lastfm/ScrobblerAdapter.cpp)
#ifdef Q_WS_X11
QString lpath = QDir::home().filePath( ".local/share/Last.fm" );
QDir ldir = QDir( lpath );
if( !ldir.exists() )
{
ldir.mkpath( lpath );
}
#endif
connect( TomahawkSettings::instance(), SIGNAL( changed() ),
SLOT( settingsChanged() ), Qt::QueuedConnection );
}
LastFmPlugin::~LastFmPlugin()
{
delete m_scrobbler;
}
void
LastFmPlugin::getInfo( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomDataHash customData )
{
qDebug() << Q_FUNC_INFO;
if ( type == InfoMiscSubmitNowPlaying )
nowPlaying( caller, type, data, customData );
else if ( type == InfoMiscSubmitScrobble )
scrobble( caller, type, data, customData );
else
dataError( caller, type, data, customData );
}
void
LastFmPlugin::nowPlaying( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomDataHash &customData )
{
if ( !data.canConvert< Tomahawk::InfoSystem::InfoCustomDataHash >() || !m_scrobbler )
{
dataError( caller, type, data, customData );
return;
}
InfoCustomDataHash hash = data.value< Tomahawk::InfoSystem::InfoCustomDataHash >();
if ( !hash.contains( "title" ) || !hash.contains( "artist" ) || !hash.contains( "album" ) || !hash.contains( "duration" ) )
{
dataError( caller, type, data, customData );
return;
}
m_track = lastfm::MutableTrack();
m_track.stamp();
m_track.setTitle( hash["title"].toString() );
m_track.setArtist( hash["artist"].toString() );
m_track.setAlbum( hash["album"].toString() );
m_track.setDuration( hash["duration"].toUInt() );
m_track.setSource( lastfm::Track::Player );
m_scrobbler->nowPlaying( m_track );
emit info( caller, type, data, QVariant(), customData );
emit finished( caller, type );
}
void
LastFmPlugin::dataError( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomDataHash &customData )
{
emit info( caller, type, data, QVariant(), customData );
emit finished( caller, type );
return;
}
void
LastFmPlugin::scrobble( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomDataHash &customData )
{
Q_ASSERT( QThread::currentThread() == thread() );
if ( !m_scrobbler || m_track.isNull() )
{
dataError( caller, type, data, customData );
return;
}
qDebug() << Q_FUNC_INFO << m_track.toString();
m_scrobbler->cache( m_track );
m_scrobbler->submit();
emit info( caller, type, data, QVariant(), customData );
emit finished( caller, type );
}
void
LastFmPlugin::settingsChanged()
{
if( !m_scrobbler && TomahawkSettings::instance()->scrobblingEnabled() )
{ // can simply create the scrobbler
lastfm::ws::Username = TomahawkSettings::instance()->lastFmUsername();
m_pw = TomahawkSettings::instance()->lastFmPassword();
createScrobbler();
}
else if( m_scrobbler && !TomahawkSettings::instance()->scrobblingEnabled() )
{
delete m_scrobbler;
m_scrobbler = 0;
}
else if( TomahawkSettings::instance()->lastFmUsername() != lastfm::ws::Username ||
TomahawkSettings::instance()->lastFmPassword() != m_pw )
{
lastfm::ws::Username = TomahawkSettings::instance()->lastFmUsername();
// credentials have changed, have to re-create scrobbler for them to take effect
if( m_scrobbler )
delete m_scrobbler;
createScrobbler();
}
}
void
LastFmPlugin::onAuthenticated()
{
if( !m_authJob )
{
qDebug() << Q_FUNC_INFO << "Help! No longer got a last.fm auth job!";
return;
}
if( m_authJob->error() == QNetworkReply::NoError )
{
lastfm::XmlQuery lfm = lastfm::XmlQuery( m_authJob->readAll() );
if( lfm.children( "error" ).size() > 0 )
{
qDebug() << "Error from authenticating with Last.fm service:" << lfm.text();
TomahawkSettings::instance()->setLastFmSessionKey( QByteArray() );
}
else
{
lastfm::ws::SessionKey = lfm[ "session" ][ "key" ].text();
TomahawkSettings::instance()->setLastFmSessionKey( lastfm::ws::SessionKey.toLatin1() );
if( TomahawkSettings::instance()->scrobblingEnabled() )
m_scrobbler = new lastfm::Audioscrobbler( "thk" );
}
}
else
{
qDebug() << "Got error in Last.fm authentication job:" << m_authJob->errorString();
}
m_authJob->deleteLater();
}
void
LastFmPlugin::createScrobbler()
{
if( TomahawkSettings::instance()->lastFmSessionKey().isEmpty() ) // no session key, so get one
{
QString authToken = md5( ( lastfm::ws::Username.toLower() + md5( m_pw.toUtf8() ) ).toUtf8() );
QMap<QString, QString> query;
query[ "method" ] = "auth.getMobileSession";
query[ "username" ] = lastfm::ws::Username;
query[ "authToken" ] = authToken;
m_authJob = lastfm::ws::post( query );
connect( m_authJob, SIGNAL( finished() ), SLOT( onAuthenticated() ) );
}
else
{
lastfm::ws::SessionKey = TomahawkSettings::instance()->lastFmSessionKey();
m_scrobbler = new lastfm::Audioscrobbler( "thk" );
m_scrobbler->moveToThread( thread() );
}
}

View File

@ -0,0 +1,69 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
*
* Tomahawk 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 3 of the License, or
* (at your option) any later version.
*
* Tomahawk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LASTFMPLUGIN_H
#define LASTFMPLUGIN_H
#include "tomahawk/infosystem.h"
#include "result.h"
#include <lastfm/Track>
#include <lastfm/Audioscrobbler>
#include <lastfm/ScrobblePoint>
#include <QObject>
class QNetworkReply;
namespace Tomahawk
{
namespace InfoSystem
{
class LastFmPlugin : public InfoPlugin
{
Q_OBJECT
public:
LastFmPlugin( QObject *parent );
virtual ~LastFmPlugin();
void getInfo( const QString &caller, const InfoType type, const QVariant &data, InfoCustomDataHash customData );
public slots:
void settingsChanged();
void onAuthenticated();
private:
void scrobble( const QString &caller, const InfoType type, const QVariant& data, InfoCustomDataHash &customData );
void createScrobbler();
void nowPlaying( const QString &caller, const InfoType type, const QVariant& data, InfoCustomDataHash &customData );
void dataError( const QString &caller, const InfoType type, const QVariant& data, InfoCustomDataHash &customData );
lastfm::MutableTrack m_track;
lastfm::Audioscrobbler* m_scrobbler;
QString m_pw;
QNetworkReply* m_authJob;
};
}
}
#endif // LASTFMPLUGIN_H

View File

@ -19,6 +19,7 @@
#include "tomahawk/infosystem.h"
#include "infoplugins/echonestplugin.h"
#include "infoplugins/musixmatchplugin.h"
#include "infoplugins/lastfmplugin.h"
using namespace Tomahawk::InfoSystem;
@ -33,6 +34,8 @@ InfoSystem::InfoSystem(QObject *parent)
m_plugins.append(enptr);
InfoPluginPtr mmptr(new MusixMatchPlugin(this));
m_plugins.append(mmptr);
InfoPluginPtr lfmptr(new LastFmPlugin(this));
m_plugins.append(lfmptr);
}
void InfoSystem::registerInfoTypes(const InfoPluginPtr &plugin, const QSet< InfoType >& types)

View File

@ -27,67 +27,27 @@
#include "audio/audioengine.h"
#include "tomahawksettings.h"
#include "tomahawk/tomahawkapp.h"
#include "tomahawk/infosystem.h"
#include <lastfm/ws.h>
#include <lastfm/XmlQuery>
static QString
md5( const QByteArray& src )
{
QByteArray const digest = QCryptographicHash::hash( src, QCryptographicHash::Md5 );
return QString::fromLatin1( digest.toHex() ).rightJustified( 32, '0' );
}
static QString s_infoIdentifier = QString("SCROBBLER");
Scrobbler::Scrobbler( QObject* parent )
: QObject( parent )
, m_scrobbler( 0 )
, m_reachedScrobblePoint( false )
, m_authJob( 0 )
{
/*
Your API Key is 7194b85b6d1f424fe1668173a78c0c4a
Your secret is ba80f1df6d27ae63e9cb1d33ccf2052f
*/
// Flush session key cache
TomahawkSettings::instance()->setLastFmSessionKey( QByteArray() );
lastfm::ws::ApiKey = "7194b85b6d1f424fe1668173a78c0c4a";
lastfm::ws::SharedSecret = "ba80f1df6d27ae63e9cb1d33ccf2052f";
lastfm::ws::Username = TomahawkSettings::instance()->lastFmUsername();
m_pw = TomahawkSettings::instance()->lastFmPassword();
if( TomahawkSettings::instance()->scrobblingEnabled() && !lastfm::ws::Username.isEmpty() )
{
createScrobbler();
}
//HACK work around a bug in liblastfm---it doesn't create its config dir, so when it
// tries to write the track cache, it fails silently. until we have a fixed version, do this
// code taken from Amarok (src/services/lastfm/ScrobblerAdapter.cpp)
#ifdef Q_WS_X11
QString lpath = QDir::home().filePath( ".local/share/Last.fm" );
QDir ldir = QDir( lpath );
if( !ldir.exists() )
{
ldir.mkpath( lpath );
}
#endif
connect( TomahawkSettings::instance(), SIGNAL( changed() ),
SLOT( settingsChanged() ), Qt::QueuedConnection );
connect( AudioEngine::instance(), SIGNAL( timerSeconds( unsigned int ) ),
SLOT( engineTick( unsigned int ) ), Qt::QueuedConnection );
connect( TomahawkApp::instance()->infoSystem(),
SIGNAL( info( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash ) ),
SLOT( infoSystemInfo( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash ) ) );
connect( TomahawkApp::instance()->infoSystem(), SIGNAL( finished( QString ) ), SLOT( infoSystemFinished( QString ) ) );
}
Scrobbler::~Scrobbler()
{
delete m_scrobbler;
}
@ -97,26 +57,23 @@ Scrobbler::trackStarted( const Tomahawk::result_ptr& track )
Q_ASSERT( QThread::currentThread() == thread() );
// qDebug() << Q_FUNC_INFO;
if( !m_scrobbler )
return;
if( m_reachedScrobblePoint )
{
m_reachedScrobblePoint = false;
scrobble();
}
m_track = lastfm::MutableTrack();
m_track.stamp();
m_track.setTitle( track->track() );
m_track.setArtist( track->artist()->name() );
m_track.setAlbum( track->album()->name() );
m_track.setDuration( track->duration() );
m_track.setSource( lastfm::Track::Player );
m_scrobbler->nowPlaying( m_track );
m_scrobblePoint = ScrobblePoint( m_track.duration() / 2 );
Tomahawk::InfoSystem::InfoCustomDataHash trackInfo;
trackInfo["title"] = QVariant::fromValue< QString >( track->track() );
trackInfo["artist"] = QVariant::fromValue< QString >( track->artist()->name() );
trackInfo["album"] = QVariant::fromValue< QString >( track->album()->name() );
trackInfo["duration"] = QVariant::fromValue< uint >( track->duration() );
TomahawkApp::instance()->infoSystem()->getInfo(
s_infoIdentifier, Tomahawk::InfoSystem::InfoMiscSubmitNowPlaying,
QVariant::fromValue< Tomahawk::InfoSystem::InfoCustomDataHash >( trackInfo ), Tomahawk::InfoSystem::InfoCustomDataHash() );
m_scrobblePoint = ScrobblePoint( track->duration() / 2 );
}
@ -159,102 +116,31 @@ void
Scrobbler::scrobble()
{
Q_ASSERT( QThread::currentThread() == thread() );
if ( !m_scrobbler || m_track.isNull() )
return;
qDebug() << Q_FUNC_INFO << m_track.toString();
m_scrobbler->cache( m_track );
m_scrobbler->submit();
}
void
Scrobbler::settingsChanged()
{
if( !m_scrobbler && TomahawkSettings::instance()->scrobblingEnabled() )
{ // can simply create the scrobbler
lastfm::ws::Username = TomahawkSettings::instance()->lastFmUsername();
m_pw = TomahawkSettings::instance()->lastFmPassword();
createScrobbler();
}
else if( m_scrobbler && !TomahawkSettings::instance()->scrobblingEnabled() )
{
delete m_scrobbler;
m_scrobbler = 0;
}
else if( TomahawkSettings::instance()->lastFmUsername() != lastfm::ws::Username ||
TomahawkSettings::instance()->lastFmPassword() != m_pw )
{
lastfm::ws::Username = TomahawkSettings::instance()->lastFmUsername();
// credentials have changed, have to re-create scrobbler for them to take effect
if( m_scrobbler )
delete m_scrobbler;
createScrobbler();
}
}
void
Scrobbler::onAuthenticated()
{
if( !m_authJob )
{
qDebug() << Q_FUNC_INFO << "Help! No longer got a last.fm auth job!";
return;
}
if( m_authJob->error() == QNetworkReply::NoError )
{
lastfm::XmlQuery lfm = lastfm::XmlQuery( m_authJob->readAll() );
if( lfm.children( "error" ).size() > 0 )
{
qDebug() << "Error from authenticating with Last.fm service:" << lfm.text();
TomahawkSettings::instance()->setLastFmSessionKey( QByteArray() );
}
else
{
lastfm::ws::SessionKey = lfm[ "session" ][ "key" ].text();
TomahawkSettings::instance()->setLastFmSessionKey( lastfm::ws::SessionKey.toLatin1() );
if( TomahawkSettings::instance()->scrobblingEnabled() )
m_scrobbler = new lastfm::Audioscrobbler( "thk" );
}
}
else
{
qDebug() << "Got error in Last.fm authentication job:" << m_authJob->errorString();
}
m_authJob->deleteLater();
TomahawkApp::instance()->infoSystem()->getInfo(
s_infoIdentifier, Tomahawk::InfoSystem::InfoMiscSubmitScrobble,
QVariant(), Tomahawk::InfoSystem::InfoCustomDataHash() );
}
void
Scrobbler::createScrobbler()
Scrobbler::infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData )
{
if( TomahawkSettings::instance()->lastFmSessionKey().isEmpty() ) // no session key, so get one
if ( caller == s_infoIdentifier )
{
QString authToken = md5( ( lastfm::ws::Username.toLower() + md5( m_pw.toUtf8() ) ).toUtf8() );
QMap<QString, QString> query;
query[ "method" ] = "auth.getMobileSession";
query[ "username" ] = lastfm::ws::Username;
query[ "authToken" ] = authToken;
m_authJob = lastfm::ws::post( query );
connect( m_authJob, SIGNAL( finished() ), SLOT( onAuthenticated() ) );
}
else
{
lastfm::ws::SessionKey = TomahawkSettings::instance()->lastFmSessionKey();
m_scrobbler = new lastfm::Audioscrobbler( "thk" );
m_scrobbler->moveToThread( thread() );
qDebug() << Q_FUNC_INFO;
if ( type == Tomahawk::InfoSystem::InfoMiscSubmitNowPlaying )
qDebug() << "Scrobbler received now playing response from InfoSystem";
else if ( type == Tomahawk::InfoSystem::InfoMiscSubmitScrobble )
qDebug() << "Scrobbler received scrobble response from InfoSystem";
}
}
void
Scrobbler::infoSystemFinished( QString target )
{
if ( target == s_infoIdentifier )
{
qDebug() << Q_FUNC_INFO;
qDebug() << "Scrobbler received done signal from InfoSystem";
}
}

View File

@ -21,13 +21,12 @@
#include "result.h"
#include <lastfm/Track>
#include <lastfm/Audioscrobbler>
#include <lastfm/ScrobblePoint>
#include "lastfm/ScrobblePoint"
#include "tomahawk/infosystem.h"
#include <QObject>
class QNetworkReply;
/**
* Simple class that listens to signals from AudioEngine and scrobbles
* what it is playing.
@ -46,20 +45,14 @@ public slots:
void trackStopped();
void engineTick( unsigned int secondsElapsed );
void settingsChanged();
void onAuthenticated();
void infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData );
void infoSystemFinished( QString target );
private:
void scrobble();
void createScrobbler();
lastfm::MutableTrack m_track;
lastfm::Audioscrobbler* m_scrobbler;
QString m_pw;
bool m_reachedScrobblePoint;
ScrobblePoint m_scrobblePoint;
QNetworkReply* m_authJob;
};

View File

@ -213,6 +213,9 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] )
connect( m_shortcutHandler, SIGNAL( mute() ), m_audioEngine, SLOT( mute() ) );
}
qDebug() << "Init InfoSystem.";
m_infoSystem = new Tomahawk::InfoSystem::InfoSystem( this );
#ifdef LIBLASTFM_FOUND
qDebug() << "Init Scrobbler.";
m_scrobbler = new Scrobbler( this );
@ -236,6 +239,7 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] )
#endif
// Set up proxy
//FIXME: This overrides the lastfm proxy above?
if( TomahawkSettings::instance()->proxyType() != QNetworkProxy::NoProxy &&
!TomahawkSettings::instance()->proxyHost().isEmpty() )
{
@ -252,8 +256,6 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] )
qDebug() << "Init SIP system.";
m_sipHandler = new SipHandler( this );
qDebug() << "Init InfoSystem.";
m_infoSystem = new Tomahawk::InfoSystem::InfoSystem( this );
#ifndef TOMAHAWK_HEADLESS
if ( !m_headless )