/src/libtomahawk/infosystem/infoplugins/generic/spotifyPlugin.cpp

http://github.com/tomahawk-player/tomahawk · C++ · 397 lines · 293 code · 78 blank · 26 comment · 45 complexity · 06a3b3e3606f11072973754a1a9d19e3 MD5 · raw file

  1. /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
  2. *
  3. * Copyright 2010-2011, Hugo Lindstr??m <hugolm84@gmail.com>
  4. *
  5. * Tomahawk is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * Tomahawk is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. #include "spotifyPlugin.h"
  19. #include <QDir>
  20. #include <QSettings>
  21. #include <QCryptographicHash>
  22. #include <QNetworkConfiguration>
  23. #include <QNetworkReply>
  24. #include "album.h"
  25. #include "typedefs.h"
  26. #include "audio/audioengine.h"
  27. #include "tomahawksettings.h"
  28. #include "utils/tomahawkutils.h"
  29. #include "utils/logger.h"
  30. #include "chartsplugin_data_p.h"
  31. #define SPOTIFY_API_URL "http://spotikea.tomahawk-player.org/"
  32. #include <qjson/parser.h>
  33. #include <qjson/serializer.h>
  34. using namespace Tomahawk::InfoSystem;
  35. SpotifyPlugin::SpotifyPlugin()
  36. : InfoPlugin()
  37. , m_chartsFetchJobs( 0 )
  38. {
  39. m_supportedGetTypes << InfoChart << InfoChartCapabilities;
  40. }
  41. SpotifyPlugin::~SpotifyPlugin()
  42. {
  43. qDebug() << Q_FUNC_INFO;
  44. }
  45. void
  46. SpotifyPlugin::dataError( Tomahawk::InfoSystem::InfoRequestData requestData )
  47. {
  48. emit info( requestData, QVariant() );
  49. return;
  50. }
  51. void
  52. SpotifyPlugin::getInfo( Tomahawk::InfoSystem::InfoRequestData requestData )
  53. {
  54. qDebug() << Q_FUNC_INFO << requestData.caller;
  55. qDebug() << Q_FUNC_INFO << requestData.customData;
  56. InfoStringHash hash = requestData.input.value< Tomahawk::InfoSystem::InfoStringHash >();
  57. switch ( requestData.type )
  58. {
  59. case InfoChart:
  60. if ( !hash.contains( "chart_source" ) || hash["chart_source"] != "spotify" )
  61. {
  62. dataError( requestData );
  63. break;
  64. }
  65. qDebug() << Q_FUNC_INFO << "InfoCHart req for" << hash["chart_source"];
  66. fetchChart( requestData );
  67. break;
  68. case InfoChartCapabilities:
  69. fetchChartCapabilities( requestData );
  70. break;
  71. default:
  72. dataError( requestData );
  73. }
  74. }
  75. void
  76. SpotifyPlugin::fetchChart( Tomahawk::InfoSystem::InfoRequestData requestData )
  77. {
  78. if ( !requestData.input.canConvert< Tomahawk::InfoSystem::InfoStringHash >() )
  79. {
  80. dataError( requestData );
  81. return;
  82. }
  83. InfoStringHash hash = requestData.input.value< Tomahawk::InfoSystem::InfoStringHash >();
  84. Tomahawk::InfoSystem::InfoStringHash criteria;
  85. /// Each request needs to contain both a id and source
  86. if ( !hash.contains( "chart_id" ) && !hash.contains( "chart_source" ) )
  87. {
  88. tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Hash did not contain required params!";
  89. dataError( requestData );
  90. return;
  91. }
  92. /// Set the criterias for current chart
  93. criteria["chart_id"] = hash["chart_id"];
  94. criteria["chart_source"] = hash["chart_source"];
  95. emit getCachedInfo( criteria, 86400000 /* Expire chart cache in 1 day */, requestData );
  96. }
  97. void
  98. SpotifyPlugin::fetchChartCapabilities( Tomahawk::InfoSystem::InfoRequestData requestData )
  99. {
  100. if ( !requestData.input.canConvert< Tomahawk::InfoSystem::InfoStringHash >() )
  101. {
  102. dataError( requestData );
  103. return;
  104. }
  105. Tomahawk::InfoSystem::InfoStringHash criteria;
  106. criteria[ "InfoChartCapabilities" ] = "spotifyplugin";
  107. emit getCachedInfo( criteria, 604800000, requestData );
  108. }
  109. void
  110. SpotifyPlugin::notInCacheSlot( Tomahawk::InfoSystem::InfoStringHash criteria, Tomahawk::InfoSystem::InfoRequestData requestData )
  111. {
  112. switch ( requestData.type )
  113. {
  114. case InfoChart:
  115. {
  116. /// Fetch the chart, we need source and id
  117. tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "InfoChart not in cache! Fetching...";
  118. QUrl url = QUrl( QString( SPOTIFY_API_URL "toplist/%1/" ).arg( criteria["chart_id"] ) );
  119. qDebug() << Q_FUNC_INFO << "Getting chart url" << url;
  120. QNetworkReply* reply = TomahawkUtils::nam()->get( QNetworkRequest( url ) );
  121. reply->setProperty( "requestData", QVariant::fromValue< Tomahawk::InfoSystem::InfoRequestData >( requestData ) );
  122. connect( reply, SIGNAL( finished() ), SLOT( chartReturned() ) );
  123. return;
  124. }
  125. case InfoChartCapabilities:
  126. {
  127. tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "InfoChartCapabilities not in cache! Fetching...";
  128. // we never need to re-fetch
  129. if ( !m_allChartsMap.isEmpty() )
  130. return;
  131. /// We need to fetch possible types before they are asked for
  132. tDebug() << "SpotifyPlugin: InfoChart fetching possible resources";
  133. QUrl url = QUrl( QString( SPOTIFY_API_URL "toplist/charts" ) );
  134. QNetworkReply* reply = TomahawkUtils::nam()->get( QNetworkRequest( url ) );
  135. tDebug() << Q_FUNC_INFO << "fetching:" << url;
  136. connect( reply, SIGNAL( finished() ), SLOT( chartTypes() ) );
  137. m_chartsFetchJobs++;
  138. if ( m_chartsFetchJobs > 0 )
  139. {
  140. qDebug() << Q_FUNC_INFO << "InfoChartCapabilities still fetching!";
  141. m_cachedRequests.append( requestData );
  142. return;
  143. }
  144. emit info( requestData, m_allChartsMap );
  145. return;
  146. }
  147. default:
  148. {
  149. tLog() << Q_FUNC_INFO << "Couldn't figure out what to do with this type of request after cache miss";
  150. emit info( requestData, QVariant() );
  151. return;
  152. }
  153. }
  154. }
  155. void
  156. SpotifyPlugin::chartTypes()
  157. {
  158. /// Get possible chart type for specificSpotifyPlugin: InfoChart types returned chart source
  159. tDebug() << Q_FUNC_INFO << "Got spotifychart type result";
  160. QNetworkReply* reply = qobject_cast<QNetworkReply*>( sender() );
  161. if ( reply->error() == QNetworkReply::NoError )
  162. {
  163. QJson::Parser p;
  164. bool ok;
  165. const QVariantMap res = p.parse( reply, &ok ).toMap();
  166. const QVariantMap chartObj = res;
  167. if ( !ok )
  168. {
  169. tLog() << Q_FUNC_INFO << "Failed to parse resources" << p.errorString() << "On line" << p.errorLine();
  170. return;
  171. }
  172. QVariantMap charts;
  173. foreach(QVariant geos, chartObj.value("Charts").toList().takeLast().toMap().value("geo").toList() )
  174. {
  175. const QString geo = geos.toMap().value( "name" ).toString();
  176. const QString geoId = geos.toMap().value( "id" ).toString();
  177. QString country;
  178. if( geo == "For me" )
  179. continue; /// country = geo; Lets use this later, when we can get the spotify username from tomahawk
  180. else if( geo == "Everywhere" )
  181. country = geo;
  182. else
  183. {
  184. QLocale l( QString( "en_%1" ).arg( geo ) );
  185. country = Tomahawk::CountryUtils::fullCountryFromCode( geo );
  186. for ( int i = 1; i < country.size(); i++ )
  187. {
  188. if ( country.at( i ).isUpper() )
  189. {
  190. country.insert( i, " " );
  191. i++;
  192. }
  193. }
  194. }
  195. QList< InfoStringHash > chart_types;
  196. foreach(QVariant types, chartObj.value("Charts").toList().takeFirst().toMap().value("types").toList() )
  197. {
  198. QString type = types.toMap().value( "id" ).toString();
  199. QString label = types.toMap().value( "name" ).toString();
  200. InfoStringHash c;
  201. c[ "id" ] = type + "/" + geoId;
  202. c[ "label" ] = label;
  203. c[ "type" ] = type;
  204. chart_types.append( c );
  205. }
  206. charts.insert( country.toUtf8(), QVariant::fromValue<QList< InfoStringHash > >( chart_types ) );
  207. }
  208. QVariantMap defaultMap;
  209. defaultMap[ "spotify" ] = QStringList() << "United States" << "Top Albums";
  210. m_allChartsMap[ "defaults" ] = defaultMap;
  211. m_allChartsMap.insert( "Spotify", QVariant::fromValue<QVariantMap>( charts ) );
  212. }
  213. else
  214. {
  215. tLog() << Q_FUNC_INFO << "Error fetching charts:" << reply->errorString();
  216. }
  217. m_chartsFetchJobs--;
  218. if ( !m_cachedRequests.isEmpty() && m_chartsFetchJobs == 0 )
  219. {
  220. foreach ( InfoRequestData request, m_cachedRequests )
  221. {
  222. emit info( request, m_allChartsMap );
  223. Tomahawk::InfoSystem::InfoStringHash criteria;
  224. criteria[ "InfoChartCapabilities" ] = "spotifyplugin";
  225. emit updateCache( criteria,604800000, request.type, m_allChartsMap );
  226. }
  227. m_cachedRequests.clear();
  228. }
  229. }
  230. void
  231. SpotifyPlugin::chartReturned()
  232. {
  233. /// Chart request returned something! Woho
  234. QNetworkReply* reply = qobject_cast<QNetworkReply*>( sender() );
  235. QString url = reply->url().toString();
  236. QVariantMap returnedData;
  237. if ( reply->error() == QNetworkReply::NoError )
  238. {
  239. QJson::Parser p;
  240. bool ok;
  241. QVariantMap res = p.parse( reply, &ok ).toMap();
  242. if ( !ok )
  243. {
  244. tLog() << "Failed to parse json from chart lookup:" << p.errorString() << "On line" << p.errorLine();
  245. return;
  246. }
  247. /// SO we have a result, parse it!
  248. QList< InfoStringHash > top_tracks;
  249. QList< InfoStringHash > top_albums;
  250. QStringList top_artists;
  251. if( url.contains( "albums" ) )
  252. setChartType( Album );
  253. else if( url.contains( "tracks" ) )
  254. setChartType( Track );
  255. else if( url.contains( "artists" ) )
  256. setChartType( Artist );
  257. else
  258. setChartType( None );
  259. foreach(QVariant result, res.value("toplist").toMap().value("result").toList() )
  260. {
  261. QString title, artist;
  262. QVariantMap chartMap = result.toMap();
  263. if ( !chartMap.isEmpty() )
  264. {
  265. title = chartMap.value( "title" ).toString();
  266. artist = chartMap.value( "artist" ).toString();
  267. if( chartType() == Track )
  268. {
  269. InfoStringHash pair;
  270. pair["artist"] = artist;
  271. pair["track"] = title;
  272. top_tracks << pair;
  273. qDebug() << "SpotifyChart type is track";
  274. }
  275. if( chartType() == Album )
  276. {
  277. InfoStringHash pair;
  278. pair["artist"] = artist;
  279. pair["album"] = title;
  280. top_albums << pair;
  281. qDebug() << "SpotifyChart type is album";
  282. }
  283. if( chartType() == Artist )
  284. {
  285. top_artists << chartMap.value( "name" ).toString();
  286. qDebug() << "SpotifyChart type is artist";
  287. }
  288. }
  289. }
  290. if( chartType() == Track )
  291. {
  292. tDebug() << "ChartsPlugin:" << "\tgot " << top_tracks.size() << " tracks";
  293. returnedData["tracks"] = QVariant::fromValue( top_tracks );
  294. returnedData["type"] = "tracks";
  295. }
  296. if( chartType() == Album )
  297. {
  298. tDebug() << "ChartsPlugin:" << "\tgot " << top_albums.size() << " albums";
  299. returnedData["albums"] = QVariant::fromValue( top_albums );
  300. returnedData["type"] = "albums";
  301. }
  302. if( chartType() == Artist )
  303. {
  304. tDebug() << "ChartsPlugin:" << "\tgot " << top_artists.size() << " artists";
  305. returnedData["artists"] = top_artists;
  306. returnedData["type"] = "artists";
  307. }
  308. Tomahawk::InfoSystem::InfoRequestData requestData = reply->property( "requestData" ).value< Tomahawk::InfoSystem::InfoRequestData >();
  309. emit info( requestData, returnedData );
  310. // update cache
  311. Tomahawk::InfoSystem::InfoStringHash criteria;
  312. Tomahawk::InfoSystem::InfoStringHash origData = requestData.input.value< Tomahawk::InfoSystem::InfoStringHash >();
  313. criteria[ "chart_id" ] = origData[ "chart_id" ];
  314. criteria[ "chart_source" ] = origData[ "chart_source" ];
  315. emit updateCache( criteria, 86400000, requestData.type, returnedData );
  316. }
  317. else
  318. qDebug() << "Network error in fetching chart:" << reply->url().toString();
  319. }