File indexing completed on 2024-05-19 04:49:16

0001 /****************************************************************************************
0002  * Copyright (c) 2007-2008 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 "CollectionManager"
0018 
0019 #include "CollectionManager.h"
0020 
0021 #include "core/capabilities/CollectionScanCapability.h"
0022 #include "core/collections/Collection.h"
0023 #include "core/collections/MetaQueryMaker.h"
0024 #include "core/support/Amarok.h"
0025 #include "core/support/Debug.h"
0026 #include "core-impl/meta/file/FileTrackProvider.h"
0027 #include "core-impl/meta/stream/Stream.h"
0028 #include "core-impl/meta/timecode/TimecodeTrackProvider.h"
0029 
0030 #include <QMetaEnum>
0031 #include <QMetaObject>
0032 
0033 #include <QCoreApplication>
0034 #include <QList>
0035 #include <QPair>
0036 #include <QTimer>
0037 #include <QReadWriteLock>
0038 
0039 
0040 typedef QPair<Collections::Collection*, CollectionManager::CollectionStatus> CollectionPair;
0041 
0042 
0043 /** Private structure of the collection manager */
0044 struct CollectionManager::Private
0045 {
0046     QList<CollectionPair> collections;
0047     QList<QSharedPointer<Plugins::PluginFactory> > factories; // factories belong to PluginManager
0048 
0049     QList<Collections::TrackProvider*> trackProviders;
0050     TimecodeTrackProvider *timecodeTrackProvider;
0051     Collections::TrackProvider *fileTrackProvider; // special case
0052 
0053     Collections::Collection *primaryCollection;
0054 
0055     QReadWriteLock lock; ///< protects all other variables against threading issues
0056 };
0057 
0058 CollectionManager *CollectionManager::s_instance = nullptr;
0059 
0060 CollectionManager *
0061 CollectionManager::instance()
0062 {
0063     if( !s_instance ) {
0064         s_instance = new CollectionManager();
0065         s_instance->init();
0066     }
0067 
0068     return s_instance;
0069 }
0070 
0071 void
0072 CollectionManager::destroy()
0073 {
0074     if( s_instance ) {
0075         delete s_instance;
0076         s_instance = nullptr;
0077     }
0078 }
0079 
0080 CollectionManager::CollectionManager()
0081     : QObject()
0082     , d( new Private )
0083 {
0084     DEBUG_BLOCK
0085     // ensure this object is created in a main thread
0086     Q_ASSERT( thread() == QCoreApplication::instance()->thread() );
0087 
0088     setObjectName( QStringLiteral("CollectionManager") );
0089     d->primaryCollection = nullptr;
0090     d->timecodeTrackProvider = nullptr;
0091     d->fileTrackProvider = nullptr;
0092 }
0093 
0094 CollectionManager::~CollectionManager()
0095 {
0096     DEBUG_BLOCK
0097 
0098     {
0099         QWriteLocker locker( &d->lock );
0100 
0101         while (!d->collections.isEmpty() )
0102             delete d->collections.takeFirst().first;
0103 
0104         d->trackProviders.clear();
0105         delete d->timecodeTrackProvider;
0106         delete d->fileTrackProvider;
0107     }
0108 
0109     delete d;
0110 }
0111 
0112 void
0113 CollectionManager::init()
0114 {
0115     // register the timecode track provider now, as it needs to get added before loading
0116     // the stored playlist... Since it can have playable urls that might also match other providers, it needs to get added first.
0117     d->timecodeTrackProvider = new TimecodeTrackProvider();
0118     addTrackProvider( d->timecodeTrackProvider );
0119 
0120     // addint fileTrackProvider second since local tracks should be preferred even if the url matches two tracks
0121     d->fileTrackProvider = new FileTrackProvider();
0122     addTrackProvider( d->fileTrackProvider );
0123 }
0124 
0125 void
0126 CollectionManager::setFactories( const QList<QSharedPointer<Plugins::PluginFactory> > &factories )
0127 {
0128     using Collections::CollectionFactory;
0129 
0130 
0131     QSet<QSharedPointer<Plugins::PluginFactory> > newFactories(factories.begin(), factories.end());
0132     QSet<QSharedPointer<Plugins::PluginFactory> > oldFactories;
0133 
0134     {
0135         QReadLocker locker( &d->lock );
0136         QSet<QSharedPointer<Plugins::PluginFactory> > addFactoriesSet(d->factories.begin(), d->factories.end());
0137         oldFactories += addFactoriesSet;
0138     }
0139 
0140     // remove old factories
0141     for( const auto &pFactory : oldFactories - newFactories )
0142     {
0143 
0144         auto factory = qobject_cast<CollectionFactory*>( pFactory );
0145         if( !factory )
0146             continue;
0147 
0148         disconnect( factory.data(), &CollectionFactory::newCollection,
0149                     this, &CollectionManager::slotNewCollection );
0150         {
0151             QWriteLocker locker( &d->lock );
0152             d->factories.removeAll( factory );
0153         }
0154     }
0155 
0156     // create new factories
0157     for( const auto &pFactory : newFactories - oldFactories )
0158     {
0159         auto factory = qobject_cast<CollectionFactory*>( pFactory );
0160         if( !factory )
0161             continue;
0162 
0163         connect( factory.data(), &CollectionFactory::newCollection,
0164                  this, &CollectionManager::slotNewCollection );
0165         {
0166             QWriteLocker locker( &d->lock );
0167             d->factories.append( factory );
0168         }
0169     }
0170 }
0171 
0172 
0173 void
0174 CollectionManager::startFullScan()
0175 {
0176     QReadLocker locker( &d->lock );
0177 
0178     foreach( const CollectionPair &pair, d->collections )
0179     {
0180         QScopedPointer<Capabilities::CollectionScanCapability> csc( pair.first->create<Capabilities::CollectionScanCapability>() );
0181         if( csc )
0182             csc->startFullScan();
0183     }
0184 }
0185 
0186 void
0187 CollectionManager::startIncrementalScan( const QString &directory )
0188 {
0189     QReadLocker locker( &d->lock );
0190 
0191     foreach( const CollectionPair &pair, d->collections )
0192     {
0193         QScopedPointer<Capabilities::CollectionScanCapability> csc( pair.first->create<Capabilities::CollectionScanCapability>() );
0194         if( csc )
0195             csc->startIncrementalScan( directory );
0196     }
0197 }
0198 
0199 void
0200 CollectionManager::stopScan()
0201 {
0202     QReadLocker locker( &d->lock );
0203 
0204     foreach( const CollectionPair &pair, d->collections )
0205     {
0206         QScopedPointer<Capabilities::CollectionScanCapability> csc( pair.first->create<Capabilities::CollectionScanCapability>() );
0207         if( csc )
0208             csc->stopScan();
0209     }
0210 }
0211 
0212 void
0213 CollectionManager::checkCollectionChanges()
0214 {
0215     startIncrementalScan( QString() );
0216 }
0217 
0218 Collections::QueryMaker*
0219 CollectionManager::queryMaker() const
0220 {
0221     QReadLocker locker( &d->lock );
0222 
0223     QList<Collections::Collection*> colls;
0224     foreach( const CollectionPair &pair, d->collections )
0225     {
0226         if( pair.second & CollectionQueryable )
0227         {
0228             colls << pair.first;
0229         }
0230     }
0231     return new Collections::MetaQueryMaker( colls );
0232 }
0233 
0234 void
0235 CollectionManager::slotNewCollection( Collections::Collection* newCollection )
0236 {
0237     DEBUG_BLOCK
0238 
0239     if( !newCollection )
0240     {
0241         error() << "newCollection in slotNewCollection is 0";
0242         return;
0243     }
0244     {
0245         QWriteLocker locker( &d->lock );
0246         foreach( const CollectionPair &p, d->collections )
0247         {
0248             if( p.first == newCollection )
0249             {
0250                 error() << "newCollection " << newCollection->collectionId() << " is already being managed";
0251                 return;
0252             }
0253         }
0254     }
0255 
0256     const QMetaObject *mo = metaObject();
0257     const QMetaEnum me = mo->enumerator( mo->indexOfEnumerator( "CollectionStatus" ) );
0258     const QString &value = Amarok::config( QStringLiteral("CollectionManager") ).readEntry( newCollection->collectionId() );
0259     int enumValue = me.keyToValue( value.toLocal8Bit().constData() );
0260     CollectionStatus status;
0261     enumValue == -1 ? status = CollectionEnabled : status = (CollectionStatus) enumValue;
0262     CollectionPair pair( newCollection, status );
0263 
0264     {
0265         QWriteLocker locker( &d->lock );
0266         if( newCollection->collectionId() == QLatin1String("localCollection") )
0267         {
0268             d->primaryCollection = newCollection;
0269             d->collections.insert( 0, pair ); // the primary collection should be the first collection to be searched
0270             d->trackProviders.insert( 1, newCollection ); // the primary collection should be between the timecode track provider and the local file track provider
0271         }
0272         else
0273         {
0274             d->collections.append( pair );
0275             d->trackProviders.append( newCollection );
0276         }
0277         connect( newCollection, &Collections::Collection::remove, this, &CollectionManager::slotRemoveCollection, Qt::QueuedConnection );
0278         connect( newCollection, &Collections::Collection::updated, this, &CollectionManager::slotCollectionChanged, Qt::QueuedConnection );
0279 
0280         debug() << "new Collection " << newCollection->collectionId();
0281     }
0282 
0283     if( status & CollectionViewable )
0284     {
0285 //         Q_EMIT collectionAdded( newCollection );
0286         Q_EMIT collectionAdded( newCollection, status );
0287     }
0288 }
0289 
0290 void
0291 CollectionManager::slotRemoveCollection()
0292 {
0293     Collections::Collection* collection = qobject_cast<Collections::Collection*>( sender() );
0294     if( collection )
0295     {
0296         CollectionStatus status = collectionStatus( collection->collectionId() );
0297         CollectionPair pair( collection, status );
0298 
0299         {
0300             QWriteLocker locker( &d->lock );
0301             d->collections.removeAll( pair );
0302             d->trackProviders.removeAll( collection );
0303         }
0304 
0305         Q_EMIT collectionRemoved( collection->collectionId() );
0306         QTimer::singleShot( 500, collection, &QObject::deleteLater ); // give the tree some time to update itself until we really delete the collection pointers.
0307     }
0308 }
0309 
0310 void
0311 CollectionManager::slotCollectionChanged()
0312 {
0313     Collections::Collection *collection = dynamic_cast<Collections::Collection*>( sender() );
0314     if( collection )
0315     {
0316         CollectionStatus status = collectionStatus( collection->collectionId() );
0317         if( status & CollectionViewable )
0318         {
0319             Q_EMIT collectionDataChanged( collection );
0320         }
0321     }
0322 }
0323 
0324 QList<Collections::Collection*>
0325 CollectionManager::viewableCollections() const
0326 {
0327     QReadLocker locker( &d->lock );
0328 
0329     QList<Collections::Collection*> result;
0330     foreach( const CollectionPair &pair, d->collections )
0331     {
0332         if( pair.second & CollectionViewable )
0333         {
0334             result << pair.first;
0335         }
0336     }
0337     return result;
0338 }
0339 
0340 Collections::Collection*
0341 CollectionManager::primaryCollection() const
0342 {
0343     QReadLocker locker( &d->lock );
0344 
0345     return d->primaryCollection;
0346 }
0347 
0348 Meta::TrackPtr
0349 CollectionManager::trackForUrl( const QUrl &url )
0350 {
0351     QReadLocker locker( &d->lock );
0352 
0353     // TODO:
0354     // might be a podcast, in that case we'll have additional meta information
0355     // might be a lastfm track, another stream
0356     if( !url.isValid() )
0357         return Meta::TrackPtr( nullptr );
0358 
0359     foreach( Collections::TrackProvider *provider, d->trackProviders )
0360     {
0361         if( provider->possiblyContainsTrack( url ) )
0362         {
0363             Meta::TrackPtr track = provider->trackForUrl( url );
0364             if( track )
0365                 return track;
0366         }
0367     }
0368 
0369     // TODO: create specific TrackProviders for these:
0370     static const QSet<QString> remoteProtocols = QSet<QString>()
0371             << QStringLiteral("http") << QStringLiteral("https") << QStringLiteral("mms") << QStringLiteral("smb"); // consider unifying with TrackLoader::tracksLoaded()
0372     if( remoteProtocols.contains( url.scheme() ) )
0373         return Meta::TrackPtr( new MetaStream::Track( url ) );
0374 
0375     return Meta::TrackPtr( nullptr );
0376 }
0377 
0378 
0379 CollectionManager::CollectionStatus
0380 CollectionManager::collectionStatus( const QString &collectionId ) const
0381 {
0382     QReadLocker locker( &d->lock );
0383 
0384     foreach( const CollectionPair &pair, d->collections )
0385     {
0386         if( pair.first->collectionId() == collectionId )
0387         {
0388             return pair.second;
0389         }
0390     }
0391     return CollectionDisabled;
0392 }
0393 
0394 QHash<Collections::Collection*, CollectionManager::CollectionStatus>
0395 CollectionManager::collections() const
0396 {
0397     QReadLocker locker( &d->lock );
0398 
0399     QHash<Collections::Collection*, CollectionManager::CollectionStatus> result;
0400     foreach( const CollectionPair &pair, d->collections )
0401     {
0402         result.insert( pair.first, pair.second );
0403     }
0404     return result;
0405 }
0406 
0407 void
0408 CollectionManager::addTrackProvider( Collections::TrackProvider *provider )
0409 {
0410     {
0411         QWriteLocker locker( &d->lock );
0412         d->trackProviders.append( provider );
0413     }
0414     Q_EMIT trackProviderAdded( provider );
0415 }
0416 
0417 void
0418 CollectionManager::removeTrackProvider( Collections::TrackProvider *provider )
0419 {
0420     QWriteLocker locker( &d->lock );
0421     d->trackProviders.removeAll( provider );
0422 }
0423