File indexing completed on 2024-05-05 04:48:49
0001 /**************************************************************************************** 0002 * Copyright (c) 2007 Bart Cerneels <bart.cerneels@kde.org> * 0003 * Copyright (c) 2011 Lucas Lira Gomes <x8lucas8x@gmail.com> * 0004 * * 0005 * This program is free software; you can redistribute it and/or modify it under * 0006 * the terms of the GNU General Public License as published by the Free Software * 0007 * Foundation; either version 2 of the License, or (at your option) any later * 0008 * version. * 0009 * * 0010 * This program is distributed in the hope that it will be useful, but WITHOUT ANY * 0011 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * 0012 * PARTICULAR PURPOSE. See the GNU General Public License for more details. * 0013 * * 0014 * You should have received a copy of the GNU General Public License along with * 0015 * this program. If not, see <http://www.gnu.org/licenses/>. * 0016 ****************************************************************************************/ 0017 0018 #include "PlaylistManager.h" 0019 0020 #include "amarokurls/AmarokUrl.h" 0021 #include "amarokconfig.h" 0022 #include "App.h" 0023 #include "core-impl/collections/support/CollectionManager.h" 0024 #include "core-impl/playlists/types/file/PlaylistFile.h" 0025 #include "playlist/PlaylistModelStack.h" 0026 #include "core-impl/playlists/types/file/PlaylistFileSupport.h" 0027 #include "core/podcasts/PodcastProvider.h" 0028 #include "file/PlaylistFileProvider.h" 0029 #include "file/KConfigSyncRelStore.h" 0030 #include "core-impl/podcasts/sql/SqlPodcastProvider.h" 0031 #include "playlistmanager/sql/SqlUserPlaylistProvider.h" 0032 #include "playlistmanager/SyncedPlaylist.h" 0033 #include "core/support/Debug.h" 0034 #include "core/support/Components.h" 0035 #include "core/logger/Logger.h" 0036 #include "browsers/playlistbrowser/UserPlaylistModel.h" 0037 0038 #include <kdirlister.h> 0039 #include <kio/jobclasses.h> 0040 #include <kio/job.h> 0041 #include <QInputDialog> 0042 #include <KLocalizedString> 0043 #include <QUrl> 0044 0045 #include <QFileInfo> 0046 0047 #include <typeinfo> 0048 0049 using namespace Meta; 0050 using namespace Playlists; 0051 0052 PlaylistManager *PlaylistManager::s_instance = nullptr; 0053 0054 namespace The 0055 { 0056 PlaylistManager *playlistManager() { return PlaylistManager::instance(); } 0057 } 0058 0059 PlaylistManager * 0060 PlaylistManager::instance() 0061 { 0062 return s_instance ? s_instance : new PlaylistManager(); 0063 } 0064 0065 void 0066 PlaylistManager::destroy() 0067 { 0068 if (s_instance) { 0069 delete s_instance; 0070 s_instance = nullptr; 0071 } 0072 } 0073 0074 PlaylistManager::PlaylistManager() 0075 { 0076 s_instance = this; 0077 0078 m_syncRelStore = new KConfigSyncRelStore(); 0079 0080 m_playlistFileProvider = new Playlists::PlaylistFileProvider(); 0081 addProvider( m_playlistFileProvider, UserPlaylist ); 0082 0083 m_defaultPodcastProvider = new Podcasts::SqlPodcastProvider(); 0084 addProvider( m_defaultPodcastProvider, PlaylistManager::PodcastChannel ); 0085 CollectionManager::instance()->addTrackProvider( m_defaultPodcastProvider ); 0086 0087 m_defaultUserPlaylistProvider = new Playlists::SqlUserPlaylistProvider(); 0088 addProvider( m_defaultUserPlaylistProvider, UserPlaylist ); 0089 0090 } 0091 0092 PlaylistManager::~PlaylistManager() 0093 { 0094 delete m_defaultPodcastProvider; 0095 delete m_defaultUserPlaylistProvider; 0096 delete m_playlistFileProvider; 0097 delete m_syncRelStore; 0098 } 0099 0100 bool 0101 PlaylistManager::hasToSync( Playlists::PlaylistPtr master, Playlists::PlaylistPtr slave ) 0102 { 0103 DEBUG_BLOCK 0104 debug() << "master: " << master->uidUrl(); 0105 debug() << "slave: " << slave->uidUrl(); 0106 0107 if( !m_syncRelStore ) 0108 return false; 0109 0110 return m_syncRelStore->hasToSync( master, slave ); 0111 } 0112 0113 void 0114 PlaylistManager::addProvider( Playlists::PlaylistProvider *provider, int category ) 0115 { 0116 bool newCategory = false; 0117 if( !m_providerMap.uniqueKeys().contains( category ) ) 0118 newCategory = true; 0119 0120 //disconnect all signals connected to this object to be sure. 0121 provider->disconnect( this, nullptr ); 0122 0123 m_providerMap.insert( category, provider ); 0124 connect( provider, &Playlists::PlaylistProvider::updated, 0125 this, &PlaylistManager::slotUpdated ); 0126 connect( provider, &Playlists::PlaylistProvider::playlistAdded, 0127 this, &PlaylistManager::slotPlaylistAdded ); 0128 connect( provider, &Playlists::PlaylistProvider::playlistRemoved, 0129 this, &PlaylistManager::slotPlaylistRemoved ); 0130 0131 if( newCategory ) 0132 Q_EMIT categoryAdded( category ); 0133 0134 Q_EMIT providerAdded( provider, category ); 0135 Q_EMIT updated( category ); 0136 0137 loadPlaylists( provider, category ); 0138 } 0139 0140 void 0141 PlaylistManager::loadPlaylists( Playlists::PlaylistProvider *provider, int category ) 0142 { 0143 foreach( Playlists::PlaylistPtr playlist, provider->playlists() ) 0144 addPlaylist( playlist, category ); 0145 } 0146 0147 void 0148 PlaylistManager::addPlaylist( Playlists::PlaylistPtr playlist, int category ) 0149 { 0150 SyncedPlaylistPtr syncedPlaylist = m_syncRelStore->asSyncedPlaylist( playlist ); 0151 0152 //NULL when not synced or a slave added before it's master copy ("early slave") 0153 if( syncedPlaylist ) 0154 { 0155 if( !m_syncedPlaylistMap.keys().contains( syncedPlaylist ) ) 0156 { 0157 //this can only happen when playlist == the master of the syncedPlaylist 0158 0159 //Search for any slaves created before their master ("early slaves") 0160 //To set-up a sync between them 0161 //Only search in the category of the new playlist, i.e. no cross category syncing. 0162 foreach( Playlists::PlaylistPtr existingPlaylist, m_playlistMap.values( category ) ) 0163 { 0164 //If this is a slave asSyncedPlaylist() will make it part of the syncedPlaylist 0165 if( m_syncRelStore->asSyncedPlaylist( existingPlaylist ) == syncedPlaylist ) 0166 { 0167 m_playlistMap.remove( category, existingPlaylist ); 0168 0169 if( !m_syncedPlaylistMap.values( syncedPlaylist ).contains( existingPlaylist ) ) 0170 m_syncedPlaylistMap.insert( syncedPlaylist, existingPlaylist ); 0171 } 0172 } 0173 } 0174 0175 if( !m_syncedPlaylistMap.values( syncedPlaylist ).contains( playlist ) ) 0176 { 0177 m_syncedPlaylistMap.insert( syncedPlaylist, playlist ); 0178 0179 //The synchronization will be done in the next mainloop run 0180 m_syncNeeded.append( syncedPlaylist ); 0181 QTimer::singleShot( 0, this, &PlaylistManager::slotSyncNeeded ); 0182 } 0183 0184 //deliberately reusing the passed argument 0185 playlist = PlaylistPtr::dynamicCast( syncedPlaylist ); 0186 0187 if( m_playlistMap.values( category ).contains( playlist ) ) 0188 { 0189 //no need to add it again but do let the model know something changed. 0190 Q_EMIT playlistUpdated( playlist, category ); 0191 return; 0192 } 0193 } 0194 0195 m_playlistMap.insert( category, playlist ); 0196 //reemit so models know about new playlist in their category 0197 Q_EMIT playlistAdded( playlist, category ); 0198 } 0199 0200 void 0201 PlaylistManager::removeProvider( Playlists::PlaylistProvider *provider ) 0202 { 0203 DEBUG_BLOCK 0204 0205 if( !provider ) 0206 return; 0207 0208 if( !m_providerMap.values( provider->category() ).contains( provider ) ) 0209 { 0210 return; 0211 } 0212 0213 removePlaylists( provider ); 0214 0215 m_providerMap.remove( provider->category(), provider ); 0216 0217 Q_EMIT providerRemoved( provider, provider->category() ); 0218 Q_EMIT updated( provider->category() ); 0219 } 0220 0221 void 0222 PlaylistManager::removePlaylists( Playlists::PlaylistProvider *provider ) 0223 { 0224 foreach( Playlists::PlaylistPtr playlist, m_playlistMap.values( provider->category() ) ) 0225 if( playlist->provider() && playlist->provider() == provider ) 0226 { 0227 foreach( SyncedPlaylistPtr syncedPlaylist, m_syncedPlaylistMap.keys( playlist ) ) 0228 m_syncedPlaylistMap.remove( syncedPlaylist, playlist ); 0229 0230 removePlaylist( playlist, provider->category() ); 0231 } 0232 } 0233 0234 void 0235 PlaylistManager::removePlaylist( Playlists::PlaylistPtr playlist, int category ) 0236 { 0237 if( auto syncedPlaylist = SyncedPlaylistPtr::dynamicCast( playlist ) ) 0238 { 0239 //TODO: this might be wrong if there were multiple playlists from the same provider. 0240 //remove the specific child playlist, not all from same provider. 0241 syncedPlaylist->removePlaylistsFrom( playlist->provider() ); 0242 if( syncedPlaylist->isEmpty() ) 0243 m_playlistMap.remove( category, playlist ); 0244 0245 m_syncNeeded.removeAll( syncedPlaylist ); 0246 } 0247 else 0248 { 0249 m_playlistMap.remove( category, playlist ); 0250 } 0251 0252 Q_EMIT playlistRemoved( playlist, category ); 0253 } 0254 0255 void 0256 PlaylistManager::slotUpdated() 0257 { 0258 Playlists::PlaylistProvider *provider = 0259 dynamic_cast<Playlists::PlaylistProvider *>( QObject::sender() ); 0260 if( !provider ) 0261 return; 0262 0263 //forceful reload all the providers playlists. 0264 //This is an expensive operation, the provider should use playlistAdded/Removed signals instead. 0265 removePlaylists( provider ); 0266 loadPlaylists( provider, provider->category() ); 0267 Q_EMIT updated( provider->category() ); 0268 } 0269 0270 void 0271 PlaylistManager::slotPlaylistAdded( Playlists::PlaylistPtr playlist ) 0272 { 0273 addPlaylist( playlist, playlist->provider()->category() ); 0274 } 0275 0276 void 0277 PlaylistManager::slotPlaylistRemoved( Playlists::PlaylistPtr playlist ) 0278 { 0279 removePlaylist( playlist, playlist->provider()->category() ); 0280 } 0281 0282 Playlists::PlaylistList 0283 PlaylistManager::playlistsOfCategory( int playlistCategory ) 0284 { 0285 return m_playlistMap.values( playlistCategory ); 0286 } 0287 0288 PlaylistProviderList 0289 PlaylistManager::providersForCategory( int playlistCategory ) 0290 { 0291 return m_providerMap.values( playlistCategory ); 0292 } 0293 0294 Playlists::PlaylistProvider * 0295 PlaylistManager::playlistProvider(int category, QString name) 0296 { 0297 QList<Playlists::PlaylistProvider *> providers( m_providerMap.values( category ) ); 0298 0299 QListIterator<Playlists::PlaylistProvider *> i(providers); 0300 while( i.hasNext() ) 0301 { 0302 Playlists::PlaylistProvider * p = static_cast<Playlists::PlaylistProvider *>( i.next() ); 0303 if( p->prettyName() == name ) 0304 return p; 0305 } 0306 0307 return nullptr; 0308 } 0309 0310 bool 0311 PlaylistManager::save( Meta::TrackList tracks, const QString &name, 0312 Playlists::PlaylistProvider *toProvider, bool editName ) 0313 { 0314 //if toProvider is 0 use the default Playlists::UserPlaylistProvider (SQL) 0315 Playlists::UserPlaylistProvider *prov = toProvider 0316 ? qobject_cast<Playlists::UserPlaylistProvider *>( toProvider ) 0317 : m_defaultUserPlaylistProvider; 0318 if( !prov || !prov->isWritable() ) 0319 return false; 0320 0321 Playlists::PlaylistPtr playlist = prov->save( tracks, name ); 0322 if( playlist.isNull() ) 0323 return false; 0324 0325 if( editName ) 0326 rename( playlist ); 0327 return true; 0328 } 0329 0330 bool 0331 PlaylistManager::import( const QUrl& fromLocation ) 0332 { 0333 // used by: PlaylistBrowserNS::UserModel::dropMimeData() 0334 AMAROK_DEPRECATED 0335 DEBUG_BLOCK 0336 if( !m_playlistFileProvider ) 0337 { 0338 debug() << "ERROR: m_playlistFileProvider was null"; 0339 return false; 0340 } 0341 return m_playlistFileProvider->import( fromLocation ); 0342 } 0343 0344 void 0345 PlaylistManager::rename( Playlists::PlaylistPtr playlist ) 0346 { 0347 if( playlist.isNull() ) 0348 return; 0349 0350 AmarokUrl(QStringLiteral("amarok://navigate/playlists/user playlists")).run(); 0351 Q_EMIT renamePlaylist( playlist ); // connected to PlaylistBrowserModel 0352 } 0353 0354 bool 0355 PlaylistManager::rename( PlaylistPtr playlist, const QString &newName ) 0356 { 0357 Playlists::UserPlaylistProvider *provider = 0358 qobject_cast<Playlists::UserPlaylistProvider *>( playlist->provider() ); 0359 if( !provider || !provider->isWritable() ) 0360 return false; 0361 0362 provider->renamePlaylist( playlist, newName ); 0363 return true; 0364 } 0365 0366 bool 0367 PlaylistManager::deletePlaylists( Playlists::PlaylistList playlistlist ) 0368 { 0369 // Map the playlists to their respective providers 0370 QHash<Playlists::UserPlaylistProvider*, Playlists::PlaylistList> provLists; 0371 foreach( Playlists::PlaylistPtr playlist, playlistlist ) 0372 { 0373 // Get the providers of the respective playlists 0374 Playlists::UserPlaylistProvider *prov = qobject_cast<Playlists::UserPlaylistProvider *>( 0375 getProvidersForPlaylist( playlist ).first() ); 0376 0377 if( prov ) 0378 { 0379 Playlists::PlaylistList pllist; 0380 pllist << playlist; 0381 // If the provider already has at least one playlist to delete, add another to its list 0382 if( provLists.contains( prov ) ) 0383 { 0384 provLists[ prov ] << pllist; 0385 0386 } 0387 // If we are adding a new provider, put it in the hash, initialize its list 0388 else 0389 provLists.insert( prov, pllist ); 0390 } 0391 } 0392 0393 // Pass each list of playlists to the respective provider for deletion 0394 0395 bool removedSuccess = true; 0396 foreach( Playlists::UserPlaylistProvider* prov, provLists.keys() ) 0397 { 0398 removedSuccess = prov->deletePlaylists( provLists.value( prov ) ) && removedSuccess; 0399 } 0400 0401 return removedSuccess; 0402 } 0403 0404 QList<Playlists::PlaylistProvider*> 0405 PlaylistManager::getProvidersForPlaylist( const Playlists::PlaylistPtr playlist ) 0406 { 0407 QList<Playlists::PlaylistProvider*> providers; 0408 0409 if( playlist.isNull() ) 0410 return providers; 0411 0412 SyncedPlaylistPtr syncedPlaylist = SyncedPlaylistPtr::dynamicCast( playlist ); 0413 if( syncedPlaylist && m_syncedPlaylistMap.keys().contains( syncedPlaylist ) ) 0414 { 0415 foreach( Playlists::PlaylistPtr playlist, m_syncedPlaylistMap.values( syncedPlaylist ) ) 0416 if( !providers.contains( playlist->provider() ) ) 0417 providers << playlist->provider(); 0418 0419 return providers; 0420 } 0421 0422 Playlists::PlaylistProvider* provider = playlist->provider(); 0423 if( provider ) 0424 return providers << provider; 0425 0426 //Iteratively check all providers' playlists for ownership 0427 QList< Playlists::PlaylistProvider* > userPlaylists = m_providerMap.values( UserPlaylist ); 0428 foreach( Playlists::PlaylistProvider* provider, userPlaylists ) 0429 { 0430 if( provider->playlists().contains( playlist ) ) 0431 return providers << provider; 0432 } 0433 0434 return providers; 0435 } 0436 0437 0438 bool 0439 PlaylistManager::isWritable( const Playlists::PlaylistPtr &playlist ) 0440 { 0441 Playlists::UserPlaylistProvider *provider 0442 = qobject_cast<Playlists::UserPlaylistProvider *>( getProvidersForPlaylist( playlist ).first() ); 0443 0444 if( provider ) 0445 return provider->isWritable(); 0446 else 0447 return false; 0448 } 0449 0450 void 0451 PlaylistManager::completePodcastDownloads() 0452 { 0453 foreach( Playlists::PlaylistProvider *prov, providersForCategory( PodcastChannel ) ) 0454 { 0455 Podcasts::PodcastProvider *podcastProvider = dynamic_cast<Podcasts::PodcastProvider *>( prov ); 0456 if( !podcastProvider ) 0457 continue; 0458 0459 podcastProvider->completePodcastDownloads(); 0460 } 0461 } 0462 0463 void 0464 PlaylistManager::setupSync( const Playlists::PlaylistPtr master, const Playlists::PlaylistPtr slave ) 0465 { 0466 DEBUG_BLOCK 0467 debug() << "master: " << master->uidUrl(); 0468 debug() << "slave: " << slave->uidUrl(); 0469 0470 //If there is no sync relation established between these two, then we must setup a sync. 0471 if( hasToSync( master, slave ) ) 0472 return; 0473 0474 Playlists::PlaylistPtr tempMaster; 0475 Playlists::PlaylistPtr tempSlave; 0476 0477 m_syncRelStore->addSync( master, slave ); 0478 0479 foreach( const Playlists::PlaylistPtr tempPlaylist, m_playlistMap ) 0480 { 0481 if( master == tempPlaylist ) 0482 { 0483 tempMaster = tempPlaylist; 0484 break; 0485 } 0486 } 0487 0488 foreach( const Playlists::PlaylistPtr tempPlaylist, m_playlistMap ) 0489 { 0490 if( slave == tempPlaylist ) 0491 { 0492 tempSlave = tempPlaylist; 0493 break; 0494 } 0495 } 0496 0497 if( tempMaster && tempSlave ) 0498 { 0499 SyncedPlaylistPtr syncedPlaylist = m_syncRelStore->asSyncedPlaylist( tempMaster ); 0500 0501 m_syncRelStore->asSyncedPlaylist( tempSlave ); 0502 0503 Playlists::PlaylistPtr syncedPlaylistPtr = 0504 Playlists::PlaylistPtr::dynamicCast( syncedPlaylist ); 0505 0506 int category = syncedPlaylist->master()->provider()->category(); 0507 0508 if( !m_playlistMap.values( category ).contains( syncedPlaylistPtr ) ) 0509 { 0510 removePlaylist( tempMaster, tempMaster->provider()->category() ); 0511 removePlaylist( tempSlave, tempSlave->provider()->category() ); 0512 0513 m_syncedPlaylistMap.insert( syncedPlaylist, tempMaster ); 0514 m_syncedPlaylistMap.insert( syncedPlaylist, tempSlave ); 0515 0516 m_playlistMap.insert( category, syncedPlaylistPtr ); 0517 //reemit so models know about new playlist in their category 0518 Q_EMIT playlistAdded( syncedPlaylistPtr, category ); 0519 } 0520 } 0521 } 0522 0523 void PlaylistManager::slotSyncNeeded() 0524 { 0525 foreach( SyncedPlaylistPtr syncedPlaylist, m_syncNeeded ) 0526 if ( syncedPlaylist->syncNeeded() ) 0527 syncedPlaylist->doSync(); 0528 0529 m_syncNeeded.clear(); 0530 } 0531