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