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