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