File indexing completed on 2025-01-05 04:25:57

0001 /****************************************************************************************
0002  * Copyright (c) 2007 Maximilian Kossick <maximilian.kossick@googlemail.com>            *
0003  * Copyright (c) 2010 Ralf Engels <ralf-engels@gmx.de>                                  *
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 #define DEBUG_PREFIX "SqlRegistry"
0019 
0020 #include "SqlRegistry.h"
0021 
0022 #include "DatabaseUpdater.h"
0023 #include "SqlRegistry_p.h"
0024 #include "SqlCollection.h"
0025 #include "core/support/Debug.h"
0026 #include "core-impl/collections/db/MountPointManager.h"
0027 #include "scanner/GenericScanManager.h"
0028 
0029 #include <QMutableHashIterator>
0030 #include <QMutexLocker>
0031 
0032 SqlRegistry::SqlRegistry( Collections::SqlCollection* collection )
0033     : QObject( nullptr )
0034     , m_collection( collection )
0035     , m_blockDatabaseUpdateCount( 0 )
0036     , m_collectionChanged( false )
0037 {
0038     DEBUG_BLOCK
0039     setObjectName( "SqlRegistry" );
0040 
0041     // -- remove unneeded entries from the database.
0042     // we have to do this now before anyone can hold references
0043     // to those objects.
0044     DatabaseUpdater databaseUpdater( m_collection );
0045 
0046     // url entries without associated directory just stick around and cannot be processed
0047     // by SqlScanResultProcessor. Delete them before checking tracks
0048     databaseUpdater.deleteOrphanedByDirectory( "urls" );
0049 
0050     // tracks with no associated url entry are useless, just a bunch of medatada with
0051     // nothing to associate them to; remove those first
0052     databaseUpdater.deleteOrphanedByUrl( "tracks" );
0053 
0054     databaseUpdater.deleteAllRedundant( "album" ); // what about cover images in database and disk cache?
0055     databaseUpdater.deleteAllRedundant( "artist" );
0056     databaseUpdater.deleteAllRedundant( "genre" );
0057     databaseUpdater.deleteAllRedundant( "composer" );
0058     databaseUpdater.deleteAllRedundant( "url" );
0059     databaseUpdater.deleteAllRedundant( "year" );
0060 
0061     databaseUpdater.deleteOrphanedByUrl( "lyrics" );
0062     databaseUpdater.deleteOrphanedByUrl( "statistics" );
0063     databaseUpdater.deleteOrphanedByUrl( "urls_labels" );
0064 
0065     m_timer = new QTimer( this );
0066     m_timer->setInterval( 30 * 1000 );  //try to clean up every 30 seconds, change if necessary
0067     m_timer->setSingleShot( false );
0068     connect( m_timer, &QTimer::timeout, this, &SqlRegistry::emptyCache );
0069     m_timer->start();
0070 }
0071 
0072 SqlRegistry::~SqlRegistry()
0073 {
0074     //don't delete m_collection
0075 }
0076 
0077 // ------ directory
0078 int
0079 SqlRegistry::getDirectory( const QString &path, uint mtime )
0080 {
0081     int dirId;
0082     int deviceId = m_collection->mountPointManager()->getIdForUrl( QUrl::fromLocalFile(path) );
0083     QString rdir = m_collection->mountPointManager()->getRelativePath( deviceId, path );
0084 
0085     auto storage = m_collection->sqlStorage();
0086 
0087     // - find existing entry
0088     QString query = QString( "SELECT id, changedate FROM directories "
0089                              "WHERE  deviceid = %1 AND dir = '%2';" )
0090                         .arg( QString::number( deviceId ), storage->escape( rdir ) );
0091     QStringList res = storage->query( query );
0092 
0093     // - create new entry
0094     if( res.isEmpty() )
0095     {
0096         debug() << "SqlRegistry::getDirectory(): new directory" << path;
0097         QString insert = QString( "INSERT INTO directories(deviceid,changedate,dir) "
0098                                   "VALUES (%1,%2,'%3');" )
0099                         .arg( QString::number( deviceId ), QString::number( mtime ),
0100                                 storage->escape( rdir ) );
0101         dirId = storage->insert( insert, "directories" );
0102         m_collectionChanged = true;
0103     }
0104     else
0105     {
0106         // update old one
0107         dirId = res[0].toUInt();
0108         uint oldMtime = res[1].toUInt();
0109         if( oldMtime != mtime )
0110         {
0111             QString update = QString( "UPDATE directories SET changedate = %1 "
0112                                       "WHERE id = %2;" )
0113                 .arg( QString::number( mtime ), res[0] );
0114             debug() << "SqlRegistry::getDirectory(): update directory" << path << "(id" <<
0115                     res[0] << ") from" << oldMtime << "to" << mtime << "UNIX time";
0116             storage->query( update );
0117         }
0118     }
0119     return dirId;
0120 }
0121 
0122 // ------ track
0123 
0124 Meta::TrackPtr
0125 SqlRegistry::getTrack( int urlId )
0126 {
0127     QString query = "SELECT %1 FROM urls %2 WHERE urls.id = %3";
0128     query = query.arg( Meta::SqlTrack::getTrackReturnValues(),
0129                        Meta::SqlTrack::getTrackJoinConditions(),
0130                        QString::number( urlId ) );
0131     QStringList rowData = m_collection->sqlStorage()->query( query );
0132     if( rowData.isEmpty() )
0133         return Meta::TrackPtr();
0134 
0135     TrackPath id( rowData[Meta::SqlTrack::returnIndex_urlDeviceId].toInt(),
0136                 rowData[Meta::SqlTrack::returnIndex_urlRPath] );
0137     QString uid = rowData[Meta::SqlTrack::returnIndex_urlUid];
0138 
0139     QMutexLocker locker( &m_trackMutex );
0140     if( m_trackMap.contains( id ) )
0141     {
0142         Meta::SqlTrackPtr track = Meta::SqlTrackPtr::staticCast( m_trackMap[ id ] );
0143         // yes, it may happen that we get a different track in corner cases, see bug 323156
0144         if( track->urlId() == urlId )
0145             return Meta::TrackPtr::staticCast( track );
0146         warning() << Q_FUNC_INFO << "track with (deviceId, rpath)" << id << "found in"
0147                   << "m_trackMap, but it had different urlId (" << track->urlId() << ")"
0148                   << "than requested (" << urlId << "). This may happen in corner-cases.";
0149     }
0150     if( m_uidMap.contains( uid ) )
0151     {
0152         Meta::SqlTrackPtr track = Meta::SqlTrackPtr::staticCast( m_uidMap[ uid ] );
0153         // yes, it may happen that we get a different track in corner cases, see bug 323156
0154         if( track->urlId() == urlId )
0155             return Meta::TrackPtr::staticCast( track );
0156         warning() << Q_FUNC_INFO << "track with uid" << uid << "found in m_uidMap, but it"
0157                   << "had different urlId (" << track->urlId() << ") than requested ("
0158                   << urlId << "). This may happen in corner-cases.";
0159     }
0160 
0161     Meta::SqlTrack *sqlTrack = new Meta::SqlTrack( m_collection, rowData );
0162     Meta::TrackPtr trackPtr( sqlTrack );
0163 
0164     m_trackMap.insert( id, trackPtr );
0165     m_uidMap.insert( sqlTrack->uidUrl(), trackPtr );
0166     return trackPtr;
0167 }
0168 
0169 Meta::TrackPtr
0170 SqlRegistry::getTrack( const QString &path )
0171 {
0172     int deviceId = m_collection->mountPointManager()->getIdForUrl( QUrl::fromLocalFile(path) );
0173     QString rpath = m_collection->mountPointManager()->getRelativePath( deviceId, path );
0174     TrackPath id( deviceId, rpath );
0175 
0176     QMutexLocker locker( &m_trackMutex );
0177     if( m_trackMap.contains( id ) )
0178         return m_trackMap.value( id );
0179     else
0180     {
0181         QString query;
0182         QStringList result;
0183 
0184         query = "SELECT %1 FROM urls %2 "
0185             "WHERE urls.deviceid = %3 AND urls.rpath = '%4';";
0186         query = query.arg( Meta::SqlTrack::getTrackReturnValues(),
0187                            Meta::SqlTrack::getTrackJoinConditions(),
0188                            QString::number( deviceId ),
0189                            m_collection->sqlStorage()->escape( rpath ) );
0190         result = m_collection->sqlStorage()->query( query );
0191         if( result.isEmpty() )
0192             return Meta::TrackPtr();
0193 
0194         Meta::SqlTrack *sqlTrack = new Meta::SqlTrack( m_collection, result );
0195 
0196         Meta::TrackPtr trackPtr( sqlTrack );
0197         m_trackMap.insert( id, trackPtr );
0198         m_uidMap.insert( sqlTrack->uidUrl(), trackPtr );
0199         return trackPtr;
0200     }
0201 }
0202 
0203 
0204 Meta::TrackPtr
0205 SqlRegistry::getTrack( int deviceId, const QString &rpath, int directoryId, const QString &uidUrl )
0206 {
0207     TrackPath id( deviceId, rpath );
0208 
0209     QMutexLocker locker( &m_trackMutex );
0210     if( m_trackMap.contains( id ) )
0211         return m_trackMap.value( id );
0212     else
0213     {
0214         QString query;
0215         QStringList result;
0216         Meta::SqlTrack *sqlTrack = nullptr;
0217 
0218         // -- get it from the database
0219         query = "SELECT %1 FROM urls %2 "
0220             "WHERE urls.deviceid = %3 AND urls.rpath = '%4';";
0221         query = query.arg( Meta::SqlTrack::getTrackReturnValues(),
0222                            Meta::SqlTrack::getTrackJoinConditions(),
0223                            QString::number( deviceId ),
0224                            m_collection->sqlStorage()->escape( rpath ) );
0225         result = m_collection->sqlStorage()->query( query );
0226 
0227         if( !result.isEmpty() )
0228             sqlTrack = new Meta::SqlTrack( m_collection, result );
0229 
0230         // -- we have to create a new track
0231         if( !sqlTrack )
0232             sqlTrack = new Meta::SqlTrack( m_collection, deviceId, rpath, directoryId, uidUrl );
0233 
0234         Meta::TrackPtr trackPtr( sqlTrack );
0235         m_trackMap.insert( id, trackPtr );
0236         m_uidMap.insert( sqlTrack->uidUrl(), trackPtr );
0237         return trackPtr;
0238     }
0239 }
0240 
0241 Meta::TrackPtr
0242 SqlRegistry::getTrack( int trackId, const QStringList &rowData )
0243 {
0244     Q_ASSERT( trackId == rowData[Meta::SqlTrack::returnIndex_trackId].toInt() );
0245     Q_UNUSED( trackId );
0246 
0247     TrackPath path( rowData[Meta::SqlTrack::returnIndex_urlDeviceId].toInt(),
0248                     rowData[Meta::SqlTrack::returnIndex_urlRPath] );
0249     QString uid = rowData[Meta::SqlTrack::returnIndex_urlUid];
0250 
0251     QMutexLocker locker( &m_trackMutex );
0252     if( m_trackMap.contains( path ) )
0253         return m_trackMap.value( path );
0254     else if( m_uidMap.contains( uid ) )
0255         return m_uidMap.value( uid );
0256     else
0257     {
0258         Meta::SqlTrack *sqlTrack =  new Meta::SqlTrack( m_collection, rowData );
0259         Meta::TrackPtr track( sqlTrack );
0260 
0261         m_trackMap.insert( path, track );
0262         m_uidMap.insert( AmarokSharedPointer<Meta::SqlTrack>::staticCast( track )->uidUrl(), track );
0263         return track;
0264     }
0265 }
0266 
0267 bool
0268 SqlRegistry::updateCachedUrl( const QString &oldPath, const QString &newPath )
0269 {
0270     int deviceId = m_collection->mountPointManager()->getIdForUrl( QUrl::fromLocalFile(oldPath) );
0271     QString rpath = m_collection->mountPointManager()->getRelativePath( deviceId, oldPath );
0272     TrackPath oldId( deviceId, rpath );
0273 
0274     int newdeviceId = m_collection->mountPointManager()->getIdForUrl( QUrl::fromLocalFile(newPath) );
0275     QString newRpath = m_collection->mountPointManager()->getRelativePath( newdeviceId, newPath );
0276     TrackPath newId( newdeviceId, newRpath );
0277 
0278     QMutexLocker locker( &m_trackMutex );
0279     if( m_trackMap.contains( newId ) )
0280         warning() << "updating path to an already existing path.";
0281     else if( !m_trackMap.contains( oldId ) )
0282         warning() << "updating path from a non existing path.";
0283     else
0284     {
0285         Meta::TrackPtr track = m_trackMap.take( oldId );
0286         m_trackMap.insert( newId, track );
0287         return true;
0288     }
0289     return false;
0290 }
0291 
0292 bool
0293 SqlRegistry::updateCachedUid( const QString &oldUid, const QString &newUid )
0294 {
0295     QMutexLocker locker( &m_trackMutex );
0296     // TODO: improve uid handling
0297     if( m_uidMap.contains( newUid ) )
0298         warning() << "updating uid to an already existing uid.";
0299     else if( !oldUid.isEmpty() && !m_uidMap.contains( oldUid ) )
0300         warning() << "updating uid from a non existing uid.";
0301     else
0302     {
0303         Meta::TrackPtr track = m_uidMap.take(oldUid);
0304         m_uidMap.insert( newUid, track );
0305         return true;
0306     }
0307     return false;
0308 }
0309 
0310 Meta::TrackPtr
0311 SqlRegistry::getTrackFromUid( const QString &uid )
0312 {
0313     QMutexLocker locker( &m_trackMutex );
0314     if( m_uidMap.contains( uid ) )
0315         return m_uidMap.value( uid );
0316     {
0317         QString query;
0318         QStringList result;
0319 
0320         // -- get all the track info
0321         query = "SELECT %1 FROM urls %2 "
0322             "WHERE urls.uniqueid = '%3';";
0323         query = query.arg( Meta::SqlTrack::getTrackReturnValues(),
0324                            Meta::SqlTrack::getTrackJoinConditions(),
0325                            m_collection->sqlStorage()->escape( uid ) );
0326         result = m_collection->sqlStorage()->query( query );
0327         if( result.isEmpty() )
0328             return Meta::TrackPtr();
0329 
0330         Meta::SqlTrack *sqlTrack = new Meta::SqlTrack( m_collection, result );
0331         Meta::TrackPtr trackPtr( sqlTrack );
0332 
0333         int deviceid = m_collection->mountPointManager()->getIdForUrl( trackPtr->playableUrl() );
0334         QString rpath = m_collection->mountPointManager()->getRelativePath( deviceid, trackPtr->playableUrl().path() );
0335         TrackPath id(deviceid, rpath);
0336         m_trackMap.insert( id, trackPtr );
0337         m_uidMap.insert( uid, trackPtr );
0338         return trackPtr;
0339     }
0340 }
0341 
0342 void
0343 SqlRegistry::removeTrack( int urlId, const QString &uid )
0344 {
0345     // delete all entries linked to the url, including track
0346     QStringList tables = QStringList() << "tracks" << "lyrics" << "statistics" << "urls_labels";
0347     foreach( const QString &table, tables )
0348     {
0349         QString query = QString( "DELETE FROM %1 WHERE url=%2" ).arg( table ).arg( urlId );
0350         m_collection->sqlStorage()->query( query );
0351     }
0352 
0353     // delete url entry from database; we used to keep it and keep its statistics, but
0354     // DatabaseUpdater::deleteAllRedundant( url ) removes the url entry on the next Amarok
0355     // startup, plus we don't know how long we should keep the entry, so just delete
0356     // everything. ScanResultProcessor should be witty enough not to delete tracks that
0357     // have been moved to another directory and/or device, even if it is currently
0358     // unavailable.
0359     QString query = QString( "DELETE FROM urls WHERE id=%1" ).arg( urlId );
0360     m_collection->sqlStorage()->query( query );
0361 
0362     // --- delete the track from memory
0363     QMutexLocker locker( &m_trackMutex );
0364     if( m_uidMap.contains( uid ) )
0365     {
0366         // -- remove from hashes
0367         Meta::TrackPtr track = m_uidMap.take( uid );
0368         Meta::SqlTrack *sqlTrack = static_cast<Meta::SqlTrack*>( track.data() );
0369 
0370         int deviceId = m_collection->mountPointManager()->getIdForUrl( sqlTrack->playableUrl() );
0371         QString rpath = m_collection->mountPointManager()->getRelativePath( deviceId, sqlTrack->playableUrl().path() );
0372         TrackPath id(deviceId, rpath);
0373         m_trackMap.remove( id );
0374     }
0375 }
0376 
0377 // -------- artist
0378 
0379 Meta::ArtistPtr
0380 SqlRegistry::getArtist( const QString &oName )
0381 {
0382     QMutexLocker locker( &m_artistMutex );
0383 
0384     QString name = oName.left( DatabaseUpdater::textColumnLength() );
0385     if( m_artistMap.contains( name ) )
0386         return m_artistMap.value( name );
0387 
0388     int id;
0389 
0390     QString query = QString( "SELECT id FROM artists WHERE name = '%1';" ).arg( m_collection->sqlStorage()->escape( name ) );
0391     QStringList res = m_collection->sqlStorage()->query( query );
0392     if( res.isEmpty() )
0393     {
0394         QString insert = QString( "INSERT INTO artists( name ) VALUES ('%1');" ).arg( m_collection->sqlStorage()->escape( name ) );
0395         id = m_collection->sqlStorage()->insert( insert, "artists" );
0396         m_collectionChanged = true;
0397     }
0398     else
0399     {
0400         id = res[0].toInt();
0401     }
0402 
0403     if( !id )
0404         return Meta::ArtistPtr();
0405 
0406     Meta::ArtistPtr artist( new Meta::SqlArtist( m_collection, id, name ) );
0407     m_artistMap.insert( name, artist );
0408     m_artistIdMap.insert( id, artist );
0409     return artist;
0410 }
0411 
0412 Meta::ArtistPtr
0413 SqlRegistry::getArtist( int id )
0414 {
0415     QMutexLocker locker( &m_artistMutex );
0416 
0417     if( m_artistIdMap.contains( id ) )
0418         return m_artistIdMap.value( id );
0419 
0420     QString query = QString( "SELECT name FROM artists WHERE id = %1;" ).arg( id );
0421     QStringList res = m_collection->sqlStorage()->query( query );
0422     if( res.isEmpty() )
0423         return Meta::ArtistPtr();
0424 
0425     QString name = res[0];
0426     Meta::ArtistPtr artist( new Meta::SqlArtist( m_collection, id, name ) );
0427     m_artistMap.insert( name, artist );
0428     m_artistIdMap.insert( id, artist );
0429     return artist;
0430 }
0431 
0432 Meta::ArtistPtr
0433 SqlRegistry::getArtist( int id, const QString &name )
0434 {
0435     Q_ASSERT( id > 0 ); // must be a valid id
0436     QMutexLocker locker( &m_artistMutex );
0437 
0438     if( m_artistMap.contains( name ) )
0439         return m_artistMap.value( name );
0440 
0441     Meta::ArtistPtr artist( new Meta::SqlArtist( m_collection, id, name ) );
0442     m_artistMap.insert( name, artist );
0443     m_artistIdMap.insert( id, artist );
0444     return artist;
0445 }
0446 
0447 // -------- genre
0448 
0449 Meta::GenrePtr
0450 SqlRegistry::getGenre( const QString &oName )
0451 {
0452     QMutexLocker locker( &m_genreMutex );
0453 
0454     QString name = oName.left( DatabaseUpdater::textColumnLength() );
0455     if( m_genreMap.contains( name ) )
0456         return m_genreMap.value( name );
0457 
0458     int id;
0459 
0460     QString query = QString( "SELECT id FROM genres WHERE name = '%1';" ).arg( m_collection->sqlStorage()->escape( name ) );
0461     QStringList res = m_collection->sqlStorage()->query( query );
0462     if( res.isEmpty() )
0463     {
0464         QString insert = QString( "INSERT INTO genres( name ) VALUES ('%1');" ).arg( m_collection->sqlStorage()->escape( name ) );
0465         id = m_collection->sqlStorage()->insert( insert, "genres" );
0466         m_collectionChanged = true;
0467     }
0468     else
0469     {
0470         id = res[0].toInt();
0471     }
0472 
0473     if( !id )
0474         return Meta::GenrePtr();
0475 
0476     Meta::GenrePtr genre( new Meta::SqlGenre( m_collection, id, name ) );
0477     m_genreMap.insert( name, genre );
0478     return genre;
0479 }
0480 
0481 Meta::GenrePtr
0482 SqlRegistry::getGenre( int id )
0483 {
0484     QMutexLocker locker( &m_genreMutex );
0485 
0486     QString query = QString( "SELECT name FROM genres WHERE id = '%1';" ).arg( id );
0487     QStringList res = m_collection->sqlStorage()->query( query );
0488     if( res.isEmpty() )
0489         return Meta::GenrePtr();
0490 
0491     QString name = res[0];
0492     Meta::GenrePtr genre( new Meta::SqlGenre( m_collection, id, name ) );
0493     m_genreMap.insert( name, genre );
0494     return genre;
0495 }
0496 
0497 Meta::GenrePtr
0498 SqlRegistry::getGenre( int id, const QString &name )
0499 {
0500     Q_ASSERT( id > 0 ); // must be a valid id
0501     QMutexLocker locker( &m_genreMutex );
0502 
0503     if( m_genreMap.contains( name ) )
0504         return m_genreMap.value( name );
0505 
0506     Meta::GenrePtr genre( new Meta::SqlGenre( m_collection, id, name ) );
0507     m_genreMap.insert( name, genre );
0508     return genre;
0509 }
0510 
0511 // -------- composer
0512 
0513 Meta::ComposerPtr
0514 SqlRegistry::getComposer( const QString &oName )
0515 {
0516     QMutexLocker locker( &m_composerMutex );
0517 
0518     QString name = oName.left( DatabaseUpdater::textColumnLength() );
0519     if( m_composerMap.contains( name ) )
0520         return m_composerMap.value( name );
0521 
0522     int id;
0523 
0524     QString query = QString( "SELECT id FROM composers WHERE name = '%1';" ).arg( m_collection->sqlStorage()->escape( name ) );
0525     QStringList res = m_collection->sqlStorage()->query( query );
0526     if( res.isEmpty() )
0527     {
0528         QString insert = QString( "INSERT INTO composers( name ) VALUES ('%1');" ).arg( m_collection->sqlStorage()->escape( name ) );
0529         id = m_collection->sqlStorage()->insert( insert, "composers" );
0530         m_collectionChanged = true;
0531     }
0532     else
0533     {
0534         id = res[0].toInt();
0535     }
0536 
0537     if( !id )
0538         return Meta::ComposerPtr();
0539 
0540     Meta::ComposerPtr composer( new Meta::SqlComposer( m_collection, id, name ) );
0541     m_composerMap.insert( name, composer );
0542     return composer;
0543 }
0544 
0545 Meta::ComposerPtr
0546 SqlRegistry::getComposer( int id )
0547 {
0548     if( id <= 0 )
0549         return Meta::ComposerPtr();
0550 
0551     QMutexLocker locker( &m_composerMutex );
0552 
0553     QString query = QString( "SELECT name FROM composers WHERE id = '%1';" ).arg( id );
0554     QStringList res = m_collection->sqlStorage()->query( query );
0555     if( res.isEmpty() )
0556         return Meta::ComposerPtr();
0557 
0558     QString name = res[0];
0559     Meta::ComposerPtr composer( new Meta::SqlComposer( m_collection, id, name ) );
0560     m_composerMap.insert( name, composer );
0561     return composer;
0562 }
0563 
0564 Meta::ComposerPtr
0565 SqlRegistry::getComposer( int id, const QString &name )
0566 {
0567     Q_ASSERT( id > 0 ); // must be a valid id
0568     QMutexLocker locker( &m_composerMutex );
0569 
0570     if( m_composerMap.contains( name ) )
0571         return m_composerMap.value( name );
0572 
0573     Meta::ComposerPtr composer( new Meta::SqlComposer( m_collection, id, name ) );
0574     m_composerMap.insert( name, composer );
0575     return composer;
0576 }
0577 
0578 // -------- year
0579 
0580 Meta::YearPtr
0581 SqlRegistry::getYear( int year, int yearId )
0582 {
0583     QMutexLocker locker( &m_yearMutex );
0584 
0585     if( m_yearMap.contains( year ) )
0586         return m_yearMap.value( year );
0587 
0588     // don't know the id yet
0589     if( yearId <= 0 )
0590     {
0591         QString query = QString( "SELECT id FROM years WHERE name = '%1';" ).arg( QString::number( year ) );
0592         QStringList res = m_collection->sqlStorage()->query( query );
0593         if( res.isEmpty() )
0594         {
0595             QString insert = QString( "INSERT INTO years( name ) VALUES ('%1');" ).arg( QString::number( year ) );
0596             yearId = m_collection->sqlStorage()->insert( insert, "years" );
0597             m_collectionChanged = true;
0598         }
0599         else
0600         {
0601             yearId = res[0].toInt();
0602         }
0603     }
0604 
0605     if( !yearId )
0606         return Meta::YearPtr();
0607 
0608     Meta::YearPtr yearPtr( new Meta::SqlYear( m_collection, yearId, year ) );
0609     m_yearMap.insert( year, yearPtr );
0610     return yearPtr;
0611 }
0612 
0613 // -------- album
0614 
0615 Meta::AlbumPtr
0616 SqlRegistry::getAlbum( const QString &oName, const QString &oArtist )
0617 {
0618     // we allow albums with empty name but nonempty artist, see bug 272471
0619     QString name = oName.left( DatabaseUpdater::textColumnLength() );
0620     QString albumArtist = oArtist.left( DatabaseUpdater::textColumnLength() );
0621     AlbumKey key( name, albumArtist );
0622 
0623     QMutexLocker locker( &m_albumMutex );
0624     if( m_albumMap.contains( key ) )
0625         return m_albumMap.value( key );
0626 
0627     int albumId = -1;
0628     int artistId = -1;
0629 
0630     QString query = QString( "SELECT id FROM albums WHERE name = '%1' AND " ).arg( m_collection->sqlStorage()->escape( name ) );
0631 
0632     if( albumArtist.isEmpty() )
0633     {
0634         query += QString( "artist IS NULL" );
0635     }
0636     else
0637     {
0638         Meta::ArtistPtr artistPtr = getArtist( albumArtist );
0639         if( !artistPtr )
0640             return Meta::AlbumPtr();
0641         Meta::SqlArtist *sqlArtist = static_cast<Meta::SqlArtist*>(artistPtr.data());
0642         artistId = sqlArtist->id();
0643 
0644         query += QString( "artist=%1" ).arg( artistId );
0645     }
0646 
0647     QStringList res = m_collection->sqlStorage()->query( query );
0648     if( res.isEmpty() )
0649     {
0650         // ok. have to create a new album
0651         QString insert = QString( "INSERT INTO albums( name, artist ) VALUES ('%1',%2);" ).
0652             arg( m_collection->sqlStorage()->escape( name ),
0653                  artistId > 0 ? QString::number( artistId ) : "NULL" );
0654         albumId = m_collection->sqlStorage()->insert( insert, "albums" );
0655         m_collectionChanged = true; // we just added a new album
0656     }
0657     else
0658     {
0659         albumId = res[0].toInt();
0660     }
0661 
0662     if( !albumId )
0663         return Meta::AlbumPtr();
0664 
0665     Meta::SqlAlbum *sqlAlbum = new Meta::SqlAlbum( m_collection, albumId, name, artistId );
0666     Meta::AlbumPtr album( sqlAlbum );
0667     m_albumMap.insert( key, album );
0668     m_albumIdMap.insert( albumId, album );
0669     locker.unlock(); // prevent deadlock
0670     return album;
0671 }
0672 
0673 Meta::AlbumPtr
0674 SqlRegistry::getAlbum( int albumId )
0675 {
0676     Q_ASSERT( albumId > 0 ); // must be a valid id
0677 
0678     {
0679         // we want locker only for this block because we call another getAlbum() below
0680         QMutexLocker locker( &m_albumMutex );
0681         if( m_albumIdMap.contains( albumId ) )
0682             return m_albumIdMap.value( albumId );
0683     }
0684 
0685     QString query = QString( "SELECT name, artist FROM albums WHERE id = %1" ).arg( albumId );
0686     QStringList res = m_collection->sqlStorage()->query( query );
0687     if( res.isEmpty() )
0688         return Meta::AlbumPtr(); // someone messed up
0689 
0690     QString name = res[0];
0691     int artistId = res[1].toInt();
0692     return getAlbum( albumId, name, artistId );
0693 }
0694 
0695 Meta::AlbumPtr
0696 SqlRegistry::getAlbum( int albumId, const QString &name, int artistId )
0697 {
0698     Q_ASSERT( albumId > 0 ); // must be a valid id
0699 
0700     QMutexLocker locker( &m_albumMutex );
0701     if( m_albumIdMap.contains( albumId ) )
0702         return m_albumIdMap.value( albumId );
0703 
0704     Meta::ArtistPtr artist = getArtist( artistId );
0705     AlbumKey key(name, artist ? artist->name() : QString() );
0706     if( m_albumMap.contains( key ) )
0707         return m_albumMap.value( key );
0708 
0709     Meta::SqlAlbum *sqlAlbum = new Meta::SqlAlbum( m_collection, albumId, name, artistId );
0710     Meta::AlbumPtr album( sqlAlbum );
0711     m_albumMap.insert( key, album );
0712     m_albumIdMap.insert( albumId, album );
0713     return album;
0714 }
0715 
0716 // ------------ label
0717 
0718 Meta::LabelPtr
0719 SqlRegistry::getLabel( const QString &oLabel )
0720 {
0721     QMutexLocker locker( &m_labelMutex );
0722     QString label = oLabel.left( DatabaseUpdater::textColumnLength() );
0723     if( m_labelMap.contains( label ) )
0724         return m_labelMap.value( label );
0725 
0726     int id;
0727 
0728     QString query = QString( "SELECT id FROM labels WHERE label = '%1';" ).arg( m_collection->sqlStorage()->escape( label ) );
0729     QStringList res = m_collection->sqlStorage()->query( query );
0730     if( res.isEmpty() )
0731     {
0732         QString insert = QString( "INSERT INTO labels( label ) VALUES ('%1');" ).arg( m_collection->sqlStorage()->escape( label ) );
0733         id = m_collection->sqlStorage()->insert( insert, "albums" );
0734     }
0735     else
0736     {
0737         id = res[0].toInt();
0738     }
0739 
0740     if( !id )
0741         return Meta::LabelPtr();
0742 
0743     Meta::LabelPtr labelPtr( new Meta::SqlLabel( m_collection, id, label ) );
0744     m_labelMap.insert( label, labelPtr );
0745     return labelPtr;
0746 }
0747 
0748 Meta::LabelPtr
0749 SqlRegistry::getLabel( int id )
0750 {
0751     Q_ASSERT( id > 0 ); // must be a valid id
0752     QMutexLocker locker( &m_labelMutex );
0753 
0754     QString query = QString( "SELECT label FROM labels WHERE id = '%1';" ).arg( id );
0755     QStringList res = m_collection->sqlStorage()->query( query );
0756     if( res.isEmpty() )
0757         return Meta::LabelPtr();
0758 
0759     QString label = res[0];
0760     Meta::LabelPtr labelPtr( new Meta::SqlLabel( m_collection, id, label ) );
0761     m_labelMap.insert( label, labelPtr );
0762     return labelPtr;
0763 }
0764 
0765 Meta::LabelPtr
0766 SqlRegistry::getLabel( int id, const QString &label )
0767 {
0768     Q_ASSERT( id > 0 ); // must be a valid id
0769     QMutexLocker locker( &m_labelMutex );
0770 
0771     if( m_labelMap.contains( label ) )
0772         return m_labelMap.value( label );
0773 
0774     Meta::LabelPtr labelPtr( new Meta::SqlLabel( m_collection, id, label ) );
0775     m_labelMap.insert( label, labelPtr );
0776     return labelPtr;
0777 }
0778 
0779 
0780 
0781 // ---------------- generic database management --------------
0782 
0783 void
0784 SqlRegistry::blockDatabaseUpdate()
0785 {
0786     QMutexLocker locker( &m_blockMutex );
0787     m_blockDatabaseUpdateCount++;
0788 }
0789 
0790 void
0791 SqlRegistry::unblockDatabaseUpdate()
0792 {
0793     {
0794         QMutexLocker locker( &m_blockMutex );
0795         Q_ASSERT( m_blockDatabaseUpdateCount > 0 );
0796         m_blockDatabaseUpdateCount--;
0797     }
0798 
0799     // update the database
0800     commitDirtyTracks();
0801 }
0802 
0803 void
0804 SqlRegistry::commitDirtyTracks()
0805 {
0806     QMutexLocker locker( &m_blockMutex );
0807 
0808     if( m_blockDatabaseUpdateCount > 0 )
0809         return;
0810 
0811     QList< Meta::SqlYearPtr > dirtyYears = m_dirtyYears.values();
0812     QList< Meta::SqlGenrePtr > dirtyGenres = m_dirtyGenres.values();
0813     QList< Meta::SqlAlbumPtr > dirtyAlbums = m_dirtyAlbums.values();
0814     QList< Meta::SqlTrackPtr > dirtyTracks = m_dirtyTracks.values();
0815     QList< Meta::SqlArtistPtr > dirtyArtists = m_dirtyArtists.values();
0816     QList< Meta::SqlComposerPtr > dirtyComposers = m_dirtyComposers.values();
0817 
0818     m_dirtyYears.clear();
0819     m_dirtyGenres.clear();
0820     m_dirtyAlbums.clear();
0821     m_dirtyTracks.clear();
0822     m_dirtyArtists.clear();
0823     m_dirtyComposers.clear();
0824     locker.unlock(); // need to unlock before notifying the observers
0825 
0826     // -- commit all the dirty tracks
0827     TrackUrlsTableCommitter().commit( dirtyTracks );
0828     TrackTracksTableCommitter().commit( dirtyTracks );
0829     TrackStatisticsTableCommitter().commit( dirtyTracks );
0830 
0831     // -- notify all observers
0832     foreach( Meta::SqlYearPtr year, dirtyYears )
0833     {
0834         // this means that a new year was added to track or an old removed (or both),
0835         // Collection docs says we need to Q_EMIT updated() in this case. Ditto below.
0836         m_collectionChanged = true;
0837         year->invalidateCache();
0838         year->notifyObservers();
0839     }
0840     foreach( Meta::SqlGenrePtr genre, dirtyGenres )
0841     {
0842         m_collectionChanged = true;
0843         genre->invalidateCache();
0844         genre->notifyObservers();
0845     }
0846     foreach( Meta::SqlAlbumPtr album, dirtyAlbums )
0847     {
0848         m_collectionChanged = true;
0849         album->invalidateCache();
0850         album->notifyObservers();
0851     }
0852     foreach( Meta::SqlTrackPtr track, dirtyTracks )
0853     {
0854         // if only track changes, no need to Q_EMIT updated() from here
0855         track->notifyObservers();
0856     }
0857     foreach( Meta::SqlArtistPtr artist, dirtyArtists )
0858     {
0859         m_collectionChanged = true;
0860         artist->invalidateCache();
0861         artist->notifyObservers();
0862     }
0863     foreach( Meta::SqlComposerPtr composer, dirtyComposers )
0864     {
0865         m_collectionChanged = true;
0866         composer->invalidateCache();
0867         composer->notifyObservers();
0868     }
0869     if( m_collectionChanged )
0870         m_collection->collectionUpdated();
0871     m_collectionChanged = false;
0872 }
0873 
0874 
0875 
0876 void
0877 SqlRegistry::emptyCache()
0878 {
0879     if( m_collection->scanManager() && m_collection->scanManager()->isRunning() )
0880         return; // don't clean the cache if a scan is done
0881 
0882     bool hasTrack, hasAlbum, hasArtist, hasYear, hasGenre, hasComposer, hasLabel;
0883     hasTrack = hasAlbum = hasArtist = hasYear = hasGenre = hasComposer = hasLabel = false;
0884 
0885     //try to avoid possible deadlocks by aborting when we can't get all locks
0886     if ( ( hasTrack = m_trackMutex.tryLock() )
0887          && ( hasAlbum = m_albumMutex.tryLock() )
0888          && ( hasArtist = m_artistMutex.tryLock() )
0889          && ( hasYear = m_yearMutex.tryLock() )
0890          && ( hasGenre = m_genreMutex.tryLock() )
0891          && ( hasComposer = m_composerMutex.tryLock() )
0892          && ( hasLabel = m_labelMutex.tryLock() ) )
0893     {
0894         #define mapCached( Cache, Key, Map, Res ) \
0895         Cache[Key] = qMakePair( Map.count(), Res.join(QLatin1String(" ")).toInt() );
0896 
0897         QMap<QString, QPair<int, int> > cachedBefore;
0898 
0899         QString query = QString( "SELECT COUNT(*) FROM albums;" );
0900         QStringList res = m_collection->sqlStorage()->query( query );
0901         mapCached( cachedBefore, "albums", m_albumMap, res );
0902 
0903         query = QString( "SELECT COUNT(*) FROM tracks;" );
0904         res = m_collection->sqlStorage()->query( query );
0905         mapCached( cachedBefore, "tracks", m_trackMap, res );
0906 
0907         query = QString( "SELECT COUNT(*) FROM artists;" );
0908         res = m_collection->sqlStorage()->query( query );
0909         mapCached( cachedBefore, "artists", m_artistMap, res );
0910 
0911         query = QString( "SELECT COUNT(*) FROM genres;" );
0912         res = m_collection->sqlStorage()->query( query );
0913         mapCached( cachedBefore, "genres", m_genreMap, res );
0914 
0915         //this very simple garbage collector doesn't handle cyclic object graphs
0916         //so care has to be taken to make sure that we are not dealing with a cyclic graph
0917         //by invalidating the tracks cache on all objects
0918         #define foreachInvalidateCache( Key, Type, RealType, x ) \
0919         for( QMutableHashIterator<Key,Type > iter(x); iter.hasNext(); ) \
0920             RealType::staticCast( iter.next().value() )->invalidateCache()
0921 
0922         foreachInvalidateCache( AlbumKey, Meta::AlbumPtr, AmarokSharedPointer<Meta::SqlAlbum>, m_albumMap );
0923         foreachInvalidateCache( QString, Meta::ArtistPtr, AmarokSharedPointer<Meta::SqlArtist>, m_artistMap );
0924         foreachInvalidateCache( QString, Meta::GenrePtr, AmarokSharedPointer<Meta::SqlGenre>, m_genreMap );
0925         foreachInvalidateCache( QString, Meta::ComposerPtr, AmarokSharedPointer<Meta::SqlComposer>, m_composerMap );
0926         foreachInvalidateCache( int, Meta::YearPtr, AmarokSharedPointer<Meta::SqlYear>, m_yearMap );
0927         foreachInvalidateCache( QString, Meta::LabelPtr, AmarokSharedPointer<Meta::SqlLabel>, m_labelMap );
0928         #undef foreachInvalidateCache
0929 
0930         // elem.count() == 2 is correct because elem is one pointer to the object
0931         // and the other is stored in the hash map (except for m_trackMap, m_albumMap
0932         // and m_artistMap , where another reference is stored in m_uidMap, m_albumIdMap
0933         // and m_artistIdMap
0934         #define foreachCollectGarbage( Key, Type, RefCount, x ) \
0935         for( QMutableHashIterator<Key,Type > iter(x); iter.hasNext(); ) \
0936         { \
0937             Type elem = iter.next().value(); \
0938             if( elem.count() == RefCount ) \
0939                 iter.remove(); \
0940         }
0941 
0942         foreachCollectGarbage( TrackPath, Meta::TrackPtr, 3, m_trackMap );
0943         foreachCollectGarbage( QString, Meta::TrackPtr, 2, m_uidMap );
0944         // run before artist so that album artist pointers can be garbage collected
0945         foreachCollectGarbage( AlbumKey, Meta::AlbumPtr, 3, m_albumMap );
0946         foreachCollectGarbage( int, Meta::AlbumPtr, 2, m_albumIdMap );
0947         foreachCollectGarbage( QString, Meta::ArtistPtr, 3, m_artistMap );
0948         foreachCollectGarbage( int, Meta::ArtistPtr, 2, m_artistIdMap );
0949         foreachCollectGarbage( QString, Meta::GenrePtr, 2, m_genreMap );
0950         foreachCollectGarbage( QString, Meta::ComposerPtr, 2, m_composerMap );
0951         foreachCollectGarbage( int, Meta::YearPtr, 2, m_yearMap );
0952         foreachCollectGarbage( QString, Meta::LabelPtr, 2, m_labelMap );
0953         #undef foreachCollectGarbage
0954 
0955         QMap<QString, QPair<int, int> > cachedAfter;
0956 
0957         query = QString( "SELECT COUNT(*) FROM albums;" );
0958         res = m_collection->sqlStorage()->query( query );
0959         mapCached( cachedAfter, "albums", m_albumMap, res );
0960 
0961         query = QString( "SELECT COUNT(*) FROM tracks;" );
0962         res = m_collection->sqlStorage()->query( query );
0963         mapCached( cachedAfter, "tracks", m_trackMap, res );
0964 
0965         query = QString( "SELECT COUNT(*) FROM artists;" );
0966         res = m_collection->sqlStorage()->query( query );
0967         mapCached( cachedAfter, "artists", m_artistMap, res );
0968 
0969         query = QString( "SELECT COUNT(*) FROM genres;" );
0970         res = m_collection->sqlStorage()->query( query );
0971         mapCached( cachedAfter, "genres", m_genreMap, res );
0972         #undef mapCached
0973 
0974         if( cachedBefore != cachedAfter )
0975         {
0976             QMapIterator<QString, QPair<int, int> > i(cachedAfter), iLast(cachedBefore);
0977             while( i.hasNext() && iLast.hasNext() )
0978             {
0979                 i.next();
0980                 iLast.next();
0981                 int count = i.value().first;
0982                 int total = i.value().second;
0983                 QString diff = QString::number( count - iLast.value().first );
0984                 QString text = QString( "%1 (%2) of %3 cached" ).arg( count ).arg( diff ).arg( total );
0985                 debug() << QString( "%1: %2" ).arg( i.key(), 8 ).arg( text ).toLocal8Bit().constData();
0986             }
0987         }
0988     }
0989 
0990     //make sure to unlock all necessary locks
0991     //important: calling unlock() on an unlocked mutex gives an undefined result
0992     //unlocking a mutex locked by another thread results in an error, so be careful
0993     if( hasTrack ) m_trackMutex.unlock();
0994     if( hasAlbum ) m_albumMutex.unlock();
0995     if( hasArtist ) m_artistMutex.unlock();
0996     if( hasYear ) m_yearMutex.unlock();
0997     if( hasGenre ) m_genreMutex.unlock();
0998     if( hasComposer ) m_composerMutex.unlock();
0999     if( hasLabel ) m_labelMutex.unlock();
1000 }
1001 
1002