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 }