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

http://github.com/tomahawk-player/tomahawk · C++ · 572 lines · 418 code · 99 blank · 55 comment · 84 complexity · 7502fdc9f7f9642f9f67dae7effa5252 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. * Copyright 2011, Leo Franchi <lfranchi@kde.org>
  5. *
  6. * Tomahawk is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * Tomahawk is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. #include "chartsplugin.h"
  20. #include <QtCore/QDir>
  21. #include <QtCore/QSettings>
  22. #include <QtNetwork/QNetworkConfiguration>
  23. #include <QtNetwork/QNetworkReply>
  24. #include "album.h"
  25. #include "chartsplugin_data_p.h"
  26. #include "typedefs.h"
  27. #include "audio/audioengine.h"
  28. #include "tomahawksettings.h"
  29. #include "utils/tomahawkutils.h"
  30. #include "utils/logger.h"
  31. #define CHART_URL "http://charts.tomahawk-player.org/"
  32. //#define CHART_URL "http://localhost:8080/"
  33. #include <qjson/parser.h>
  34. #include <qjson/serializer.h>
  35. using namespace Tomahawk::InfoSystem;
  36. ChartsPlugin::ChartsPlugin()
  37. : InfoPlugin()
  38. , m_chartsFetchJobs( 0 )
  39. {
  40. /// Add resources here
  41. m_chartResources << "billboard" << "itunes" << "rdio" << "wearehunted" << "ex.fm" << "soundcloudwall.com";
  42. m_supportedGetTypes << InfoChart << InfoChartCapabilities;
  43. }
  44. ChartsPlugin::~ChartsPlugin()
  45. {
  46. tDebug( LOGVERBOSE ) << Q_FUNC_INFO;
  47. }
  48. void
  49. ChartsPlugin::dataError( Tomahawk::InfoSystem::InfoRequestData requestData )
  50. {
  51. emit info( requestData, QVariant() );
  52. return;
  53. }
  54. void
  55. ChartsPlugin::getInfo( Tomahawk::InfoSystem::InfoRequestData requestData )
  56. {
  57. //qDebug() << Q_FUNC_INFO << requestData.caller;
  58. //qDebug() << Q_FUNC_INFO << requestData.customData;
  59. InfoStringHash hash = requestData.input.value< Tomahawk::InfoSystem::InfoStringHash >();
  60. bool foundSource = false;
  61. switch ( requestData.type )
  62. {
  63. case InfoChart:
  64. /// We need something to check if the request is actually ment to go to this plugin
  65. if ( !hash.contains( "chart_source" ) )
  66. {
  67. tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Hash did not contain required param!";
  68. dataError( requestData );
  69. break;
  70. }
  71. else
  72. {
  73. foreach( QString resource, m_chartResources )
  74. {
  75. if( resource == hash["chart_source"] )
  76. {
  77. foundSource = true;
  78. }
  79. }
  80. if( !foundSource )
  81. {
  82. dataError( requestData );
  83. break;
  84. }
  85. }
  86. fetchChart( requestData );
  87. break;
  88. case InfoChartCapabilities:
  89. fetchChartCapabilities( requestData );
  90. break;
  91. default:
  92. dataError( requestData );
  93. }
  94. }
  95. void
  96. ChartsPlugin::fetchChart( Tomahawk::InfoSystem::InfoRequestData requestData )
  97. {
  98. if ( !requestData.input.canConvert< Tomahawk::InfoSystem::InfoStringHash >() )
  99. {
  100. dataError( requestData );
  101. return;
  102. }
  103. InfoStringHash hash = requestData.input.value< Tomahawk::InfoSystem::InfoStringHash >();
  104. Tomahawk::InfoSystem::InfoStringHash criteria;
  105. /// Each request needs to contain both a id and source
  106. if ( !hash.contains( "chart_id" ) && !hash.contains( "chart_source" ) )
  107. {
  108. tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Hash did not contain required params!";
  109. dataError( requestData );
  110. return;
  111. }
  112. /// Set the criterias for current chart
  113. criteria["chart_id"] = hash["chart_id"];
  114. criteria["chart_source"] = hash["chart_source"];
  115. emit getCachedInfo( criteria, 86400000, requestData );
  116. }
  117. void
  118. ChartsPlugin::fetchChartCapabilities( Tomahawk::InfoSystem::InfoRequestData requestData )
  119. {
  120. if ( !requestData.input.canConvert< Tomahawk::InfoSystem::InfoStringHash >() )
  121. {
  122. tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Could not convert requestData to InfoStringHash!";
  123. dataError( requestData );
  124. return;
  125. }
  126. Tomahawk::InfoSystem::InfoStringHash criteria;
  127. criteria[ "InfoChartCapabilities" ] = "chartsplugin";
  128. emit getCachedInfo( criteria, 604800000, requestData );
  129. }
  130. void
  131. ChartsPlugin::notInCacheSlot( QHash<QString, QString> criteria, Tomahawk::InfoSystem::InfoRequestData requestData )
  132. {
  133. switch ( requestData.type )
  134. {
  135. case InfoChart:
  136. {
  137. tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "InfoChart not in cache! Fetching...";
  138. /// Fetch the chart, we need source and id
  139. QUrl url = QUrl( QString( CHART_URL "source/%1/chart/%2" ).arg( criteria["chart_source"] ).arg( criteria["chart_id"] ) );
  140. tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Getting chart url" << url;
  141. QNetworkReply* reply = TomahawkUtils::nam()->get( QNetworkRequest( url ) );
  142. reply->setProperty( "requestData", QVariant::fromValue< Tomahawk::InfoSystem::InfoRequestData >( requestData ) );
  143. connect( reply, SIGNAL( finished() ), SLOT( chartReturned() ) );
  144. return;
  145. }
  146. case InfoChartCapabilities:
  147. {
  148. tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "InfoChartCapabilities not in cache! Fetching...";
  149. // we never need to re-fetch
  150. if ( !m_allChartsMap.isEmpty() )
  151. return;
  152. /// Then get each chart from resource
  153. if ( !m_chartResources.isEmpty() && m_allChartsMap.isEmpty() )
  154. {
  155. tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "InfoChart fetching possible resources";
  156. foreach ( QString resource, m_chartResources )
  157. {
  158. QUrl url = QUrl( QString( CHART_URL "source/%1" ).arg( resource ) );
  159. QNetworkReply* reply = TomahawkUtils::nam()->get( QNetworkRequest( url ) );
  160. reply->setProperty( "chart_resource", resource);
  161. tDebug() << "fetching:" << url;
  162. connect( reply, SIGNAL( finished() ), SLOT( chartTypes() ) );
  163. m_chartsFetchJobs++;
  164. }
  165. }
  166. if ( m_chartsFetchJobs > 0 )
  167. {
  168. tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "InfoChartCapabilities still fetching!";
  169. m_cachedRequests.append( requestData );
  170. return;
  171. }
  172. emit info( requestData, m_allChartsMap );
  173. return;
  174. }
  175. default:
  176. {
  177. tLog() << Q_FUNC_INFO << "Couldn't figure out what to do with this type of request after cache miss";
  178. emit info( requestData, QVariant() );
  179. return;
  180. }
  181. }
  182. }
  183. void
  184. ChartsPlugin::chartTypes()
  185. {
  186. /// Get possible chart type for specificChartsPlugin: InfoChart types returned chart source
  187. tDebug( LOGVERBOSE ) << "Got chart type result";
  188. QNetworkReply* reply = qobject_cast<QNetworkReply*>( sender() );
  189. if ( reply->error() == QNetworkReply::NoError )
  190. {
  191. QJson::Parser p;
  192. bool ok;
  193. const QVariantMap res = p.parse( reply, &ok ).toMap();
  194. const QVariantMap chartObjs = res.value( "charts" ).toMap();
  195. if ( !ok )
  196. {
  197. tLog() << "Failed to parse resources" << p.errorString() << "On line" << p.errorLine();
  198. return;
  199. }
  200. /// Got types, append!
  201. const QString source = res.value( "source" ).toString();
  202. // We'll populate charts with the data from the server
  203. QVariantMap charts;
  204. QString chartName;
  205. QStringList defaultChain;
  206. if ( source == "wearehunted" || source == "itunes" )
  207. {
  208. // Some charts can have an extra param, itunes has geo, WAH has emerging/mainstream
  209. // Itunes has geographic-area based charts. So we build a breadcrumb of
  210. // ITunes - Country - Albums - Top Chart Type
  211. // - Tracks - Top Chart Type
  212. // WeAreHunted has Mainstream/Emerging
  213. // WeAreHunted - Type - Artists - Chart Type
  214. // - Tracks - Chart Type
  215. QHash< QString, QVariantMap > extraType;
  216. foreach( const QVariant& chartObj, chartObjs.values() )
  217. {
  218. if( !chartObj.toMap().isEmpty() )
  219. {
  220. const QVariantMap chart = chartObj.toMap();
  221. const QString id = chart.value( "id" ).toString();
  222. const QString geo = chart.value( "geo" ).toString();
  223. QString name = chart.value( "genre" ).toString();
  224. const QString type = chart.value( "type" ).toString();
  225. const bool isDefault = ( chart.contains( "default" ) && chart[ "default" ].toInt() == 1 );
  226. QString extra;
  227. if( !geo.isEmpty() )
  228. {
  229. if ( !m_cachedCountries.contains( geo ) )
  230. {
  231. QLocale l( QString( "en_%1" ).arg( geo ) );
  232. extra = Tomahawk::CountryUtils::fullCountryFromCode( geo );
  233. for ( int i = 1; i < extra.size(); i++ )
  234. {
  235. if ( extra.at( i ).isUpper() )
  236. {
  237. extra.insert( i, " " );
  238. i++;
  239. }
  240. }
  241. m_cachedCountries[ geo ] = extra;
  242. }
  243. else
  244. extra = m_cachedCountries[ geo ];
  245. }
  246. else
  247. extra = chart.value( "extra" ).toString();
  248. if ( name.isEmpty() ) // not a specific chart, an all chart
  249. name = tr( "Top Overall" );
  250. InfoStringHash c;
  251. c[ "id" ] = id;
  252. c[ "label" ] = name;
  253. c[ "type" ] = "album";
  254. if ( isDefault )
  255. c[ "default" ] = "true";
  256. QList< Tomahawk::InfoSystem::InfoStringHash > extraTypeData = extraType[ extra ][ type ].value< QList< Tomahawk::InfoSystem::InfoStringHash > >();
  257. extraTypeData.append( c );
  258. extraType[ extra ][ type ] = QVariant::fromValue< QList< Tomahawk::InfoSystem::InfoStringHash > >( extraTypeData );
  259. if ( isDefault )
  260. {
  261. defaultChain.clear();
  262. defaultChain.append( extra );
  263. defaultChain.append( type );
  264. defaultChain.append( name );
  265. }
  266. }
  267. foreach( const QString& c, extraType.keys() )
  268. {
  269. charts[ c ] = extraType[ c ];
  270. // tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "extraType has types:" << c;
  271. }
  272. if( source == "itunes" ){
  273. chartName = "iTunes";
  274. }
  275. if( source == "wearehunted" ){
  276. chartName = "WeAreHunted";
  277. }
  278. }
  279. }
  280. else
  281. {
  282. // We'll just build:
  283. // [Source] - Album - Chart Type
  284. // [Source] - Track - Chart Type
  285. QList< InfoStringHash > albumCharts;
  286. QList< InfoStringHash > trackCharts;
  287. QList< InfoStringHash > artistCharts;
  288. foreach( const QVariant& chartObj, chartObjs.values() )
  289. {
  290. if( !chartObj.toMap().isEmpty() ){
  291. const QVariantMap chart = chartObj.toMap();
  292. const QString type = chart.value( "type" ).toString();
  293. const bool isDefault = ( chart.contains( "default" ) && chart[ "default" ].toInt() == 1 );
  294. InfoStringHash c;
  295. c[ "id" ] = chart.value( "id" ).toString();
  296. if( chart.value( "genre").isValid() )
  297. c[ "label" ] = chart.value( "genre" ).toString();
  298. else
  299. c[ "label" ] = chart.value( "name" ).toString();
  300. if ( isDefault )
  301. c[ "default" ] = "true";
  302. if ( type == "Album" )
  303. {
  304. c[ "type" ] = "album";
  305. albumCharts.append( c );
  306. }
  307. else if ( type == "Track" )
  308. {
  309. c[ "type" ] = "tracks";
  310. trackCharts.append( c );
  311. }else if ( type == "Artist" )
  312. {
  313. c[ "type" ] = "artists";
  314. artistCharts.append( c );
  315. }
  316. if ( isDefault )
  317. {
  318. defaultChain.clear();
  319. defaultChain.append( type + "s" ); //UGLY but it's plural to the user, see below
  320. defaultChain.append( c[ "label" ] );
  321. }
  322. }
  323. if( !artistCharts.isEmpty() )
  324. charts.insert( tr( "Artists" ), QVariant::fromValue< QList< Tomahawk::InfoSystem::InfoStringHash > >( artistCharts ) );
  325. if( !albumCharts.isEmpty() )
  326. charts.insert( tr( "Albums" ), QVariant::fromValue< QList< Tomahawk::InfoSystem::InfoStringHash > >( albumCharts ) );
  327. if( !trackCharts.isEmpty() )
  328. charts.insert( tr( "Tracks" ), QVariant::fromValue< QList< Tomahawk::InfoSystem::InfoStringHash > >( trackCharts ) );
  329. /// @note For displaying purposes, upper the first letter
  330. /// @note Remeber to lower it when fetching this!
  331. chartName = source;
  332. chartName[0] = chartName[0].toUpper();
  333. }
  334. }
  335. /// Add the possible charts and its types to breadcrumb
  336. // tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "ADDING CHART TYPE TO CHARTS:" << chartName;
  337. QVariantMap defaultMap = m_allChartsMap.value( "defaults" ).value< QVariantMap >();
  338. defaultMap[ source ] = defaultChain;
  339. m_allChartsMap[ "defaults" ] = defaultMap;
  340. m_allChartsMap[ "defaultSource" ] = "itunes";
  341. m_allChartsMap.insert( chartName , QVariant::fromValue< QVariantMap >( charts ) );
  342. }
  343. else
  344. {
  345. tLog() << "Error fetching charts:" << reply->errorString();
  346. }
  347. m_chartsFetchJobs--;
  348. if ( !m_cachedRequests.isEmpty() && m_chartsFetchJobs == 0 )
  349. {
  350. foreach ( InfoRequestData request, m_cachedRequests )
  351. {
  352. emit info( request, m_allChartsMap );
  353. // update cache
  354. Tomahawk::InfoSystem::InfoStringHash criteria;
  355. criteria[ "InfoChartCapabilities" ] = "chartsplugin";
  356. emit updateCache( criteria, 604800000, request.type, m_allChartsMap );
  357. }
  358. m_cachedRequests.clear();
  359. }
  360. }
  361. void
  362. ChartsPlugin::chartReturned()
  363. {
  364. /// Chart request returned something! Woho
  365. QNetworkReply* reply = qobject_cast<QNetworkReply*>( sender() );
  366. QVariantMap returnedData;
  367. if ( reply->error() == QNetworkReply::NoError )
  368. {
  369. QJson::Parser p;
  370. bool ok;
  371. QVariantMap res = p.parse( reply, &ok ).toMap();
  372. if ( !ok )
  373. {
  374. tLog() << "Failed to parse json from chart lookup:" << p.errorString() << "On line" << p.errorLine();
  375. return;
  376. }
  377. /// SO we have a result, parse it!
  378. QVariantList chartResponse = res.value( "list" ).toList();
  379. QList< Tomahawk::InfoSystem::InfoStringHash > top_tracks;
  380. QList< Tomahawk::InfoSystem::InfoStringHash > top_albums;
  381. QStringList top_artists;
  382. /// Deside what type, we need to handle it differently
  383. /// @todo: We allready know the type, append it to breadcrumb hash
  384. if( res.value( "type" ).toString() == "Album" )
  385. setChartType( Album );
  386. else if( res.value( "type" ).toString() == "Track" )
  387. setChartType( Track );
  388. else if( res.value( "type" ).toString() == "Artist" )
  389. setChartType( Artist );
  390. else
  391. setChartType( None );
  392. // qDebug() << "Got chart returned!" << res;
  393. foreach ( QVariant chartR, chartResponse )
  394. {
  395. QString title, artist, album;
  396. QVariantMap chartMap = chartR.toMap();
  397. if ( !chartMap.isEmpty() )
  398. {
  399. title = chartMap.value( "track" ).toString();
  400. album = chartMap.value( "album" ).toString();
  401. artist = chartMap.value( "artist" ).toString();
  402. /// Maybe we can use rank later on, to display something nice
  403. /// rank = chartMap.value( "rank" ).toString();
  404. if ( chartType() == Album )
  405. {
  406. if ( album.isEmpty() && artist.isEmpty() ) // don't have enough...
  407. {
  408. tDebug( LOGVERBOSE ) << "Didn't get an artist and album name from chart, not enough to build a query on. Aborting" << title << album << artist;
  409. }
  410. else
  411. {
  412. Tomahawk::InfoSystem::InfoStringHash pair;
  413. pair["artist"] = artist;
  414. pair["album"] = album;
  415. top_albums.append( pair );
  416. }
  417. }
  418. else if ( chartType() == Track )
  419. {
  420. if ( title.isEmpty() && artist.isEmpty() ) // don't have enough...
  421. {
  422. tDebug( LOGVERBOSE ) << "Didn't get an artist and track name from charts, not enough to build a query on. Aborting" << title << artist << album;
  423. }
  424. else
  425. {
  426. Tomahawk::InfoSystem::InfoStringHash pair;
  427. pair["artist"] = artist;
  428. pair["track"] = title;
  429. top_tracks.append( pair );
  430. }
  431. }else if( chartType() == Artist )
  432. {
  433. if ( artist.isEmpty() ) // don't have enough...
  434. {
  435. tDebug( LOGVERBOSE ) << "Didn't get an artist from charts, not enough to build a query on. Aborting" << artist;
  436. }
  437. else
  438. {
  439. top_artists.append( artist );
  440. }
  441. }
  442. }
  443. }
  444. if( chartType() == Artist )
  445. {
  446. tDebug() << "ChartsPlugin:" << "\tgot " << top_artists.size() << " artists";
  447. returnedData[ "artists" ] = QVariant::fromValue< QStringList >( top_artists );
  448. returnedData[ "type" ] = "artists";
  449. }
  450. if( chartType() == Track )
  451. {
  452. tDebug() << "ChartsPlugin:" << "\tgot " << top_tracks.size() << " tracks";
  453. returnedData[ "tracks" ] = QVariant::fromValue< QList< Tomahawk::InfoSystem::InfoStringHash > >( top_tracks );
  454. returnedData[ "type" ] = "tracks";
  455. }
  456. if( chartType() == Album )
  457. {
  458. tDebug() << "ChartsPlugin:" << "\tgot " << top_albums.size() << " albums";
  459. returnedData[ "albums" ] = QVariant::fromValue< QList< Tomahawk::InfoSystem::InfoStringHash > >( top_albums );
  460. returnedData[ "type" ] = "albums";
  461. }
  462. Tomahawk::InfoSystem::InfoRequestData requestData = reply->property( "requestData" ).value< Tomahawk::InfoSystem::InfoRequestData >();
  463. emit info( requestData, returnedData );
  464. // update cache
  465. Tomahawk::InfoSystem::InfoStringHash criteria;
  466. Tomahawk::InfoSystem::InfoStringHash origData = requestData.input.value< Tomahawk::InfoSystem::InfoStringHash >();
  467. criteria[ "chart_id" ] = origData[ "chart_id" ];
  468. criteria[ "chart_source" ] = origData[ "chart_source" ];
  469. emit updateCache( criteria, 86400000, requestData.type, returnedData );
  470. }
  471. else
  472. tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Network error in fetching chart:" << reply->url().toString();
  473. }