File indexing completed on 2024-05-05 04:49:22
0001 /**************************************************************************************** 0002 * Copyright (c) 2009 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 #include "SynchronizationBaseJob.h" 0018 0019 #include "core/collections/Collection.h" 0020 #include "core/collections/QueryMaker.h" 0021 #include "core/meta/Meta.h" 0022 #include "core/meta/support/MetaConstants.h" 0023 #include "core/support/Debug.h" 0024 0025 #include <QMetaEnum> 0026 #include <QMetaObject> 0027 #include <QHashIterator> 0028 0029 //this class might cause SqlQueryMaker to generate very large SQL statements 0030 //see http://dev.mysql.com/doc/refman/5.0/en/packet-too-large.html 0031 //if it turns out that the generated SQL query is too large 0032 0033 SynchronizationBaseJob::SynchronizationBaseJob() 0034 : QObject() 0035 , m_state( NotStarted ) 0036 , m_currentResultCount( 0 ) 0037 , m_collectionA( nullptr ) 0038 , m_collectionB( nullptr ) 0039 { 0040 connect( &m_timer, &QTimer::timeout, this, &SynchronizationBaseJob::timeout ); 0041 //abort syncing if both queries have not returned within 30 seconds for a given state 0042 //probably needs to be adjusted during testing 0043 m_timer.setInterval( 30000 ); 0044 m_timer.setSingleShot( true ); 0045 } 0046 0047 SynchronizationBaseJob::~SynchronizationBaseJob() 0048 { 0049 //nothing to do 0050 } 0051 0052 void 0053 SynchronizationBaseJob::setCollectionA( Collections::Collection *collection ) 0054 { 0055 m_collectionA = collection; 0056 } 0057 0058 void 0059 SynchronizationBaseJob::setCollectionB( Collections::Collection *collection ) 0060 { 0061 m_collectionB = collection; 0062 } 0063 0064 void 0065 SynchronizationBaseJob::setFilter( const QString &filter ) 0066 { 0067 Q_UNUSED( filter ) 0068 } 0069 0070 Collections::QueryMaker* 0071 SynchronizationBaseJob::createQueryMaker( Collections::Collection *collection ) 0072 { 0073 //TODO: apply filters. This allows us to only sync a subset of a collection 0074 Collections::QueryMaker *qm = collection->queryMaker(); 0075 qm->setAutoDelete( true ); 0076 m_queryMakers.insert( qm, collection ); 0077 return qm; 0078 } 0079 0080 void 0081 SynchronizationBaseJob::synchronize() 0082 { 0083 DEBUG_BLOCK 0084 if( !m_collectionA || !m_collectionB ) 0085 { 0086 debug() << "aborting synchronization, at least one collection is missing"; 0087 deleteLater(); 0088 return; 0089 } 0090 m_state = ComparingArtists; 0091 setupArtistQuery( m_collectionA )->run(); 0092 setupArtistQuery( m_collectionB )->run(); 0093 m_timer.start(); 0094 } 0095 0096 void 0097 SynchronizationBaseJob::timeout() 0098 { 0099 const QMetaObject *mo = metaObject(); 0100 QMetaEnum me = mo->enumerator( mo->indexOfEnumerator( "State" ) ); 0101 debug() << "syncing aborted due to a timeout in state " << me.valueToKey( m_state ); 0102 deleteLater(); 0103 } 0104 0105 void 0106 SynchronizationBaseJob::slotQueryDone() 0107 { 0108 DEBUG_BLOCK; 0109 m_currentResultCount += 1; 0110 if( m_currentResultCount < 2 ) 0111 return; 0112 m_currentResultCount = 0; 0113 0114 m_timer.stop(); 0115 switch( m_state ) 0116 { 0117 case ComparingArtists: 0118 { 0119 m_state = ComparingAlbums; 0120 handleArtistResult(); 0121 break; 0122 } 0123 case ComparingAlbums: 0124 { 0125 m_state = ComparingTracks; 0126 handleAlbumResult(); 0127 break; 0128 } 0129 case ComparingTracks: 0130 { 0131 m_state = Syncing; 0132 handleTrackResult(); 0133 break; 0134 } 0135 default: 0136 { 0137 const QMetaObject *mo = metaObject(); 0138 QMetaEnum me = mo->enumerator( mo->indexOfEnumerator( "State" ) ); 0139 debug() << "detected state " << me.valueToKey( m_state ) << " in slotQueryDone(), do not know how to handle this. Aborting"; 0140 deleteLater(); 0141 break; 0142 } 0143 } 0144 } 0145 0146 Collections::QueryMaker* 0147 SynchronizationBaseJob::setupArtistQuery( Collections::Collection *coll ) 0148 { 0149 Collections::QueryMaker *qm = createQueryMaker( coll ); 0150 qm->setQueryType( Collections::QueryMaker::Artist ); 0151 connect( qm, &Collections::QueryMaker::queryDone, this, &SynchronizationBaseJob::slotQueryDone, Qt::QueuedConnection ); 0152 connect( qm, &Collections::QueryMaker::newArtistsReady, this, &SynchronizationBaseJob::slotArtistsReady, Qt::QueuedConnection ); 0153 return qm; 0154 } 0155 0156 Collections::QueryMaker* 0157 SynchronizationBaseJob::setupAlbumQuery( Collections::Collection *coll ) 0158 { 0159 Collections::QueryMaker *qm = createQueryMaker( coll ); 0160 qm->setQueryType( Collections::QueryMaker::Album ); 0161 connect( qm, &Collections::QueryMaker::queryDone, this, &SynchronizationBaseJob::slotQueryDone, Qt::QueuedConnection ); 0162 connect( qm, &Collections::QueryMaker::newAlbumsReady, this, &SynchronizationBaseJob::slotAlbumsReady, Qt::QueuedConnection ); 0163 return qm; 0164 } 0165 0166 Collections::QueryMaker* 0167 SynchronizationBaseJob::setupTrackQuery( Collections::Collection *coll ) 0168 { 0169 Collections::QueryMaker *qm = createQueryMaker( coll ); 0170 qm->setQueryType( Collections::QueryMaker::Track ); 0171 connect( qm, &Collections::QueryMaker::queryDone, this, &SynchronizationBaseJob::slotQueryDone, Qt::QueuedConnection ); 0172 connect( qm, &Collections::QueryMaker::newTracksReady, this, &SynchronizationBaseJob::slotTracksReady, Qt::QueuedConnection ); 0173 return qm; 0174 } 0175 0176 void 0177 SynchronizationBaseJob::slotArtistsReady( const Meta::ArtistList &artists ) 0178 { 0179 DEBUG_BLOCK; 0180 Collections::Collection *senderColl = m_queryMakers.value( qobject_cast<Collections::QueryMaker*>(sender()) ); 0181 QSet<QString> artistSet; 0182 foreach( const Meta::ArtistPtr &artist, artists ) 0183 { 0184 if( artist ) 0185 artistSet.insert( artist->name() ); 0186 } 0187 if( senderColl == m_collectionA ) 0188 { 0189 m_artistsA.unite( artistSet ); 0190 } 0191 else if( senderColl == m_collectionB ) 0192 { 0193 m_artistsB.unite( artistSet ); 0194 } 0195 else 0196 { 0197 //huh? how did we get here? 0198 } 0199 } 0200 0201 void 0202 SynchronizationBaseJob::slotAlbumsReady( const Meta::AlbumList &albums ) 0203 { 0204 DEBUG_BLOCK 0205 Collections::Collection *senderColl = m_queryMakers.value( qobject_cast<Collections::QueryMaker*>(sender()) ); 0206 QSet<Meta::AlbumKey> albumSet; 0207 foreach( const Meta::AlbumPtr &albumPtr, albums ) 0208 { 0209 albumSet.insert( Meta::AlbumKey( albumPtr ) ); 0210 } 0211 if( senderColl == m_collectionA ) 0212 { 0213 m_albumsA.unite( albumSet ); 0214 } 0215 else if( senderColl == m_collectionB ) 0216 { 0217 m_albumsB.unite( albumSet ); 0218 } 0219 else 0220 { 0221 //huh? how did we get here? 0222 } 0223 } 0224 0225 void 0226 SynchronizationBaseJob::slotTracksReady( const Meta::TrackList &tracks ) 0227 { 0228 DEBUG_BLOCK 0229 Collections::Collection *senderColl = m_queryMakers.value( qobject_cast<Collections::QueryMaker*>(sender()) ); 0230 if( senderColl == m_collectionA ) 0231 { 0232 0233 foreach( const Meta::TrackPtr &track, tracks ) 0234 { 0235 Meta::TrackKey key( track ); 0236 m_tracksA.insert( key ); 0237 m_keyToTrackA.insert( key, track ); 0238 } 0239 } 0240 else if( senderColl == m_collectionB ) 0241 { 0242 foreach( const Meta::TrackPtr &track, tracks ) 0243 { 0244 Meta::TrackKey key( track ); 0245 m_tracksB.insert( key ); 0246 m_keyToTrackB.insert( key, track ); 0247 } 0248 } 0249 else 0250 { 0251 //huh? how did we get here? 0252 } 0253 } 0254 0255 void 0256 SynchronizationBaseJob::handleArtistResult() 0257 { 0258 DEBUG_BLOCK 0259 QSet<QString> artistsOnlyInA = m_artistsA - m_artistsB; 0260 QSet<QString> artistsOnlyInB = m_artistsB - m_artistsA; 0261 QSet<QString> artistsInBoth = m_artistsA & m_artistsB; 0262 foreach( const QString &artist, artistsOnlyInA ) 0263 { 0264 m_artistResult.insert( artist, OnlyInA ); 0265 } 0266 foreach( const QString &artist, artistsOnlyInB ) 0267 { 0268 m_artistResult.insert( artist, OnlyInB ); 0269 } 0270 foreach( const QString &artist, artistsInBoth ) 0271 { 0272 m_artistResult.insert( artist, InBoth ); 0273 } 0274 Collections::QueryMaker *qmA = setupAlbumQuery( m_collectionA ); 0275 Collections::QueryMaker *qmB = setupAlbumQuery( m_collectionB ); 0276 //we are going to exclude artists below, so make sure we exclude all of them by setting the QMs to And mode 0277 qmA->beginAnd(); 0278 qmB->beginAnd(); 0279 QHashIterator<QString, InSet> iter( m_artistResult ); 0280 while( iter.hasNext() ) 0281 { 0282 iter.next(); 0283 QString artist = iter.key(); 0284 InSet currentStatus = iter.value(); 0285 if( currentStatus == OnlyInA ) 0286 { 0287 qmA->excludeFilter( Meta::valArtist, artist, true, true ); 0288 } 0289 if( currentStatus == OnlyInB ) 0290 { 0291 qmB->excludeFilter( Meta::valArtist, artist, true, true ); 0292 } 0293 } 0294 qmA->endAndOr(); 0295 qmB->endAndOr(); 0296 m_timer.start(); 0297 qmA->run(); 0298 qmB->run(); 0299 } 0300 0301 void 0302 SynchronizationBaseJob::handleAlbumResult() 0303 { 0304 DEBUG_BLOCK 0305 QSet<Meta::AlbumKey> albumsOnlyInA = m_albumsA - m_albumsB; 0306 QSet<Meta::AlbumKey> albumsOnlyInB = m_albumsB - m_albumsA; 0307 QSet<Meta::AlbumKey> albumsInBoth = m_albumsA & m_albumsB; 0308 0309 foreach( const Meta::AlbumKey &album, albumsOnlyInA ) 0310 { 0311 m_albumResult.insert( album, OnlyInA ); 0312 } 0313 foreach( const Meta::AlbumKey &album, albumsOnlyInB ) 0314 { 0315 m_albumResult.insert( album, OnlyInB ); 0316 } 0317 foreach( const Meta::AlbumKey &album, albumsInBoth ) 0318 { 0319 m_albumResult.insert( album, InBoth ); 0320 } 0321 Collections::QueryMaker *qmA = setupTrackQuery( m_collectionA ); 0322 Collections::QueryMaker *qmB = setupTrackQuery( m_collectionB ); 0323 qmA->beginAnd(); 0324 qmB->beginAnd(); 0325 { 0326 QHashIterator<QString, InSet> iter( m_artistResult ); 0327 while( iter.hasNext() ) 0328 { 0329 iter.next(); 0330 QString artist = iter.key(); 0331 InSet currentStatus = iter.value(); 0332 if( currentStatus == OnlyInA ) 0333 { 0334 qmA->excludeFilter( Meta::valArtist, artist, true, true ); 0335 } 0336 if( currentStatus == OnlyInB ) 0337 { 0338 qmB->excludeFilter( Meta::valArtist, artist, true, true ); 0339 } 0340 } 0341 } 0342 { 0343 QHashIterator<Meta::AlbumKey, InSet> iter( m_albumResult ); 0344 while( iter.hasNext() ) 0345 { 0346 iter.next(); 0347 Meta::AlbumKey album = iter.key(); 0348 InSet currentStatus = iter.value(); 0349 if( currentStatus == OnlyInA ) 0350 { 0351 qmA->beginOr(); 0352 qmA->excludeFilter( Meta::valAlbum, album.albumName(), true, true ); 0353 qmA->excludeFilter( Meta::valAlbumArtist, album.artistName(), true, true ); 0354 qmA->endAndOr(); 0355 } 0356 if( currentStatus == OnlyInB ) 0357 { 0358 qmB->beginOr(); 0359 qmB->excludeFilter( Meta::valAlbum, album.albumName(), true, true ); 0360 qmB->excludeFilter( Meta::valAlbumArtist, album.artistName(), true, true ); 0361 qmB->endAndOr(); 0362 } 0363 } 0364 } 0365 qmA->endAndOr(); 0366 qmB->endAndOr(); 0367 m_timer.start(); 0368 qmA->run(); 0369 qmB->run(); 0370 } 0371 0372 void 0373 SynchronizationBaseJob::handleTrackResult() 0374 { 0375 DEBUG_BLOCK 0376 QSet<Meta::TrackKey> tracksOnlyInA = m_tracksA - m_tracksB; 0377 QSet<Meta::TrackKey> tracksOnlyInB = m_tracksB - m_tracksA; 0378 0379 foreach( const Meta::TrackKey &key, tracksOnlyInA ) 0380 { 0381 m_trackResultOnlyInA << m_keyToTrackA.value( key ); 0382 } 0383 foreach( const Meta::TrackKey &key, tracksOnlyInB ) 0384 { 0385 m_trackResultOnlyInB << m_keyToTrackB.value( key ); 0386 } 0387 0388 //we have to make sure that we do not start queries that will return *all* tracks of the collection 0389 //because we did not add any filter to it 0390 bool haveToStartQueryA = false; 0391 bool haveToStartQueryB = false; 0392 0393 //we do not care about tracks in both collections 0394 Collections::QueryMaker *qmA = createQueryMaker( m_collectionA ); 0395 Collections::QueryMaker *qmB = createQueryMaker( m_collectionB ); 0396 qmA->setQueryType( Collections::QueryMaker::Track ); 0397 qmB->setQueryType( Collections::QueryMaker::Track ); 0398 qmA->beginOr(); 0399 qmB->beginOr(); 0400 { 0401 QHashIterator<QString, InSet> iter( m_artistResult ); 0402 while( iter.hasNext() ) 0403 { 0404 iter.next(); 0405 QString artist = iter.key(); 0406 InSet currentStatus = iter.value(); 0407 if( currentStatus == OnlyInA ) 0408 { 0409 qmA->addFilter( Meta::valArtist, artist, true, true ); 0410 haveToStartQueryA = true; 0411 } 0412 if( currentStatus == OnlyInB ) 0413 { 0414 qmB->addFilter( Meta::valArtist, artist, true, true ); 0415 haveToStartQueryB = true; 0416 } 0417 } 0418 } 0419 { 0420 QHashIterator<Meta::AlbumKey, InSet> iter( m_albumResult ); 0421 while( iter.hasNext() ) 0422 { 0423 iter.next(); 0424 Meta::AlbumKey album = iter.key(); 0425 InSet currentStatus = iter.value(); 0426 if( currentStatus == OnlyInA ) 0427 { 0428 qmA->beginAnd(); 0429 qmA->addFilter( Meta::valAlbum, album.albumName(), true, true ); 0430 qmA->addFilter( Meta::valAlbumArtist, album.artistName(), true, true ); 0431 qmA->endAndOr(); 0432 haveToStartQueryA = true; 0433 } 0434 if( currentStatus == OnlyInB ) 0435 { 0436 qmB->beginAnd(); 0437 qmB->addFilter( Meta::valAlbum, album.albumName(), true, true ); 0438 qmB->addFilter( Meta::valAlbumArtist, album.artistName(), true, true ); 0439 qmB->endAndOr(); 0440 haveToStartQueryB = true; 0441 } 0442 } 0443 } 0444 qmA->endAndOr(); 0445 qmB->endAndOr(); 0446 connect( qmA, &Collections::QueryMaker::newTracksReady, this, &SynchronizationBaseJob::slotSyncTracks ); 0447 connect( qmB, &Collections::QueryMaker::newTracksReady, this, &SynchronizationBaseJob::slotSyncTracks ); 0448 connect( qmA, &Collections::QueryMaker::queryDone, this, &SynchronizationBaseJob::slotSyncQueryDone ); 0449 connect( qmB, &Collections::QueryMaker::queryDone, this, &SynchronizationBaseJob::slotSyncQueryDone ); 0450 m_timer.start(); 0451 if( haveToStartQueryA ) 0452 { 0453 qmA->run(); 0454 } 0455 else 0456 { 0457 delete qmA; 0458 m_currentResultCount += 1; 0459 } 0460 if( haveToStartQueryB ) 0461 { 0462 qmB->run(); 0463 } 0464 else 0465 { 0466 delete qmB; 0467 m_currentResultCount += 1; 0468 } 0469 if( !( haveToStartQueryA || haveToStartQueryB ) ) 0470 { 0471 slotSyncQueryDone(); 0472 } 0473 } 0474 0475 void 0476 SynchronizationBaseJob::slotSyncTracks( const Meta::TrackList &tracks ) 0477 { 0478 DEBUG_BLOCK 0479 Collections::Collection *senderColl = m_queryMakers.value( qobject_cast<Collections::QueryMaker*>(sender()) ); 0480 if( senderColl == m_collectionA ) 0481 { 0482 m_trackResultOnlyInA << tracks; 0483 } 0484 else if( senderColl == m_collectionB ) 0485 { 0486 m_trackResultOnlyInB << tracks; 0487 } 0488 else 0489 { 0490 //huh? 0491 debug() << "received data from unknown collection"; 0492 } 0493 } 0494 0495 void 0496 SynchronizationBaseJob::slotSyncQueryDone() 0497 { 0498 DEBUG_BLOCK; 0499 m_currentResultCount += 1; 0500 if( m_currentResultCount < 2 ) 0501 return; 0502 m_currentResultCount = 0; 0503 0504 m_timer.stop(); 0505 if( m_state == Syncing ) 0506 { 0507 doSynchronization( m_trackResultOnlyInA, OnlyInA, m_collectionA, m_collectionB ); 0508 doSynchronization( m_trackResultOnlyInB, OnlyInB, m_collectionA, m_collectionB ); 0509 deleteLater(); 0510 } 0511 else 0512 { 0513 const QMetaObject *mo = metaObject(); 0514 QMetaEnum me = mo->enumerator( mo->indexOfEnumerator( "State" ) ); 0515 debug() << "detected state " << me.valueToKey( m_state ) << " in slotSyncQueryDone(), do not know how to handle this. Aborting"; 0516 deleteLater(); 0517 } 0518 }