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 }