File indexing completed on 2025-01-19 04:24:26
0001 /**************************************************************************************** 0002 * Copyright (c) 2007 Maximilian Kossick <maximilian.kossick@googlemail.com> * 0003 * Copyright (c) 2008 Seb Ruiz <ruiz@kde.org> * 0004 * Copyright (c) 2009-2010 Jeff Mitchell <mitchell@kde.org> * 0005 * Copyright (c) 2013 Ralf Engels <ralf-engels@gmx.de> * 0006 * * 0007 * This program is free software; you can redistribute it and/or modify it under * 0008 * the terms of the GNU General Public License as published by the Free Software * 0009 * Foundation; either version 2 of the License, or (at your option) any later * 0010 * version. * 0011 * * 0012 * This program is distributed in the hope that it will be useful, but WITHOUT ANY * 0013 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * 0014 * PARTICULAR PURPOSE. See the GNU General Public License for more details. * 0015 * * 0016 * You should have received a copy of the GNU General Public License along with * 0017 * this program. If not, see <http://www.gnu.org/licenses/>. * 0018 ****************************************************************************************/ 0019 0020 #define DEBUG_PREFIX "SqlScanResultProcessor" 0021 0022 #include "SqlScanResultProcessor.h" 0023 0024 #include "MainWindow.h" 0025 #include "collectionscanner/Directory.h" 0026 #include "collectionscanner/Album.h" 0027 #include "collectionscanner/Track.h" 0028 #include "collectionscanner/Playlist.h" 0029 #include "core/support/Debug.h" 0030 #include "core-impl/collections/db/MountPointManager.h" 0031 #include "core-impl/collections/db/sql/SqlQueryMaker.h" 0032 #include "playlistmanager/PlaylistManager.h" 0033 0034 #include <KMessageBox> 0035 0036 #include <QAbstractEventDispatcher> 0037 #include <QApplication> 0038 0039 SqlScanResultProcessor::SqlScanResultProcessor( GenericScanManager* manager, 0040 Collections::SqlCollection* collection, 0041 QObject *parent ) 0042 : AbstractScanResultProcessor( manager, parent ) 0043 , m_collection( collection ) 0044 { } 0045 0046 SqlScanResultProcessor::~SqlScanResultProcessor() 0047 { } 0048 0049 void 0050 SqlScanResultProcessor::scanStarted( GenericScanManager::ScanType type ) 0051 { 0052 AbstractScanResultProcessor::scanStarted( type ); 0053 0054 m_collection->sqlStorage()->clearLastErrors(); 0055 m_messages.clear(); 0056 } 0057 0058 void 0059 SqlScanResultProcessor::scanSucceeded() 0060 { 0061 DEBUG_BLOCK; 0062 0063 // we are blocking the updated signal for maximum of one second. 0064 m_blockedTime = QDateTime::currentDateTime(); 0065 blockUpdates(); 0066 0067 urlsCacheInit(); 0068 0069 // -- call the base implementation 0070 AbstractScanResultProcessor::scanSucceeded(); 0071 0072 // -- error reporting 0073 m_messages.append( m_collection->sqlStorage()->getLastErrors() ); 0074 0075 if( !m_messages.isEmpty() && qobject_cast<QGuiApplication*>(qApp) ) 0076 QTimer::singleShot(0, this, &SqlScanResultProcessor::displayMessages); // do in the UI thread 0077 0078 unblockUpdates(); 0079 } 0080 0081 void 0082 SqlScanResultProcessor::displayMessages() 0083 { 0084 QString errorList = m_messages.join( "</li><li>" ).replace( '\n', "<br>" ); 0085 QString text = i18n( "<ul><li>%1</li></ul>" 0086 "In most cases this means that not all of your tracks were imported.<br>" 0087 "See <a href='http://userbase.kde.org/Amarok/Manual/Various/TroubleshootingAndCommonProblems#Duplicate_Tracks'>" 0088 "Amarok Manual</a> for information about duplicate tracks.", errorList ); 0089 KMessageBox::error( The::mainWindow(), text, i18n( "Errors During Collection Scan" ), 0090 KMessageBox::AllowLink ); 0091 0092 m_messages.clear(); 0093 } 0094 0095 void 0096 SqlScanResultProcessor::blockUpdates() 0097 { 0098 DEBUG_BLOCK 0099 0100 m_collection->blockUpdatedSignal(); 0101 m_collection->registry()->blockDatabaseUpdate(); 0102 } 0103 0104 void 0105 SqlScanResultProcessor::unblockUpdates() 0106 { 0107 DEBUG_BLOCK 0108 0109 m_collection->registry()->unblockDatabaseUpdate(); 0110 m_collection->unblockUpdatedSignal(); 0111 } 0112 0113 void 0114 SqlScanResultProcessor::message( const QString& message ) 0115 { 0116 m_messages.append( message ); 0117 } 0118 0119 void 0120 SqlScanResultProcessor::commitDirectory( QSharedPointer<CollectionScanner::Directory> directory ) 0121 { 0122 QString path = directory->path(); 0123 // a bit of paranoia: 0124 if( m_foundDirectories.contains( path ) ) 0125 warning() << "commitDirectory(): duplicate directory path" << path << "in" 0126 << "collectionscanner output. This shouldn't happen."; 0127 0128 // getDirectory() updates the directory entry mtime: 0129 int dirId = m_collection->registry()->getDirectory( path, directory->mtime() ); 0130 // we never dereference key of m_directoryIds, it is safe to add it as a plain pointer 0131 m_directoryIds.insert( directory.data(), dirId ); 0132 m_foundDirectories.insert( path, dirId ); 0133 0134 AbstractScanResultProcessor::commitDirectory( directory ); 0135 0136 // --- unblock every 5 second. Maybe not really needed, but still nice 0137 if( m_blockedTime.secsTo( QDateTime::currentDateTime() ) >= 5 ) 0138 { 0139 unblockUpdates(); 0140 m_blockedTime = QDateTime::currentDateTime(); 0141 blockUpdates(); 0142 } 0143 } 0144 0145 void 0146 SqlScanResultProcessor::commitAlbum( CollectionScanner::Album *album ) 0147 { 0148 debug() << "commitAlbum on"<<album->name()<< "artist"<<album->artist(); 0149 0150 // --- get or create the album 0151 Meta::SqlAlbumPtr metaAlbum; 0152 metaAlbum = Meta::SqlAlbumPtr::staticCast( m_collection->getAlbum( album->name(), album->artist() ) ); 0153 if( !metaAlbum ) 0154 return; 0155 m_albumIds.insert( album, metaAlbum->id() ); 0156 0157 // --- add all tracks 0158 foreach( CollectionScanner::Track *track, album->tracks() ) 0159 commitTrack( track, album ); 0160 0161 // --- set the cover if we have one 0162 // we need to do this after the tracks are added in case of an embedded cover 0163 bool suppressAutoFetch = metaAlbum->suppressImageAutoFetch(); 0164 metaAlbum->setSuppressImageAutoFetch( true ); 0165 if( m_type == GenericScanManager::FullScan ) 0166 { 0167 if( !album->cover().isEmpty() ) 0168 { 0169 metaAlbum->removeImage(); 0170 metaAlbum->setImage( album->cover() ); 0171 } 0172 } 0173 else 0174 { 0175 if( !metaAlbum->hasImage() && !album->cover().isEmpty() ) 0176 metaAlbum->setImage( album->cover() ); 0177 } 0178 metaAlbum->setSuppressImageAutoFetch( suppressAutoFetch ); 0179 } 0180 0181 void 0182 SqlScanResultProcessor::commitTrack( CollectionScanner::Track *track, 0183 CollectionScanner::Album *srcAlbum ) 0184 { 0185 // debug() << "commitTrack on"<<track->title()<< "album"<<srcAlbum->name() << "dir:" << track->directory()->path()<<track->directory(); 0186 0187 Q_ASSERT( track ); 0188 Q_ASSERT( srcAlbum ); 0189 0190 Q_ASSERT( m_directoryIds.contains( track->directory() ) ); 0191 int directoryId = m_directoryIds.value( track->directory() ); 0192 Q_ASSERT( m_albumIds.contains( srcAlbum ) ); 0193 int albumId = m_albumIds.value( srcAlbum ); 0194 0195 QString uid = track->uniqueid(); 0196 if( uid.isEmpty() ) 0197 { 0198 warning() << "commitTrack(): got track with empty unique id from the scanner," 0199 << "not adding it"; 0200 m_messages.append( QString( "Not adding track %1 because it has no unique id." ). 0201 arg(track->path()) ); 0202 return; 0203 } 0204 uid = m_collection->generateUidUrl( uid ); 0205 0206 int deviceId = m_collection->mountPointManager()->getIdForUrl( QUrl::fromUserInput(track->path()) ); 0207 QString rpath = m_collection->mountPointManager()->getRelativePath( deviceId, track->path() ); 0208 0209 if( m_foundTracks.contains( uid ) ) 0210 { 0211 const UrlEntry old = m_urlsCache.value( m_uidCache.value( uid ) ); 0212 const char *pattern = I18N_NOOP( "Duplicates found, the second file will be ignored:\n%1\n%2" ); 0213 0214 // we want translated version for GUI and non-translated for debug log 0215 warning() << "commitTrack():" << QString( pattern ).arg( old.path, track->path() ); 0216 m_messages.append( i18n( pattern, old.path, track->path() ) ); 0217 return; 0218 } 0219 0220 Meta::SqlTrackPtr metaTrack; 0221 UrlEntry entry; 0222 // find an existing track by uid 0223 if( m_uidCache.contains( uid ) ) 0224 { 0225 // uid is sadly not unique. Try to find the best url id. 0226 int urlId = findBestUrlId( uid, track->path() ); 0227 Q_ASSERT( urlId > 0 ); 0228 Q_ASSERT( m_urlsCache.contains( urlId ) ); 0229 entry = m_urlsCache.value( urlId ); 0230 entry.path = track->path(); 0231 entry.directoryId = directoryId; 0232 0233 metaTrack = Meta::SqlTrackPtr::staticCast( m_collection->registry()->getTrack( urlId ) ); 0234 Q_ASSERT( metaTrack->urlId() == entry.id ); 0235 } 0236 // find an existing track by path 0237 else if( m_pathCache.contains( track->path() ) ) 0238 { 0239 int urlId = m_pathCache.value( track->path() ); 0240 Q_ASSERT( m_urlsCache.contains( urlId ) ); 0241 entry = m_urlsCache.value( urlId ); 0242 entry.uid = uid; 0243 entry.directoryId = directoryId; 0244 0245 metaTrack = Meta::SqlTrackPtr::staticCast( m_collection->registry()->getTrack( urlId ) ); 0246 Q_ASSERT( metaTrack->urlId() == entry.id ); 0247 } 0248 // create a new one 0249 else 0250 { 0251 static int autoDecrementId = -1; 0252 entry.id = autoDecrementId--; 0253 entry.path = track->path(); 0254 entry.uid = uid; 0255 entry.directoryId = directoryId; 0256 0257 metaTrack = Meta::SqlTrackPtr::staticCast( m_collection->getTrack( deviceId, rpath, directoryId, uid ) ); 0258 } 0259 0260 if( !metaTrack ) 0261 { 0262 QString text = QString( "Something went wrong when importing track %1, metaTrack " 0263 "is null while it shouldn't be." ).arg( track->path() ); 0264 warning() << "commitTrack():" << text.toLocal8Bit().data(); 0265 m_messages.append( text ); 0266 return; 0267 } 0268 urlsCacheInsert( entry ); // removes the previous entry (by id) first if necessary 0269 m_foundTracks.insert( uid, entry.id ); 0270 0271 // TODO: we need to check the modified date of the file against the last updated of the file 0272 // to figure out if the track information was updated from outside Amarok. 0273 // In such a case we would fully reread all the information as if in a FullScan 0274 0275 // -- set the values 0276 metaTrack->setWriteFile( false ); // no need to write the tags back 0277 metaTrack->beginUpdate(); 0278 0279 metaTrack->setUidUrl( uid ); 0280 metaTrack->setUrl( deviceId, rpath, directoryId ); 0281 0282 if( m_type == GenericScanManager::FullScan || 0283 !track->title().isEmpty() ) 0284 metaTrack->setTitle( track->title() ); 0285 0286 if( m_type == GenericScanManager::FullScan || 0287 albumId != -1 ) 0288 metaTrack->setAlbum( albumId ); 0289 0290 if( m_type == GenericScanManager::FullScan || 0291 !track->artist().isEmpty() ) 0292 metaTrack->setArtist( track->artist() ); 0293 0294 if( m_type == GenericScanManager::FullScan || 0295 !track->composer().isEmpty() ) 0296 metaTrack->setComposer( track->composer() ); 0297 0298 if( m_type == GenericScanManager::FullScan || 0299 track->year() >= 0 ) 0300 metaTrack->setYear( (track->year() >= 0) ? track->year() : 0 ); 0301 0302 if( m_type == GenericScanManager::FullScan || 0303 !track->genre().isEmpty() ) 0304 metaTrack->setGenre( track->genre() ); 0305 0306 metaTrack->setType( track->filetype() ); 0307 0308 if( m_type == GenericScanManager::FullScan || 0309 track->bpm() >= 0 ) 0310 metaTrack->setBpm( track->bpm() ); 0311 0312 if( m_type == GenericScanManager::FullScan || 0313 !track->comment().isEmpty() ) 0314 metaTrack->setComment( track->comment() ); 0315 0316 if( (m_type == GenericScanManager::FullScan || metaTrack->score() == 0) && 0317 track->score() >= 0 ) 0318 metaTrack->setScore( track->score() ); 0319 0320 if( (m_type == GenericScanManager::FullScan || metaTrack->rating() == 0.0) && 0321 track->rating() >= 0 ) 0322 metaTrack->setRating( track->rating() ); 0323 0324 if( (m_type == GenericScanManager::FullScan || metaTrack->length() == 0) && 0325 track->length() >= 0 ) 0326 metaTrack->setLength( track->length() ); 0327 0328 // the filesize is updated every time after the 0329 // file is changed. Doesn't make sense to set it. 0330 0331 if( (m_type == GenericScanManager::FullScan || !metaTrack->modifyDate().isValid()) && 0332 track->modified().isValid() ) 0333 metaTrack->setModifyDate( track->modified() ); 0334 0335 if( (m_type == GenericScanManager::FullScan || metaTrack->sampleRate() == 0) && 0336 track->samplerate() >= 0 ) 0337 metaTrack->setSampleRate( track->samplerate() ); 0338 0339 if( (m_type == GenericScanManager::FullScan || metaTrack->bitrate() == 0) && 0340 track->bitrate() >= 0 ) 0341 metaTrack->setBitrate( track->bitrate() ); 0342 0343 if( (m_type == GenericScanManager::FullScan || metaTrack->trackNumber() == 0) && 0344 track->track() >= 0 ) 0345 metaTrack->setTrackNumber( track->track() ); 0346 0347 if( (m_type == GenericScanManager::FullScan || metaTrack->discNumber() == 0) && 0348 track->disc() >= 0 ) 0349 metaTrack->setDiscNumber( track->disc() ); 0350 0351 if( m_type == GenericScanManager::FullScan && track->playcount() >= metaTrack->playCount() ) 0352 metaTrack->setPlayCount( track->playcount() ); 0353 0354 0355 Meta::ReplayGainTag modes[] = { Meta::ReplayGain_Track_Gain, 0356 Meta::ReplayGain_Track_Peak, 0357 Meta::ReplayGain_Album_Gain, 0358 Meta::ReplayGain_Album_Peak }; 0359 0360 for( int i=0; i<4; i++ ) 0361 { 0362 if( track->replayGain( modes[i] ) != 0.0 ) 0363 metaTrack->setReplayGain( modes[i], track->replayGain( modes[i] ) ); 0364 } 0365 0366 metaTrack->endUpdate(); 0367 metaTrack->setWriteFile( true ); 0368 } 0369 0370 void 0371 SqlScanResultProcessor::deleteDeletedDirectories() 0372 { 0373 auto storage = m_collection->sqlStorage(); 0374 0375 QList<DirectoryEntry> toCheck; 0376 switch( m_type ) 0377 { 0378 case GenericScanManager::FullScan: 0379 case GenericScanManager::UpdateScan: 0380 toCheck = mountedDirectories(); 0381 break; 0382 case GenericScanManager::PartialUpdateScan: 0383 toCheck = deletedDirectories(); 0384 } 0385 0386 // -- check if the have been found during the scan 0387 foreach( const DirectoryEntry &e, toCheck ) 0388 { 0389 /* we need to match directories by their (absolute) path, otherwise following 0390 * scenario triggers statistics loss (bug 298275): 0391 * 0392 * 1. user relocates collection to different filesystem, but clones path structure 0393 * or toggles MassStorageDeviceHandler enabled in Config -> plugins. 0394 * 2. collectionscanner knows nothings about directory ids, so it doesn't detect 0395 * any track changes and emits a bunch of skipped (unchanged) dirs with no 0396 * tracks. 0397 * 3. SqlRegistry::getDirectory() called there from returns different directory id 0398 * then in past. 0399 * 4. deleteDeletedDirectories() is called, and if it operates on directory ids, 0400 * it happily removes _all_ directories, taking tracks with it. 0401 * 5. Tracks disappear from the UI until full rescan, stats, lyrics, labels are 0402 * lost forever. 0403 */ 0404 QString path = m_collection->mountPointManager()->getAbsolutePath( e.deviceId, e.dir ); 0405 bool deleteThisDir = false; 0406 if( !m_foundDirectories.contains( path ) ) 0407 deleteThisDir = true; 0408 else if( m_foundDirectories.value( path ) != e.dirId ) 0409 { 0410 int newDirId = m_foundDirectories.value( path ); 0411 // as a safety measure, we don't delete the old dir if relocation fails 0412 deleteThisDir = relocateTracksToNewDirectory( e.dirId, newDirId ); 0413 } 0414 0415 if( deleteThisDir ) 0416 { 0417 deleteDeletedTracks( e.dirId ); 0418 QString query = QString( "DELETE FROM directories WHERE id = %1;" ).arg( e.dirId ); 0419 storage->query( query ); 0420 } 0421 } 0422 } 0423 0424 void 0425 SqlScanResultProcessor::deleteDeletedTracksAndSubdirs( QSharedPointer<CollectionScanner::Directory> directory ) 0426 { 0427 Q_ASSERT( m_directoryIds.contains( directory.data() ) ); 0428 int directoryId = m_directoryIds.value( directory.data() ); 0429 // only deletes tracks directly in this dir 0430 deleteDeletedTracks( directoryId ); 0431 // trigger deletion of deleted subdirectories in deleteDeletedDirectories(): 0432 m_scannedDirectoryIds.insert( directoryId ); 0433 } 0434 0435 void 0436 SqlScanResultProcessor::deleteDeletedTracks( int directoryId ) 0437 { 0438 // -- find all tracks 0439 QList<int> urlIds = m_directoryCache.values( directoryId ); 0440 0441 // -- check if the tracks have been found during the scan 0442 foreach( int urlId, urlIds ) 0443 { 0444 Q_ASSERT( m_urlsCache.contains( urlId ) ); 0445 const UrlEntry &entry = m_urlsCache[ urlId ]; 0446 Q_ASSERT( entry.directoryId == directoryId ); 0447 // we need to match both uid and url id, because uid is not unique 0448 if( !m_foundTracks.contains( entry.uid, entry.id ) ) 0449 { 0450 removeTrack( entry ); 0451 urlsCacheRemove( entry ); 0452 } 0453 } 0454 } 0455 0456 int 0457 SqlScanResultProcessor::findBestUrlId( const QString &uid, const QString &path ) 0458 { 0459 QList<int> urlIds = m_uidCache.values( uid ); 0460 if( urlIds.isEmpty() ) 0461 return -1; 0462 if( urlIds.size() == 1 ) 0463 return urlIds.at( 0 ); // normal operation 0464 0465 foreach( int testedUrlId, urlIds ) 0466 { 0467 Q_ASSERT( m_urlsCache.contains( testedUrlId ) ); 0468 if( m_urlsCache[ testedUrlId ].path == path ) 0469 return testedUrlId; 0470 } 0471 0472 warning() << "multiple url entries with uid" << uid << "found in the database, but" 0473 << "none with current path" << path << "Choosing blindly the last one out" 0474 << "of url id candidates" << urlIds; 0475 return urlIds.last(); 0476 } 0477 0478 bool 0479 SqlScanResultProcessor::relocateTracksToNewDirectory( int oldDirId, int newDirId ) 0480 { 0481 QList<int> urlIds = m_directoryCache.values( oldDirId ); 0482 if( urlIds.isEmpty() ) 0483 return true; // nothing to do 0484 0485 MountPointManager *manager = m_collection->mountPointManager(); 0486 SqlRegistry *reg = m_collection->registry(); 0487 auto storage = m_collection->sqlStorage(); 0488 0489 // sanity checking, not strictly needed, but imagine new device appearing in the 0490 // middle of the scan, so rather prevent db corruption: 0491 QStringList res = storage->query( QString( "SELECT deviceid FROM directories " 0492 "WHERE id = %1" ).arg( newDirId ) ); 0493 if( res.count() != 1 ) 0494 { 0495 warning() << "relocateTracksToNewDirectory(): no or multiple entries when" 0496 << "querying directory with id" << newDirId; 0497 return false; 0498 } 0499 int newDirDeviceId = res.at( 0 ).toInt(); 0500 0501 foreach( int urlId, urlIds ) 0502 { 0503 Q_ASSERT( m_urlsCache.contains( urlId ) ); 0504 UrlEntry entry = m_urlsCache.value( urlId ); 0505 Meta::SqlTrackPtr track = Meta::SqlTrackPtr::staticCast( reg->getTrack( urlId ) ); 0506 Q_ASSERT( track ); 0507 0508 // not strictly needed, but we want to sanity check it to prevent corrupt db 0509 int deviceId = manager->getIdForUrl( QUrl::fromUserInput(entry.path) ); 0510 if( newDirDeviceId != deviceId ) 0511 { 0512 warning() << "relocateTracksToNewDirectory(): device id from newDirId (" 0513 << res.at( 0 ).toInt() << ") and device id from mountPointManager (" 0514 << deviceId << ") don't match!"; 0515 return false; 0516 } 0517 QString rpath = manager->getRelativePath( deviceId, entry.path ); 0518 0519 track->setUrl( deviceId, rpath, newDirId ); 0520 entry.directoryId = newDirId; 0521 urlsCacheInsert( entry ); // removes the previous entry (by id) first 0522 } 0523 return true; 0524 } 0525 0526 void 0527 SqlScanResultProcessor::removeTrack( const UrlEntry &entry ) 0528 { 0529 debug() << "removeTrack(" << entry << ")"; 0530 // we used to skip track removal is m_messages wasn't empty, but that lead to to 0531 // tracks left laying around. We now hope that the result processor got better and 0532 // only removes tracks that should be removed. 0533 0534 SqlRegistry *reg = m_collection->registry(); 0535 // we must get the track by id, uid is not unique 0536 Meta::SqlTrackPtr track = Meta::SqlTrackPtr::staticCast( reg->getTrack( entry.id ) ); 0537 Q_ASSERT( track->urlId() == entry.id ); 0538 track->remove(); 0539 } 0540 0541 QList<SqlScanResultProcessor::DirectoryEntry> 0542 SqlScanResultProcessor::mountedDirectories() const 0543 { 0544 auto storage = m_collection->sqlStorage(); 0545 0546 // -- get a list of all mounted device ids 0547 QList<int> idList = m_collection->mountPointManager()->getMountedDeviceIds(); 0548 QString deviceIds; 0549 foreach( int id, idList ) 0550 { 0551 if ( !deviceIds.isEmpty() ) 0552 deviceIds += ','; 0553 deviceIds += QString::number( id ); 0554 } 0555 0556 // -- get all (mounted) directories 0557 QString query = QString( "SELECT id, deviceid, dir FROM directories " 0558 "WHERE deviceid IN (%1)" ).arg( deviceIds ); 0559 QStringList res = storage->query( query ); 0560 0561 QList<DirectoryEntry> result; 0562 for( int i = 0; i < res.count(); ) 0563 { 0564 DirectoryEntry e; 0565 e.dirId = res.at( i++ ).toInt(); 0566 e.deviceId = res.at( i++ ).toInt(); 0567 e.dir = res.at( i++ ); 0568 result << e; 0569 } 0570 0571 return result; 0572 } 0573 0574 QList<SqlScanResultProcessor::DirectoryEntry> 0575 SqlScanResultProcessor::deletedDirectories() const 0576 { 0577 auto storage = m_collection->sqlStorage(); 0578 0579 QHash<int, DirectoryEntry> idToDirEntryMap; // for faster processing during filtering 0580 foreach( int directoryId, m_scannedDirectoryIds ) 0581 { 0582 QString query = QString( "SELECT deviceid, dir FROM directories WHERE id = %1" ) 0583 .arg( directoryId ); 0584 QStringList res = storage->query( query ); 0585 if( res.count() != 2 ) 0586 { 0587 warning() << "unexpected query result" << res << "in deletedDirectories()"; 0588 continue; 0589 } 0590 0591 int deviceId = res.at( 0 ).toInt(); 0592 QString dir = res.at( 1 ); 0593 // select all child directories 0594 query = QString( "SELECT id, deviceid, dir FROM directories WHERE deviceid = %1 " 0595 "AND dir LIKE '%2_%'" ).arg( deviceId ).arg( storage->escape( dir ) ); 0596 res = storage->query( query ); 0597 for( int i = 0; i < res.count(); ) 0598 { 0599 DirectoryEntry e; 0600 e.dirId = res.at( i++ ).toInt(); 0601 e.deviceId = res.at( i++ ).toInt(); 0602 e.dir = res.at( i++ ); 0603 idToDirEntryMap.insert( e.dirId, e ); 0604 } 0605 } 0606 0607 // now we must filter out all found directories *and their children*, because the 0608 // children are *not* in m_foundDirectories and deleteDeletedDirectories() would 0609 // remove them erroneously 0610 foreach( int foundDirectoryId, m_foundDirectories ) 0611 { 0612 if( idToDirEntryMap.contains( foundDirectoryId ) ) 0613 { 0614 int existingDeviceId = idToDirEntryMap[ foundDirectoryId ].deviceId; 0615 QString existingPath = idToDirEntryMap[ foundDirectoryId ].dir; 0616 idToDirEntryMap.remove( foundDirectoryId ); 0617 0618 // now remove all children of the existing directory 0619 QMutableHashIterator<int, DirectoryEntry> it( idToDirEntryMap ); 0620 while( it.hasNext() ) 0621 { 0622 const DirectoryEntry &e = it.next().value(); 0623 if( e.deviceId == existingDeviceId && e.dir.startsWith( existingPath ) ) 0624 it.remove(); 0625 } 0626 } 0627 } 0628 0629 return idToDirEntryMap.values(); 0630 } 0631 0632 void 0633 SqlScanResultProcessor::urlsCacheInit() 0634 { 0635 DEBUG_BLOCK 0636 0637 auto storage = m_collection->sqlStorage(); 0638 0639 QString query = QStringLiteral( "SELECT id, deviceid, rpath, directory, uniqueid FROM urls;"); 0640 QStringList res = storage->query( query ); 0641 0642 for( int i = 0; i < res.count(); ) 0643 { 0644 int id = res.at(i++).toInt(); 0645 int deviceId = res.at(i++).toInt(); 0646 QString rpath = res.at(i++); 0647 int directoryId = res.at(i++).toInt(); 0648 QString uid = res.at(i++); 0649 0650 QString path; 0651 if( deviceId ) 0652 path = m_collection->mountPointManager()->getAbsolutePath( deviceId, rpath ); 0653 else 0654 path = rpath; 0655 0656 UrlEntry entry; 0657 entry.id = id; 0658 entry.path = path; 0659 entry.directoryId = directoryId; 0660 entry.uid = uid; 0661 0662 if( !directoryId ) 0663 { 0664 warning() << "Found urls entry without directory. A phantom track. Removing" << path; 0665 removeTrack( entry ); 0666 continue; 0667 } 0668 0669 urlsCacheInsert( entry ); 0670 0671 QAbstractEventDispatcher::instance()->processEvents( QEventLoop::AllEvents ); 0672 } 0673 } 0674 0675 void 0676 SqlScanResultProcessor::urlsCacheInsert( const UrlEntry &entry ) 0677 { 0678 // this case is normal operation 0679 if( m_urlsCache.contains( entry.id ) ) 0680 urlsCacheRemove( m_urlsCache[ entry.id ] ); 0681 0682 // following shouldn't normally happen: 0683 if( m_pathCache.contains( entry.path ) ) 0684 { 0685 int oldId = m_pathCache.value( entry.path ); 0686 Q_ASSERT( m_urlsCache.contains( oldId ) ); 0687 const UrlEntry &old = m_urlsCache[ oldId ]; 0688 warning() << "urlsCacheInsert(): found duplicate in path. old" << old 0689 << "will be hidden by the new one in the cache:" << entry; 0690 } 0691 0692 // this will signify error in this class: 0693 Q_ASSERT( !m_uidCache.contains( entry.uid, entry.id ) ); 0694 Q_ASSERT( !m_directoryCache.contains( entry.directoryId, entry.id ) ); 0695 0696 m_urlsCache.insert( entry.id, entry ); 0697 m_uidCache.insert( entry.uid, entry.id ); 0698 m_pathCache.insert( entry.path, entry.id ); 0699 m_directoryCache.insert( entry.directoryId, entry.id ); 0700 } 0701 0702 void 0703 SqlScanResultProcessor::cleanupMembers() 0704 { 0705 m_foundDirectories.clear(); 0706 m_foundTracks.clear(); 0707 m_scannedDirectoryIds.clear(); 0708 m_directoryIds.clear(); 0709 m_albumIds.clear(); 0710 0711 m_urlsCache.clear(); 0712 m_uidCache.clear(); 0713 m_pathCache.clear(); 0714 m_directoryCache.clear(); 0715 0716 AbstractScanResultProcessor::cleanupMembers(); 0717 } 0718 0719 void 0720 SqlScanResultProcessor::urlsCacheRemove( const UrlEntry &entry ) 0721 { 0722 if( !m_urlsCache.contains( entry.id ) ) 0723 return; 0724 0725 m_uidCache.remove( entry.uid, entry.id ); 0726 m_pathCache.remove( entry.path ); 0727 m_directoryCache.remove( entry.directoryId, entry.id ); 0728 m_urlsCache.remove( entry.id ); 0729 } 0730 0731 QDebug 0732 operator<<( QDebug dbg, const SqlScanResultProcessor::UrlEntry &entry ) 0733 { 0734 dbg.nospace() << "Entry(id=" << entry.id << ", path=" << entry.path << ", dirId=" 0735 << entry.directoryId << ", uid=" << entry.uid << ")"; 0736 return dbg.space(); 0737 }