File indexing completed on 2025-01-05 04:25:51

0001 /****************************************************************************************
0002  * Copyright (c) 2009,2010 Maximilian Kossick <maximilian.kossick@googlemail.com>       *
0003  *                                                                                      *
0004  * This program is free software; you can redistribute it and/or modify it under        *
0005  * the terms of the GNU General Public License as published by the Free Software        *
0006  * Foundation; either version 2 of the License, or (at your option) any later           *
0007  * version.                                                                             *
0008  *                                                                                      *
0009  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0010  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0011  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0012  *                                                                                      *
0013  * You should have received a copy of the GNU General Public License along with         *
0014  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0015  ****************************************************************************************/
0016 
0017 #define DEBUG_PREFIX "AggregateMeta"
0018 
0019 #include "AggregateMeta.h"
0020 
0021 #include "SvgHandler.h"
0022 #include "core/meta/TrackEditor.h"
0023 #include "core/meta/support/MetaUtility.h"
0024 #include "core/support/Debug.h"
0025 #include "core-impl/collections/aggregate/AggregateCollection.h"
0026 
0027 #include <QDateTime>
0028 #include <QSet>
0029 #include <QTimer>
0030 
0031 namespace Meta
0032 {
0033 
0034 #define FORWARD( call ) { foreach( TrackEditorPtr e, m_editors ) { e->call; } \
0035                             if( !m_batchMode ) QTimer::singleShot( 0, m_collection, &Collections::AggregateCollection::slotUpdated ); }
0036 
0037 class AggregateTrackEditor : public TrackEditor
0038 {
0039 public:
0040     AggregateTrackEditor( Collections::AggregateCollection *coll, const QList<TrackEditorPtr> &editors )
0041         : TrackEditor()
0042         , m_batchMode( false )
0043         , m_collection( coll )
0044         , m_editors( editors )
0045     {}
0046 
0047     void beginUpdate() override
0048     {
0049         m_batchMode = true;
0050         foreach( TrackEditorPtr ec, m_editors ) ec->beginUpdate();
0051     }
0052     void endUpdate() override
0053     {
0054         foreach( TrackEditorPtr ec, m_editors ) ec->endUpdate();
0055         m_batchMode = false;
0056         QTimer::singleShot( 0, m_collection, &Collections::AggregateCollection::slotUpdated );
0057     }
0058     void setComment( const QString &newComment ) override { FORWARD( setComment( newComment ) ) }
0059     void setTrackNumber( int newTrackNumber ) override { FORWARD( setTrackNumber( newTrackNumber ) ) }
0060     void setDiscNumber( int newDiscNumber ) override { FORWARD( setDiscNumber( newDiscNumber ) ) }
0061     void setBpm( const qreal newBpm ) override { FORWARD( setBpm( newBpm ) ) }
0062     void setTitle( const QString &newTitle ) override { FORWARD( setTitle( newTitle ) ) }
0063     void setArtist( const QString &newArtist ) override { FORWARD( setArtist( newArtist ) ) }
0064     void setAlbum( const QString &newAlbum ) override { FORWARD( setAlbum( newAlbum ) ) }
0065     void setAlbumArtist( const QString &newAlbumArtist ) override { FORWARD( setAlbumArtist ( newAlbumArtist ) ) }
0066     void setGenre( const QString &newGenre ) override { FORWARD( setGenre( newGenre ) ) }
0067     void setComposer( const QString &newComposer ) override { FORWARD( setComposer( newComposer ) ) }
0068     void setYear( int newYear ) override { FORWARD( setYear( newYear ) ) }
0069 private:
0070     bool m_batchMode;
0071     Collections::AggregateCollection *m_collection;
0072     QList<TrackEditorPtr> m_editors;
0073 };
0074 
0075 #undef FORWARD
0076 
0077 AggregateTrack::AggregateTrack( Collections::AggregateCollection *coll, const TrackPtr &track )
0078         : Track()
0079         , Observer()
0080         , m_collection( coll )
0081         , m_name( track->name() )
0082         , m_album( nullptr )
0083         , m_artist( nullptr )
0084         , m_genre( nullptr )
0085         , m_composer( nullptr )
0086         , m_year( nullptr )
0087 {
0088     subscribeTo( track );
0089     m_tracks.append( track );
0090 
0091     if( track->album() )
0092         m_album = Meta::AlbumPtr( m_collection->getAlbum( track->album() ) );
0093     if( track->artist() )
0094         m_artist = Meta::ArtistPtr( m_collection->getArtist( track->artist() ) );
0095     if( track->genre() )
0096         m_genre = Meta::GenrePtr( m_collection->getGenre( track->genre() ) );
0097     if( track->composer() )
0098         m_composer = Meta::ComposerPtr( m_collection->getComposer( track->composer() ) );
0099     if( track->year() )
0100         m_year = Meta::YearPtr( m_collection->getYear( track->year() ) );
0101 }
0102 
0103 AggregateTrack::~AggregateTrack()
0104 {
0105 }
0106 
0107 QString
0108 AggregateTrack::name() const
0109 {
0110     return m_name;
0111 }
0112 
0113 QString
0114 AggregateTrack::prettyName() const
0115 {
0116     return m_name;
0117 }
0118 
0119 QString
0120 AggregateTrack::sortableName() const
0121 {
0122     if( !m_tracks.isEmpty() )
0123         return m_tracks.first()->sortableName();
0124 
0125     return m_name;
0126 }
0127 
0128 QUrl
0129 AggregateTrack::playableUrl() const
0130 {
0131     Meta::TrackPtr bestPlayableTrack;
0132     foreach( const Meta::TrackPtr &track, m_tracks )
0133     {
0134         if( track->isPlayable() )
0135         {
0136             bool local = track->playableUrl().isLocalFile();
0137             if( local )
0138             {
0139                 bestPlayableTrack = track;
0140                 break;
0141             }
0142             else
0143             {
0144                 //we might want to add some more sophisticated logic to figure out
0145                 //the best remote track to play, but this works for now
0146                 bestPlayableTrack = track;
0147             }
0148         }
0149     }
0150     if( bestPlayableTrack )
0151         return bestPlayableTrack->playableUrl();
0152 
0153     return QUrl();
0154 }
0155 
0156 QString
0157 AggregateTrack::prettyUrl() const
0158 {
0159     if( m_tracks.count() == 1 )
0160     {
0161         return m_tracks.first()->prettyUrl();
0162     }
0163     else
0164     {
0165         return QString();
0166     }
0167 }
0168 
0169 QString
0170 AggregateTrack::uidUrl() const
0171 {
0172     // this is where it gets interesting
0173     // a uidUrl for a AggregateTrack probably has to be generated
0174     // from the parts of the key in AggregateCollection
0175     // need to think about this some more
0176     return QString();
0177 }
0178 
0179 QString
0180 AggregateTrack::notPlayableReason() const
0181 {
0182     QStringList reasons;
0183     foreach( const Meta::TrackPtr &track, m_tracks )
0184     {
0185         if( !track->isPlayable() )
0186             reasons.append( track->notPlayableReason() );
0187         else
0188             return QString(); // no reason if at least one playable
0189     }
0190     return reasons.join( QStringLiteral( ", " ) );
0191 }
0192 
0193 Meta::AlbumPtr
0194 AggregateTrack::album() const
0195 {
0196     return m_album;
0197 }
0198 
0199 Meta::ArtistPtr
0200 AggregateTrack::artist() const
0201 {
0202     return m_artist;
0203 }
0204 
0205 Meta::ComposerPtr
0206 AggregateTrack::composer() const
0207 {
0208     return m_composer;
0209 }
0210 
0211 Meta::GenrePtr
0212 AggregateTrack::genre() const
0213 {
0214     return m_genre;
0215 }
0216 
0217 Meta::YearPtr
0218 AggregateTrack::year() const
0219 {
0220     return m_year;
0221 }
0222 
0223 QString
0224 AggregateTrack::comment() const
0225 {
0226     //try to return something sensible here...
0227     //do not show a comment if the internal tracks disagree about the comment
0228     QString comment;
0229     if( !m_tracks.isEmpty() )
0230         comment = m_tracks.first()->comment();
0231 
0232     foreach( const Meta::TrackPtr &track, m_tracks )
0233     {
0234         if( track->comment() != comment )
0235         {
0236             comment.clear();
0237             break;
0238         }
0239     }
0240     return comment;
0241 }
0242 
0243 qreal
0244 AggregateTrack::bpm() const
0245 {
0246     //Similar to comment(), try to return something sensible here...
0247     //do not show a common bpm value if the internal tracks disagree about the bpm
0248     qreal bpm = -1.0;
0249     if( !m_tracks.isEmpty() )
0250         bpm = m_tracks.first()->bpm();
0251 
0252     foreach( const Meta::TrackPtr &track, m_tracks )
0253     {
0254         if( track->bpm() != bpm )
0255         {
0256             bpm = -1.0;
0257             break;
0258         }
0259     }
0260     return bpm;
0261 }
0262 
0263 double
0264 AggregateTrack::score() const
0265 {
0266     //again, multiple ways to implement this method:
0267     //return the maximum score, the minimum score, the average
0268     //the score of the track with the maximum play count,
0269     //or an average weighted by play count. And probably a couple of ways that
0270     //I cannot think of right now...
0271 
0272     //implementing the weighted average here...
0273     double weightedSum = 0.0;
0274     int totalCount = 0;
0275     foreach( const Meta::TrackPtr &track, m_tracks )
0276     {
0277         ConstStatisticsPtr statistics = track->statistics();
0278         totalCount += statistics->playCount();
0279         weightedSum += statistics->playCount() * statistics->score();
0280     }
0281     if( totalCount )
0282         return weightedSum / totalCount;
0283 
0284     return 0.0;
0285 }
0286 
0287 void
0288 AggregateTrack::setScore( double newScore )
0289 {
0290     foreach( Meta::TrackPtr track, m_tracks )
0291     {
0292         track->statistics()->setScore( newScore );
0293     }
0294 }
0295 
0296 int
0297 AggregateTrack::rating() const
0298 {
0299     //yay, multiple options again. As this has to be defined by the user, let's take
0300     //the maximum here.
0301     int result = 0;
0302     foreach( const Meta::TrackPtr &track, m_tracks )
0303     {
0304         if( track->statistics()->rating() > result )
0305             result = track->statistics()->rating();
0306     }
0307     return result;
0308 }
0309 
0310 void
0311 AggregateTrack::setRating( int newRating )
0312 {
0313     foreach( Meta::TrackPtr track, m_tracks )
0314     {
0315         track->statistics()->setRating( newRating );
0316     }
0317 }
0318 
0319 QDateTime
0320 AggregateTrack::firstPlayed() const
0321 {
0322     QDateTime result;
0323     foreach( const Meta::TrackPtr &track, m_tracks )
0324     {
0325         ConstStatisticsPtr statistics = track->statistics();
0326         //use the track's firstPlayed value if it represents an earlier timestamp than
0327         //the current result, or use it directly if result has not been set yet
0328         //this should result in the earliest timestamp for first play of all internal
0329         //tracks being returned
0330         if( ( statistics->firstPlayed().isValid() && result.isValid() && statistics->firstPlayed() < result ) ||
0331             ( statistics->firstPlayed().isValid() && !result.isValid() ) )
0332         {
0333             result = statistics->firstPlayed();
0334         }
0335     }
0336     return result;
0337 }
0338 
0339 void
0340 AggregateTrack::setFirstPlayed( const QDateTime &date )
0341 {
0342     foreach( Meta::TrackPtr track, m_tracks )
0343     {
0344         // only "lower" the first played
0345         Meta::StatisticsPtr trackStats = track->statistics();
0346         if( !trackStats->firstPlayed().isValid() ||
0347             trackStats->firstPlayed() > date )
0348         {
0349             trackStats->setFirstPlayed( date );
0350         }
0351     }
0352 }
0353 
0354 QDateTime
0355 AggregateTrack::lastPlayed() const
0356 {
0357     QDateTime result;
0358     //return the latest timestamp. Easier than firstPlayed because we do not have to
0359     //care about 0.
0360     //when are we going to perform the refactoring as discussed in Berlin?
0361     foreach( const Meta::TrackPtr &track, m_tracks )
0362     {
0363         if( track->statistics()->lastPlayed() > result )
0364         {
0365             result = track->statistics()->lastPlayed();
0366         }
0367     }
0368     return result;
0369 }
0370 
0371 void
0372 AggregateTrack::setLastPlayed(const QDateTime& date)
0373 {
0374     foreach( Meta::TrackPtr track, m_tracks )
0375     {
0376         // only "raise" the last played
0377         Meta::StatisticsPtr trackStats = track->statistics();
0378         if( !trackStats->lastPlayed().isValid() ||
0379             trackStats->lastPlayed() < date )
0380         {
0381             trackStats->setLastPlayed( date );
0382         }
0383     }
0384 }
0385 
0386 int
0387 AggregateTrack::playCount() const
0388 {
0389     // show the maximum of all play counts.
0390     int result = 0;
0391     foreach( const Meta::TrackPtr &track, m_tracks )
0392     {
0393         if( track->statistics()->playCount() > result )
0394         {
0395             result = track->statistics()->playCount();
0396         }
0397     }
0398     return result;
0399 }
0400 
0401 void
0402 AggregateTrack::setPlayCount( int newPlayCount )
0403 {
0404     Q_UNUSED( newPlayCount )
0405     // no safe thing to do here. Notice we override finishedPlaying()
0406 }
0407 
0408 void
0409 AggregateTrack::finishedPlaying( double playedFraction )
0410 {
0411     foreach( Meta::TrackPtr track, m_tracks )
0412     {
0413         track->finishedPlaying( playedFraction );
0414     }
0415 }
0416 
0417 qint64
0418 AggregateTrack::length() const
0419 {
0420     foreach( const Meta::TrackPtr &track, m_tracks )
0421     {
0422         if( track->length() )
0423             return track->length();
0424     }
0425     return 0;
0426 }
0427 
0428 int
0429 AggregateTrack::filesize() const
0430 {
0431     foreach( const Meta::TrackPtr &track, m_tracks )
0432     {
0433         if( track->filesize() )
0434         {
0435             return track->filesize();
0436         }
0437     }
0438     return 0;
0439 }
0440 
0441 int
0442 AggregateTrack::sampleRate() const
0443 {
0444     foreach( const Meta::TrackPtr &track, m_tracks )
0445     {
0446         if( track->sampleRate() )
0447             return track->sampleRate();
0448     }
0449     return 0;
0450 }
0451 
0452 int
0453 AggregateTrack::bitrate() const
0454 {
0455     foreach( const Meta::TrackPtr &track, m_tracks )
0456     {
0457         if( track->bitrate() )
0458             return track->bitrate();
0459     }
0460     return 0;
0461 }
0462 
0463 QDateTime
0464 AggregateTrack::createDate() const
0465 {
0466     QDateTime result;
0467     foreach( const Meta::TrackPtr &track, m_tracks )
0468     {
0469         //use the track's firstPlayed value if it represents an earlier timestamp than
0470         //the current result, or use it directly if result has not been set yet
0471         //this should result in the earliest timestamp for first play of all internal
0472         //tracks being returned
0473         if( ( track->createDate().isValid() && result.isValid() && track->createDate() < result ) ||
0474             ( track->createDate().isValid() && !result.isValid() ) )
0475         {
0476             result = track->createDate();
0477         }
0478     }
0479     return result;
0480 }
0481 
0482 int
0483 AggregateTrack::trackNumber() const
0484 {
0485     int result = 0;
0486     foreach( const Meta::TrackPtr &track, m_tracks )
0487     {
0488         if( ( !result && track->trackNumber() ) || ( result && result == track->trackNumber() ) )
0489         {
0490             result = track->trackNumber();
0491         }
0492         else if( result && result != track->trackNumber() )
0493         {
0494             //tracks disagree about the tracknumber
0495             return 0;
0496         }
0497     }
0498     return result;
0499 }
0500 
0501 int
0502 AggregateTrack::discNumber() const
0503 {
0504     int result = 0;
0505     foreach( const Meta::TrackPtr &track, m_tracks )
0506     {
0507         if( ( !result && track->discNumber() ) || ( result && result == track->discNumber() ) )
0508         {
0509             result = track->discNumber();
0510         }
0511         else if( result && result != track->discNumber() )
0512         {
0513             //tracks disagree about the disc number
0514             return 0;
0515         }
0516     }
0517     return result;
0518 }
0519 
0520 QString
0521 AggregateTrack::type() const
0522 {
0523     if( m_tracks.size() == 1 )
0524     {
0525         return m_tracks.first()->type();
0526     }
0527     else
0528     {
0529         //TODO: figure something out
0530         return QString();
0531     }
0532 }
0533 
0534 Collections::Collection*
0535 AggregateTrack::collection() const
0536 {
0537     return m_collection;
0538 }
0539 
0540 bool
0541 AggregateTrack::hasCapabilityInterface( Capabilities::Capability::Type type ) const
0542 {
0543     if( m_tracks.count() == 1 )
0544         // if we aggregate only one track, simply return the tracks capability directly
0545         return m_tracks.first()->hasCapabilityInterface( type );
0546     else
0547         return false;
0548 }
0549 
0550 Capabilities::Capability*
0551 AggregateTrack::createCapabilityInterface( Capabilities::Capability::Type type )
0552 {
0553     if( m_tracks.count() == 1 )
0554         return m_tracks.first()->createCapabilityInterface( type );
0555     else
0556         return nullptr;
0557 }
0558 
0559 TrackEditorPtr
0560 AggregateTrack::editor()
0561 {
0562     if( m_tracks.count() == 1 )
0563         return m_tracks.first()->editor();
0564 
0565     QList<Meta::TrackEditorPtr> editors;
0566     foreach( Meta::TrackPtr track, m_tracks )
0567     {
0568         Meta::TrackEditorPtr ec = track->editor();
0569         if( ec )
0570             editors << ec;
0571         else
0572             return TrackEditorPtr();
0573     }
0574     return TrackEditorPtr( new AggregateTrackEditor( m_collection, editors ) );
0575 }
0576 
0577 void
0578 AggregateTrack::addLabel( const QString &label )
0579 {
0580     foreach( Meta::TrackPtr track, m_tracks )
0581     {
0582         track->addLabel( label );
0583     }
0584 }
0585 
0586 void
0587 AggregateTrack::addLabel( const Meta::LabelPtr &label )
0588 {
0589     foreach( Meta::TrackPtr track, m_tracks )
0590     {
0591         track->addLabel( label );
0592     }
0593 }
0594 
0595 void
0596 AggregateTrack::removeLabel( const Meta::LabelPtr &label )
0597 {
0598     foreach( Meta::TrackPtr track, m_tracks )
0599     {
0600         track->removeLabel( label );
0601     }
0602 }
0603 
0604 Meta::LabelList
0605 AggregateTrack::labels() const
0606 {
0607     QSet<AggregateLabel *> aggregateLabels;
0608     foreach( const Meta::TrackPtr &track, m_tracks )
0609     {
0610         foreach( Meta::LabelPtr label, track->labels() )
0611         {
0612             aggregateLabels.insert( m_collection->getLabel( label ) );
0613         }
0614     }
0615     Meta::LabelList result;
0616     foreach( AggregateLabel *label, aggregateLabels )
0617     {
0618         result << Meta::LabelPtr( label );
0619     }
0620     return result;
0621 }
0622 
0623 StatisticsPtr
0624 AggregateTrack::statistics()
0625 {
0626     return StatisticsPtr( this );
0627 }
0628 
0629 void
0630 AggregateTrack::add( const Meta::TrackPtr &track )
0631 {
0632     if( !track || m_tracks.contains( track ) )
0633         return;
0634 
0635     m_tracks.append( track );
0636     subscribeTo( track );
0637 
0638     notifyObservers();
0639 }
0640 
0641 void
0642 AggregateTrack::metadataChanged(const TrackPtr &track )
0643 {
0644     if( !track )
0645         return;
0646 
0647     if( !m_tracks.contains( track ) )
0648     {
0649         //why are we subscribed?
0650         unsubscribeFrom( track );
0651         return;
0652     }
0653 
0654     const TrackKey myKey( Meta::TrackPtr( this ) );
0655     const TrackKey otherKey( track );
0656     if( myKey == otherKey )
0657     {
0658         //no key relevant metadata did change
0659         notifyObservers();
0660         return;
0661     }
0662     else
0663     {
0664         if( m_tracks.size() == 1 )
0665         {
0666             if( m_collection->hasTrack( otherKey ) )
0667             {
0668                 unsubscribeFrom( track );
0669                 m_collection->getTrack( track );
0670                 m_tracks.removeAll( track );
0671                 m_collection->removeTrack( myKey );
0672                 return; //do not notify observers, this track is not valid anymore!
0673             }
0674             else
0675             {
0676                 m_name = track->name();
0677                 if( track->album() )
0678                      m_album = Meta::AlbumPtr( m_collection->getAlbum( track->album() ) );
0679                 if( track->artist() )
0680                     m_artist = Meta::ArtistPtr( m_collection->getArtist( track->artist() ) );
0681                 if( track->genre() )
0682                     m_genre = Meta::GenrePtr( m_collection->getGenre( track->genre() ) );
0683                 if( track->composer() )
0684                     m_composer = Meta::ComposerPtr( m_collection->getComposer( track->composer() ) );
0685                 if( track->year() )
0686                     m_year = Meta::YearPtr( m_collection->getYear( track->year() ) );
0687 
0688                 m_collection->setTrack( this );
0689                 m_collection->removeTrack( myKey );
0690             }
0691         }
0692         else
0693         {
0694             unsubscribeFrom( track );
0695             m_collection->getTrack( track );
0696             m_tracks.removeAll( track );
0697         }
0698         notifyObservers();
0699     }
0700 }
0701 
0702 AggregateAlbum::AggregateAlbum( Collections::AggregateCollection *coll, Meta::AlbumPtr album )
0703         : Meta::Album()
0704         , Meta::Observer()
0705         , m_collection( coll )
0706         , m_name( album->name() )
0707 {
0708     m_albums.append( album );
0709     if( album->hasAlbumArtist() )
0710         m_albumArtist = Meta::ArtistPtr( m_collection->getArtist( album->albumArtist() ) );
0711 }
0712 
0713 AggregateAlbum::~AggregateAlbum()
0714 {
0715 }
0716 
0717 QString
0718 AggregateAlbum::name() const
0719 {
0720     return m_name;
0721 }
0722 
0723 QString
0724 AggregateAlbum::prettyName() const
0725 {
0726     return m_name;
0727 }
0728 
0729 QString
0730 AggregateAlbum::sortableName() const
0731 {
0732     if( !m_albums.isEmpty() )
0733         return m_albums.first()->sortableName();
0734 
0735     return m_name;
0736 }
0737 
0738 Meta::TrackList
0739 AggregateAlbum::tracks()
0740 {
0741     QSet<AggregateTrack*> tracks;
0742     foreach( Meta::AlbumPtr album, m_albums )
0743     {
0744         Meta::TrackList tmp = album->tracks();
0745         foreach( const Meta::TrackPtr &track, tmp )
0746         {
0747             tracks.insert( m_collection->getTrack( track ) );
0748         }
0749     }
0750 
0751     Meta::TrackList result;
0752     foreach( AggregateTrack *track, tracks )
0753     {
0754         result.append( Meta::TrackPtr( track ) );
0755     }
0756     return result;
0757 }
0758 
0759 Meta::ArtistPtr
0760 AggregateAlbum::albumArtist() const
0761 {
0762     return m_albumArtist;
0763 }
0764 
0765 bool
0766 AggregateAlbum::isCompilation() const
0767 {
0768     return m_albumArtist.isNull();
0769 }
0770 
0771 bool
0772 AggregateAlbum::hasAlbumArtist() const
0773 {
0774     return !m_albumArtist.isNull();
0775 }
0776 
0777 bool
0778 AggregateAlbum::hasCapabilityInterface(Capabilities::Capability::Type type ) const
0779 {
0780 
0781     if( m_albums.count() == 1 )
0782     {
0783         return m_albums.first()->hasCapabilityInterface( type );
0784     }
0785     else
0786     {
0787         return false;
0788     }
0789 }
0790 
0791 Capabilities::Capability*
0792 AggregateAlbum::createCapabilityInterface( Capabilities::Capability::Type type )
0793 {
0794     if( m_albums.count() == 1 )
0795     {
0796         return m_albums.first()->createCapabilityInterface( type );
0797     }
0798     else
0799     {
0800         return nullptr;
0801     }
0802 }
0803 
0804 void
0805 AggregateAlbum::add( const Meta::AlbumPtr &album )
0806 {
0807     if( !album || m_albums.contains( album ) )
0808         return;
0809 
0810     m_albums.append( album );
0811     subscribeTo( album );
0812 
0813     notifyObservers();
0814 }
0815 
0816 bool
0817 AggregateAlbum::hasImage( int size ) const
0818 {
0819     foreach( const Meta::AlbumPtr &album, m_albums )
0820     {
0821         if( album->hasImage( size ) )
0822             return true;
0823     }
0824     return false;
0825 }
0826 
0827 QImage
0828 AggregateAlbum::image( int size ) const
0829 {
0830     foreach( Meta::AlbumPtr album, m_albums )
0831     {
0832         if( album->hasImage( size ) )
0833         {
0834             return album->image( size );
0835         }
0836     }
0837     return Meta::Album::image( size );
0838 }
0839 
0840 QUrl
0841 AggregateAlbum::imageLocation( int size )
0842 {
0843     foreach( Meta::AlbumPtr album, m_albums )
0844     {
0845         if( album->hasImage( size ) )
0846         {
0847             QUrl url = album->imageLocation( size );
0848             if( url.isValid() )
0849             {
0850                 return url;
0851             }
0852         }
0853     }
0854     return QUrl();
0855 }
0856 
0857 QPixmap
0858 AggregateAlbum::imageWithBorder( int size, int borderWidth )
0859 {
0860     foreach( Meta::AlbumPtr album, m_albums )
0861     {
0862         if( album->hasImage( size ) )
0863         {
0864             return The::svgHandler()->imageWithBorder( album, size, borderWidth );
0865         }
0866     }
0867     return QPixmap();
0868 }
0869 
0870 bool
0871 AggregateAlbum::canUpdateImage() const
0872 {
0873     if( m_albums.isEmpty() )
0874         return false;
0875 
0876     foreach( const Meta::AlbumPtr &album, m_albums )
0877     {
0878         //we can only update the image for all albums at the same time
0879         if( !album->canUpdateImage() )
0880             return false;
0881     }
0882     return true;
0883 }
0884 
0885 void
0886 AggregateAlbum::setImage( const QImage &image )
0887 {
0888     foreach( Meta::AlbumPtr album, m_albums )
0889     {
0890         album->setImage( image );
0891     }
0892 }
0893 
0894 void
0895 AggregateAlbum::removeImage()
0896 {
0897     foreach( Meta::AlbumPtr album, m_albums )
0898     {
0899         album->removeImage();
0900     }
0901 }
0902 
0903 void
0904 AggregateAlbum::setSuppressImageAutoFetch( bool suppress )
0905 {
0906     foreach( Meta::AlbumPtr album, m_albums )
0907     {
0908         album->setSuppressImageAutoFetch( suppress );
0909     }
0910 }
0911 
0912 bool
0913 AggregateAlbum::suppressImageAutoFetch() const
0914 {
0915     foreach( const Meta::AlbumPtr &album, m_albums )
0916     {
0917         if( !album->suppressImageAutoFetch() )
0918             return false;
0919     }
0920     return true;
0921 }
0922 
0923 void
0924 AggregateAlbum::metadataChanged(const AlbumPtr &album )
0925 {
0926     if( !album || !m_albums.contains( album ) )
0927         return;
0928 
0929     if( album->name() != m_name ||
0930         hasAlbumArtist() != album->hasAlbumArtist() ||
0931         ( hasAlbumArtist() && m_albumArtist->name() != album->albumArtist()->name() ) )
0932     {
0933         if( m_albums.count() > 1 )
0934         {
0935             m_collection->getAlbum( album );
0936             unsubscribeFrom( album );
0937             m_albums.removeAll( album );
0938         }
0939         else
0940         {
0941             Meta::ArtistPtr albumartist;
0942             if( album->hasAlbumArtist() )
0943                  albumartist = Meta::ArtistPtr( m_collection->getArtist( album->albumArtist() ) );
0944 
0945             QString artistname = m_albumArtist ? m_albumArtist->name() : QString();
0946             m_collection->removeAlbum( m_name, artistname );
0947             m_name = album->name();
0948             m_albumArtist = albumartist;
0949             m_collection->setAlbum( this );
0950         }
0951     }
0952 
0953     notifyObservers();
0954 }
0955 
0956 AggregateArtist::AggregateArtist( Collections::AggregateCollection *coll, const Meta::ArtistPtr &artist )
0957         : Meta::Artist()
0958         , Meta::Observer()
0959         , m_collection( coll )
0960         , m_name( artist->name() )
0961 {
0962     m_artists.append( artist );
0963     subscribeTo( artist );
0964 }
0965 
0966 AggregateArtist::~AggregateArtist()
0967 {
0968 }
0969 
0970 QString
0971 AggregateArtist::name() const
0972 {
0973     return m_name;
0974 }
0975 
0976 QString
0977 AggregateArtist::prettyName() const
0978 {
0979     return m_name;
0980 }
0981 
0982 QString
0983 AggregateArtist::sortableName() const
0984 {
0985     if( !m_artists.isEmpty() )
0986         return m_artists.first()->sortableName();
0987 
0988     return m_name;
0989 }
0990 
0991 Meta::TrackList
0992 AggregateArtist::tracks()
0993 {
0994     QSet<AggregateTrack*> tracks;
0995     foreach( Meta::ArtistPtr artist, m_artists )
0996     {
0997         Meta::TrackList tmp = artist->tracks();
0998         foreach( const Meta::TrackPtr &track, tmp )
0999         {
1000             tracks.insert( m_collection->getTrack( track ) );
1001         }
1002     }
1003 
1004     Meta::TrackList result;
1005     foreach( AggregateTrack *track, tracks )
1006     {
1007         result.append( Meta::TrackPtr( track ) );
1008     }
1009     return result;
1010 }
1011 
1012 bool
1013 AggregateArtist::hasCapabilityInterface(Capabilities::Capability::Type type ) const
1014 {
1015 
1016     if( m_artists.count() == 1 )
1017     {
1018         return m_artists.first()->hasCapabilityInterface( type );
1019     }
1020     else
1021     {
1022         return false;
1023     }
1024 }
1025 
1026 Capabilities::Capability*
1027 AggregateArtist::createCapabilityInterface( Capabilities::Capability::Type type )
1028 {
1029     if( m_artists.count() == 1 )
1030     {
1031         return m_artists.first()->createCapabilityInterface( type );
1032     }
1033     else
1034     {
1035         return nullptr;
1036     }
1037 }
1038 
1039 void
1040 AggregateArtist::add( const Meta::ArtistPtr &artist )
1041 {
1042     if( !artist || m_artists.contains( artist ) )
1043         return;
1044 
1045     m_artists.append( artist );
1046     subscribeTo( artist );
1047 
1048     notifyObservers();
1049 }
1050 
1051 void
1052 AggregateArtist::metadataChanged(const ArtistPtr &artist )
1053 {
1054     if( !artist || !m_artists.contains( artist ) )
1055         return;
1056 
1057     if( artist->name() != m_name )
1058     {
1059         if( m_artists.count() > 1 )
1060         {
1061             m_collection->getArtist( artist );
1062             unsubscribeFrom( artist );
1063             m_artists.removeAll( artist );
1064         }
1065         else
1066         {
1067             //possible race condition here:
1068             //if another thread creates an Artist with the new name
1069             //we will have two instances that have the same name!
1070             //TODO: figure out a way around that
1071             //the race condition is a problem for all other metadataChanged methods too
1072             m_collection->removeArtist( m_name );
1073             m_name = artist->name();
1074             m_collection->setArtist( this );
1075 
1076         }
1077     }
1078 
1079     notifyObservers();
1080 }
1081 
1082 AggregateGenre::AggregateGenre( Collections::AggregateCollection *coll, const Meta::GenrePtr &genre )
1083         : Meta::Genre()
1084         , Meta::Observer()
1085         , m_collection( coll )
1086         , m_name( genre->name() )
1087 {
1088     m_genres.append( genre );
1089     subscribeTo( genre );
1090 }
1091 
1092 AggregateGenre::~AggregateGenre()
1093 {
1094 }
1095 
1096 QString
1097 AggregateGenre::name() const
1098 {
1099     return m_name;
1100 }
1101 
1102 QString
1103 AggregateGenre::prettyName() const
1104 {
1105     return m_name;
1106 }
1107 
1108 QString
1109 AggregateGenre::sortableName() const
1110 {
1111     if( !m_genres.isEmpty() )
1112         return m_genres.first()->sortableName();
1113 
1114     return m_name;
1115 }
1116 
1117 Meta::TrackList
1118 AggregateGenre::tracks()
1119 {
1120     QSet<AggregateTrack*> tracks;
1121     foreach( Meta::GenrePtr genre, m_genres )
1122     {
1123         Meta::TrackList tmp = genre->tracks();
1124         foreach( const Meta::TrackPtr &track, tmp )
1125         {
1126             tracks.insert( m_collection->getTrack( track ) );
1127         }
1128     }
1129 
1130     Meta::TrackList result;
1131     foreach( AggregateTrack *track, tracks )
1132     {
1133         result.append( Meta::TrackPtr( track ) );
1134     }
1135     return result;
1136 }
1137 
1138 bool
1139 AggregateGenre::hasCapabilityInterface(Capabilities::Capability::Type type ) const
1140 {
1141 
1142     if( m_genres.count() == 1 )
1143     {
1144         return m_genres.first()->hasCapabilityInterface( type );
1145     }
1146     else
1147     {
1148         return false;
1149     }
1150 }
1151 
1152 Capabilities::Capability*
1153 AggregateGenre::createCapabilityInterface( Capabilities::Capability::Type type )
1154 {
1155     if( m_genres.count() == 1 )
1156     {
1157         return m_genres.first()->createCapabilityInterface( type );
1158     }
1159     else
1160     {
1161         return nullptr;
1162     }
1163 }
1164 
1165 void
1166 AggregateGenre::add( const Meta::GenrePtr &genre )
1167 {
1168     if( !genre || m_genres.contains( genre ) )
1169         return;
1170 
1171     m_genres.append( genre );
1172     subscribeTo( genre );
1173 
1174     notifyObservers();
1175 }
1176 
1177 void
1178 AggregateGenre::metadataChanged( const Meta::GenrePtr &genre )
1179 {
1180     if( !genre || !m_genres.contains( genre ) )
1181         return;
1182 
1183     if( genre->name() != m_name )
1184     {
1185         if( m_genres.count() > 1 )
1186         {
1187             m_collection->getGenre( genre );
1188             unsubscribeFrom( genre );
1189             m_genres.removeAll( genre );
1190         }
1191         else
1192         {
1193             m_collection->removeGenre( m_name );
1194             m_collection->setGenre( this );
1195             m_name = genre->name();
1196         }
1197     }
1198 
1199     notifyObservers();
1200 }
1201 
1202 AggregateComposer::AggregateComposer( Collections::AggregateCollection *coll, const Meta::ComposerPtr &composer )
1203         : Meta::Composer()
1204         , Meta::Observer()
1205         , m_collection( coll )
1206         , m_name( composer->name() )
1207 {
1208     m_composers.append( composer );
1209     subscribeTo( composer );
1210 }
1211 
1212 AggregateComposer::~AggregateComposer()
1213 {
1214 }
1215 
1216 QString
1217 AggregateComposer::name() const
1218 {
1219     return m_name;
1220 }
1221 
1222 QString
1223 AggregateComposer::prettyName() const
1224 {
1225     return m_name;
1226 }
1227 
1228 QString
1229 AggregateComposer::sortableName() const
1230 {
1231     if( !m_composers.isEmpty() )
1232         return m_composers.first()->sortableName();
1233 
1234     return m_name;
1235 }
1236 
1237 Meta::TrackList
1238 AggregateComposer::tracks()
1239 {
1240     QSet<AggregateTrack*> tracks;
1241     foreach( Meta::ComposerPtr composer, m_composers )
1242     {
1243         Meta::TrackList tmp = composer->tracks();
1244         foreach( const Meta::TrackPtr &track, tmp )
1245         {
1246             tracks.insert( m_collection->getTrack( track ) );
1247         }
1248     }
1249 
1250     Meta::TrackList result;
1251     foreach( AggregateTrack *track, tracks )
1252     {
1253         result.append( Meta::TrackPtr( track ) );
1254     }
1255     return result;
1256 }
1257 
1258 bool
1259 AggregateComposer::hasCapabilityInterface(Capabilities::Capability::Type type ) const
1260 {
1261 
1262     if( m_composers.count() == 1 )
1263     {
1264         return m_composers.first()->hasCapabilityInterface( type );
1265     }
1266     else
1267     {
1268         return false;
1269     }
1270 }
1271 
1272 Capabilities::Capability*
1273 AggregateComposer::createCapabilityInterface( Capabilities::Capability::Type type )
1274 {
1275     if( m_composers.count() == 1 )
1276     {
1277         return m_composers.first()->createCapabilityInterface( type );
1278     }
1279     else
1280     {
1281         return nullptr;
1282     }
1283 }
1284 
1285 void
1286 AggregateComposer::add( const Meta::ComposerPtr &composer )
1287 {
1288     if( !composer || m_composers.contains( composer ) )
1289         return;
1290 
1291     m_composers.append( composer );
1292     subscribeTo( composer );
1293 
1294     notifyObservers();
1295 }
1296 
1297 void
1298 AggregateComposer::metadataChanged(const ComposerPtr &composer )
1299 {
1300     if( !composer || !m_composers.contains( composer ) )
1301         return;
1302 
1303     if( composer->name() != m_name )
1304     {
1305         if( m_composers.count() > 1 )
1306         {
1307             m_collection->getComposer( composer );
1308             unsubscribeFrom( composer );
1309             m_composers.removeAll( composer );
1310         }
1311         else
1312         {
1313             m_collection->removeComposer( m_name );
1314             m_collection->setComposer( this );
1315             m_name = composer->name();
1316         }
1317     }
1318 
1319     notifyObservers();
1320 }
1321 
1322 AggreagateYear::AggreagateYear( Collections::AggregateCollection *coll, const Meta::YearPtr &year )
1323         : Meta::Year()
1324         , Meta::Observer()
1325         , m_collection( coll )
1326         , m_name( year->name() )
1327 {
1328     m_years.append( year );
1329     subscribeTo( year );
1330 }
1331 
1332 AggreagateYear::~AggreagateYear()
1333 {
1334     //nothing to do
1335 }
1336 
1337 QString
1338 AggreagateYear::name() const
1339 {
1340     return m_name;
1341 }
1342 
1343 QString
1344 AggreagateYear::prettyName() const
1345 {
1346     return m_name;
1347 }
1348 
1349 QString
1350 AggreagateYear::sortableName() const
1351 {
1352     if( !m_years.isEmpty() )
1353         return m_years.first()->sortableName();
1354 
1355     return m_name;
1356 }
1357 
1358 Meta::TrackList
1359 AggreagateYear::tracks()
1360 {
1361     QSet<AggregateTrack*> tracks;
1362     foreach( Meta::YearPtr year, m_years )
1363     {
1364         Meta::TrackList tmp = year->tracks();
1365         foreach( const Meta::TrackPtr &track, tmp )
1366         {
1367             tracks.insert( m_collection->getTrack( track ) );
1368         }
1369     }
1370 
1371     Meta::TrackList result;
1372     foreach( AggregateTrack *track, tracks )
1373     {
1374         result.append( Meta::TrackPtr( track ) );
1375     }
1376     return result;
1377 }
1378 
1379 bool
1380 AggreagateYear::hasCapabilityInterface(Capabilities::Capability::Type type ) const
1381 {
1382 
1383     if( m_years.count() == 1 )
1384     {
1385         return m_years.first()->hasCapabilityInterface( type );
1386     }
1387     else
1388     {
1389         return false;
1390     }
1391 }
1392 
1393 Capabilities::Capability*
1394 AggreagateYear::createCapabilityInterface( Capabilities::Capability::Type type )
1395 {
1396     if( m_years.count() == 1 )
1397     {
1398         return m_years.first()->createCapabilityInterface( type );
1399     }
1400     else
1401     {
1402         return nullptr;
1403     }
1404 }
1405 
1406 void
1407 AggreagateYear::add( const Meta::YearPtr &year )
1408 {
1409     if( !year || m_years.contains( year ) )
1410         return;
1411 
1412     m_years.append( year );
1413     subscribeTo( year );
1414 
1415     notifyObservers();
1416 }
1417 
1418 void
1419 AggreagateYear::metadataChanged( const Meta::YearPtr &year )
1420 {
1421     if( !year || !m_years.contains( year ) )
1422         return;
1423 
1424     if( year->name() != m_name )
1425     {
1426         if( m_years.count() > 1 )
1427         {
1428             m_collection->getYear( year );
1429             unsubscribeFrom( year );
1430             m_years.removeAll( year );
1431         }
1432         else
1433         {
1434             if( m_collection->hasYear( year->name() ) )
1435             {
1436                 unsubscribeFrom( year );
1437                 m_collection->getYear( year );
1438                 m_years.removeAll( year );
1439                 m_collection->removeYear( m_name );
1440                 return; //do NOT notify observers, the instance is not valid anymore!
1441             }
1442             else
1443             {
1444                 // be careful with the ordering of instructions here
1445                 // AggregateCollection uses AmarokSharedPointer internally
1446                 // so we have to make sure that there is more than one pointer
1447                 // to this instance by registering this instance under the new name
1448                 // before removing the old one. Otherwise kSharedPtr might delete this
1449                 // instance in removeYear()
1450                 QString tmpName = m_name;
1451                 m_name = year->name();
1452                 m_collection->setYear( this );
1453                 m_collection->removeYear( tmpName );
1454             }
1455         }
1456     }
1457 
1458     notifyObservers();
1459 }
1460 
1461 AggregateLabel::AggregateLabel( Collections::AggregateCollection *coll, const Meta::LabelPtr &label )
1462     : Meta::Label()
1463     , m_collection( coll )
1464     , m_name( label->name() )
1465 {
1466     m_labels.append( label );
1467     Q_UNUSED(m_collection); // might be needed later
1468 }
1469 
1470 AggregateLabel::~AggregateLabel()
1471 {
1472     //nothing to do
1473 }
1474 
1475 QString
1476 AggregateLabel::name() const
1477 {
1478     return m_name;
1479 }
1480 
1481 QString
1482 AggregateLabel::prettyName() const
1483 {
1484     return m_name;
1485 }
1486 
1487 QString
1488 AggregateLabel::sortableName() const
1489 {
1490     if( !m_labels.isEmpty() )
1491         return m_labels.first()->sortableName();
1492 
1493     return m_name;
1494 }
1495 
1496 bool
1497 AggregateLabel::hasCapabilityInterface( Capabilities::Capability::Type type ) const
1498 {
1499 
1500     if( m_labels.count() == 1 )
1501     {
1502         return m_labels.first()->hasCapabilityInterface( type );
1503     }
1504     else
1505     {
1506         return false;
1507     }
1508 }
1509 
1510 Capabilities::Capability*
1511 AggregateLabel::createCapabilityInterface( Capabilities::Capability::Type type )
1512 {
1513     if( m_labels.count() == 1 )
1514     {
1515         return m_labels.first()->createCapabilityInterface( type );
1516     }
1517     else
1518     {
1519         return nullptr;
1520     }
1521 }
1522 
1523 void
1524 AggregateLabel::add( const Meta::LabelPtr &label )
1525 {
1526     if( !label || m_labels.contains( label ) )
1527         return;
1528 
1529     m_labels.append( label );
1530 }
1531 
1532 } //namespace Meta