PageRenderTime 258ms CodeModel.GetById 61ms app.highlight 134ms RepoModel.GetById 57ms app.codeStats 0ms

/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
 21#include "Artist.h"
 22
 23#include "collection/Collection.h"
 24#include "database/Database.h"
 25#include "database/DatabaseImpl.h"
 26#include "database/DatabaseCommand_AllAlbums.h"
 27#include "database/DatabaseCommand_ArtistStats.h"
 28#include "database/DatabaseCommand_TrackStats.h"
 29#include "database/IdThreadWorker.h"
 30#include "utils/TomahawkUtilsGui.h"
 31#include "utils/Logger.h"
 32
 33#include "ArtistPlaylistInterface.h"
 34#include "PlaylistEntry.h"
 35#include "Source.h"
 36
 37#include <QReadWriteLock>
 38#include <QPixmapCache>
 39#include <QCoreApplication>
 40
 41using namespace Tomahawk;
 42
 43QHash< QString, artist_wptr > Artist::s_artistsByName = QHash< QString, artist_wptr >();
 44QHash< unsigned int, artist_wptr > Artist::s_artistsById = QHash< unsigned int, artist_wptr >();
 45
 46static QMutex s_nameCacheMutex;
 47static QReadWriteLock s_idMutex;
 48static QMutex s_memberMutex;
 49
 50
 51Artist::~Artist()
 52{
 53    FINEGRAINED_MSG( Q_FUNC_INFO << "Deleting artist:" << m_name );
 54    m_ownRef.clear();
 55
 56    delete m_cover;
 57}
 58
 59
 60artist_ptr
 61Artist::get( const QString& name, bool autoCreate )
 62{
 63    if ( name.isEmpty() )
 64        return artist_ptr();
 65
 66    QMutexLocker lock( &s_nameCacheMutex );
 67    const QString key = name.toLower();
 68    if ( s_artistsByName.contains( key ) )
 69    {
 70        artist_wptr artist = s_artistsByName.value( key );
 71        if ( artist )
 72            return artist.toStrongRef();
 73    }
 74
 75    if ( !Database::instance() || !Database::instance()->impl() )
 76        return artist_ptr();
 77
 78    artist_ptr artist = artist_ptr( new Artist( name ), &Artist::deleteLater );
 79    artist->setWeakRef( artist.toWeakRef() );
 80    artist->loadId( autoCreate );
 81    s_artistsByName.insert( key, artist );
 82
 83    return artist;
 84}
 85
 86
 87artist_ptr
 88Artist::get( unsigned int id, const QString& name )
 89{
 90    Q_ASSERT( id > 0 );
 91
 92    s_idMutex.lockForRead();
 93    if ( s_artistsById.contains( id ) )
 94    {
 95        artist_wptr artist = s_artistsById.value( id );
 96        s_idMutex.unlock();
 97
 98        if ( !artist.isNull() )
 99            return artist;
100    }
101    s_idMutex.unlock();
102
103    QMutexLocker lock( &s_nameCacheMutex );
104    const QString key = name.toLower();
105    if ( s_artistsByName.contains( key ) )
106    {
107        artist_wptr artist = s_artistsByName.value( key );
108        if ( !artist.isNull() )
109            return artist;
110    }
111
112    artist_ptr a = artist_ptr( new Artist( id, name ), &Artist::deleteLater );
113    a->moveToThread( QCoreApplication::instance()->thread() );
114    a->setWeakRef( a.toWeakRef() );
115    s_artistsByName.insert( key, a );
116
117    if ( id > 0 )
118    {
119        s_idMutex.lockForWrite();
120        s_artistsById.insert( id, a );
121        s_idMutex.unlock();
122    }
123
124    return a;
125}
126
127
128Artist::Artist( unsigned int id, const QString& name )
129    : QObject()
130    , m_waitingForFuture( false )
131    , m_id( id )
132    , m_name( name )
133    , m_coverLoaded( false )
134    , m_coverLoading( false )
135    , m_simArtistsLoaded( false )
136    , m_biographyLoaded( false )
137    , m_infoJobs( 0 )
138    , m_chartPosition( 0 )
139    , m_chartCount( 0 )
140    , m_cover( 0 )
141{
142    FINEGRAINED_MSG( Q_FUNC_INFO << "Creating artist:" << id << name );
143    m_sortname = DatabaseImpl::sortname( name, true );
144}
145
146
147Artist::Artist( const QString& name )
148    : QObject()
149    , m_waitingForFuture( true )
150    , m_id( 0 )
151    , m_name( name )
152    , m_coverLoaded( false )
153    , m_coverLoading( false )
154    , m_simArtistsLoaded( false )
155    , m_biographyLoaded( false )
156    , m_infoJobs( 0 )
157    , m_chartPosition( 0 )
158    , m_chartCount( 0 )
159    , m_cover( 0 )
160{
161    FINEGRAINED_MSG( Q_FUNC_INFO << "Creating artist:" << name );
162    m_sortname = DatabaseImpl::sortname( name, true );
163}
164
165
166void
167Artist::deleteLater()
168{
169    QMutexLocker lock( &s_nameCacheMutex );
170
171    const QString key = m_name.toLower();
172    if ( s_artistsByName.contains( key ) )
173    {
174        s_artistsByName.remove( key );
175    }
176
177    if ( m_id > 0 )
178    {
179        s_idMutex.lockForWrite();
180        if ( s_artistsById.contains( m_id ) )
181        {
182            s_artistsById.remove( m_id );
183        }
184        s_idMutex.unlock();
185    }
186
187    QObject::deleteLater();
188}
189
190
191void
192Artist::onArtistStatsLoaded( unsigned int /* plays */, unsigned int chartPos, unsigned int chartCount )
193{
194    m_chartPosition = chartPos;
195    m_chartCount = chartCount;
196
197    emit statsLoaded();
198}
199
200
201void
202Artist::onTracksLoaded( Tomahawk::ModelMode mode, const Tomahawk::collection_ptr& collection )
203{
204    emit tracksAdded( playlistInterface( mode, collection )->tracks(), mode, collection );
205}
206
207
208QList<album_ptr>
209Artist::albums( ModelMode mode, const Tomahawk::collection_ptr& collection ) const
210{
211    artist_ptr artist = m_ownRef.toStrongRef();
212
213    bool dbLoaded = m_albumsLoaded.value( DatabaseMode );
214    const bool infoLoaded = m_albumsLoaded.value( InfoSystemMode );
215    if ( !collection.isNull() )
216        dbLoaded = false;
217
218    if ( ( mode == DatabaseMode || mode == Mixed ) && !dbLoaded )
219    {
220        if ( collection.isNull() )
221        {
222            DatabaseCommand_AllAlbums* cmd = new DatabaseCommand_AllAlbums( collection, artist );
223            cmd->setData( QVariant( collection.isNull() ) );
224
225            connect( cmd, SIGNAL( albums( QList<Tomahawk::album_ptr>, QVariant ) ),
226                          SLOT( onAlbumsFound( QList<Tomahawk::album_ptr>, QVariant ) ) );
227
228            Database::instance()->enqueue( Tomahawk::dbcmd_ptr( cmd ) );
229        }
230        else
231        {
232            //collection is *surely* not null, and might be a ScriptCollection
233            Tomahawk::AlbumsRequest* cmd = collection->requestAlbums( artist );
234
235            // There is also a signal albums( QList, QVariant ).
236            // The QVariant might carry a bool that says whether the dbcmd was executed for a null collection
237            // but here we know for a fact that the collection is not null, so we'll happily ignore it
238            connect( dynamic_cast< QObject* >( cmd ), SIGNAL( albums( QList<Tomahawk::album_ptr> ) ),
239                     this, SLOT( onAlbumsFound( QList<Tomahawk::album_ptr> ) ) );
240
241            cmd->enqueue();
242        }
243    }
244
245    if ( ( mode == InfoSystemMode || mode == Mixed ) && !infoLoaded )
246    {
247        Tomahawk::InfoSystem::InfoStringHash artistInfo;
248        artistInfo["artist"] = name();
249
250        Tomahawk::InfoSystem::InfoRequestData requestData;
251        requestData.caller = infoid();
252        requestData.input = QVariant::fromValue< Tomahawk::InfoSystem::InfoStringHash >( artistInfo );
253        requestData.type = Tomahawk::InfoSystem::InfoArtistReleases;
254        requestData.allSources = true;
255
256        connect( Tomahawk::InfoSystem::InfoSystem::instance(),
257                 SIGNAL( info( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ),
258                 SLOT( infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ), Qt::UniqueConnection );
259
260        connect( Tomahawk::InfoSystem::InfoSystem::instance(),
261                 SIGNAL( finished( QString ) ),
262                 SLOT( infoSystemFinished( QString ) ), Qt::UniqueConnection );
263
264        m_infoJobs++;
265        Tomahawk::InfoSystem::InfoSystem::instance()->getInfo( requestData );
266    }
267
268    if ( !collection.isNull() )
269        return QList<album_ptr>();
270
271    switch ( mode )
272    {
273        case DatabaseMode:
274            return m_databaseAlbums;
275        case InfoSystemMode:
276            return m_officialAlbums;
277        default:
278            return m_databaseAlbums + m_officialAlbums;
279    }
280}
281
282
283QList<Tomahawk::artist_ptr>
284Artist::similarArtists() const
285{
286    if ( !m_simArtistsLoaded )
287    {
288        Tomahawk::InfoSystem::InfoStringHash artistInfo;
289        artistInfo["artist"] = name();
290
291        Tomahawk::InfoSystem::InfoRequestData requestData;
292        requestData.caller = infoid();
293        requestData.customData = QVariantMap();
294
295        requestData.input = QVariant::fromValue< Tomahawk::InfoSystem::InfoStringHash >( artistInfo );
296        requestData.type = Tomahawk::InfoSystem::InfoArtistSimilars;
297        requestData.requestId = TomahawkUtils::infosystemRequestId();
298
299        connect( Tomahawk::InfoSystem::InfoSystem::instance(),
300                SIGNAL( info( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ),
301                SLOT( infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ), Qt::UniqueConnection );
302
303        connect( Tomahawk::InfoSystem::InfoSystem::instance(),
304                SIGNAL( finished( QString ) ),
305                SLOT( infoSystemFinished( QString ) ), Qt::UniqueConnection );
306
307        m_infoJobs++;
308        Tomahawk::InfoSystem::InfoSystem::instance()->getInfo( requestData );
309    }
310
311    return m_similarArtists;
312}
313
314
315void
316Artist::loadId( bool autoCreate )
317{
318    Q_ASSERT( m_waitingForFuture );
319
320    IdThreadWorker::getArtistId( m_ownRef.toStrongRef(), autoCreate );
321}
322
323
324void
325Artist::setIdFuture( QFuture<unsigned int> future )
326{
327    m_idFuture = future;
328}
329
330
331unsigned int
332Artist::id() const
333{
334    s_idMutex.lockForRead();
335    const bool waiting = m_waitingForFuture;
336    s_idMutex.unlock();
337
338    if ( waiting )
339    {
340//        qDebug() << Q_FUNC_INFO << "Asked for artist ID and NOT loaded yet" << m_name << m_idFuture.isFinished();
341        m_idFuture.waitForFinished();
342//        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();
343//        qDebug() << Q_FUNC_INFO << "Got loaded artist:" << m_name << finalid;
344
345        s_idMutex.lockForWrite();
346        m_id = m_idFuture.result();
347        m_waitingForFuture = false;
348
349        if ( m_id > 0 )
350            s_artistsById.insert( m_id, m_ownRef.toStrongRef() );
351
352        s_idMutex.unlock();
353    }
354
355    return m_id;
356}
357
358
359QString
360Artist::biography() const
361{
362    if ( !m_biographyLoaded )
363    {
364        Tomahawk::InfoSystem::InfoStringHash trackInfo;
365        trackInfo["artist"] = name();
366
367        Tomahawk::InfoSystem::InfoRequestData requestData;
368        requestData.caller = infoid();
369        requestData.type = Tomahawk::InfoSystem::InfoArtistBiography;
370        requestData.input = QVariant::fromValue< Tomahawk::InfoSystem::InfoStringHash >( trackInfo );
371        requestData.customData = QVariantMap();
372
373        connect( Tomahawk::InfoSystem::InfoSystem::instance(),
374                SIGNAL( info( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ),
375                SLOT( infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ), Qt::UniqueConnection );
376
377        connect( Tomahawk::InfoSystem::InfoSystem::instance(),
378                SIGNAL( finished( QString ) ),
379                SLOT( infoSystemFinished( QString ) ), Qt::UniqueConnection );
380
381        m_infoJobs++;
382        Tomahawk::InfoSystem::InfoSystem::instance()->getInfo( requestData );
383    }
384
385    return m_biography;
386}
387
388
389void
390Artist::loadStats()
391{
392    artist_ptr a = m_ownRef.toStrongRef();
393
394    {
395        DatabaseCommand_TrackStats* cmd = new DatabaseCommand_TrackStats( a );
396        Database::instance()->enqueue( Tomahawk::dbcmd_ptr(cmd) );
397    }
398
399    {
400        DatabaseCommand_ArtistStats* cmd = new DatabaseCommand_ArtistStats( a );
401        connect( cmd, SIGNAL( done( unsigned int, unsigned int, unsigned int ) ), SLOT( onArtistStatsLoaded( unsigned int, unsigned int, unsigned int ) ) );
402        Database::instance()->enqueue( Tomahawk::dbcmd_ptr(cmd) );
403    }
404}
405
406
407QList< Tomahawk::PlaybackLog >
408Artist::playbackHistory( const Tomahawk::source_ptr& source ) const
409{
410    QList< Tomahawk::PlaybackLog > history;
411
412    foreach ( const PlaybackLog& log, m_playbackHistory )
413    {
414        if ( source.isNull() || log.source == source )
415        {
416            history << log;
417        }
418    }
419
420    return history;
421}
422
423
424void
425Artist::setPlaybackHistory( const QList< Tomahawk::PlaybackLog >& playbackData )
426{
427    {
428        QMutexLocker locker( &s_memberMutex );
429        m_playbackHistory = playbackData;
430    }
431
432    emit statsLoaded();
433}
434
435
436unsigned int
437Artist::playbackCount( const source_ptr& source ) const
438{
439    QMutexLocker locker( &s_memberMutex );
440
441    unsigned int count = 0;
442    foreach ( const PlaybackLog& log, m_playbackHistory )
443    {
444        if ( source.isNull() || log.source == source )
445            count++;
446    }
447
448    return count;
449}
450
451
452unsigned int
453Artist::chartPosition() const
454{
455    return m_chartPosition;
456}
457
458
459unsigned int
460Artist::chartCount() const
461{
462    return m_chartCount;
463}
464
465
466void
467Artist::onAlbumsFound( const QList< album_ptr >& albums, const QVariant& collectionIsNull )
468{
469    if ( collectionIsNull.toBool() )
470    {
471        m_databaseAlbums << albums;
472        m_albumsLoaded.insert( DatabaseMode, true );
473    }
474
475    emit albumsAdded( albums, DatabaseMode );
476}
477
478
479void
480Artist::infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData requestData, QVariant output )
481{
482    if ( requestData.caller != infoid() )
483        return;
484
485    QVariantMap returnedData = output.value< QVariantMap >();
486    switch ( requestData.type )
487    {
488        case Tomahawk::InfoSystem::InfoArtistReleases:
489        {
490            QStringList albumNames = returnedData[ "albums" ].toStringList();
491            Tomahawk::InfoSystem::InfoStringHash inputInfo;
492            inputInfo = requestData.input.value< InfoSystem::InfoStringHash >();
493
494            QList< album_ptr > albums;
495            foreach ( const QString& albumName, albumNames )
496            {
497                Tomahawk::album_ptr album = Tomahawk::Album::get( m_ownRef.toStrongRef(), albumName, false );
498                m_officialAlbums << album;
499                albums << album;
500            }
501
502            m_albumsLoaded.insert( InfoSystemMode, true );
503            if ( !m_officialAlbums.isEmpty() )
504                emit albumsAdded( albums, InfoSystemMode );
505
506            break;
507        }
508
509        case Tomahawk::InfoSystem::InfoArtistImages:
510        {
511            if ( output.isNull() )
512            {
513                m_coverLoaded = true;
514            }
515            else if ( output.isValid() )
516            {
517                const QByteArray ba = returnedData["imgbytes"].toByteArray();
518                if ( !ba.isEmpty() )
519                {
520                    m_coverBuffer = ba;
521                }
522
523                m_coverLoaded = true;
524                emit coverChanged();
525            }
526
527            break;
528        }
529
530        case InfoSystem::InfoArtistSimilars:
531        {
532            const QStringList artists = returnedData["artists"].toStringList();
533            foreach ( const QString& artist, artists )
534            {
535                m_similarArtists << Artist::get( artist );
536            }
537
538            m_simArtistsLoaded = true;
539            emit similarArtistsLoaded();
540
541            break;
542        }
543
544        case InfoSystem::InfoArtistBiography:
545        {
546            QVariantMap bmap = output.toMap();
547            foreach ( const QString& source, bmap.keys() )
548            {
549                if ( source == "last.fm" )
550                    m_biography = bmap[ source ].toMap()[ "text" ].toString();
551            }
552
553            m_biographyLoaded = true;
554            emit biographyLoaded();
555
556            break;
557        }
558
559        default:
560            Q_ASSERT( false );
561    }
562}
563
564
565void
566Artist::infoSystemFinished( QString target )
567{
568    Q_UNUSED( target );
569
570    if ( target != infoid() )
571        return;
572
573    if ( --m_infoJobs == 0 )
574    {
575        disconnect( Tomahawk::InfoSystem::InfoSystem::instance(), SIGNAL( info( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ),
576                    this, SLOT( infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ) );
577
578        disconnect( Tomahawk::InfoSystem::InfoSystem::instance(), SIGNAL( finished( QString ) ),
579                    this, SLOT( infoSystemFinished( QString ) ) );
580    }
581
582    m_coverLoading = false;
583
584    emit updated();
585}
586
587
588QPixmap
589Artist::cover( const QSize& size, bool forceLoad ) const
590{
591    if ( !m_coverLoaded && !m_coverLoading )
592    {
593        if ( !forceLoad )
594            return QPixmap();
595
596        Tomahawk::InfoSystem::InfoStringHash trackInfo;
597        trackInfo["artist"] = name();
598
599        Tomahawk::InfoSystem::InfoRequestData requestData;
600        requestData.caller = infoid();
601        requestData.type = Tomahawk::InfoSystem::InfoArtistImages;
602        requestData.input = QVariant::fromValue< Tomahawk::InfoSystem::InfoStringHash >( trackInfo );
603        requestData.customData = QVariantMap();
604
605        connect( Tomahawk::InfoSystem::InfoSystem::instance(),
606                SIGNAL( info( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ),
607                SLOT( infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ), Qt::UniqueConnection );
608
609        connect( Tomahawk::InfoSystem::InfoSystem::instance(),
610                SIGNAL( finished( QString ) ),
611                SLOT( infoSystemFinished( QString ) ), Qt::UniqueConnection );
612
613        m_infoJobs++;
614        Tomahawk::InfoSystem::InfoSystem::instance()->getInfo( requestData );
615
616        m_coverLoading = true;
617    }
618
619    if ( !m_cover && !m_coverBuffer.isEmpty() )
620    {
621        QPixmap cover;
622        cover.loadFromData( m_coverBuffer );
623        m_coverBuffer.clear();
624
625        m_cover = new QPixmap( TomahawkUtils::squareCenterPixmap( cover ) );
626    }
627
628    if ( m_cover && !m_cover->isNull() && !size.isEmpty() )
629    {
630        const QString cacheKey = QString( "%1_%2_%3" ).arg( infoid() ).arg( size.width() ).arg( size.height() );
631        QPixmap cover;
632
633        if ( !QPixmapCache::find( cacheKey, &cover ) )
634        {
635            cover = m_cover->scaled( size, Qt::KeepAspectRatio, Qt::SmoothTransformation );
636            QPixmapCache::insert( cacheKey, cover );
637            return cover;
638        }
639        return cover;
640    }
641
642    if ( m_cover )
643        return *m_cover;
644    else
645        return QPixmap();
646}
647
648
649Tomahawk::playlistinterface_ptr
650Artist::playlistInterface( ModelMode mode, const Tomahawk::collection_ptr& collection )
651{
652    playlistinterface_ptr pli = m_playlistInterface[ mode ][ collection ];
653
654    if ( pli.isNull() )
655    {
656        pli = Tomahawk::playlistinterface_ptr( new Tomahawk::ArtistPlaylistInterface( this, mode, collection ) );
657        connect( pli.data(), SIGNAL( tracksLoaded( Tomahawk::ModelMode, Tomahawk::collection_ptr ) ),
658                               SLOT( onTracksLoaded( Tomahawk::ModelMode, Tomahawk::collection_ptr ) ) );
659
660        m_playlistInterface[ mode ][ collection ] = pli;
661    }
662
663    return pli;
664}
665
666
667QList<Tomahawk::query_ptr>
668Artist::tracks( ModelMode mode, const Tomahawk::collection_ptr& collection )
669{
670    return playlistInterface( mode, collection )->tracks();
671}
672
673
674QString
675Artist::infoid() const
676{
677    if ( m_uuid.isEmpty() )
678        m_uuid = uuid();
679
680    return m_uuid;
681}