File indexing completed on 2025-01-05 04:25:50
0001 /**************************************************************************************** 0002 * Copyright (c) 2009 Maximilian Kossick <maximilian.kossick@googlemail.com> * 0003 * * 0004 * This program is free software; you can redistribute it and/or modify it under * 0005 * the terms of the GNU General Public License as published by the Free Software * 0006 * Foundation; either version 2 of the License, or (at your option) any later * 0007 * version. * 0008 * * 0009 * This program is distributed in the hope that it will be useful, but WITHOUT ANY * 0010 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * 0011 * PARTICULAR PURPOSE. See the GNU General Public License for more details. * 0012 * * 0013 * You should have received a copy of the GNU General Public License along with * 0014 * this program. If not, see <http://www.gnu.org/licenses/>. * 0015 ****************************************************************************************/ 0016 0017 #define DEBUG_PREFIX "AggregateCollection" 0018 0019 #include "AggregateCollection.h" 0020 0021 #include "core/support/Debug.h" 0022 #include "core-impl/collections/aggregate/AggregateMeta.h" 0023 #include "core-impl/collections/aggregate/AggregateQueryMaker.h" 0024 #include "core-impl/collections/support/CollectionManager.h" 0025 0026 #include <QIcon> 0027 0028 #include <QReadLocker> 0029 #include <QTimer> 0030 0031 using namespace Collections; 0032 0033 AggregateCollection::AggregateCollection() 0034 : Collections::Collection() 0035 { 0036 QTimer *timer = new QTimer( this ); 0037 timer->setSingleShot( false ); 0038 timer->setInterval( 60000 ); //clean up every 60 seconds 0039 connect( timer, &QTimer::timeout, this, &AggregateCollection::emptyCache ); 0040 timer->start(); 0041 } 0042 0043 AggregateCollection::~AggregateCollection() 0044 { 0045 } 0046 0047 QString 0048 AggregateCollection::prettyName() const 0049 { 0050 return i18nc( "Name of the virtual collection that merges tracks from all collections", 0051 "Aggregate Collection" ); 0052 } 0053 0054 QIcon 0055 AggregateCollection::icon() const 0056 { 0057 return QIcon::fromTheme(QStringLiteral("drive-harddisk")); 0058 } 0059 0060 bool 0061 AggregateCollection::possiblyContainsTrack( const QUrl &url ) const 0062 { 0063 foreach( Collections::Collection *collection, m_idCollectionMap ) 0064 { 0065 if( collection->possiblyContainsTrack( url ) ) 0066 return true; 0067 } 0068 return false; 0069 } 0070 0071 Meta::TrackPtr 0072 AggregateCollection::trackForUrl( const QUrl &url ) 0073 { 0074 foreach( Collections::Collection *collection, m_idCollectionMap ) 0075 { 0076 Meta::TrackPtr track = collection->trackForUrl( url ); 0077 if( track ) 0078 { 0079 //theoretically we should now query the other collections for the same track 0080 //not sure how to do that yet though... 0081 return Meta::TrackPtr( getTrack( track ) ); 0082 } 0083 } 0084 return Meta::TrackPtr(); 0085 } 0086 0087 QueryMaker* 0088 AggregateCollection::queryMaker() 0089 { 0090 QList<QueryMaker*> list; 0091 foreach( Collections::Collection *collection, m_idCollectionMap ) 0092 { 0093 list.append( collection->queryMaker() ); 0094 } 0095 return new Collections::AggregateQueryMaker( this, list ); 0096 } 0097 0098 QString 0099 AggregateCollection::collectionId() const 0100 { 0101 // do we need more than one AggregateCollection? 0102 return QStringLiteral( "AggregateCollection" ); 0103 } 0104 0105 void 0106 AggregateCollection::addCollection( Collections::Collection *collection, CollectionManager::CollectionStatus status ) 0107 { 0108 if( !collection ) 0109 return; 0110 0111 if( !( status & CollectionManager::CollectionViewable ) ) 0112 return; 0113 0114 m_idCollectionMap.insert( collection->collectionId(), collection ); 0115 //TODO 0116 Q_EMIT updated(); 0117 } 0118 0119 void 0120 AggregateCollection::removeCollectionById( const QString &collectionId ) 0121 { 0122 m_idCollectionMap.remove( collectionId ); 0123 Q_EMIT updated(); 0124 } 0125 0126 void 0127 AggregateCollection::removeCollection( Collections::Collection *collection ) 0128 { 0129 m_idCollectionMap.remove( collection->collectionId() ); 0130 Q_EMIT updated(); 0131 } 0132 0133 void 0134 AggregateCollection::slotUpdated() 0135 { 0136 //TODO 0137 Q_EMIT updated(); 0138 } 0139 0140 void 0141 AggregateCollection::removeYear( const QString &name ) 0142 { 0143 m_yearLock.lockForWrite(); 0144 m_yearMap.remove( name ); 0145 m_yearLock.unlock(); 0146 } 0147 0148 Meta::AggreagateYear* 0149 AggregateCollection::getYear( Meta::YearPtr year ) 0150 { 0151 m_yearLock.lockForRead(); 0152 if( m_yearMap.contains( year->name() ) ) 0153 { 0154 AmarokSharedPointer<Meta::AggreagateYear> aggregateYear = m_yearMap.value( year->name() ); 0155 aggregateYear->add( year ); 0156 m_yearLock.unlock(); 0157 return aggregateYear.data(); 0158 } 0159 else 0160 { 0161 m_yearLock.unlock(); 0162 m_yearLock.lockForWrite(); 0163 //we might create two year instances with the same name here, 0164 //which would show some weird behaviour in other places 0165 Meta::AggreagateYear *aggregateYear = new Meta::AggreagateYear( this, year ); 0166 m_yearMap.insert( year->name(), AmarokSharedPointer<Meta::AggreagateYear>( aggregateYear ) ); 0167 m_yearLock.unlock(); 0168 return aggregateYear; 0169 } 0170 } 0171 0172 void 0173 AggregateCollection::setYear( Meta::AggreagateYear *year ) 0174 { 0175 m_yearLock.lockForWrite(); 0176 m_yearMap.insert( year->name(), AmarokSharedPointer<Meta::AggreagateYear>( year ) ); 0177 m_yearLock.unlock(); 0178 } 0179 0180 bool 0181 AggregateCollection::hasYear( const QString &name ) 0182 { 0183 QReadLocker locker( &m_yearLock ); 0184 return m_yearMap.contains( name ); 0185 } 0186 0187 void 0188 AggregateCollection::removeGenre( const QString &name ) 0189 { 0190 m_genreLock.lockForWrite(); 0191 m_genreMap.remove( name ); 0192 m_genreLock.unlock(); 0193 } 0194 0195 Meta::AggregateGenre* 0196 AggregateCollection::getGenre( Meta::GenrePtr genre ) 0197 { 0198 m_genreLock.lockForRead(); 0199 if( m_genreMap.contains( genre->name() ) ) 0200 { 0201 AmarokSharedPointer<Meta::AggregateGenre> aggregateGenre = m_genreMap.value( genre->name() ); 0202 aggregateGenre->add( genre ); 0203 m_genreLock.unlock(); 0204 return aggregateGenre.data(); 0205 } 0206 else 0207 { 0208 m_genreLock.unlock(); 0209 m_genreLock.lockForWrite(); 0210 //we might create two instances with the same name here, 0211 //which would show some weird behaviour in other places 0212 Meta::AggregateGenre *aggregateGenre = new Meta::AggregateGenre( this, genre ); 0213 m_genreMap.insert( genre->name(), AmarokSharedPointer<Meta::AggregateGenre>( aggregateGenre ) ); 0214 m_genreLock.unlock(); 0215 return aggregateGenre; 0216 } 0217 } 0218 0219 void 0220 AggregateCollection::setGenre( Meta::AggregateGenre *genre ) 0221 { 0222 m_genreLock.lockForWrite(); 0223 m_genreMap.insert( genre->name(), AmarokSharedPointer<Meta::AggregateGenre>( genre ) ); 0224 m_genreLock.unlock(); 0225 } 0226 0227 bool 0228 AggregateCollection::hasGenre( const QString &genre ) 0229 { 0230 QReadLocker locker( &m_genreLock ); 0231 return m_genreMap.contains( genre ); 0232 } 0233 0234 void 0235 AggregateCollection::removeComposer( const QString &name ) 0236 { 0237 m_composerLock.lockForWrite(); 0238 m_composerMap.remove( name ); 0239 m_composerLock.unlock(); 0240 } 0241 0242 Meta::AggregateComposer* 0243 AggregateCollection::getComposer( Meta::ComposerPtr composer ) 0244 { 0245 m_composerLock.lockForRead(); 0246 if( m_composerMap.contains( composer->name() ) ) 0247 { 0248 AmarokSharedPointer<Meta::AggregateComposer> aggregateComposer = m_composerMap.value( composer->name() ); 0249 aggregateComposer->add( composer ); 0250 m_composerLock.unlock(); 0251 return aggregateComposer.data(); 0252 } 0253 else 0254 { 0255 m_composerLock.unlock(); 0256 m_composerLock.lockForWrite(); 0257 //we might create two instances with the same name here, 0258 //which would show some weird behaviour in other places 0259 Meta::AggregateComposer *aggregateComposer = new Meta::AggregateComposer( this, composer ); 0260 m_composerMap.insert( composer->name(), AmarokSharedPointer<Meta::AggregateComposer>( aggregateComposer ) ); 0261 m_composerLock.unlock(); 0262 return aggregateComposer; 0263 } 0264 } 0265 0266 void 0267 AggregateCollection::setComposer( Meta::AggregateComposer *composer ) 0268 { 0269 m_composerLock.lockForWrite(); 0270 m_composerMap.insert( composer->name(), AmarokSharedPointer<Meta::AggregateComposer>( composer ) ); 0271 m_composerLock.unlock(); 0272 } 0273 0274 bool 0275 AggregateCollection::hasComposer( const QString &name ) 0276 { 0277 QReadLocker locker( &m_composerLock ); 0278 return m_composerMap.contains( name ); 0279 } 0280 0281 void 0282 AggregateCollection::removeArtist( const QString &name ) 0283 { 0284 m_artistLock.lockForWrite(); 0285 m_artistMap.remove( name ); 0286 m_artistLock.unlock(); 0287 } 0288 0289 Meta::AggregateArtist* 0290 AggregateCollection::getArtist( Meta::ArtistPtr artist ) 0291 { 0292 m_artistLock.lockForRead(); 0293 if( m_artistMap.contains( artist->name() ) ) 0294 { 0295 AmarokSharedPointer<Meta::AggregateArtist> aggregateArtist = m_artistMap.value( artist->name() ); 0296 aggregateArtist->add( artist ); 0297 m_artistLock.unlock(); 0298 return aggregateArtist.data(); 0299 } 0300 else 0301 { 0302 m_artistLock.unlock(); 0303 m_artistLock.lockForWrite(); 0304 //we might create two instances with the same name here, 0305 //which would show some weird behaviour in other places 0306 Meta::AggregateArtist *aggregateArtist = new Meta::AggregateArtist( this, artist ); 0307 m_artistMap.insert( artist->name(), AmarokSharedPointer<Meta::AggregateArtist>( aggregateArtist ) ); 0308 m_artistLock.unlock(); 0309 return aggregateArtist; 0310 } 0311 } 0312 0313 void 0314 AggregateCollection::setArtist( Meta::AggregateArtist *artist ) 0315 { 0316 m_artistLock.lockForWrite(); 0317 m_artistMap.insert( artist->name(), AmarokSharedPointer<Meta::AggregateArtist>( artist ) ); 0318 m_artistLock.unlock(); 0319 } 0320 0321 bool 0322 AggregateCollection::hasArtist( const QString &artist ) 0323 { 0324 QReadLocker locker( &m_artistLock ); 0325 return m_artistMap.contains( artist ); 0326 } 0327 0328 void 0329 AggregateCollection::removeAlbum( const QString &album, const QString &albumartist ) 0330 { 0331 Meta::AlbumKey key( album, albumartist ); 0332 m_albumLock.lockForWrite(); 0333 m_albumMap.remove( key ); 0334 m_albumLock.unlock(); 0335 } 0336 0337 Meta::AggregateAlbum* 0338 AggregateCollection::getAlbum( const Meta::AlbumPtr &album ) 0339 { 0340 Meta::AlbumKey key( album ); 0341 m_albumLock.lockForRead(); 0342 if( m_albumMap.contains( key ) ) 0343 { 0344 AmarokSharedPointer<Meta::AggregateAlbum> aggregateAlbum = m_albumMap.value( key ); 0345 aggregateAlbum->add( album ); 0346 m_albumLock.unlock(); 0347 return aggregateAlbum.data(); 0348 } 0349 else 0350 { 0351 m_albumLock.unlock(); 0352 m_albumLock.lockForWrite(); 0353 //we might create two instances with the same name here, 0354 //which would show some weird behaviour in other places 0355 Meta::AggregateAlbum *aggregateAlbum = new Meta::AggregateAlbum( this, album ); 0356 m_albumMap.insert( key, AmarokSharedPointer<Meta::AggregateAlbum>( aggregateAlbum ) ); 0357 m_albumLock.unlock(); 0358 return aggregateAlbum; 0359 } 0360 } 0361 0362 void 0363 AggregateCollection::setAlbum( Meta::AggregateAlbum *album ) 0364 { 0365 m_albumLock.lockForWrite(); 0366 m_albumMap.insert( Meta::AlbumKey( Meta::AlbumPtr( album ) ), 0367 AmarokSharedPointer<Meta::AggregateAlbum>( album ) ); 0368 m_albumLock.unlock(); 0369 } 0370 0371 bool 0372 AggregateCollection::hasAlbum( const QString &album, const QString &albumArtist ) 0373 { 0374 QReadLocker locker( &m_albumLock ); 0375 return m_albumMap.contains( Meta::AlbumKey( album, albumArtist ) ); 0376 } 0377 0378 void 0379 AggregateCollection::removeTrack( const Meta::TrackKey &key ) 0380 { 0381 m_trackLock.lockForWrite(); 0382 m_trackMap.remove( key ); 0383 m_trackLock.unlock(); 0384 } 0385 0386 Meta::AggregateTrack* 0387 AggregateCollection::getTrack( const Meta::TrackPtr &track ) 0388 { 0389 const Meta::TrackKey key( track ); 0390 m_trackLock.lockForRead(); 0391 if( m_trackMap.contains( key ) ) 0392 { 0393 AmarokSharedPointer<Meta::AggregateTrack> aggregateTrack = m_trackMap.value( key ); 0394 aggregateTrack->add( track ); 0395 m_trackLock.unlock(); 0396 return aggregateTrack.data(); 0397 } 0398 else 0399 { 0400 m_trackLock.unlock(); 0401 m_trackLock.lockForWrite(); 0402 //we might create two instances with the same name here, 0403 //which would show some weird behaviour in other places 0404 Meta::AggregateTrack *aggregateTrack = new Meta::AggregateTrack( this, track ); 0405 m_trackMap.insert( key, AmarokSharedPointer<Meta::AggregateTrack>( aggregateTrack ) ); 0406 m_trackLock.unlock(); 0407 return aggregateTrack; 0408 } 0409 } 0410 0411 void 0412 AggregateCollection::setTrack( Meta::AggregateTrack *track ) 0413 { 0414 Meta::TrackPtr ptr( track ); 0415 const Meta::TrackKey key( ptr ); 0416 m_trackLock.lockForWrite(); 0417 m_trackMap.insert( key, AmarokSharedPointer<Meta::AggregateTrack>( track ) ); 0418 m_trackLock.unlock(); 0419 } 0420 0421 bool 0422 AggregateCollection::hasTrack( const Meta::TrackKey &key ) 0423 { 0424 QReadLocker locker( &m_trackLock ); 0425 return m_trackMap.contains( key ); 0426 } 0427 0428 bool 0429 AggregateCollection::hasLabel( const QString &name ) 0430 { 0431 QReadLocker locker( &m_labelLock ); 0432 return m_labelMap.contains( name ); 0433 } 0434 0435 void 0436 AggregateCollection::removeLabel( const QString &name ) 0437 { 0438 QWriteLocker locker( &m_labelLock ); 0439 m_labelMap.remove( name ); 0440 } 0441 0442 Meta::AggregateLabel* 0443 AggregateCollection::getLabel( Meta::LabelPtr label ) 0444 { 0445 m_labelLock.lockForRead(); 0446 if( m_labelMap.contains( label->name() ) ) 0447 { 0448 AmarokSharedPointer<Meta::AggregateLabel> aggregateLabel = m_labelMap.value( label->name() ); 0449 aggregateLabel->add( label ); 0450 m_labelLock.unlock(); 0451 return aggregateLabel.data(); 0452 } 0453 else 0454 { 0455 m_labelLock.unlock(); 0456 m_labelLock.lockForWrite(); 0457 //we might create two year instances with the same name here, 0458 //which would show some weird behaviour in other places 0459 Meta::AggregateLabel *aggregateLabel = new Meta::AggregateLabel( this, label ); 0460 m_labelMap.insert( label->name(), AmarokSharedPointer<Meta::AggregateLabel>( aggregateLabel ) ); 0461 m_labelLock.unlock(); 0462 return aggregateLabel; 0463 } 0464 } 0465 0466 void 0467 AggregateCollection::setLabel( Meta::AggregateLabel *label ) 0468 { 0469 QWriteLocker locker( &m_labelLock ); 0470 m_labelMap.insert( label->name(), AmarokSharedPointer<Meta::AggregateLabel>( label ) ); 0471 } 0472 0473 void 0474 AggregateCollection::emptyCache() 0475 { 0476 bool hasTrack, hasAlbum, hasArtist, hasYear, hasGenre, hasComposer, hasLabel; 0477 hasTrack = hasAlbum = hasArtist = hasYear = hasGenre = hasComposer = hasLabel = false; 0478 0479 //try to avoid possible deadlocks by aborting when we can't get all locks 0480 if ( ( hasTrack = m_trackLock.tryLockForWrite() ) 0481 && ( hasAlbum = m_albumLock.tryLockForWrite() ) 0482 && ( hasArtist = m_artistLock.tryLockForWrite() ) 0483 && ( hasYear = m_yearLock.tryLockForWrite() ) 0484 && ( hasGenre = m_genreLock.tryLockForWrite() ) 0485 && ( hasComposer = m_composerLock.tryLockForWrite() ) 0486 && ( hasLabel = m_labelLock.tryLockForWrite() ) ) 0487 { 0488 //this very simple garbage collector doesn't handle cyclic object graphs 0489 //so care has to be taken to make sure that we are not dealing with a cyclic graph 0490 //by invalidating the tracks cache on all objects 0491 #define foreachInvalidateCache( Type, RealType, x ) \ 0492 for( QMutableHashIterator<int,Type > iter(x); iter.hasNext(); ) \ 0493 RealType::staticCast( iter.next().value() )->invalidateCache() 0494 0495 //elem.count() == 2 is correct because elem is one pointer to the object 0496 //and the other is stored in the hash map (except for m_trackMap, where 0497 //another reference is stored in m_uidMap 0498 #define foreachCollectGarbage( Key, Type, RefCount, x ) \ 0499 for( QMutableHashIterator<Key,Type > iter(x); iter.hasNext(); ) \ 0500 { \ 0501 Type elem = iter.next().value(); \ 0502 if( elem.count() == RefCount ) \ 0503 iter.remove(); \ 0504 } 0505 0506 foreachCollectGarbage( Meta::TrackKey, AmarokSharedPointer<Meta::AggregateTrack>, 2, m_trackMap ) 0507 //run before artist so that album artist pointers can be garbage collected 0508 foreachCollectGarbage( Meta::AlbumKey, AmarokSharedPointer<Meta::AggregateAlbum>, 2, m_albumMap ) 0509 foreachCollectGarbage( QString, AmarokSharedPointer<Meta::AggregateArtist>, 2, m_artistMap ) 0510 foreachCollectGarbage( QString, AmarokSharedPointer<Meta::AggregateGenre>, 2, m_genreMap ) 0511 foreachCollectGarbage( QString, AmarokSharedPointer<Meta::AggregateComposer>, 2, m_composerMap ) 0512 foreachCollectGarbage( QString, AmarokSharedPointer<Meta::AggreagateYear>, 2, m_yearMap ) 0513 foreachCollectGarbage( QString, AmarokSharedPointer<Meta::AggregateLabel>, 2, m_labelMap ) 0514 } 0515 0516 //make sure to unlock all necessary locks 0517 //important: calling unlock() on an unlocked mutex gives an undefined result 0518 //unlocking a mutex locked by another thread results in an error, so be careful 0519 if( hasTrack ) m_trackLock.unlock(); 0520 if( hasAlbum ) m_albumLock.unlock(); 0521 if( hasArtist ) m_artistLock.unlock(); 0522 if( hasYear ) m_yearLock.unlock(); 0523 if( hasGenre ) m_genreLock.unlock(); 0524 if( hasComposer ) m_composerLock.unlock(); 0525 if( hasLabel ) m_labelLock.unlock(); 0526 }