/src/libtomahawk/Artist.cpp

http://github.com/tomahawk-player/tomahawk · C++ · 681 lines · 507 code · 148 blank · 26 comment · 64 complexity · a86bba051bd1b947bfdd880f3b767f85 MD5 · raw file

  1. /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
  2. *
  3. * Copyright 2010-2015, Christian Muehlhaeuser <muesli@tomahawk-player.org>
  4. * Copyright 2010-2012, Jeff Mitchell <jeff@tomahawk-player.org>
  5. * Copyright 2013, Teo Mrnjavac <teo@kde.org>
  6. *
  7. * Tomahawk is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License as published by
  9. * the Free Software Foundation, either version 3 of the License, or
  10. * (at your option) any later version.
  11. *
  12. * Tomahawk is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20. #include "Artist.h"
  21. #include "collection/Collection.h"
  22. #include "database/Database.h"
  23. #include "database/DatabaseImpl.h"
  24. #include "database/DatabaseCommand_AllAlbums.h"
  25. #include "database/DatabaseCommand_ArtistStats.h"
  26. #include "database/DatabaseCommand_TrackStats.h"
  27. #include "database/IdThreadWorker.h"
  28. #include "utils/TomahawkUtilsGui.h"
  29. #include "utils/Logger.h"
  30. #include "ArtistPlaylistInterface.h"
  31. #include "PlaylistEntry.h"
  32. #include "Source.h"
  33. #include <QReadWriteLock>
  34. #include <QPixmapCache>
  35. #include <QCoreApplication>
  36. using namespace Tomahawk;
  37. QHash< QString, artist_wptr > Artist::s_artistsByName = QHash< QString, artist_wptr >();
  38. QHash< unsigned int, artist_wptr > Artist::s_artistsById = QHash< unsigned int, artist_wptr >();
  39. static QMutex s_nameCacheMutex;
  40. static QReadWriteLock s_idMutex;
  41. static QMutex s_memberMutex;
  42. Artist::~Artist()
  43. {
  44. FINEGRAINED_MSG( Q_FUNC_INFO << "Deleting artist:" << m_name );
  45. m_ownRef.clear();
  46. delete m_cover;
  47. }
  48. artist_ptr
  49. Artist::get( const QString& name, bool autoCreate )
  50. {
  51. if ( name.isEmpty() )
  52. return artist_ptr();
  53. QMutexLocker lock( &s_nameCacheMutex );
  54. const QString key = name.toLower();
  55. if ( s_artistsByName.contains( key ) )
  56. {
  57. artist_wptr artist = s_artistsByName.value( key );
  58. if ( artist )
  59. return artist.toStrongRef();
  60. }
  61. if ( !Database::instance() || !Database::instance()->impl() )
  62. return artist_ptr();
  63. artist_ptr artist = artist_ptr( new Artist( name ), &Artist::deleteLater );
  64. artist->setWeakRef( artist.toWeakRef() );
  65. artist->loadId( autoCreate );
  66. s_artistsByName.insert( key, artist );
  67. return artist;
  68. }
  69. artist_ptr
  70. Artist::get( unsigned int id, const QString& name )
  71. {
  72. Q_ASSERT( id > 0 );
  73. s_idMutex.lockForRead();
  74. if ( s_artistsById.contains( id ) )
  75. {
  76. artist_wptr artist = s_artistsById.value( id );
  77. s_idMutex.unlock();
  78. if ( !artist.isNull() )
  79. return artist;
  80. }
  81. s_idMutex.unlock();
  82. QMutexLocker lock( &s_nameCacheMutex );
  83. const QString key = name.toLower();
  84. if ( s_artistsByName.contains( key ) )
  85. {
  86. artist_wptr artist = s_artistsByName.value( key );
  87. if ( !artist.isNull() )
  88. return artist;
  89. }
  90. artist_ptr a = artist_ptr( new Artist( id, name ), &Artist::deleteLater );
  91. a->moveToThread( QCoreApplication::instance()->thread() );
  92. a->setWeakRef( a.toWeakRef() );
  93. s_artistsByName.insert( key, a );
  94. if ( id > 0 )
  95. {
  96. s_idMutex.lockForWrite();
  97. s_artistsById.insert( id, a );
  98. s_idMutex.unlock();
  99. }
  100. return a;
  101. }
  102. Artist::Artist( unsigned int id, const QString& name )
  103. : QObject()
  104. , m_waitingForFuture( false )
  105. , m_id( id )
  106. , m_name( name )
  107. , m_coverLoaded( false )
  108. , m_coverLoading( false )
  109. , m_simArtistsLoaded( false )
  110. , m_biographyLoaded( false )
  111. , m_infoJobs( 0 )
  112. , m_chartPosition( 0 )
  113. , m_chartCount( 0 )
  114. , m_cover( 0 )
  115. {
  116. FINEGRAINED_MSG( Q_FUNC_INFO << "Creating artist:" << id << name );
  117. m_sortname = DatabaseImpl::sortname( name, true );
  118. }
  119. Artist::Artist( const QString& name )
  120. : QObject()
  121. , m_waitingForFuture( true )
  122. , m_id( 0 )
  123. , m_name( name )
  124. , m_coverLoaded( false )
  125. , m_coverLoading( false )
  126. , m_simArtistsLoaded( false )
  127. , m_biographyLoaded( false )
  128. , m_infoJobs( 0 )
  129. , m_chartPosition( 0 )
  130. , m_chartCount( 0 )
  131. , m_cover( 0 )
  132. {
  133. FINEGRAINED_MSG( Q_FUNC_INFO << "Creating artist:" << name );
  134. m_sortname = DatabaseImpl::sortname( name, true );
  135. }
  136. void
  137. Artist::deleteLater()
  138. {
  139. QMutexLocker lock( &s_nameCacheMutex );
  140. const QString key = m_name.toLower();
  141. if ( s_artistsByName.contains( key ) )
  142. {
  143. s_artistsByName.remove( key );
  144. }
  145. if ( m_id > 0 )
  146. {
  147. s_idMutex.lockForWrite();
  148. if ( s_artistsById.contains( m_id ) )
  149. {
  150. s_artistsById.remove( m_id );
  151. }
  152. s_idMutex.unlock();
  153. }
  154. QObject::deleteLater();
  155. }
  156. void
  157. Artist::onArtistStatsLoaded( unsigned int /* plays */, unsigned int chartPos, unsigned int chartCount )
  158. {
  159. m_chartPosition = chartPos;
  160. m_chartCount = chartCount;
  161. emit statsLoaded();
  162. }
  163. void
  164. Artist::onTracksLoaded( Tomahawk::ModelMode mode, const Tomahawk::collection_ptr& collection )
  165. {
  166. emit tracksAdded( playlistInterface( mode, collection )->tracks(), mode, collection );
  167. }
  168. QList<album_ptr>
  169. Artist::albums( ModelMode mode, const Tomahawk::collection_ptr& collection ) const
  170. {
  171. artist_ptr artist = m_ownRef.toStrongRef();
  172. bool dbLoaded = m_albumsLoaded.value( DatabaseMode );
  173. const bool infoLoaded = m_albumsLoaded.value( InfoSystemMode );
  174. if ( !collection.isNull() )
  175. dbLoaded = false;
  176. if ( ( mode == DatabaseMode || mode == Mixed ) && !dbLoaded )
  177. {
  178. if ( collection.isNull() )
  179. {
  180. DatabaseCommand_AllAlbums* cmd = new DatabaseCommand_AllAlbums( collection, artist );
  181. cmd->setData( QVariant( collection.isNull() ) );
  182. connect( cmd, SIGNAL( albums( QList<Tomahawk::album_ptr>, QVariant ) ),
  183. SLOT( onAlbumsFound( QList<Tomahawk::album_ptr>, QVariant ) ) );
  184. Database::instance()->enqueue( Tomahawk::dbcmd_ptr( cmd ) );
  185. }
  186. else
  187. {
  188. //collection is *surely* not null, and might be a ScriptCollection
  189. Tomahawk::AlbumsRequest* cmd = collection->requestAlbums( artist );
  190. // There is also a signal albums( QList, QVariant ).
  191. // The QVariant might carry a bool that says whether the dbcmd was executed for a null collection
  192. // but here we know for a fact that the collection is not null, so we'll happily ignore it
  193. connect( dynamic_cast< QObject* >( cmd ), SIGNAL( albums( QList<Tomahawk::album_ptr> ) ),
  194. this, SLOT( onAlbumsFound( QList<Tomahawk::album_ptr> ) ) );
  195. cmd->enqueue();
  196. }
  197. }
  198. if ( ( mode == InfoSystemMode || mode == Mixed ) && !infoLoaded )
  199. {
  200. Tomahawk::InfoSystem::InfoStringHash artistInfo;
  201. artistInfo["artist"] = name();
  202. Tomahawk::InfoSystem::InfoRequestData requestData;
  203. requestData.caller = infoid();
  204. requestData.input = QVariant::fromValue< Tomahawk::InfoSystem::InfoStringHash >( artistInfo );
  205. requestData.type = Tomahawk::InfoSystem::InfoArtistReleases;
  206. requestData.allSources = true;
  207. connect( Tomahawk::InfoSystem::InfoSystem::instance(),
  208. SIGNAL( info( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ),
  209. SLOT( infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ), Qt::UniqueConnection );
  210. connect( Tomahawk::InfoSystem::InfoSystem::instance(),
  211. SIGNAL( finished( QString ) ),
  212. SLOT( infoSystemFinished( QString ) ), Qt::UniqueConnection );
  213. m_infoJobs++;
  214. Tomahawk::InfoSystem::InfoSystem::instance()->getInfo( requestData );
  215. }
  216. if ( !collection.isNull() )
  217. return QList<album_ptr>();
  218. switch ( mode )
  219. {
  220. case DatabaseMode:
  221. return m_databaseAlbums;
  222. case InfoSystemMode:
  223. return m_officialAlbums;
  224. default:
  225. return m_databaseAlbums + m_officialAlbums;
  226. }
  227. }
  228. QList<Tomahawk::artist_ptr>
  229. Artist::similarArtists() const
  230. {
  231. if ( !m_simArtistsLoaded )
  232. {
  233. Tomahawk::InfoSystem::InfoStringHash artistInfo;
  234. artistInfo["artist"] = name();
  235. Tomahawk::InfoSystem::InfoRequestData requestData;
  236. requestData.caller = infoid();
  237. requestData.customData = QVariantMap();
  238. requestData.input = QVariant::fromValue< Tomahawk::InfoSystem::InfoStringHash >( artistInfo );
  239. requestData.type = Tomahawk::InfoSystem::InfoArtistSimilars;
  240. requestData.requestId = TomahawkUtils::infosystemRequestId();
  241. connect( Tomahawk::InfoSystem::InfoSystem::instance(),
  242. SIGNAL( info( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ),
  243. SLOT( infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ), Qt::UniqueConnection );
  244. connect( Tomahawk::InfoSystem::InfoSystem::instance(),
  245. SIGNAL( finished( QString ) ),
  246. SLOT( infoSystemFinished( QString ) ), Qt::UniqueConnection );
  247. m_infoJobs++;
  248. Tomahawk::InfoSystem::InfoSystem::instance()->getInfo( requestData );
  249. }
  250. return m_similarArtists;
  251. }
  252. void
  253. Artist::loadId( bool autoCreate )
  254. {
  255. Q_ASSERT( m_waitingForFuture );
  256. IdThreadWorker::getArtistId( m_ownRef.toStrongRef(), autoCreate );
  257. }
  258. void
  259. Artist::setIdFuture( QFuture<unsigned int> future )
  260. {
  261. m_idFuture = future;
  262. }
  263. unsigned int
  264. Artist::id() const
  265. {
  266. s_idMutex.lockForRead();
  267. const bool waiting = m_waitingForFuture;
  268. s_idMutex.unlock();
  269. if ( waiting )
  270. {
  271. // qDebug() << Q_FUNC_INFO << "Asked for artist ID and NOT loaded yet" << m_name << m_idFuture.isFinished();
  272. m_idFuture.waitForFinished();
  273. // qDebug() << "DONE WAITING:" << m_idFuture.resultCount() << m_idFuture.isResultReadyAt( 0 ) << m_idFuture.isCanceled() << m_idFuture.isFinished() << m_idFuture.isPaused() << m_idFuture.isRunning() << m_idFuture.isStarted();
  274. // qDebug() << Q_FUNC_INFO << "Got loaded artist:" << m_name << finalid;
  275. s_idMutex.lockForWrite();
  276. m_id = m_idFuture.result();
  277. m_waitingForFuture = false;
  278. if ( m_id > 0 )
  279. s_artistsById.insert( m_id, m_ownRef.toStrongRef() );
  280. s_idMutex.unlock();
  281. }
  282. return m_id;
  283. }
  284. QString
  285. Artist::biography() const
  286. {
  287. if ( !m_biographyLoaded )
  288. {
  289. Tomahawk::InfoSystem::InfoStringHash trackInfo;
  290. trackInfo["artist"] = name();
  291. Tomahawk::InfoSystem::InfoRequestData requestData;
  292. requestData.caller = infoid();
  293. requestData.type = Tomahawk::InfoSystem::InfoArtistBiography;
  294. requestData.input = QVariant::fromValue< Tomahawk::InfoSystem::InfoStringHash >( trackInfo );
  295. requestData.customData = QVariantMap();
  296. connect( Tomahawk::InfoSystem::InfoSystem::instance(),
  297. SIGNAL( info( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ),
  298. SLOT( infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ), Qt::UniqueConnection );
  299. connect( Tomahawk::InfoSystem::InfoSystem::instance(),
  300. SIGNAL( finished( QString ) ),
  301. SLOT( infoSystemFinished( QString ) ), Qt::UniqueConnection );
  302. m_infoJobs++;
  303. Tomahawk::InfoSystem::InfoSystem::instance()->getInfo( requestData );
  304. }
  305. return m_biography;
  306. }
  307. void
  308. Artist::loadStats()
  309. {
  310. artist_ptr a = m_ownRef.toStrongRef();
  311. {
  312. DatabaseCommand_TrackStats* cmd = new DatabaseCommand_TrackStats( a );
  313. Database::instance()->enqueue( Tomahawk::dbcmd_ptr(cmd) );
  314. }
  315. {
  316. DatabaseCommand_ArtistStats* cmd = new DatabaseCommand_ArtistStats( a );
  317. connect( cmd, SIGNAL( done( unsigned int, unsigned int, unsigned int ) ), SLOT( onArtistStatsLoaded( unsigned int, unsigned int, unsigned int ) ) );
  318. Database::instance()->enqueue( Tomahawk::dbcmd_ptr(cmd) );
  319. }
  320. }
  321. QList< Tomahawk::PlaybackLog >
  322. Artist::playbackHistory( const Tomahawk::source_ptr& source ) const
  323. {
  324. QList< Tomahawk::PlaybackLog > history;
  325. foreach ( const PlaybackLog& log, m_playbackHistory )
  326. {
  327. if ( source.isNull() || log.source == source )
  328. {
  329. history << log;
  330. }
  331. }
  332. return history;
  333. }
  334. void
  335. Artist::setPlaybackHistory( const QList< Tomahawk::PlaybackLog >& playbackData )
  336. {
  337. {
  338. QMutexLocker locker( &s_memberMutex );
  339. m_playbackHistory = playbackData;
  340. }
  341. emit statsLoaded();
  342. }
  343. unsigned int
  344. Artist::playbackCount( const source_ptr& source ) const
  345. {
  346. QMutexLocker locker( &s_memberMutex );
  347. unsigned int count = 0;
  348. foreach ( const PlaybackLog& log, m_playbackHistory )
  349. {
  350. if ( source.isNull() || log.source == source )
  351. count++;
  352. }
  353. return count;
  354. }
  355. unsigned int
  356. Artist::chartPosition() const
  357. {
  358. return m_chartPosition;
  359. }
  360. unsigned int
  361. Artist::chartCount() const
  362. {
  363. return m_chartCount;
  364. }
  365. void
  366. Artist::onAlbumsFound( const QList< album_ptr >& albums, const QVariant& collectionIsNull )
  367. {
  368. if ( collectionIsNull.toBool() )
  369. {
  370. m_databaseAlbums << albums;
  371. m_albumsLoaded.insert( DatabaseMode, true );
  372. }
  373. emit albumsAdded( albums, DatabaseMode );
  374. }
  375. void
  376. Artist::infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData requestData, QVariant output )
  377. {
  378. if ( requestData.caller != infoid() )
  379. return;
  380. QVariantMap returnedData = output.value< QVariantMap >();
  381. switch ( requestData.type )
  382. {
  383. case Tomahawk::InfoSystem::InfoArtistReleases:
  384. {
  385. QStringList albumNames = returnedData[ "albums" ].toStringList();
  386. Tomahawk::InfoSystem::InfoStringHash inputInfo;
  387. inputInfo = requestData.input.value< InfoSystem::InfoStringHash >();
  388. QList< album_ptr > albums;
  389. foreach ( const QString& albumName, albumNames )
  390. {
  391. Tomahawk::album_ptr album = Tomahawk::Album::get( m_ownRef.toStrongRef(), albumName, false );
  392. m_officialAlbums << album;
  393. albums << album;
  394. }
  395. m_albumsLoaded.insert( InfoSystemMode, true );
  396. if ( !m_officialAlbums.isEmpty() )
  397. emit albumsAdded( albums, InfoSystemMode );
  398. break;
  399. }
  400. case Tomahawk::InfoSystem::InfoArtistImages:
  401. {
  402. if ( output.isNull() )
  403. {
  404. m_coverLoaded = true;
  405. }
  406. else if ( output.isValid() )
  407. {
  408. const QByteArray ba = returnedData["imgbytes"].toByteArray();
  409. if ( !ba.isEmpty() )
  410. {
  411. m_coverBuffer = ba;
  412. }
  413. m_coverLoaded = true;
  414. emit coverChanged();
  415. }
  416. break;
  417. }
  418. case InfoSystem::InfoArtistSimilars:
  419. {
  420. const QStringList artists = returnedData["artists"].toStringList();
  421. foreach ( const QString& artist, artists )
  422. {
  423. m_similarArtists << Artist::get( artist );
  424. }
  425. m_simArtistsLoaded = true;
  426. emit similarArtistsLoaded();
  427. break;
  428. }
  429. case InfoSystem::InfoArtistBiography:
  430. {
  431. QVariantMap bmap = output.toMap();
  432. foreach ( const QString& source, bmap.keys() )
  433. {
  434. if ( source == "last.fm" )
  435. m_biography = bmap[ source ].toMap()[ "text" ].toString();
  436. }
  437. m_biographyLoaded = true;
  438. emit biographyLoaded();
  439. break;
  440. }
  441. default:
  442. Q_ASSERT( false );
  443. }
  444. }
  445. void
  446. Artist::infoSystemFinished( QString target )
  447. {
  448. Q_UNUSED( target );
  449. if ( target != infoid() )
  450. return;
  451. if ( --m_infoJobs == 0 )
  452. {
  453. disconnect( Tomahawk::InfoSystem::InfoSystem::instance(), SIGNAL( info( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ),
  454. this, SLOT( infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ) );
  455. disconnect( Tomahawk::InfoSystem::InfoSystem::instance(), SIGNAL( finished( QString ) ),
  456. this, SLOT( infoSystemFinished( QString ) ) );
  457. }
  458. m_coverLoading = false;
  459. emit updated();
  460. }
  461. QPixmap
  462. Artist::cover( const QSize& size, bool forceLoad ) const
  463. {
  464. if ( !m_coverLoaded && !m_coverLoading )
  465. {
  466. if ( !forceLoad )
  467. return QPixmap();
  468. Tomahawk::InfoSystem::InfoStringHash trackInfo;
  469. trackInfo["artist"] = name();
  470. Tomahawk::InfoSystem::InfoRequestData requestData;
  471. requestData.caller = infoid();
  472. requestData.type = Tomahawk::InfoSystem::InfoArtistImages;
  473. requestData.input = QVariant::fromValue< Tomahawk::InfoSystem::InfoStringHash >( trackInfo );
  474. requestData.customData = QVariantMap();
  475. connect( Tomahawk::InfoSystem::InfoSystem::instance(),
  476. SIGNAL( info( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ),
  477. SLOT( infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ), Qt::UniqueConnection );
  478. connect( Tomahawk::InfoSystem::InfoSystem::instance(),
  479. SIGNAL( finished( QString ) ),
  480. SLOT( infoSystemFinished( QString ) ), Qt::UniqueConnection );
  481. m_infoJobs++;
  482. Tomahawk::InfoSystem::InfoSystem::instance()->getInfo( requestData );
  483. m_coverLoading = true;
  484. }
  485. if ( !m_cover && !m_coverBuffer.isEmpty() )
  486. {
  487. QPixmap cover;
  488. cover.loadFromData( m_coverBuffer );
  489. m_coverBuffer.clear();
  490. m_cover = new QPixmap( TomahawkUtils::squareCenterPixmap( cover ) );
  491. }
  492. if ( m_cover && !m_cover->isNull() && !size.isEmpty() )
  493. {
  494. const QString cacheKey = QString( "%1_%2_%3" ).arg( infoid() ).arg( size.width() ).arg( size.height() );
  495. QPixmap cover;
  496. if ( !QPixmapCache::find( cacheKey, &cover ) )
  497. {
  498. cover = m_cover->scaled( size, Qt::KeepAspectRatio, Qt::SmoothTransformation );
  499. QPixmapCache::insert( cacheKey, cover );
  500. return cover;
  501. }
  502. return cover;
  503. }
  504. if ( m_cover )
  505. return *m_cover;
  506. else
  507. return QPixmap();
  508. }
  509. Tomahawk::playlistinterface_ptr
  510. Artist::playlistInterface( ModelMode mode, const Tomahawk::collection_ptr& collection )
  511. {
  512. playlistinterface_ptr pli = m_playlistInterface[ mode ][ collection ];
  513. if ( pli.isNull() )
  514. {
  515. pli = Tomahawk::playlistinterface_ptr( new Tomahawk::ArtistPlaylistInterface( this, mode, collection ) );
  516. connect( pli.data(), SIGNAL( tracksLoaded( Tomahawk::ModelMode, Tomahawk::collection_ptr ) ),
  517. SLOT( onTracksLoaded( Tomahawk::ModelMode, Tomahawk::collection_ptr ) ) );
  518. m_playlistInterface[ mode ][ collection ] = pli;
  519. }
  520. return pli;
  521. }
  522. QList<Tomahawk::query_ptr>
  523. Artist::tracks( ModelMode mode, const Tomahawk::collection_ptr& collection )
  524. {
  525. return playlistInterface( mode, collection )->tracks();
  526. }
  527. QString
  528. Artist::infoid() const
  529. {
  530. if ( m_uuid.isEmpty() )
  531. m_uuid = uuid();
  532. return m_uuid;
  533. }