1 /****************************************************************************************
2  * Copyright (c) 2009,2010 Maximilian Kossick <maximilian.kossick@googlemail.com>       *
3  *                                                                                      *
4  * This program is free software; you can redistribute it and/or modify it under        *
5  * the terms of the GNU General Public License as published by the Free Software        *
6  * Foundation; either version 2 of the License, or (at your option) any later           *
7  * version.                                                                             *
8  *                                                                                      *
9  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
10  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
11  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
12  *                                                                                      *
13  * You should have received a copy of the GNU General Public License along with         *
14  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
15  ****************************************************************************************/
16 
17 #define DEBUG_PREFIX "AggregateMeta"
18 
19 #include "AggregateMeta.h"
20 
21 #include "SvgHandler.h"
22 #include "core/meta/TrackEditor.h"
23 #include "core/meta/support/MetaUtility.h"
24 #include "core/support/Debug.h"
25 #include "core-impl/collections/aggregate/AggregateCollection.h"
26 
27 #include <QDateTime>
28 #include <QSet>
29 #include <QTimer>
30 
31 namespace Meta
32 {
33 
34 #define FORWARD( call ) { foreach( TrackEditorPtr e, m_editors ) { e->call; } \
35                             if( !m_batchMode ) QTimer::singleShot( 0, m_collection, &Collections::AggregateCollection::slotUpdated ); }
36 
37 class AggregateTrackEditor : public TrackEditor
38 {
39 public:
AggregateTrackEditor(Collections::AggregateCollection * coll,const QList<TrackEditorPtr> & editors)40     AggregateTrackEditor( Collections::AggregateCollection *coll, const QList<TrackEditorPtr> &editors )
41         : TrackEditor()
42         , m_batchMode( false )
43         , m_collection( coll )
44         , m_editors( editors )
45     {}
46 
beginUpdate()47     void beginUpdate() override
48     {
49         m_batchMode = true;
50         foreach( TrackEditorPtr ec, m_editors ) ec->beginUpdate();
51     }
endUpdate()52     void endUpdate() override
53     {
54         foreach( TrackEditorPtr ec, m_editors ) ec->endUpdate();
55         m_batchMode = false;
56         QTimer::singleShot( 0, m_collection, &Collections::AggregateCollection::slotUpdated );
57     }
setComment(const QString & newComment)58     void setComment( const QString &newComment ) override { FORWARD( setComment( newComment ) ) }
setTrackNumber(int newTrackNumber)59     void setTrackNumber( int newTrackNumber ) override { FORWARD( setTrackNumber( newTrackNumber ) ) }
setDiscNumber(int newDiscNumber)60     void setDiscNumber( int newDiscNumber ) override { FORWARD( setDiscNumber( newDiscNumber ) ) }
setBpm(const qreal newBpm)61     void setBpm( const qreal newBpm ) override { FORWARD( setBpm( newBpm ) ) }
setTitle(const QString & newTitle)62     void setTitle( const QString &newTitle ) override { FORWARD( setTitle( newTitle ) ) }
setArtist(const QString & newArtist)63     void setArtist( const QString &newArtist ) override { FORWARD( setArtist( newArtist ) ) }
setAlbum(const QString & newAlbum)64     void setAlbum( const QString &newAlbum ) override { FORWARD( setAlbum( newAlbum ) ) }
setAlbumArtist(const QString & newAlbumArtist)65     void setAlbumArtist( const QString &newAlbumArtist ) override { FORWARD( setAlbumArtist ( newAlbumArtist ) ) }
setGenre(const QString & newGenre)66     void setGenre( const QString &newGenre ) override { FORWARD( setGenre( newGenre ) ) }
setComposer(const QString & newComposer)67     void setComposer( const QString &newComposer ) override { FORWARD( setComposer( newComposer ) ) }
setYear(int newYear)68     void setYear( int newYear ) override { FORWARD( setYear( newYear ) ) }
69 private:
70     bool m_batchMode;
71     Collections::AggregateCollection *m_collection;
72     QList<TrackEditorPtr> m_editors;
73 };
74 
75 #undef FORWARD
76 
AggregateTrack(Collections::AggregateCollection * coll,const TrackPtr & track)77 AggregateTrack::AggregateTrack( Collections::AggregateCollection *coll, const TrackPtr &track )
78         : Track()
79         , Observer()
80         , m_collection( coll )
81         , m_name( track->name() )
82         , m_album( nullptr )
83         , m_artist( nullptr )
84         , m_genre( nullptr )
85         , m_composer( nullptr )
86         , m_year( nullptr )
87 {
88     subscribeTo( track );
89     m_tracks.append( track );
90 
91     if( track->album() )
92         m_album = Meta::AlbumPtr( m_collection->getAlbum( track->album() ) );
93     if( track->artist() )
94         m_artist = Meta::ArtistPtr( m_collection->getArtist( track->artist() ) );
95     if( track->genre() )
96         m_genre = Meta::GenrePtr( m_collection->getGenre( track->genre() ) );
97     if( track->composer() )
98         m_composer = Meta::ComposerPtr( m_collection->getComposer( track->composer() ) );
99     if( track->year() )
100         m_year = Meta::YearPtr( m_collection->getYear( track->year() ) );
101 }
102 
~AggregateTrack()103 AggregateTrack::~AggregateTrack()
104 {
105 }
106 
107 QString
name() const108 AggregateTrack::name() const
109 {
110     return m_name;
111 }
112 
113 QString
prettyName() const114 AggregateTrack::prettyName() const
115 {
116     return m_name;
117 }
118 
119 QString
sortableName() const120 AggregateTrack::sortableName() const
121 {
122     if( !m_tracks.isEmpty() )
123         return m_tracks.first()->sortableName();
124 
125     return m_name;
126 }
127 
128 QUrl
playableUrl() const129 AggregateTrack::playableUrl() const
130 {
131     Meta::TrackPtr bestPlayableTrack;
132     foreach( const Meta::TrackPtr &track, m_tracks )
133     {
134         if( track->isPlayable() )
135         {
136             bool local = track->playableUrl().isLocalFile();
137             if( local )
138             {
139                 bestPlayableTrack = track;
140                 break;
141             }
142             else
143             {
144                 //we might want to add some more sophisticated logic to figure out
145                 //the best remote track to play, but this works for now
146                 bestPlayableTrack = track;
147             }
148         }
149     }
150     if( bestPlayableTrack )
151         return bestPlayableTrack->playableUrl();
152 
153     return QUrl();
154 }
155 
156 QString
prettyUrl() const157 AggregateTrack::prettyUrl() const
158 {
159     if( m_tracks.count() == 1 )
160     {
161         return m_tracks.first()->prettyUrl();
162     }
163     else
164     {
165         return QString();
166     }
167 }
168 
169 QString
uidUrl() const170 AggregateTrack::uidUrl() const
171 {
172     // this is where it gets interesting
173     // a uidUrl for a AggregateTrack probably has to be generated
174     // from the parts of the key in AggregateCollection
175     // need to think about this some more
176     return QString();
177 }
178 
179 QString
notPlayableReason() const180 AggregateTrack::notPlayableReason() const
181 {
182     QStringList reasons;
183     foreach( const Meta::TrackPtr &track, m_tracks )
184     {
185         if( !track->isPlayable() )
186             reasons.append( track->notPlayableReason() );
187         else
188             return QString(); // no reason if at least one playable
189     }
190     return reasons.join( QStringLiteral( ", " ) );
191 }
192 
193 Meta::AlbumPtr
album() const194 AggregateTrack::album() const
195 {
196     return m_album;
197 }
198 
199 Meta::ArtistPtr
artist() const200 AggregateTrack::artist() const
201 {
202     return m_artist;
203 }
204 
205 Meta::ComposerPtr
composer() const206 AggregateTrack::composer() const
207 {
208     return m_composer;
209 }
210 
211 Meta::GenrePtr
genre() const212 AggregateTrack::genre() const
213 {
214     return m_genre;
215 }
216 
217 Meta::YearPtr
year() const218 AggregateTrack::year() const
219 {
220     return m_year;
221 }
222 
223 QString
comment() const224 AggregateTrack::comment() const
225 {
226     //try to return something sensible here...
227     //do not show a comment if the internal tracks disagree about the comment
228     QString comment;
229     if( !m_tracks.isEmpty() )
230         comment = m_tracks.first()->comment();
231 
232     foreach( const Meta::TrackPtr &track, m_tracks )
233     {
234         if( track->comment() != comment )
235         {
236             comment.clear();
237             break;
238         }
239     }
240     return comment;
241 }
242 
243 qreal
bpm() const244 AggregateTrack::bpm() const
245 {
246     //Similar to comment(), try to return something sensible here...
247     //do not show a common bpm value if the internal tracks disagree about the bpm
248     qreal bpm = -1.0;
249     if( !m_tracks.isEmpty() )
250         bpm = m_tracks.first()->bpm();
251 
252     foreach( const Meta::TrackPtr &track, m_tracks )
253     {
254         if( track->bpm() != bpm )
255         {
256             bpm = -1.0;
257             break;
258         }
259     }
260     return bpm;
261 }
262 
263 double
score() const264 AggregateTrack::score() const
265 {
266     //again, multiple ways to implement this method:
267     //return the maximum score, the minimum score, the average
268     //the score of the track with the maximum play count,
269     //or an average weighted by play count. And probably a couple of ways that
270     //I cannot think of right now...
271 
272     //implementing the weighted average here...
273     double weightedSum = 0.0;
274     int totalCount = 0;
275     foreach( const Meta::TrackPtr &track, m_tracks )
276     {
277         ConstStatisticsPtr statistics = track->statistics();
278         totalCount += statistics->playCount();
279         weightedSum += statistics->playCount() * statistics->score();
280     }
281     if( totalCount )
282         return weightedSum / totalCount;
283 
284     return 0.0;
285 }
286 
287 void
setScore(double newScore)288 AggregateTrack::setScore( double newScore )
289 {
290     foreach( Meta::TrackPtr track, m_tracks )
291     {
292         track->statistics()->setScore( newScore );
293     }
294 }
295 
296 int
rating() const297 AggregateTrack::rating() const
298 {
299     //yay, multiple options again. As this has to be defined by the user, let's take
300     //the maximum here.
301     int result = 0;
302     foreach( const Meta::TrackPtr &track, m_tracks )
303     {
304         if( track->statistics()->rating() > result )
305             result = track->statistics()->rating();
306     }
307     return result;
308 }
309 
310 void
setRating(int newRating)311 AggregateTrack::setRating( int newRating )
312 {
313     foreach( Meta::TrackPtr track, m_tracks )
314     {
315         track->statistics()->setRating( newRating );
316     }
317 }
318 
319 QDateTime
firstPlayed() const320 AggregateTrack::firstPlayed() const
321 {
322     QDateTime result;
323     foreach( const Meta::TrackPtr &track, m_tracks )
324     {
325         ConstStatisticsPtr statistics = track->statistics();
326         //use the track's firstPlayed value if it represents an earlier timestamp than
327         //the current result, or use it directly if result has not been set yet
328         //this should result in the earliest timestamp for first play of all internal
329         //tracks being returned
330         if( ( statistics->firstPlayed().isValid() && result.isValid() && statistics->firstPlayed() < result ) ||
331             ( statistics->firstPlayed().isValid() && !result.isValid() ) )
332         {
333             result = statistics->firstPlayed();
334         }
335     }
336     return result;
337 }
338 
339 void
setFirstPlayed(const QDateTime & date)340 AggregateTrack::setFirstPlayed( const QDateTime &date )
341 {
342     foreach( Meta::TrackPtr track, m_tracks )
343     {
344         // only "lower" the first played
345         Meta::StatisticsPtr trackStats = track->statistics();
346         if( !trackStats->firstPlayed().isValid() ||
347             trackStats->firstPlayed() > date )
348         {
349             trackStats->setFirstPlayed( date );
350         }
351     }
352 }
353 
354 QDateTime
lastPlayed() const355 AggregateTrack::lastPlayed() const
356 {
357     QDateTime result;
358     //return the latest timestamp. Easier than firstPlayed because we do not have to
359     //care about 0.
360     //when are we going to perform the refactoring as discussed in Berlin?
361     foreach( const Meta::TrackPtr &track, m_tracks )
362     {
363         if( track->statistics()->lastPlayed() > result )
364         {
365             result = track->statistics()->lastPlayed();
366         }
367     }
368     return result;
369 }
370 
371 void
setLastPlayed(const QDateTime & date)372 AggregateTrack::setLastPlayed(const QDateTime& date)
373 {
374     foreach( Meta::TrackPtr track, m_tracks )
375     {
376         // only "raise" the last played
377         Meta::StatisticsPtr trackStats = track->statistics();
378         if( !trackStats->lastPlayed().isValid() ||
379             trackStats->lastPlayed() < date )
380         {
381             trackStats->setLastPlayed( date );
382         }
383     }
384 }
385 
386 int
playCount() const387 AggregateTrack::playCount() const
388 {
389     // show the maximum of all play counts.
390     int result = 0;
391     foreach( const Meta::TrackPtr &track, m_tracks )
392     {
393         if( track->statistics()->playCount() > result )
394         {
395             result = track->statistics()->playCount();
396         }
397     }
398     return result;
399 }
400 
401 void
setPlayCount(int newPlayCount)402 AggregateTrack::setPlayCount( int newPlayCount )
403 {
404     Q_UNUSED( newPlayCount )
405     // no safe thing to do here. Notice we override finishedPlaying()
406 }
407 
408 void
finishedPlaying(double playedFraction)409 AggregateTrack::finishedPlaying( double playedFraction )
410 {
411     foreach( Meta::TrackPtr track, m_tracks )
412     {
413         track->finishedPlaying( playedFraction );
414     }
415 }
416 
417 qint64
length() const418 AggregateTrack::length() const
419 {
420     foreach( const Meta::TrackPtr &track, m_tracks )
421     {
422         if( track->length() )
423             return track->length();
424     }
425     return 0;
426 }
427 
428 int
filesize() const429 AggregateTrack::filesize() const
430 {
431     foreach( const Meta::TrackPtr &track, m_tracks )
432     {
433         if( track->filesize() )
434         {
435             return track->filesize();
436         }
437     }
438     return 0;
439 }
440 
441 int
sampleRate() const442 AggregateTrack::sampleRate() const
443 {
444     foreach( const Meta::TrackPtr &track, m_tracks )
445     {
446         if( track->sampleRate() )
447             return track->sampleRate();
448     }
449     return 0;
450 }
451 
452 int
bitrate() const453 AggregateTrack::bitrate() const
454 {
455     foreach( const Meta::TrackPtr &track, m_tracks )
456     {
457         if( track->bitrate() )
458             return track->bitrate();
459     }
460     return 0;
461 }
462 
463 QDateTime
createDate() const464 AggregateTrack::createDate() const
465 {
466     QDateTime result;
467     foreach( const Meta::TrackPtr &track, m_tracks )
468     {
469         //use the track's firstPlayed value if it represents an earlier timestamp than
470         //the current result, or use it directly if result has not been set yet
471         //this should result in the earliest timestamp for first play of all internal
472         //tracks being returned
473         if( ( track->createDate().isValid() && result.isValid() && track->createDate() < result ) ||
474             ( track->createDate().isValid() && !result.isValid() ) )
475         {
476             result = track->createDate();
477         }
478     }
479     return result;
480 }
481 
482 int
trackNumber() const483 AggregateTrack::trackNumber() const
484 {
485     int result = 0;
486     foreach( const Meta::TrackPtr &track, m_tracks )
487     {
488         if( ( !result && track->trackNumber() ) || ( result && result == track->trackNumber() ) )
489         {
490             result = track->trackNumber();
491         }
492         else if( result && result != track->trackNumber() )
493         {
494             //tracks disagree about the tracknumber
495             return 0;
496         }
497     }
498     return result;
499 }
500 
501 int
discNumber() const502 AggregateTrack::discNumber() const
503 {
504     int result = 0;
505     foreach( const Meta::TrackPtr &track, m_tracks )
506     {
507         if( ( !result && track->discNumber() ) || ( result && result == track->discNumber() ) )
508         {
509             result = track->discNumber();
510         }
511         else if( result && result != track->discNumber() )
512         {
513             //tracks disagree about the disc number
514             return 0;
515         }
516     }
517     return result;
518 }
519 
520 QString
type() const521 AggregateTrack::type() const
522 {
523     if( m_tracks.size() == 1 )
524     {
525         return m_tracks.first()->type();
526     }
527     else
528     {
529         //TODO: figure something out
530         return QString();
531     }
532 }
533 
534 Collections::Collection*
collection() const535 AggregateTrack::collection() const
536 {
537     return m_collection;
538 }
539 
540 bool
hasCapabilityInterface(Capabilities::Capability::Type type) const541 AggregateTrack::hasCapabilityInterface( Capabilities::Capability::Type type ) const
542 {
543     if( m_tracks.count() == 1 )
544         // if we aggregate only one track, simply return the tracks capability directly
545         return m_tracks.first()->hasCapabilityInterface( type );
546     else
547         return false;
548 }
549 
550 Capabilities::Capability*
createCapabilityInterface(Capabilities::Capability::Type type)551 AggregateTrack::createCapabilityInterface( Capabilities::Capability::Type type )
552 {
553     if( m_tracks.count() == 1 )
554         return m_tracks.first()->createCapabilityInterface( type );
555     else
556         return nullptr;
557 }
558 
559 TrackEditorPtr
editor()560 AggregateTrack::editor()
561 {
562     if( m_tracks.count() == 1 )
563         return m_tracks.first()->editor();
564 
565     QList<Meta::TrackEditorPtr> editors;
566     foreach( Meta::TrackPtr track, m_tracks )
567     {
568         Meta::TrackEditorPtr ec = track->editor();
569         if( ec )
570             editors << ec;
571         else
572             return TrackEditorPtr();
573     }
574     return TrackEditorPtr( new AggregateTrackEditor( m_collection, editors ) );
575 }
576 
577 void
addLabel(const QString & label)578 AggregateTrack::addLabel( const QString &label )
579 {
580     foreach( Meta::TrackPtr track, m_tracks )
581     {
582         track->addLabel( label );
583     }
584 }
585 
586 void
addLabel(const Meta::LabelPtr & label)587 AggregateTrack::addLabel( const Meta::LabelPtr &label )
588 {
589     foreach( Meta::TrackPtr track, m_tracks )
590     {
591         track->addLabel( label );
592     }
593 }
594 
595 void
removeLabel(const Meta::LabelPtr & label)596 AggregateTrack::removeLabel( const Meta::LabelPtr &label )
597 {
598     foreach( Meta::TrackPtr track, m_tracks )
599     {
600         track->removeLabel( label );
601     }
602 }
603 
604 Meta::LabelList
labels() const605 AggregateTrack::labels() const
606 {
607     QSet<AggregateLabel *> aggregateLabels;
608     foreach( const Meta::TrackPtr &track, m_tracks )
609     {
610         foreach( Meta::LabelPtr label, track->labels() )
611         {
612             aggregateLabels.insert( m_collection->getLabel( label ) );
613         }
614     }
615     Meta::LabelList result;
616     foreach( AggregateLabel *label, aggregateLabels )
617     {
618         result << Meta::LabelPtr( label );
619     }
620     return result;
621 }
622 
623 StatisticsPtr
statistics()624 AggregateTrack::statistics()
625 {
626     return StatisticsPtr( this );
627 }
628 
629 void
add(const Meta::TrackPtr & track)630 AggregateTrack::add( const Meta::TrackPtr &track )
631 {
632     if( !track || m_tracks.contains( track ) )
633         return;
634 
635     m_tracks.append( track );
636     subscribeTo( track );
637 
638     notifyObservers();
639 }
640 
641 void
metadataChanged(const TrackPtr & track)642 AggregateTrack::metadataChanged(const TrackPtr &track )
643 {
644     if( !track )
645         return;
646 
647     if( !m_tracks.contains( track ) )
648     {
649         //why are we subscribed?
650         unsubscribeFrom( track );
651         return;
652     }
653 
654     const TrackKey myKey( Meta::TrackPtr( this ) );
655     const TrackKey otherKey( track );
656     if( myKey == otherKey )
657     {
658         //no key relevant metadata did change
659         notifyObservers();
660         return;
661     }
662     else
663     {
664         if( m_tracks.size() == 1 )
665         {
666             if( m_collection->hasTrack( otherKey ) )
667             {
668                 unsubscribeFrom( track );
669                 m_collection->getTrack( track );
670                 m_tracks.removeAll( track );
671                 m_collection->removeTrack( myKey );
672                 return; //do not notify observers, this track is not valid anymore!
673             }
674             else
675             {
676                 m_name = track->name();
677                 if( track->album() )
678                      m_album = Meta::AlbumPtr( m_collection->getAlbum( track->album() ) );
679                 if( track->artist() )
680                     m_artist = Meta::ArtistPtr( m_collection->getArtist( track->artist() ) );
681                 if( track->genre() )
682                     m_genre = Meta::GenrePtr( m_collection->getGenre( track->genre() ) );
683                 if( track->composer() )
684                     m_composer = Meta::ComposerPtr( m_collection->getComposer( track->composer() ) );
685                 if( track->year() )
686                     m_year = Meta::YearPtr( m_collection->getYear( track->year() ) );
687 
688                 m_collection->setTrack( this );
689                 m_collection->removeTrack( myKey );
690             }
691         }
692         else
693         {
694             unsubscribeFrom( track );
695             m_collection->getTrack( track );
696             m_tracks.removeAll( track );
697         }
698         notifyObservers();
699     }
700 }
701 
AggregateAlbum(Collections::AggregateCollection * coll,Meta::AlbumPtr album)702 AggregateAlbum::AggregateAlbum( Collections::AggregateCollection *coll, Meta::AlbumPtr album )
703         : Meta::Album()
704         , Meta::Observer()
705         , m_collection( coll )
706         , m_name( album->name() )
707 {
708     m_albums.append( album );
709     if( album->hasAlbumArtist() )
710         m_albumArtist = Meta::ArtistPtr( m_collection->getArtist( album->albumArtist() ) );
711 }
712 
~AggregateAlbum()713 AggregateAlbum::~AggregateAlbum()
714 {
715 }
716 
717 QString
name() const718 AggregateAlbum::name() const
719 {
720     return m_name;
721 }
722 
723 QString
prettyName() const724 AggregateAlbum::prettyName() const
725 {
726     return m_name;
727 }
728 
729 QString
sortableName() const730 AggregateAlbum::sortableName() const
731 {
732     if( !m_albums.isEmpty() )
733         return m_albums.first()->sortableName();
734 
735     return m_name;
736 }
737 
738 Meta::TrackList
tracks()739 AggregateAlbum::tracks()
740 {
741     QSet<AggregateTrack*> tracks;
742     foreach( Meta::AlbumPtr album, m_albums )
743     {
744         Meta::TrackList tmp = album->tracks();
745         foreach( const Meta::TrackPtr &track, tmp )
746         {
747             tracks.insert( m_collection->getTrack( track ) );
748         }
749     }
750 
751     Meta::TrackList result;
752     foreach( AggregateTrack *track, tracks )
753     {
754         result.append( Meta::TrackPtr( track ) );
755     }
756     return result;
757 }
758 
759 Meta::ArtistPtr
albumArtist() const760 AggregateAlbum::albumArtist() const
761 {
762     return m_albumArtist;
763 }
764 
765 bool
isCompilation() const766 AggregateAlbum::isCompilation() const
767 {
768     return m_albumArtist.isNull();
769 }
770 
771 bool
hasAlbumArtist() const772 AggregateAlbum::hasAlbumArtist() const
773 {
774     return !m_albumArtist.isNull();
775 }
776 
777 bool
hasCapabilityInterface(Capabilities::Capability::Type type) const778 AggregateAlbum::hasCapabilityInterface(Capabilities::Capability::Type type ) const
779 {
780 
781     if( m_albums.count() == 1 )
782     {
783         return m_albums.first()->hasCapabilityInterface( type );
784     }
785     else
786     {
787         return false;
788     }
789 }
790 
791 Capabilities::Capability*
createCapabilityInterface(Capabilities::Capability::Type type)792 AggregateAlbum::createCapabilityInterface( Capabilities::Capability::Type type )
793 {
794     if( m_albums.count() == 1 )
795     {
796         return m_albums.first()->createCapabilityInterface( type );
797     }
798     else
799     {
800         return nullptr;
801     }
802 }
803 
804 void
add(const Meta::AlbumPtr & album)805 AggregateAlbum::add( const Meta::AlbumPtr &album )
806 {
807     if( !album || m_albums.contains( album ) )
808         return;
809 
810     m_albums.append( album );
811     subscribeTo( album );
812 
813     notifyObservers();
814 }
815 
816 bool
hasImage(int size) const817 AggregateAlbum::hasImage( int size ) const
818 {
819     foreach( const Meta::AlbumPtr &album, m_albums )
820     {
821         if( album->hasImage( size ) )
822             return true;
823     }
824     return false;
825 }
826 
827 QImage
image(int size) const828 AggregateAlbum::image( int size ) const
829 {
830     foreach( Meta::AlbumPtr album, m_albums )
831     {
832         if( album->hasImage( size ) )
833         {
834             return album->image( size );
835         }
836     }
837     return Meta::Album::image( size );
838 }
839 
840 QUrl
imageLocation(int size)841 AggregateAlbum::imageLocation( int size )
842 {
843     foreach( Meta::AlbumPtr album, m_albums )
844     {
845         if( album->hasImage( size ) )
846         {
847             QUrl url = album->imageLocation( size );
848             if( url.isValid() )
849             {
850                 return url;
851             }
852         }
853     }
854     return QUrl();
855 }
856 
857 QPixmap
imageWithBorder(int size,int borderWidth)858 AggregateAlbum::imageWithBorder( int size, int borderWidth )
859 {
860     foreach( Meta::AlbumPtr album, m_albums )
861     {
862         if( album->hasImage( size ) )
863         {
864             return The::svgHandler()->imageWithBorder( album, size, borderWidth );
865         }
866     }
867     return QPixmap();
868 }
869 
870 bool
canUpdateImage() const871 AggregateAlbum::canUpdateImage() const
872 {
873     if( m_albums.isEmpty() )
874         return false;
875 
876     foreach( const Meta::AlbumPtr &album, m_albums )
877     {
878         //we can only update the image for all albums at the same time
879         if( !album->canUpdateImage() )
880             return false;
881     }
882     return true;
883 }
884 
885 void
setImage(const QImage & image)886 AggregateAlbum::setImage( const QImage &image )
887 {
888     foreach( Meta::AlbumPtr album, m_albums )
889     {
890         album->setImage( image );
891     }
892 }
893 
894 void
removeImage()895 AggregateAlbum::removeImage()
896 {
897     foreach( Meta::AlbumPtr album, m_albums )
898     {
899         album->removeImage();
900     }
901 }
902 
903 void
setSuppressImageAutoFetch(bool suppress)904 AggregateAlbum::setSuppressImageAutoFetch( bool suppress )
905 {
906     foreach( Meta::AlbumPtr album, m_albums )
907     {
908         album->setSuppressImageAutoFetch( suppress );
909     }
910 }
911 
912 bool
suppressImageAutoFetch() const913 AggregateAlbum::suppressImageAutoFetch() const
914 {
915     foreach( const Meta::AlbumPtr &album, m_albums )
916     {
917         if( !album->suppressImageAutoFetch() )
918             return false;
919     }
920     return true;
921 }
922 
923 void
metadataChanged(const AlbumPtr & album)924 AggregateAlbum::metadataChanged(const AlbumPtr &album )
925 {
926     if( !album || !m_albums.contains( album ) )
927         return;
928 
929     if( album->name() != m_name ||
930         hasAlbumArtist() != album->hasAlbumArtist() ||
931         ( hasAlbumArtist() && m_albumArtist->name() != album->albumArtist()->name() ) )
932     {
933         if( m_albums.count() > 1 )
934         {
935             m_collection->getAlbum( album );
936             unsubscribeFrom( album );
937             m_albums.removeAll( album );
938         }
939         else
940         {
941             Meta::ArtistPtr albumartist;
942             if( album->hasAlbumArtist() )
943                  albumartist = Meta::ArtistPtr( m_collection->getArtist( album->albumArtist() ) );
944 
945             QString artistname = m_albumArtist ? m_albumArtist->name() : QString();
946             m_collection->removeAlbum( m_name, artistname );
947             m_name = album->name();
948             m_albumArtist = albumartist;
949             m_collection->setAlbum( this );
950         }
951     }
952 
953     notifyObservers();
954 }
955 
AggregateArtist(Collections::AggregateCollection * coll,const Meta::ArtistPtr & artist)956 AggregateArtist::AggregateArtist( Collections::AggregateCollection *coll, const Meta::ArtistPtr &artist )
957         : Meta::Artist()
958         , Meta::Observer()
959         , m_collection( coll )
960         , m_name( artist->name() )
961 {
962     m_artists.append( artist );
963     subscribeTo( artist );
964 }
965 
~AggregateArtist()966 AggregateArtist::~AggregateArtist()
967 {
968 }
969 
970 QString
name() const971 AggregateArtist::name() const
972 {
973     return m_name;
974 }
975 
976 QString
prettyName() const977 AggregateArtist::prettyName() const
978 {
979     return m_name;
980 }
981 
982 QString
sortableName() const983 AggregateArtist::sortableName() const
984 {
985     if( !m_artists.isEmpty() )
986         return m_artists.first()->sortableName();
987 
988     return m_name;
989 }
990 
991 Meta::TrackList
tracks()992 AggregateArtist::tracks()
993 {
994     QSet<AggregateTrack*> tracks;
995     foreach( Meta::ArtistPtr artist, m_artists )
996     {
997         Meta::TrackList tmp = artist->tracks();
998         foreach( const Meta::TrackPtr &track, tmp )
999         {
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
hasCapabilityInterface(Capabilities::Capability::Type type) const1013 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*
createCapabilityInterface(Capabilities::Capability::Type type)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
add(const Meta::ArtistPtr & artist)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
metadataChanged(const ArtistPtr & artist)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 
AggregateGenre(Collections::AggregateCollection * coll,const Meta::GenrePtr & genre)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 
~AggregateGenre()1092 AggregateGenre::~AggregateGenre()
1093 {
1094 }
1095 
1096 QString
name() const1097 AggregateGenre::name() const
1098 {
1099     return m_name;
1100 }
1101 
1102 QString
prettyName() const1103 AggregateGenre::prettyName() const
1104 {
1105     return m_name;
1106 }
1107 
1108 QString
sortableName() const1109 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
tracks()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
hasCapabilityInterface(Capabilities::Capability::Type type) const1139 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*
createCapabilityInterface(Capabilities::Capability::Type type)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
add(const Meta::GenrePtr & genre)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
metadataChanged(const Meta::GenrePtr & genre)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 
AggregateComposer(Collections::AggregateCollection * coll,const Meta::ComposerPtr & composer)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 
~AggregateComposer()1212 AggregateComposer::~AggregateComposer()
1213 {
1214 }
1215 
1216 QString
name() const1217 AggregateComposer::name() const
1218 {
1219     return m_name;
1220 }
1221 
1222 QString
prettyName() const1223 AggregateComposer::prettyName() const
1224 {
1225     return m_name;
1226 }
1227 
1228 QString
sortableName() const1229 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
tracks()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
hasCapabilityInterface(Capabilities::Capability::Type type) const1259 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*
createCapabilityInterface(Capabilities::Capability::Type type)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
add(const Meta::ComposerPtr & composer)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
metadataChanged(const ComposerPtr & composer)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 
AggreagateYear(Collections::AggregateCollection * coll,const Meta::YearPtr & year)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 
~AggreagateYear()1332 AggreagateYear::~AggreagateYear()
1333 {
1334     //nothing to do
1335 }
1336 
1337 QString
name() const1338 AggreagateYear::name() const
1339 {
1340     return m_name;
1341 }
1342 
1343 QString
prettyName() const1344 AggreagateYear::prettyName() const
1345 {
1346     return m_name;
1347 }
1348 
1349 QString
sortableName() const1350 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
tracks()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
hasCapabilityInterface(Capabilities::Capability::Type type) const1380 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*
createCapabilityInterface(Capabilities::Capability::Type type)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
add(const Meta::YearPtr & year)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
metadataChanged(const Meta::YearPtr & year)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 
AggregateLabel(Collections::AggregateCollection * coll,const Meta::LabelPtr & label)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 
~AggregateLabel()1470 AggregateLabel::~AggregateLabel()
1471 {
1472     //nothing to do
1473 }
1474 
1475 QString
name() const1476 AggregateLabel::name() const
1477 {
1478     return m_name;
1479 }
1480 
1481 QString
prettyName() const1482 AggregateLabel::prettyName() const
1483 {
1484     return m_name;
1485 }
1486 
1487 QString
sortableName() const1488 AggregateLabel::sortableName() const
1489 {
1490     if( !m_labels.isEmpty() )
1491         return m_labels.first()->sortableName();
1492 
1493     return m_name;
1494 }
1495 
1496 bool
hasCapabilityInterface(Capabilities::Capability::Type type) const1497 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*
createCapabilityInterface(Capabilities::Capability::Type type)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
add(const Meta::LabelPtr & label)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
1533