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