1 /****************************************************************************************
2  * Copyright (c) 2009 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 "AggregateCollection"
18 
19 #include "AggregateCollection.h"
20 
21 #include "core/support/Debug.h"
22 #include "core-impl/collections/aggregate/AggregateMeta.h"
23 #include "core-impl/collections/aggregate/AggregateQueryMaker.h"
24 #include "core-impl/collections/support/CollectionManager.h"
25 
26 #include <QIcon>
27 
28 #include <QReadLocker>
29 #include <QTimer>
30 
31 using namespace Collections;
32 
AggregateCollection()33 AggregateCollection::AggregateCollection()
34         : Collections::Collection()
35 {
36     QTimer *timer = new QTimer( this );
37     timer->setSingleShot( false );
38     timer->setInterval( 60000 ); //clean up every 60 seconds
39     connect( timer, &QTimer::timeout, this, &AggregateCollection::emptyCache );
40     timer->start();
41 }
42 
~AggregateCollection()43 AggregateCollection::~AggregateCollection()
44 {
45 }
46 
47 QString
prettyName() const48 AggregateCollection::prettyName() const
49 {
50     return i18nc( "Name of the virtual collection that merges tracks from all collections",
51                   "Aggregate Collection" );
52 }
53 
54 QIcon
icon() const55 AggregateCollection::icon() const
56 {
57     return QIcon::fromTheme(QStringLiteral("drive-harddisk"));
58 }
59 
60 bool
possiblyContainsTrack(const QUrl & url) const61 AggregateCollection::possiblyContainsTrack( const QUrl &url ) const
62 {
63     foreach( Collections::Collection *collection, m_idCollectionMap )
64     {
65         if( collection->possiblyContainsTrack( url ) )
66             return true;
67     }
68     return false;
69 }
70 
71 Meta::TrackPtr
trackForUrl(const QUrl & url)72 AggregateCollection::trackForUrl( const QUrl &url )
73 {
74     foreach( Collections::Collection *collection, m_idCollectionMap )
75     {
76         Meta::TrackPtr track = collection->trackForUrl( url );
77         if( track )
78         {
79             //theoretically we should now query the other collections for the same track
80             //not sure how to do that yet though...
81             return Meta::TrackPtr( getTrack( track ) );
82         }
83     }
84     return Meta::TrackPtr();
85 }
86 
87 QueryMaker*
queryMaker()88 AggregateCollection::queryMaker()
89 {
90     QList<QueryMaker*> list;
91     foreach( Collections::Collection *collection, m_idCollectionMap )
92     {
93         list.append( collection->queryMaker() );
94     }
95     return new Collections::AggregateQueryMaker( this, list );
96 }
97 
98 QString
collectionId() const99 AggregateCollection::collectionId() const
100 {
101     // do we need more than one AggregateCollection?
102     return QStringLiteral( "AggregateCollection" );
103 }
104 
105 void
addCollection(Collections::Collection * collection,CollectionManager::CollectionStatus status)106 AggregateCollection::addCollection( Collections::Collection *collection, CollectionManager::CollectionStatus status )
107 {
108     if( !collection )
109         return;
110 
111     if( !( status & CollectionManager::CollectionViewable ) )
112         return;
113 
114     m_idCollectionMap.insert( collection->collectionId(), collection );
115     //TODO
116     Q_EMIT updated();
117 }
118 
119 void
removeCollectionById(const QString & collectionId)120 AggregateCollection::removeCollectionById( const QString &collectionId )
121 {
122     m_idCollectionMap.remove( collectionId );
123     Q_EMIT updated();
124 }
125 
126 void
removeCollection(Collections::Collection * collection)127 AggregateCollection::removeCollection( Collections::Collection *collection )
128 {
129     m_idCollectionMap.remove( collection->collectionId() );
130     Q_EMIT updated();
131 }
132 
133 void
slotUpdated()134 AggregateCollection::slotUpdated()
135 {
136     //TODO
137     Q_EMIT updated();
138 }
139 
140 void
removeYear(const QString & name)141 AggregateCollection::removeYear( const QString &name )
142 {
143     m_yearLock.lockForWrite();
144     m_yearMap.remove( name );
145     m_yearLock.unlock();
146 }
147 
148 Meta::AggreagateYear*
getYear(Meta::YearPtr year)149 AggregateCollection::getYear( Meta::YearPtr year )
150 {
151     m_yearLock.lockForRead();
152     if( m_yearMap.contains( year->name() ) )
153     {
154         AmarokSharedPointer<Meta::AggreagateYear> aggregateYear = m_yearMap.value( year->name() );
155         aggregateYear->add( year );
156         m_yearLock.unlock();
157         return aggregateYear.data();
158     }
159     else
160     {
161         m_yearLock.unlock();
162         m_yearLock.lockForWrite();
163         //we might create two year instances with the same name here,
164         //which would show some weird behaviour in other places
165         Meta::AggreagateYear *aggregateYear = new Meta::AggreagateYear( this, year );
166         m_yearMap.insert( year->name(), AmarokSharedPointer<Meta::AggreagateYear>( aggregateYear ) );
167         m_yearLock.unlock();
168         return aggregateYear;
169     }
170 }
171 
172 void
setYear(Meta::AggreagateYear * year)173 AggregateCollection::setYear( Meta::AggreagateYear *year )
174 {
175     m_yearLock.lockForWrite();
176     m_yearMap.insert( year->name(), AmarokSharedPointer<Meta::AggreagateYear>( year ) );
177     m_yearLock.unlock();
178 }
179 
180 bool
hasYear(const QString & name)181 AggregateCollection::hasYear( const QString &name )
182 {
183     QReadLocker locker( &m_yearLock );
184     return m_yearMap.contains( name );
185 }
186 
187 void
removeGenre(const QString & name)188 AggregateCollection::removeGenre( const QString &name )
189 {
190     m_genreLock.lockForWrite();
191     m_genreMap.remove( name );
192     m_genreLock.unlock();
193 }
194 
195 Meta::AggregateGenre*
getGenre(Meta::GenrePtr genre)196 AggregateCollection::getGenre( Meta::GenrePtr genre )
197 {
198     m_genreLock.lockForRead();
199     if( m_genreMap.contains( genre->name() ) )
200     {
201         AmarokSharedPointer<Meta::AggregateGenre> aggregateGenre = m_genreMap.value( genre->name() );
202         aggregateGenre->add( genre );
203         m_genreLock.unlock();
204         return aggregateGenre.data();
205     }
206     else
207     {
208         m_genreLock.unlock();
209         m_genreLock.lockForWrite();
210         //we might create two instances with the same name here,
211         //which would show some weird behaviour in other places
212         Meta::AggregateGenre *aggregateGenre = new Meta::AggregateGenre( this, genre );
213         m_genreMap.insert( genre->name(), AmarokSharedPointer<Meta::AggregateGenre>( aggregateGenre ) );
214         m_genreLock.unlock();
215         return aggregateGenre;
216     }
217 }
218 
219 void
setGenre(Meta::AggregateGenre * genre)220 AggregateCollection::setGenre( Meta::AggregateGenre *genre )
221 {
222     m_genreLock.lockForWrite();
223     m_genreMap.insert( genre->name(), AmarokSharedPointer<Meta::AggregateGenre>( genre ) );
224     m_genreLock.unlock();
225 }
226 
227 bool
hasGenre(const QString & genre)228 AggregateCollection::hasGenre( const QString &genre )
229 {
230     QReadLocker locker( &m_genreLock );
231     return m_genreMap.contains( genre );
232 }
233 
234 void
removeComposer(const QString & name)235 AggregateCollection::removeComposer( const QString &name )
236 {
237     m_composerLock.lockForWrite();
238     m_composerMap.remove( name );
239     m_composerLock.unlock();
240 }
241 
242 Meta::AggregateComposer*
getComposer(Meta::ComposerPtr composer)243 AggregateCollection::getComposer( Meta::ComposerPtr composer )
244 {
245     m_composerLock.lockForRead();
246     if( m_composerMap.contains( composer->name() ) )
247     {
248         AmarokSharedPointer<Meta::AggregateComposer> aggregateComposer = m_composerMap.value( composer->name() );
249         aggregateComposer->add( composer );
250         m_composerLock.unlock();
251         return aggregateComposer.data();
252     }
253     else
254     {
255         m_composerLock.unlock();
256         m_composerLock.lockForWrite();
257         //we might create two instances with the same name here,
258         //which would show some weird behaviour in other places
259         Meta::AggregateComposer *aggregateComposer = new Meta::AggregateComposer( this, composer );
260         m_composerMap.insert( composer->name(), AmarokSharedPointer<Meta::AggregateComposer>( aggregateComposer ) );
261         m_composerLock.unlock();
262         return aggregateComposer;
263     }
264 }
265 
266 void
setComposer(Meta::AggregateComposer * composer)267 AggregateCollection::setComposer( Meta::AggregateComposer *composer )
268 {
269     m_composerLock.lockForWrite();
270     m_composerMap.insert( composer->name(), AmarokSharedPointer<Meta::AggregateComposer>( composer ) );
271     m_composerLock.unlock();
272 }
273 
274 bool
hasComposer(const QString & name)275 AggregateCollection::hasComposer( const QString &name )
276 {
277     QReadLocker locker( &m_composerLock );
278     return m_composerMap.contains( name );
279 }
280 
281 void
removeArtist(const QString & name)282 AggregateCollection::removeArtist( const QString &name )
283 {
284     m_artistLock.lockForWrite();
285     m_artistMap.remove( name );
286     m_artistLock.unlock();
287 }
288 
289 Meta::AggregateArtist*
getArtist(Meta::ArtistPtr artist)290 AggregateCollection::getArtist( Meta::ArtistPtr artist )
291 {
292     m_artistLock.lockForRead();
293     if( m_artistMap.contains( artist->name() ) )
294     {
295         AmarokSharedPointer<Meta::AggregateArtist> aggregateArtist = m_artistMap.value( artist->name() );
296         aggregateArtist->add( artist );
297         m_artistLock.unlock();
298         return aggregateArtist.data();
299     }
300     else
301     {
302         m_artistLock.unlock();
303         m_artistLock.lockForWrite();
304         //we might create two instances with the same name here,
305         //which would show some weird behaviour in other places
306         Meta::AggregateArtist *aggregateArtist = new Meta::AggregateArtist( this, artist );
307         m_artistMap.insert( artist->name(), AmarokSharedPointer<Meta::AggregateArtist>( aggregateArtist ) );
308         m_artistLock.unlock();
309         return aggregateArtist;
310     }
311 }
312 
313 void
setArtist(Meta::AggregateArtist * artist)314 AggregateCollection::setArtist( Meta::AggregateArtist *artist )
315 {
316     m_artistLock.lockForWrite();
317     m_artistMap.insert( artist->name(), AmarokSharedPointer<Meta::AggregateArtist>( artist ) );
318     m_artistLock.unlock();
319 }
320 
321 bool
hasArtist(const QString & artist)322 AggregateCollection::hasArtist( const QString &artist )
323 {
324     QReadLocker locker( &m_artistLock );
325     return m_artistMap.contains( artist );
326 }
327 
328 void
removeAlbum(const QString & album,const QString & albumartist)329 AggregateCollection::removeAlbum( const QString &album, const QString &albumartist )
330 {
331     Meta::AlbumKey key( album, albumartist );
332     m_albumLock.lockForWrite();
333     m_albumMap.remove( key );
334     m_albumLock.unlock();
335 }
336 
337 Meta::AggregateAlbum*
getAlbum(const Meta::AlbumPtr & album)338 AggregateCollection::getAlbum( const Meta::AlbumPtr &album )
339 {
340     Meta::AlbumKey key( album );
341     m_albumLock.lockForRead();
342     if( m_albumMap.contains( key ) )
343     {
344         AmarokSharedPointer<Meta::AggregateAlbum> aggregateAlbum = m_albumMap.value( key );
345         aggregateAlbum->add( album );
346         m_albumLock.unlock();
347         return aggregateAlbum.data();
348     }
349     else
350     {
351         m_albumLock.unlock();
352         m_albumLock.lockForWrite();
353         //we might create two instances with the same name here,
354         //which would show some weird behaviour in other places
355         Meta::AggregateAlbum *aggregateAlbum = new Meta::AggregateAlbum( this, album );
356         m_albumMap.insert( key, AmarokSharedPointer<Meta::AggregateAlbum>( aggregateAlbum ) );
357         m_albumLock.unlock();
358         return aggregateAlbum;
359     }
360 }
361 
362 void
setAlbum(Meta::AggregateAlbum * album)363 AggregateCollection::setAlbum( Meta::AggregateAlbum *album )
364 {
365     m_albumLock.lockForWrite();
366     m_albumMap.insert( Meta::AlbumKey( Meta::AlbumPtr( album ) ),
367                        AmarokSharedPointer<Meta::AggregateAlbum>( album ) );
368     m_albumLock.unlock();
369 }
370 
371 bool
hasAlbum(const QString & album,const QString & albumArtist)372 AggregateCollection::hasAlbum( const QString &album, const QString &albumArtist )
373 {
374     QReadLocker locker( &m_albumLock );
375     return m_albumMap.contains( Meta::AlbumKey( album, albumArtist ) );
376 }
377 
378 void
removeTrack(const Meta::TrackKey & key)379 AggregateCollection::removeTrack( const Meta::TrackKey &key )
380 {
381     m_trackLock.lockForWrite();
382     m_trackMap.remove( key );
383     m_trackLock.unlock();
384 }
385 
386 Meta::AggregateTrack*
getTrack(const Meta::TrackPtr & track)387 AggregateCollection::getTrack( const Meta::TrackPtr &track )
388 {
389     const Meta::TrackKey key( track );
390     m_trackLock.lockForRead();
391     if( m_trackMap.contains( key ) )
392     {
393         AmarokSharedPointer<Meta::AggregateTrack> aggregateTrack = m_trackMap.value( key );
394         aggregateTrack->add( track );
395         m_trackLock.unlock();
396         return aggregateTrack.data();
397     }
398     else
399     {
400         m_trackLock.unlock();
401         m_trackLock.lockForWrite();
402         //we might create two instances with the same name here,
403         //which would show some weird behaviour in other places
404         Meta::AggregateTrack *aggregateTrack = new Meta::AggregateTrack( this, track );
405         m_trackMap.insert( key, AmarokSharedPointer<Meta::AggregateTrack>( aggregateTrack ) );
406         m_trackLock.unlock();
407         return aggregateTrack;
408     }
409 }
410 
411 void
setTrack(Meta::AggregateTrack * track)412 AggregateCollection::setTrack( Meta::AggregateTrack *track )
413 {
414     Meta::TrackPtr ptr( track );
415     const Meta::TrackKey key( ptr );
416     m_trackLock.lockForWrite();
417     m_trackMap.insert( key, AmarokSharedPointer<Meta::AggregateTrack>( track ) );
418     m_trackLock.unlock();
419 }
420 
421 bool
hasTrack(const Meta::TrackKey & key)422 AggregateCollection::hasTrack( const Meta::TrackKey &key )
423 {
424     QReadLocker locker( &m_trackLock );
425     return m_trackMap.contains( key );
426 }
427 
428 bool
hasLabel(const QString & name)429 AggregateCollection::hasLabel( const QString &name )
430 {
431     QReadLocker locker( &m_labelLock );
432     return m_labelMap.contains( name );
433 }
434 
435 void
removeLabel(const QString & name)436 AggregateCollection::removeLabel( const QString &name )
437 {
438     QWriteLocker locker( &m_labelLock );
439     m_labelMap.remove( name );
440 }
441 
442 Meta::AggregateLabel*
getLabel(Meta::LabelPtr label)443 AggregateCollection::getLabel( Meta::LabelPtr label )
444 {
445     m_labelLock.lockForRead();
446     if( m_labelMap.contains( label->name() ) )
447     {
448         AmarokSharedPointer<Meta::AggregateLabel> aggregateLabel = m_labelMap.value( label->name() );
449         aggregateLabel->add( label );
450         m_labelLock.unlock();
451         return aggregateLabel.data();
452     }
453     else
454     {
455         m_labelLock.unlock();
456         m_labelLock.lockForWrite();
457         //we might create two year instances with the same name here,
458         //which would show some weird behaviour in other places
459         Meta::AggregateLabel *aggregateLabel = new Meta::AggregateLabel( this, label );
460         m_labelMap.insert( label->name(), AmarokSharedPointer<Meta::AggregateLabel>( aggregateLabel ) );
461         m_labelLock.unlock();
462         return aggregateLabel;
463     }
464 }
465 
466 void
setLabel(Meta::AggregateLabel * label)467 AggregateCollection::setLabel( Meta::AggregateLabel *label )
468 {
469     QWriteLocker locker( &m_labelLock );
470     m_labelMap.insert( label->name(), AmarokSharedPointer<Meta::AggregateLabel>( label ) );
471 }
472 
473 void
emptyCache()474 AggregateCollection::emptyCache()
475 {
476     bool hasTrack, hasAlbum, hasArtist, hasYear, hasGenre, hasComposer, hasLabel;
477     hasTrack = hasAlbum = hasArtist = hasYear = hasGenre = hasComposer = hasLabel = false;
478 
479     //try to avoid possible deadlocks by aborting when we can't get all locks
480     if ( ( hasTrack = m_trackLock.tryLockForWrite() )
481          && ( hasAlbum = m_albumLock.tryLockForWrite() )
482          && ( hasArtist = m_artistLock.tryLockForWrite() )
483          && ( hasYear = m_yearLock.tryLockForWrite() )
484          && ( hasGenre = m_genreLock.tryLockForWrite() )
485          && ( hasComposer = m_composerLock.tryLockForWrite() )
486          && ( hasLabel = m_labelLock.tryLockForWrite() ) )
487     {
488         //this very simple garbage collector doesn't handle cyclic object graphs
489         //so care has to be taken to make sure that we are not dealing with a cyclic graph
490         //by invalidating the tracks cache on all objects
491         #define foreachInvalidateCache( Type, RealType, x ) \
492         for( QMutableHashIterator<int,Type > iter(x); iter.hasNext(); ) \
493             RealType::staticCast( iter.next().value() )->invalidateCache()
494 
495         //elem.count() == 2 is correct because elem is one pointer to the object
496         //and the other is stored in the hash map (except for m_trackMap, where
497         //another reference is stored in m_uidMap
498         #define foreachCollectGarbage( Key, Type, RefCount, x ) \
499         for( QMutableHashIterator<Key,Type > iter(x); iter.hasNext(); ) \
500         { \
501             Type elem = iter.next().value(); \
502             if( elem.count() == RefCount ) \
503                 iter.remove(); \
504         }
505 
506         foreachCollectGarbage( Meta::TrackKey, AmarokSharedPointer<Meta::AggregateTrack>, 2, m_trackMap )
507         //run before artist so that album artist pointers can be garbage collected
508         foreachCollectGarbage( Meta::AlbumKey, AmarokSharedPointer<Meta::AggregateAlbum>, 2, m_albumMap )
509         foreachCollectGarbage( QString, AmarokSharedPointer<Meta::AggregateArtist>, 2, m_artistMap )
510         foreachCollectGarbage( QString, AmarokSharedPointer<Meta::AggregateGenre>, 2, m_genreMap )
511         foreachCollectGarbage( QString, AmarokSharedPointer<Meta::AggregateComposer>, 2, m_composerMap )
512         foreachCollectGarbage( QString, AmarokSharedPointer<Meta::AggreagateYear>, 2, m_yearMap )
513         foreachCollectGarbage( QString, AmarokSharedPointer<Meta::AggregateLabel>, 2, m_labelMap )
514     }
515 
516     //make sure to unlock all necessary locks
517     //important: calling unlock() on an unlocked mutex gives an undefined result
518     //unlocking a mutex locked by another thread results in an error, so be careful
519     if( hasTrack ) m_trackLock.unlock();
520     if( hasAlbum ) m_albumLock.unlock();
521     if( hasArtist ) m_artistLock.unlock();
522     if( hasYear ) m_yearLock.unlock();
523     if( hasGenre ) m_genreLock.unlock();
524     if( hasComposer ) m_composerLock.unlock();
525     if( hasLabel ) m_labelLock.unlock();
526 }
527