diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index 7075505fd..71481e3a7 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -192,6 +192,7 @@ set( libSources accounts/spotify/SpotifyAccount.cpp accounts/spotify/SpotifyAccountConfig.cpp accounts/spotify/SpotifyPlaylistUpdater.cpp + accounts/spotify/SpotifyInfoPlugin.cpp accounts/lastfm/LastFmAccount.cpp accounts/lastfm/LastFmConfig.cpp diff --git a/src/libtomahawk/accounts/spotify/SpotifyAccount.cpp b/src/libtomahawk/accounts/spotify/SpotifyAccount.cpp index 6380ecb67..2aaa30505 100644 --- a/src/libtomahawk/accounts/spotify/SpotifyAccount.cpp +++ b/src/libtomahawk/accounts/spotify/SpotifyAccount.cpp @@ -29,6 +29,8 @@ #include "Pipeline.h" #include "accounts/AccountManager.h" #include "utils/Closure.h" +#include "SpotifyInfoPlugin.h" +#include "infosystem/InfoSystem.h" #ifndef ENABLE_HEADLESS #include "jobview/JobStatusView.h" @@ -106,6 +108,12 @@ SpotifyAccount::init() AtticaManager::instance()->registerCustomAccount( s_resolverId, this ); qRegisterMetaType< Tomahawk::Accounts::SpotifyPlaylistInfo* >( "Tomahawk::Accounts::SpotifyPlaylist*" ); + if ( infoPlugin() && Tomahawk::InfoSystem::InfoSystem::instance()->workerThread() ) + { + infoPlugin().data()->moveToThread( Tomahawk::InfoSystem::InfoSystem::instance()->workerThread().data() ); + Tomahawk::InfoSystem::InfoSystem::instance()->addInfoPlugin( infoPlugin() ); + } + if ( !AtticaManager::instance()->resolversLoaded() ) { // If we're still waiting to load, wait for the attica resolvers to come down the pipe @@ -328,6 +336,18 @@ SpotifyAccount::connectionState() const } +InfoSystem::InfoPluginPtr +SpotifyAccount::infoPlugin() +{ + if ( m_infoPlugin.isNull() ) + { + m_infoPlugin = QWeakPointer< InfoSystem::SpotifyInfoPlugin >( new InfoSystem::SpotifyInfoPlugin( this ) ); + } + + return InfoSystem::InfoPluginPtr( m_infoPlugin.data() ); +} + + void SpotifyAccount::resolverInstalled(const QString& resolverId) { @@ -384,6 +404,14 @@ SpotifyAccount::setManualResolverPath( const QString &resolverPath ) } +bool +SpotifyAccount::loggedIn() const +{ + // TODO pending newconfigui branch + return enabled() && !m_spotifyResolver.isNull() && m_spotifyResolver.data()->running(); +} + + void SpotifyAccount::hookupAfterDeletion( bool autoEnable ) { diff --git a/src/libtomahawk/accounts/spotify/SpotifyAccount.h b/src/libtomahawk/accounts/spotify/SpotifyAccount.h index 51b55ee44..4490be8b5 100644 --- a/src/libtomahawk/accounts/spotify/SpotifyAccount.h +++ b/src/libtomahawk/accounts/spotify/SpotifyAccount.h @@ -35,6 +35,12 @@ class QTimer; class ScriptResolver; namespace Tomahawk { + +namespace InfoSystem +{ + class SpotifyInfoPlugin; +} + namespace Accounts { class SpotifyAccountConfig; @@ -89,7 +95,7 @@ public: virtual void deauthenticate(); virtual QWidget* aclWidget() { return 0; } - virtual Tomahawk::InfoSystem::InfoPluginPtr infoPlugin() { return Tomahawk::InfoSystem::InfoPluginPtr(); } + virtual Tomahawk::InfoSystem::InfoPluginPtr infoPlugin(); virtual SipPlugin* sipPlugin() { return 0; } virtual bool preventEnabling() const { return m_preventEnabling; } @@ -102,6 +108,8 @@ public: void setManualResolverPath( const QString& resolverPath ); + bool loggedIn() const; + public slots: void aboutToShow( QAction* action, const Tomahawk::playlist_ptr& playlist ); void syncActionTriggered( bool ); @@ -143,6 +151,7 @@ private: QWeakPointer m_configWidget; QWeakPointer m_aboutWidget; QWeakPointer m_spotifyResolver; + QWeakPointer< InfoSystem::SpotifyInfoPlugin > m_infoPlugin; QMap > m_qidToSlotMap; diff --git a/src/libtomahawk/accounts/spotify/SpotifyInfoPlugin.cpp b/src/libtomahawk/accounts/spotify/SpotifyInfoPlugin.cpp new file mode 100644 index 000000000..297b93fe2 --- /dev/null +++ b/src/libtomahawk/accounts/spotify/SpotifyInfoPlugin.cpp @@ -0,0 +1,263 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2012 Leo Franchi + * + * 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 . + */ + +#include "SpotifyInfoPlugin.h" + +#include "SpotifyAccount.h" +#include "utils/Closure.h" + +using namespace Tomahawk; +using namespace Tomahawk::InfoSystem; + + +SpotifyInfoPlugin::SpotifyInfoPlugin( Accounts::SpotifyAccount* account ) + : InfoPlugin() + , m_account( QWeakPointer< Accounts::SpotifyAccount >( account ) ) +{ + if ( !m_account.isNull() ) + m_supportedGetTypes << InfoAlbumSongs; +} + + +SpotifyInfoPlugin::~SpotifyInfoPlugin() +{ + +} + + +void +SpotifyInfoPlugin::getInfo( InfoRequestData requestData ) +{ + switch ( requestData.type ) + { + case InfoAlbumSongs: + { + if ( !requestData.input.canConvert< Tomahawk::InfoSystem::InfoStringHash >() ) + { + dataError( requestData ); + return; + } + + InfoStringHash hash = requestData.input.value< Tomahawk::InfoSystem::InfoStringHash >(); + if ( !hash.contains( "album" ) ) + { + dataError( requestData ); + return; + } + + Tomahawk::InfoSystem::InfoStringHash criteria; + criteria[ "album" ] = hash[ "album" ]; + if ( hash.contains( "artist" ) ) + criteria["artist"] = hash["artist"]; + + emit getCachedInfo( criteria, 2419200000, requestData ); + + return; + } + default: + dataError( requestData ); + } +} + + +void +SpotifyInfoPlugin::notInCacheSlot( InfoStringHash criteria, InfoRequestData requestData ) +{ + switch ( requestData.type ) + { + case InfoAlbumSongs: + { + const QString album = criteria[ "album" ]; + const QString artist = criteria[ "artist" ]; + + if ( m_account.isNull() || !m_account.data()->loggedIn() ) + { + // No running spotify account, use our webservice + QUrl lookupUrl( "http://ws.spotify.com/search/1/album.json" ); + lookupUrl.addQueryItem( "q", QString( "%1 %2" ).arg( album ).arg( album ) ); + + QNetworkReply * reply = TomahawkUtils::nam()->get( QNetworkRequest( lookupUrl ) ); + NewClosure( reply, SIGNAL( finished() ), this, SLOT( albumIdLookupFinished( QNetworkReply*, Tomahawk::InfoSystem::InfoRequestData ) ), reply, requestData ); + } + else + { + // Running resolver, so do the lookup through that + qDebug() << Q_FUNC_INFO << "Doing album lookup through spotify:" << album << artist; + QVariantMap message; + message[ "_msgtype" ] = "albumListing"; + message[ "artist" ] = artist; + message[ "album" ] = album; + + const QString qid = m_account.data()->sendMessage( message, this, "albumListingResult" ); + + m_waitingForResults[ qid ] = requestData; + } + break; + } + default: + { + Q_ASSERT( false ); + break; + } + } +} + + +void +SpotifyInfoPlugin::albumListingResult( const QString& msgType, const QVariantMap& msg ) +{ + Q_ASSERT( msg.contains( "qid" ) ); + Q_ASSERT( m_waitingForResults.contains( msg.value( "qid" ).toString() ) ); + + if ( !msg.contains( "qid" ) || !m_waitingForResults.contains( msg.value( "qid" ).toString() ) ) + return; + + const InfoRequestData requestData = m_waitingForResults.take( msg.value( "qid" ).toString() ); + + QVariantList tracks = msg.value( "tracks" ).toList(); + QStringList trackNameList; + + foreach ( const QVariant track, tracks ) + { + const QVariantMap trackData = track.toMap(); + if ( trackData.contains( "track" ) && !trackData[ "track" ].toString().isEmpty() ) + trackNameList << trackData[ "track" ].toString(); + } + + qDebug() << Q_FUNC_INFO << "Successfully got album listing from spotify resolver"; + trackListResult( trackNameList, requestData ); +} + + +void +SpotifyInfoPlugin::albumIdLookupFinished( QNetworkReply* reply, const InfoRequestData& requestData ) +{ + Q_ASSERT( reply ); + + reply->deleteLater(); + + if ( reply->error() == QNetworkReply::NoError ) + { + QJson::Parser p; + const QVariantMap response = p.parse( reply ).toMap(); + if ( !response.contains( "albums" ) ) + { + dataError( requestData ); + return; + } + + const QVariantList albums = response.value( "albums" ).toList(); + if ( albums.isEmpty() ) + { + dataError( requestData ); + return; + } + + const QVariantMap album = albums.first().toMap(); + const QString id = album.value( "href" ).toString(); + if ( id.isEmpty() || !id.contains( "spotify:album" ) ) + { + qDebug() << "Empty or malformed spotify album ID from json:" << id << response; + dataError( requestData ); + return; + } + + qDebug() << "Doing spotify album lookup via webservice with ID:" << id; + + QUrl lookupUrl( QString( "http://spotikea.tomahawk-player.org/browse/%1" ).arg( id ) ); + + + QNetworkReply * reply = TomahawkUtils::nam()->get( QNetworkRequest( lookupUrl ) ); + NewClosure( reply, SIGNAL( finished() ), this, SLOT( albumContentsLookupFinished( QNetworkReply*, Tomahawk::InfoSystem::InfoRequestData ) ), reply, requestData ); + } + else + { + qDebug() << "Network Error retrieving ID from spotify metadata service:" << reply->error() << reply->errorString() << reply->url(); + } +} + + +void +SpotifyInfoPlugin::albumContentsLookupFinished( QNetworkReply* reply, const InfoRequestData& requestData ) +{ + Q_ASSERT( reply ); + + reply->deleteLater(); + + if ( reply->error() == QNetworkReply::NoError ) + { + QJson::Parser p; + const QVariantMap response = p.parse( reply ).toMap(); + + if ( !response.contains( "album" ) ) + { + dataError( requestData ); + return; + } + + const QVariantMap album = response.value( "album" ).toMap(); + if ( !album.contains( "result" ) || album.value( "result" ).toList().isEmpty() ) + { + dataError( requestData ); + return; + } + + const QVariantList albumTracks = album.value( "result" ).toList(); + QStringList trackNameList; + + foreach ( const QVariant& track, albumTracks ) + { + const QVariantMap trackMap = track.toMap(); + if ( trackMap.contains( "title" ) ) + trackNameList << trackMap.value( "title" ).toString(); + } + + qDebug() << Q_FUNC_INFO << "Successfully got album listing from spotikea service!"; + + if ( trackNameList.isEmpty() ) + dataError( requestData ); + else + trackListResult( trackNameList, requestData ); + } + else + { + qDebug() << "Network Error retrieving ID from spotify metadata service:" << reply->error() << reply->errorString() << reply->url(); + } +} + +void +SpotifyInfoPlugin::dataError( InfoRequestData requestData ) +{ + emit info( requestData, QVariant() ); +} + + +void +SpotifyInfoPlugin::trackListResult( const QStringList& trackNameList, const InfoRequestData& requestData ) +{ + QVariantMap returnedData; + returnedData["tracks"] = trackNameList; + + emit info( requestData, returnedData ); + + Tomahawk::InfoSystem::InfoStringHash criteria; + criteria["artist"] = requestData.input.value< InfoStringHash>()["artist"]; + criteria["album"] = requestData.input.value< InfoStringHash>()["album"]; + + emit updateCache( criteria, 0, requestData.type, returnedData ); +} diff --git a/src/libtomahawk/accounts/spotify/SpotifyInfoPlugin.h b/src/libtomahawk/accounts/spotify/SpotifyInfoPlugin.h new file mode 100644 index 000000000..fafc0cead --- /dev/null +++ b/src/libtomahawk/accounts/spotify/SpotifyInfoPlugin.h @@ -0,0 +1,74 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2012 Leo Franchi + * + * 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 . + */ + +#ifndef SPOTIFYINFOPLUGIN_H +#define SPOTIFYINFOPLUGIN_H + +#include "infosystem/InfoSystem.h" +#include "DllMacro.h" + +#include + +class QNetworkReply; + +namespace Tomahawk +{ + +namespace Accounts +{ + class SpotifyAccount; +} + +namespace InfoSystem +{ + +class DLLEXPORT SpotifyInfoPlugin : public InfoPlugin +{ + Q_OBJECT + +public: + explicit SpotifyInfoPlugin( Accounts::SpotifyAccount* account ); + virtual ~SpotifyInfoPlugin(); + +public slots: + void albumListingResult( const QString& msgType, const QVariantMap& msg ); + +protected slots: + virtual void init() {} + virtual void getInfo( Tomahawk::InfoSystem::InfoRequestData requestData ); + virtual void notInCacheSlot( Tomahawk::InfoSystem::InfoStringHash criteria, Tomahawk::InfoSystem::InfoRequestData requestData ); + virtual void pushInfo( Tomahawk::InfoSystem::InfoPushData ) {} + +private slots: + void albumIdLookupFinished( QNetworkReply* reply, const Tomahawk::InfoSystem::InfoRequestData& requestData ); + void albumContentsLookupFinished( QNetworkReply* reply, const Tomahawk::InfoSystem::InfoRequestData& requestData ); + +private: + void dataError( InfoRequestData ); + void trackListResult( const QStringList& trackNameList, const Tomahawk::InfoSystem::InfoRequestData& requestData ); + + QHash< QString, InfoRequestData > m_waitingForResults; + + QWeakPointer< Tomahawk::Accounts::SpotifyAccount > m_account; +}; + +} + +} + +#endif // SPOTIFYINFOPLUGIN_H