File indexing completed on 2024-05-19 04:49:17

0001 /****************************************************************************************
0002  * Copyright (c) 2011 Bart Cerneels <bart.cerneels@kde.org>                             *
0003  * Copyright (c) 2012 Matěj Laitl <matej@laitl.cz>                                      *
0004  *                                                                                      *
0005  * This program is free software; you can redistribute it and/or modify it under        *
0006  * the terms of the GNU General Public License as published by the Free Software        *
0007  * Foundation; either version 2 of the License, or (at your option) any later           *
0008  * version.                                                                             *
0009  *                                                                                      *
0010  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0011  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0012  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0013  *                                                                                      *
0014  * You should have received a copy of the GNU General Public License along with         *
0015  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0016  ****************************************************************************************/
0017 
0018 #include "MemoryMeta.h"
0019 
0020 #include "core/meta/Statistics.h"
0021 #include "core/meta/TrackEditor.h"
0022 #include "core-impl/capabilities/AlbumActionsCapability.h"
0023 #include "covermanager/CoverCache.h"
0024 
0025 
0026 using namespace MemoryMeta;
0027 
0028 Meta::TrackList
0029 Base::tracks()
0030 {
0031     // construct AmarokSharedPointers on demand, see m_track comment
0032     QReadLocker locker( &m_tracksLock );
0033     Meta::TrackList list;
0034     foreach( Track *track, m_tracks )
0035     {
0036         list << Meta::TrackPtr( track );
0037     }
0038     return list;
0039 }
0040 
0041 void
0042 Base::addTrack( Track *track )
0043 {
0044     QWriteLocker locker( &m_tracksLock );
0045     m_tracks.append( track );
0046 }
0047 
0048 void
0049 Base::removeTrack( Track *track )
0050 {
0051     QWriteLocker locker( &m_tracksLock );
0052     m_tracks.removeOne( track );
0053 }
0054 
0055 Album::Album( const Meta::AlbumPtr &other )
0056     : MemoryMeta::Base( other->name() )
0057     , m_isCompilation( other->isCompilation() )
0058     , m_canUpdateCompilation( other->canUpdateCompilation() )
0059     , m_image( other->image() )
0060     , m_canUpdateImage( other->canUpdateImage() )
0061 {
0062     if( other->hasAlbumArtist() && other->albumArtist() )
0063         m_albumArtist = Meta::ArtistPtr( new Artist( other->albumArtist()->name() ) );
0064 }
0065 
0066 bool
0067 Album::hasCapabilityInterface( Capabilities::Capability::Type type ) const
0068 {
0069     switch( type )
0070     {
0071         case Capabilities::Capability::Actions:
0072             return true;
0073         default:
0074             return false;
0075     }
0076 }
0077 
0078 Capabilities::Capability*
0079 Album::createCapabilityInterface( Capabilities::Capability::Type type )
0080 {
0081     switch( type )
0082     {
0083         case Capabilities::Capability::Actions:
0084             return new Capabilities::AlbumActionsCapability( Meta::AlbumPtr( this ) );
0085         default:
0086             return nullptr;
0087     }
0088 }
0089 
0090 void
0091 Album::setCompilation( bool isCompilation )
0092 {
0093     // we re-create the albums for each track - that's how MemoryMeta works - by
0094     // aggregating multiple entities of same name into one.
0095     foreach( Meta::TrackPtr track, tracks() )
0096     {
0097         Track *memoryTrack = static_cast<Track *>( track.data() );
0098         Meta::AlbumPtr album = memoryTrack->originalTrack()->album();
0099         if( album && album->canUpdateCompilation() )
0100             album->setCompilation( isCompilation );
0101     }
0102     // no notifyObservers() etc needed - this is done from down-up by proxied tracks
0103 }
0104 
0105 QImage
0106 Album::image( int size ) const
0107 {
0108     if( size > 1 && size <= 1000 && !m_image.isNull() )
0109         return m_image.scaled( size, size, Qt::KeepAspectRatio, Qt::FastTransformation );
0110     return m_image;
0111 }
0112 
0113 void
0114 Album::setImage( const QImage &image )
0115 {
0116     foreach( Meta::TrackPtr track, tracks() )
0117     {
0118         Track *memoryTrack = static_cast<Track *>( track.data() );
0119         Meta::AlbumPtr album = memoryTrack->originalTrack()->album();
0120         if( album && album->canUpdateImage() )
0121             album->setImage( image );
0122     }
0123     // no notifyObservers() etc needed - this is done from down-up by proxied tracks
0124 }
0125 
0126 void
0127 Album::removeImage()
0128 {
0129     foreach( Meta::TrackPtr track, tracks() )
0130     {
0131         Track *memoryTrack = static_cast<Track *>( track.data() );
0132         Meta::AlbumPtr album = memoryTrack->originalTrack()->album();
0133         if( album && album->canUpdateImage() )
0134             album->removeImage();
0135     }
0136     // no notifyObservers() etc needed - this is done from down-up by proxied tracks
0137 }
0138 
0139 void
0140 Album::updateCachedValues()
0141 {
0142     m_isCompilation = false;
0143     m_canUpdateCompilation = false;
0144     m_image = QImage();
0145     m_canUpdateImage = false;
0146     foreach( Meta::TrackPtr track, tracks() )
0147     {
0148         Track *memoryTrack = static_cast<Track *>( track.data() );
0149         Meta::AlbumPtr album = memoryTrack->originalTrack()->album();
0150 
0151         if( !album )
0152             continue;
0153 
0154         if( !m_isCompilation )
0155             m_isCompilation = album->isCompilation();
0156         if( !m_canUpdateCompilation )
0157             m_canUpdateCompilation = album->canUpdateCompilation();
0158         if( m_image.isNull() && album->hasImage() )
0159             m_image = album->image();
0160         if( !m_canUpdateImage )
0161             m_canUpdateImage = album->canUpdateImage();
0162     }
0163 }
0164 
0165 Track::Track(const Meta::TrackPtr& originalTrack)
0166     : m_track( originalTrack )
0167     , m_album( nullptr )
0168     , m_artist( nullptr )
0169     , m_composer( nullptr )
0170     , m_genre( nullptr )
0171     , m_year( nullptr )
0172 {
0173     Q_ASSERT( originalTrack );
0174 }
0175 
0176 Track::~Track()
0177 {
0178     // all following static casts are valid - there is no way attributes could have been
0179     // set to different Meta::* subclasses
0180     if( m_album )
0181         static_cast<Album *>( m_album.data() )->removeTrack( this );
0182     if( m_artist )
0183         static_cast<Artist *>( m_artist.data() )->removeTrack( this );
0184     if( m_composer )
0185         static_cast<Composer *>( m_composer.data() )->removeTrack( this );
0186     if( m_genre )
0187         static_cast<Genre *>( m_genre.data() )->removeTrack( this );
0188     if( m_year )
0189         static_cast<Year *>( m_year.data() )->removeTrack( this );
0190 }
0191 
0192 Meta::TrackEditorPtr
0193 Track::editor()
0194 {
0195     return m_track->editor();
0196 }
0197 
0198 Meta::StatisticsPtr
0199 Track::statistics()
0200 {
0201     return m_track->statistics();
0202 }
0203 
0204 void
0205 Track::setAlbum( Album *album )
0206 {
0207     if( m_album )
0208         static_cast<Album *>( m_album.data() )->removeTrack( this );
0209     if( album )
0210         album->addTrack( this );
0211     m_album = Meta::AlbumPtr( album );
0212 }
0213 
0214 void
0215 Track::setArtist( Artist *artist )
0216 {
0217     if( m_artist )
0218         static_cast<Artist *>( m_artist.data() )->removeTrack( this );
0219     if( artist )
0220         artist->addTrack( this );
0221     m_artist = Meta::ArtistPtr( artist );
0222 }
0223 
0224 void
0225 Track::setComposer( Composer *composer )
0226 {
0227     if( m_composer )
0228         static_cast<Composer *>( m_composer.data() )->removeTrack( this );
0229     if( composer )
0230         composer->addTrack( this );
0231     m_composer = Meta::ComposerPtr( composer );
0232 }
0233 
0234 void
0235 Track::setGenre( Genre *genre )
0236 {
0237     if( m_genre )
0238         static_cast<Genre *>( m_genre.data() )->removeTrack( this );
0239     if( genre )
0240         genre->addTrack( this );
0241     m_genre = Meta::GenrePtr( genre );
0242 }
0243 
0244 void
0245 Track::setYear( Year *year )
0246 {
0247     if( m_year )
0248         static_cast<Year *>( m_year.data() )->removeTrack( this );
0249     if( year )
0250         year->addTrack( this );
0251     m_year = Meta::YearPtr( year );
0252 }
0253 
0254 MapChanger::MapChanger( MemoryCollection *memoryCollection )
0255     : m_mc( memoryCollection )
0256 {
0257     m_mc->acquireWriteLock();
0258 }
0259 
0260 MapChanger::~MapChanger()
0261 {
0262     m_mc->releaseLock();
0263 }
0264 
0265 Meta::TrackPtr
0266 MapChanger::addTrack( Meta::TrackPtr track )
0267 {
0268     if( !track )
0269         return Meta::TrackPtr(); // nothing to do
0270     if( m_mc->trackMap().contains( track->uidUrl() ) )
0271         return Meta::TrackPtr();
0272 
0273     Track *memoryTrack = new Track( track );
0274     return addExistingTrack( track, memoryTrack );
0275 }
0276 
0277 Meta::TrackPtr
0278 MapChanger::addExistingTrack( Meta::TrackPtr track, Track *memoryTrack )
0279 {
0280     Meta::TrackPtr metaTrackPtr = Meta::TrackPtr( memoryTrack );
0281     m_mc->addTrack( metaTrackPtr );
0282 
0283     QString artistName = track->artist().isNull() ? QString() : track->artist()->name();
0284     Meta::ArtistPtr artist = m_mc->artistMap().value( artistName );
0285     if( artist.isNull() )
0286     {
0287         artist = Meta::ArtistPtr( new Artist( artistName ) );
0288         m_mc->addArtist( artist );
0289     }
0290     memoryTrack->setArtist( static_cast<Artist *>( artist.data() ) );
0291 
0292     Meta::AlbumPtr trackAlbum = track->album();
0293     QString albumName = trackAlbum ? trackAlbum->name() : QString();
0294     QString albumArtistName;
0295     if( trackAlbum && trackAlbum->hasAlbumArtist() && trackAlbum->albumArtist() )
0296         albumArtistName = trackAlbum->albumArtist()->name();
0297     Meta::AlbumPtr album = m_mc->albumMap().value( albumName, albumArtistName );
0298     if( !album )
0299     {
0300         Meta::ArtistPtr albumArtist;
0301         const bool isCompilation = trackAlbum ? trackAlbum->isCompilation() : false;
0302         // we create even empty-named album artists if the album is not compilation
0303         // because Collection Browser wouldn't show these otherwise
0304         if( !albumArtistName.isEmpty() || !isCompilation )
0305         {
0306             /* Even if MemoryQueryMaker doesn't need albumArtists to be in MemoryCollection
0307              * maps, we add her into artist map so that album artist has the same instance as
0308              * identically-named artist (of potentially different tracks) */
0309             albumArtist = m_mc->artistMap().value( albumArtistName );
0310             if( !albumArtist )
0311             {
0312                 albumArtist = Meta::ArtistPtr( new Artist( albumArtistName ) );
0313                 m_mc->addArtist( albumArtist );
0314             }
0315         }
0316 
0317         album = Meta::AlbumPtr( new Album( albumName, albumArtist ) );
0318         m_mc->addAlbum( album );
0319     }
0320     Album *memoryAlbum = static_cast<Album *>( album.data() );
0321     memoryTrack->setAlbum( memoryAlbum );
0322     memoryAlbum->updateCachedValues();
0323 
0324     QString genreName = track->genre().isNull() ? QString() : track->genre()->name();
0325     Meta::GenrePtr genre = m_mc->genreMap().value( genreName );
0326     if( genre.isNull() )
0327     {
0328         genre = Meta::GenrePtr( new Genre( genreName ) );
0329         m_mc->addGenre( genre );
0330     }
0331     memoryTrack->setGenre( static_cast<Genre *>( genre.data() ) );
0332 
0333     QString composerName = track->composer().isNull() ? QString() : track->composer()->name();
0334     Meta::ComposerPtr composer = m_mc->composerMap().value( composerName );
0335     if( composer.isNull() )
0336     {
0337         composer = Meta::ComposerPtr( new Composer( composerName ) );
0338         m_mc->addComposer( composer );
0339     }
0340     memoryTrack->setComposer( static_cast<Composer *>( composer.data() ) );
0341 
0342     int year = track->year().isNull() ? 0 : track->year()->year();
0343     Meta::YearPtr yearPtr = m_mc->yearMap().value( year );
0344     if( yearPtr.isNull() )
0345     {
0346         yearPtr = Meta::YearPtr( new Year( year ? QString::number( year ) : QString() ) );
0347         m_mc->addYear( yearPtr );
0348     }
0349     memoryTrack->setYear( static_cast<Year *>( yearPtr.data() ) );
0350 
0351     //TODO: labels (when doing this, don't forget to tweak removeTrack too)
0352 
0353     return metaTrackPtr;
0354 }
0355 
0356 Meta::TrackPtr
0357 MapChanger::removeTrack( Meta::TrackPtr track )
0358 {
0359     if( !track )
0360         return Meta::TrackPtr(); // nothing to do
0361 
0362     TrackMap trackMap = m_mc->trackMap();
0363     ArtistMap artistMap = m_mc->artistMap();
0364     AlbumMap albumMap = m_mc->albumMap();
0365     GenreMap genreMap = m_mc->genreMap();
0366     ComposerMap composerMap = m_mc->composerMap();
0367     YearMap yearMap = m_mc->yearMap();
0368 
0369     /* Ensure that we have the memory track (not the underlying one) and that it is
0370      * actually present in MemoryCollection */
0371     track = trackMap.value( track->uidUrl() );
0372     if( !track )
0373         return Meta::TrackPtr(); // was not in collection, nothing to do
0374 
0375     /* Track added using mapadder are MemoryMeta::Tracks, but cope with different too: */
0376     Track *memoryTrack = dynamic_cast<Track *>( track.data() );
0377 
0378     trackMap.remove( track->uidUrl() );
0379 
0380     /* Remove potentially dangling entities from memory collection. We cannot simply use
0381      * if( entity && entity->tracks().count() == 1 ) because it would be racy: entity could
0382      * have referenced a track that is no longer in MemoryCollection, but not yet destroyed.
0383      *
0384      * When track to remove is MemoryMeta::Track, copy and reassign Meta:: entities so
0385      * that the track is detached from MemoryCollection completely. */
0386 
0387     Meta::ArtistPtr artist = track->artist();
0388     if( artist && !hasTrackInMap( artist->tracks(), trackMap )
0389                && !referencedAsAlbumArtist( artist, albumMap ) )
0390         artistMap.remove( artist->name() );
0391     if( artist && memoryTrack )
0392         memoryTrack->setArtist( new Artist( artist->name() ) );
0393 
0394     Meta::AlbumPtr album = track->album();
0395     if( album && !hasTrackInMap( album->tracks(), trackMap ) )
0396     {
0397         albumMap.remove( album );
0398         Meta::ArtistPtr albumArtist = album->hasAlbumArtist() ? album->albumArtist() : Meta::ArtistPtr();
0399         if( albumArtist && !hasTrackInMap( albumArtist->tracks(), trackMap )
0400                         && !referencedAsAlbumArtist( albumArtist, albumMap ) )
0401             artistMap.remove( albumArtist->name() );
0402     }
0403     if( album && memoryTrack )
0404         memoryTrack->setAlbum( new Album( album ) ); // copy-like constructor
0405 
0406     Meta::GenrePtr genre = track->genre();
0407     if( genre && !hasTrackInMap( genre->tracks(), trackMap ) )
0408         genreMap.remove( genre->name() );
0409     if( genre && memoryTrack )
0410         memoryTrack->setGenre( new Genre( genre->name() ) );
0411 
0412     Meta::ComposerPtr composer = track->composer();
0413     if( composer && !hasTrackInMap( composer->tracks(), trackMap ) )
0414         composerMap.remove( composer->name() );
0415     if( composer && memoryTrack )
0416         memoryTrack->setComposer( new Composer( composer->name() ) );
0417 
0418     Meta::YearPtr year = track->year();
0419     if( year && !hasTrackInMap( year->tracks(), trackMap ) )
0420         yearMap.remove( year->year() );
0421     if( year && memoryTrack )
0422         memoryTrack->setYear( new Year( year->name() ) );
0423 
0424     m_mc->setTrackMap( trackMap );
0425     m_mc->setArtistMap( artistMap );
0426     m_mc->setAlbumMap( albumMap );
0427     m_mc->setGenreMap( genreMap );
0428     m_mc->setComposerMap( composerMap );
0429     m_mc->setYearMap( yearMap );
0430 
0431     if( memoryTrack )
0432         return memoryTrack->originalTrack();
0433     return Meta::TrackPtr();
0434 }
0435 
0436 bool
0437 MapChanger::trackChanged( Meta::TrackPtr track )
0438 {
0439     if( !track )
0440         return false;
0441     // make sure we have a track from memory collection:
0442     track = m_mc->trackMap().value( track->uidUrl() );
0443     if( !track )
0444         return false;
0445     Track *memoryTrack = dynamic_cast<Track *>( track.data() );
0446     if( !memoryTrack )
0447         return false;
0448 
0449     Meta::TrackPtr originalTrack = memoryTrack->originalTrack();
0450     if( !originalTrack )
0451         return false; // paranoia
0452 
0453     bool mapsNeedUpdating = false;
0454     // make album first so that invalidateAlbum is called every time it is needed
0455     if( entitiesDiffer( originalTrack->album().data(), memoryTrack->album().data() ) )
0456     {
0457         CoverCache::invalidateAlbum( memoryTrack->album().data() );
0458         mapsNeedUpdating = true;
0459     }
0460     else if( entitiesDiffer( originalTrack->artist().data(), memoryTrack->artist().data() ) )
0461         mapsNeedUpdating = true;
0462     else if( entitiesDiffer( originalTrack->genre().data(), memoryTrack->genre().data() ) )
0463         mapsNeedUpdating = true;
0464     else if( entitiesDiffer( originalTrack->composer().data(), memoryTrack->composer().data() ) )
0465         mapsNeedUpdating = true;
0466     else if( entitiesDiffer( originalTrack->year().data(), memoryTrack->year().data() ) )
0467         mapsNeedUpdating = true;
0468 
0469     if( mapsNeedUpdating )
0470     {
0471         // we hold write lock so we can do the following trick:
0472         removeTrack( track );
0473         addExistingTrack( originalTrack, memoryTrack );
0474     }
0475 
0476     memoryTrack->notifyObservers();
0477     return mapsNeedUpdating;
0478 }
0479 
0480 bool
0481 MapChanger::hasTrackInMap( const Meta::TrackList &needles, const TrackMap &haystack )
0482 {
0483     foreach( Meta::TrackPtr track, needles )
0484     {
0485         if( track && haystack.contains( track->uidUrl() ) )
0486             return true;
0487     }
0488     return false;
0489 }
0490 
0491 bool
0492 MapChanger::referencedAsAlbumArtist( const Meta::ArtistPtr &artist, const AlbumMap &haystack )
0493 {
0494     foreach( Meta::AlbumPtr album, haystack )
0495     {
0496         if( album && album->hasAlbumArtist() && album->albumArtist() == artist )
0497             return true;
0498     }
0499     return false;
0500 }
0501 
0502 bool MapChanger::entitiesDiffer( const Meta::Base *first, const Meta::Base *second )
0503 {
0504     if( !first && !second )
0505         return false;  // both null -> do not differ
0506     if( !first || !second )
0507         return true;  // one of them null -> do differ
0508 
0509     return first->name() != second->name();
0510 }
0511 
0512 bool MapChanger::entitiesDiffer( const Meta::Album *first, const Meta::Album *second )
0513 {
0514     if( !first && !second )
0515         return false;  // both null -> do not differ
0516     if( !first || !second )
0517         return true;  // one of them null -> do differ
0518 
0519     if( first->name() != second->name() )
0520         return true;
0521     if( first->isCompilation() != second->isCompilation() )
0522         return true;
0523     if( first->hasImage() != second->hasImage() )
0524         return true;
0525     if( entitiesDiffer( first->albumArtist().data(), second->albumArtist().data() ) )
0526         return true;
0527 
0528     // we only compare images using dimension, it would be too costly otherwise
0529     if( first->image().width() != second->image().width() )
0530         return true;
0531     if( first->image().height() != second->image().height() )
0532         return true;
0533 
0534     return false;
0535 }