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