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 }