File indexing completed on 2024-05-05 04:48:49

0001 /****************************************************************************************
0002  * Copyright (c) 2010 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 "SyncedPlaylist.h"
0019 
0020 #include "core/meta/Meta.h"
0021 #include "core/playlists/PlaylistProvider.h"
0022 #include "core/support/Debug.h"
0023 
0024 #include <KLocalizedString>
0025 
0026 using namespace Meta;
0027 
0028 SyncedPlaylist::SyncedPlaylist( const Playlists::PlaylistPtr &playlist )
0029 {
0030     addPlaylist( playlist );
0031 }
0032 
0033 QUrl
0034 SyncedPlaylist::uidUrl() const
0035 {
0036     return QUrl( QStringLiteral( "amarok-syncedplaylist://" ) +  m_playlists.first()->name() );
0037 }
0038 
0039 QString
0040 SyncedPlaylist::name() const
0041 {
0042     if( isEmpty() )
0043         return i18n( "<Empty>" );
0044     return m_playlists.first()->name();
0045 }
0046 
0047 QString
0048 SyncedPlaylist::prettyName() const
0049 {
0050     if( isEmpty() )
0051         return i18n( "<Empty>" );
0052     return m_playlists.first()->prettyName();
0053 }
0054 
0055 Playlists::PlaylistProvider*
0056 SyncedPlaylist::provider() const
0057 {
0058     return m_playlists.first()->provider();
0059 }
0060 
0061 TrackList
0062 SyncedPlaylist::tracks()
0063 {
0064     if( isEmpty() )
0065         return TrackList();
0066 
0067     return m_playlists.first()->tracks();
0068 }
0069 
0070 int
0071 SyncedPlaylist::trackCount() const
0072 {
0073     if( isEmpty() )
0074         return -1;
0075 
0076     return m_playlists.first()->trackCount();
0077 }
0078 
0079 void
0080 SyncedPlaylist::addTrack( const Meta::TrackPtr &track, int position )
0081 {
0082     //only apply it to the first, the rest will follow in trackAdded()
0083     m_playlists.first()->addTrack( track, position );
0084 }
0085 
0086 void
0087 SyncedPlaylist::removeTrack( int position )
0088 {
0089     //only apply it to the first, the rest will follow in trackRemoved()
0090     m_playlists.first()->removeTrack( position );
0091 }
0092 
0093 void
0094 SyncedPlaylist::metadataChanged( const Playlists::PlaylistPtr &playlist )
0095 {
0096     if( !m_playlists.contains( playlist ) )
0097         return;
0098 
0099     // we pass on every subplaylist change because our description changes
0100     notifyObserversMetadataChanged();
0101 }
0102 
0103 void
0104 SyncedPlaylist::tracksLoaded( Playlists::PlaylistPtr playlist )
0105 {
0106     if( !m_playlists.contains( playlist ) )
0107         return;
0108 
0109     // TODO: me may give more thought to this and Q_EMIT tracksLoaded() only when all subplaylists load
0110     notifyObserversTracksLoaded();
0111 }
0112 
0113 void
0114 SyncedPlaylist::trackAdded( const Playlists::PlaylistPtr &playlist, const TrackPtr &track, int position )
0115 {
0116     if( !m_playlists.contains( playlist ) )
0117         return;
0118 
0119     if( playlist != m_playlists.first() )
0120         return; //we only apply changes to the master playlist to the others
0121 
0122     //update the others
0123     foreach( Playlists::PlaylistPtr playlistToUpdate, m_playlists )
0124     {
0125         if( playlistToUpdate == playlist )
0126             continue; //no use applying to the one that has already changed
0127         playlistToUpdate->addTrack( track, position );
0128     }
0129 
0130     //pass it on
0131     notifyObserversTrackAdded( track, position );
0132 }
0133 
0134 void
0135 SyncedPlaylist::trackRemoved( const Playlists::PlaylistPtr &playlist, int position )
0136 {
0137     if( !m_playlists.contains( playlist ) )
0138         return;
0139 
0140     if( playlist != m_playlists.first() )
0141         return; //we only apply changes to the master playlist to the others
0142 
0143     //update the others
0144     foreach( Playlists::PlaylistPtr playlistToUpdate, m_playlists )
0145     {
0146         if( playlistToUpdate == playlist )
0147             continue; //no use applying to the one that has already changed
0148         playlistToUpdate->removeTrack( position );
0149     }
0150 
0151     //pass it on
0152     notifyObserversTrackRemoved( position );
0153 }
0154 
0155 bool
0156 SyncedPlaylist::isEmpty() const
0157 {
0158     return m_playlists.isEmpty();
0159 }
0160 
0161 void
0162 SyncedPlaylist::addPlaylist( Playlists::PlaylistPtr playlist )
0163 {
0164     if( m_playlists.contains( playlist ) )
0165         return;
0166 
0167     //Only subscribe to the master playlist's changes
0168     if( m_playlists.isEmpty() )
0169         subscribeTo( playlist );
0170     else
0171     {
0172         //Deny syncing between playlists in the same provider because
0173         //there is no use case for it and it does make the code more complex
0174         if ( (*(m_playlists.begin()))->provider() == playlist->provider() )
0175         {
0176             error() << "BUG: You cannot synchronize playlists with the same provider!!!";
0177             return;
0178         }
0179     }
0180 
0181     m_playlists << playlist;
0182 }
0183 
0184 bool
0185 SyncedPlaylist::syncNeeded() const
0186 {
0187     DEBUG_BLOCK
0188     if( isEmpty() || m_playlists.count() == 1 )
0189         return false;
0190 
0191     /* Use the first playlist as the base, if the others have a difference
0192        compared to it a sync is needed */
0193 
0194     QList<Playlists::PlaylistPtr>::const_iterator i = m_playlists.begin();
0195     Playlists::PlaylistPtr master = *i;
0196     int masterTrackCount = master->trackCount();
0197     ++i; //From now on its only slaves on the iterator
0198     debug() << "Master Playlist: " << master->name() << " - " << master->uidUrl().url();
0199     debug() << "Master track count: " << masterTrackCount;
0200 
0201     for( ;i != m_playlists.end(); ++i)
0202     {
0203 
0204         //Playlists::PlaylistPtr slave = i.next();
0205         Playlists::PlaylistPtr slave = *i;
0206 
0207         debug() << "Slave Playlist: " << slave->name() << " - " << slave->uidUrl().url();
0208         if( masterTrackCount != -1 )
0209         {
0210             int slaveTrackCount = slave->trackCount();
0211             debug() << "Slave track count: " << slaveTrackCount;
0212             //If the number of tracks is different a sync is certainly required
0213             if( slaveTrackCount != -1 && slaveTrackCount != masterTrackCount )
0214                 return true;
0215         }
0216 
0217         //Compare track by track
0218         debug() << "Comparing track by track";
0219 
0220         TrackList masterTracks = master->tracks();
0221         TrackList slaveTracks = slave->tracks();
0222 
0223         for( int i = 0; i < masterTrackCount; i++ )
0224             if( !( *masterTracks[i] == *slaveTracks[i] ) )
0225                 return true;
0226 
0227     }
0228 
0229     debug() << "No sync needed";
0230 
0231     return false;
0232 }
0233 
0234 void
0235 SyncedPlaylist::doSync() const
0236 {
0237     DEBUG_BLOCK
0238 
0239     QList<Playlists::PlaylistPtr>::const_iterator i = m_playlists.begin();
0240     Playlists::PlaylistPtr master = *i;
0241     ++i; //From now on its only slaves on the iterator
0242 
0243     QListIterator<TrackPtr> m( master->tracks() );
0244     //debug: print list
0245     int position = 0;
0246     debug() << "Master Playlist: " << master->name() << " - " << master->uidUrl().url();
0247     while( m.hasNext() )
0248         debug() << QStringLiteral( "%1 : %2" ).arg( position++ ).arg( m.next()->name() );
0249     m.toFront();
0250 
0251     for( ;i != m_playlists.end(); ++i)
0252     {
0253         Playlists::PlaylistPtr slave = *i;
0254         TrackList slaveTracks = slave->tracks();
0255         //debug: print list
0256         position = 0;
0257         debug() << "Slave Playlist: " << slave->name() << " - " << slave->uidUrl().url();
0258         foreach( const TrackPtr track, slaveTracks )
0259             debug() << QStringLiteral( "%1 : %2" ).arg( position++ ).arg( track->name() );
0260 
0261         //Add first. Tracks in slave that are not in master will eventually shift to the end.
0262         position = 0;
0263         while( m.hasNext() )
0264         {
0265             TrackPtr track = m.next();
0266             if( position >= slaveTracks.size()
0267                     || track->uidUrl() != slaveTracks.at( position )->uidUrl() )
0268             {
0269                 debug() << QStringLiteral( "insert %2 at %1" ).arg( position ).arg( track->name() );
0270                 slave->addTrack( track, position );
0271 
0272                 slave->syncTrackStatus( position, track );
0273 
0274                 //update local copy of the tracks
0275                 slaveTracks = slave->tracks();
0276             }
0277             position++;
0278         }
0279 
0280         //debug: print list
0281         position = 0;
0282         debug() << "slave playlist after insertions:";
0283         foreach( const TrackPtr track, slaveTracks )
0284             debug() << QStringLiteral( "%1 : %2" ).arg( position++ ).arg( track->name() );
0285 
0286         //Then remove everything after the position of the last track in master.
0287         //This removes any tracks that are not in master.
0288         position = master->tracks().size();
0289 
0290         for( int removeCount = slave->trackCount() - 1; removeCount >= 0; removeCount-- )
0291             slave->removeTrack( position );
0292 
0293         //debug: print list
0294         position = 0;
0295         debug() << "slave playlist after removal:";
0296         foreach( const TrackPtr track, slave->tracks() )
0297             debug() << QStringLiteral( "%1 : %2" ).arg( position++ ).arg( track->name() );
0298 
0299     }
0300 }
0301 
0302 void
0303 SyncedPlaylist::removePlaylistsFrom( Playlists::PlaylistProvider *provider )
0304 {
0305     foreach( Playlists::PlaylistPtr playlist, m_playlists )
0306     {
0307         if( playlist->provider() == provider )
0308         {
0309             unsubscribeFrom( playlist );
0310             m_playlists.removeAll( playlist );
0311         }
0312     }
0313 }
0314 
0315 Playlists::PlaylistPtr SyncedPlaylist::master() const
0316 {
0317     if( m_playlists.isEmpty() )
0318         return Playlists::PlaylistPtr();
0319 
0320     return m_playlists.first();
0321 }
0322 
0323 Playlists::PlaylistList SyncedPlaylist::slaves() const
0324 {
0325     if( m_playlists.size() < 2 )
0326         return Playlists::PlaylistList();
0327 
0328     Playlists::PlaylistList slaves;
0329 
0330     std::copy( m_playlists.begin() + 1, m_playlists.end(), slaves.begin() );
0331 
0332     return slaves;
0333 }