File indexing completed on 2025-03-09 04:24:18
0001 /**************************************************************************************** 0002 * Copyright (c) 2007 Maximilian Kossick <maximilian.kossick@googlemail.com> * 0003 * Copyright (c) 2008 Daniel Winter <dw@danielwinter.de> * 0004 * Copyright (c) 2010 Ralf Engels <ralf-engels@gmx.de> * 0005 * * 0006 * This program is free software; you can redistribute it and/or modify it under * 0007 * the terms of the GNU General Public License as published by the Free Software * 0008 * Foundation; either version 2 of the License, or (at your option) any later * 0009 * version. * 0010 * * 0011 * This program is distributed in the hope that it will be useful, but WITHOUT ANY * 0012 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * 0013 * PARTICULAR PURPOSE. See the GNU General Public License for more details. * 0014 * * 0015 * You should have received a copy of the GNU General Public License along with * 0016 * this program. If not, see <http://www.gnu.org/licenses/>. * 0017 ****************************************************************************************/ 0018 0019 #define DEBUG_PREFIX "SqlMeta" 0020 0021 #include "SqlMeta.h" 0022 0023 #include "amarokconfig.h" 0024 0025 #include "SqlCapabilities.h" 0026 #include "SqlCollection.h" 0027 #include "SqlQueryMaker.h" 0028 #include "SqlRegistry.h" 0029 #include "SqlReadLabelCapability.h" 0030 #include "SqlWriteLabelCapability.h" 0031 0032 #include "MetaTagLib.h" // for getting an embedded cover 0033 0034 #include "amarokurls/BookmarkMetaActions.h" 0035 #include <core/storage/SqlStorage.h> 0036 #include "core/meta/support/MetaUtility.h" 0037 #include "core/support/Amarok.h" 0038 #include "core/support/Debug.h" 0039 #include "core/capabilities/BookmarkThisCapability.h" 0040 #include "core-impl/capabilities/AlbumActionsCapability.h" 0041 #include "core-impl/collections/db/MountPointManager.h" 0042 #include "core-impl/collections/support/ArtistHelper.h" 0043 #include "core-impl/collections/support/jobs/WriteTagsJob.h" 0044 #include "covermanager/CoverCache.h" 0045 #include "covermanager/CoverFetcher.h" 0046 0047 #include <QAction> 0048 #include <QDateTime> 0049 #include <QDir> 0050 #include <QFile> 0051 #include <QMultiHash> 0052 #include <QReadWriteLock> 0053 #include <QReadLocker> 0054 #include <QWriteLocker> 0055 #include <QMutexLocker> 0056 #include <QCryptographicHash> 0057 0058 #include <KCodecs> 0059 #include <KLocalizedString> 0060 #include <ThreadWeaver/Queue> 0061 0062 0063 // additional constants 0064 namespace Meta 0065 { 0066 static const qint64 valAlbumId = valCustom + 1; 0067 } 0068 0069 using namespace Meta; 0070 0071 QString 0072 SqlTrack::getTrackReturnValues() 0073 { 0074 //do not use any weird column names that contains commas: this will break getTrackReturnValuesCount() 0075 // NOTE: when changing this, always check that SqlTrack::TrackReturnIndex enum remains valid 0076 return "urls.id, urls.deviceid, urls.rpath, urls.directory, urls.uniqueid, " 0077 "tracks.id, tracks.title, tracks.comment, " 0078 "tracks.tracknumber, tracks.discnumber, " 0079 "statistics.score, statistics.rating, " 0080 "tracks.bitrate, tracks.length, " 0081 "tracks.filesize, tracks.samplerate, " 0082 "statistics.id, " 0083 "statistics.createdate, statistics.accessdate, " 0084 "statistics.playcount, tracks.filetype, tracks.bpm, " 0085 "tracks.createdate, tracks.modifydate, tracks.albumgain, tracks.albumpeakgain, " 0086 "tracks.trackgain, tracks.trackpeakgain, " 0087 "artists.name, artists.id, " // TODO: just reading the id should be sufficient 0088 "albums.name, albums.id, albums.artist, " // TODO: again here 0089 "genres.name, genres.id, " // TODO: again here 0090 "composers.name, composers.id, " // TODO: again here 0091 "years.name, years.id"; // TODO: again here 0092 } 0093 0094 QString 0095 SqlTrack::getTrackJoinConditions() 0096 { 0097 return "LEFT JOIN tracks ON urls.id = tracks.url " 0098 "LEFT JOIN statistics ON urls.id = statistics.url " 0099 "LEFT JOIN artists ON tracks.artist = artists.id " 0100 "LEFT JOIN albums ON tracks.album = albums.id " 0101 "LEFT JOIN genres ON tracks.genre = genres.id " 0102 "LEFT JOIN composers ON tracks.composer = composers.id " 0103 "LEFT JOIN years ON tracks.year = years.id"; 0104 } 0105 0106 int 0107 SqlTrack::getTrackReturnValueCount() 0108 { 0109 static int count = getTrackReturnValues().split( QLatin1Char(',') ).count(); 0110 return count; 0111 } 0112 0113 SqlTrack::SqlTrack( Collections::SqlCollection *collection, int deviceId, 0114 const QString &rpath, int directoryId, const QString &uidUrl ) 0115 : Track() 0116 , m_collection( collection ) 0117 , m_batchUpdate( 0 ) 0118 , m_writeFile( true ) 0119 , m_labelsInCache( false ) 0120 { 0121 m_batchUpdate = 1; // I don't want commits yet 0122 0123 m_urlId = -1; // this will be set with the first database write 0124 m_trackId = -1; // this will be set with the first database write 0125 m_statisticsId = -1; 0126 0127 setUrl( deviceId, rpath, directoryId ); 0128 m_url = QUrl::fromUserInput(m_cache.value( Meta::valUrl ).toString()); // SqlRegistry already has this url 0129 setUidUrl( uidUrl ); 0130 m_uid = m_cache.value( Meta::valUniqueId ).toString(); // SqlRegistry already has this uid 0131 0132 0133 // ensure that these values get a correct database id 0134 m_cache.insert( Meta::valAlbum, QVariant() ); 0135 m_cache.insert( Meta::valArtist, QVariant() ); 0136 m_cache.insert( Meta::valComposer, QVariant() ); 0137 m_cache.insert( Meta::valYear, QVariant() ); 0138 m_cache.insert( Meta::valGenre, QVariant() ); 0139 0140 m_trackNumber = 0; 0141 m_discNumber = 0; 0142 m_score = 0; 0143 m_rating = 0; 0144 m_bitrate = 0; 0145 m_length = 0; 0146 m_filesize = 0; 0147 m_sampleRate = 0; 0148 m_playCount = 0; 0149 m_bpm = 0.0; 0150 m_createDate = QDateTime::currentDateTime(); 0151 m_cache.insert( Meta::valCreateDate, m_createDate ); // ensure that the created date is written the next time 0152 0153 m_trackGain = 0.0; 0154 m_trackPeakGain = 0.0; 0155 m_albumGain = 0.0; 0156 m_albumPeakGain = 0.0; 0157 0158 m_batchUpdate = 0; // reset in-batch-update without committing 0159 0160 m_filetype = Amarok::Unknown; 0161 } 0162 0163 SqlTrack::SqlTrack( Collections::SqlCollection *collection, const QStringList &result ) 0164 : Track() 0165 , m_collection( collection ) 0166 , m_batchUpdate( 0 ) 0167 , m_writeFile( true ) 0168 , m_labelsInCache( false ) 0169 { 0170 QStringList::ConstIterator iter = result.constBegin(); 0171 m_urlId = (*(iter++)).toInt(); 0172 Q_ASSERT( m_urlId > 0 && "refusing to create SqlTrack with non-positive urlId, please file a bug" ); 0173 m_deviceId = (*(iter++)).toInt(); 0174 Q_ASSERT( m_deviceId != 0 && "refusing to create SqlTrack with zero deviceId, please file a bug" ); 0175 m_rpath = *(iter++); 0176 m_directoryId = (*(iter++)).toInt(); 0177 Q_ASSERT( m_directoryId > 0 && "refusing to create SqlTrack with non-positive directoryId, please file a bug" ); 0178 m_url = QUrl::fromLocalFile( m_collection->mountPointManager()->getAbsolutePath( m_deviceId, m_rpath ) ); 0179 m_uid = *(iter++); 0180 m_trackId = (*(iter++)).toInt(); 0181 m_title = *(iter++); 0182 m_comment = *(iter++); 0183 m_trackNumber = (*(iter++)).toInt(); 0184 m_discNumber = (*(iter++)).toInt(); 0185 m_score = (*(iter++)).toDouble(); 0186 m_rating = (*(iter++)).toInt(); 0187 m_bitrate = (*(iter++)).toInt(); 0188 m_length = (*(iter++)).toInt(); 0189 m_filesize = (*(iter++)).toInt(); 0190 m_sampleRate = (*(iter++)).toInt(); 0191 m_statisticsId = (*(iter++)).toInt(); 0192 uint time = (*(iter++)).toUInt(); 0193 if( time > 0 ) 0194 m_firstPlayed = QDateTime::fromSecsSinceEpoch(time); 0195 time = (*(iter++)).toUInt(); 0196 if( time > 0 ) 0197 m_lastPlayed = QDateTime::fromSecsSinceEpoch(time); 0198 m_playCount = (*(iter++)).toInt(); 0199 m_filetype = Amarok::FileType( (*(iter++)).toInt() ); 0200 m_bpm = (*(iter++)).toFloat(); 0201 m_createDate = QDateTime::fromSecsSinceEpoch((*(iter++)).toUInt()); 0202 m_modifyDate = QDateTime::fromSecsSinceEpoch((*(iter++)).toUInt()); 0203 0204 // if there is no track gain, we assume a gain of 0 0205 // if there is no album gain, we use the track gain 0206 QString albumGain = *(iter++); 0207 QString albumPeakGain = *(iter++); 0208 m_trackGain = (*(iter++)).toDouble(); 0209 m_trackPeakGain = (*(iter++)).toDouble(); 0210 if ( albumGain.isEmpty() ) 0211 { 0212 m_albumGain = m_trackGain; 0213 m_albumPeakGain = m_trackPeakGain; 0214 } 0215 else 0216 { 0217 m_albumGain = albumGain.toDouble(); 0218 m_albumPeakGain = albumPeakGain.toDouble(); 0219 } 0220 SqlRegistry* registry = m_collection->registry(); 0221 0222 QString artist = *(iter++); 0223 int artistId = (*(iter++)).toInt(); 0224 if( artistId > 0 ) 0225 m_artist = registry->getArtist( artistId, artist ); 0226 0227 QString album = *(iter++); 0228 int albumId =(*(iter++)).toInt(); 0229 int albumArtistId = (*(iter++)).toInt(); 0230 if( albumId > 0 ) // sanity check 0231 m_album = registry->getAlbum( albumId, album, albumArtistId ); 0232 0233 QString genre = *(iter++); 0234 int genreId = (*(iter++)).toInt(); 0235 if( genreId > 0 ) // sanity check 0236 m_genre = registry->getGenre( genreId, genre ); 0237 0238 QString composer = *(iter++); 0239 int composerId = (*(iter++)).toInt(); 0240 if( composerId > 0 ) // sanity check 0241 m_composer = registry->getComposer( composerId, composer ); 0242 0243 QString year = *(iter++); 0244 int yearId = (*(iter++)).toInt(); 0245 if( yearId > 0 ) // sanity check 0246 m_year = registry->getYear( year.toInt(), yearId ); 0247 //Q_ASSERT_X( iter == result.constEnd(), "SqlTrack( Collections::SqlCollection*, QStringList )", "number of expected fields did not match number of actual fields: expected " + result.size() ); 0248 } 0249 0250 SqlTrack::~SqlTrack() 0251 { 0252 QWriteLocker locker( &m_lock ); 0253 0254 if( !m_cache.isEmpty() ) 0255 warning() << "Destroying track with unwritten meta information." << m_title << "cache:" << m_cache; 0256 if( m_batchUpdate ) 0257 warning() << "Destroying track with unclosed batch update." << m_title; 0258 } 0259 0260 QString 0261 SqlTrack::name() const 0262 { 0263 QReadLocker locker( &m_lock ); 0264 return m_title; 0265 } 0266 0267 QString 0268 SqlTrack::prettyName() const 0269 { 0270 if ( !name().isEmpty() ) 0271 return name(); 0272 return prettyTitle( m_url.fileName() ); 0273 } 0274 0275 void 0276 SqlTrack::setTitle( const QString &newTitle ) 0277 { 0278 QWriteLocker locker( &m_lock ); 0279 0280 if ( m_title != newTitle ) 0281 commitIfInNonBatchUpdate( Meta::valTitle, newTitle ); 0282 } 0283 0284 0285 QUrl 0286 SqlTrack::playableUrl() const 0287 { 0288 QReadLocker locker( &m_lock ); 0289 return m_url; 0290 } 0291 0292 QString 0293 SqlTrack::prettyUrl() const 0294 { 0295 QReadLocker locker( &m_lock ); 0296 return m_url.path(); 0297 } 0298 0299 void 0300 SqlTrack::setUrl( int deviceId, const QString &rpath, int directoryId ) 0301 { 0302 QWriteLocker locker( &m_lock ); 0303 0304 if( m_deviceId == deviceId && 0305 m_rpath == rpath && 0306 m_directoryId == directoryId ) 0307 return; 0308 0309 m_deviceId = deviceId; 0310 m_rpath = rpath; 0311 m_directoryId = directoryId; 0312 0313 commitIfInNonBatchUpdate( Meta::valUrl, 0314 m_collection->mountPointManager()->getAbsolutePath( m_deviceId, m_rpath ) ); 0315 } 0316 0317 QString 0318 SqlTrack::uidUrl() const 0319 { 0320 QReadLocker locker( &m_lock ); 0321 return m_uid; 0322 } 0323 0324 void 0325 SqlTrack::setUidUrl( const QString &uid ) 0326 { 0327 QWriteLocker locker( &m_lock ); 0328 0329 // -- ensure that the uid starts with the collections protocol (amarok-sqltrackuid) 0330 QString newid = uid; 0331 QString protocol; 0332 if( m_collection ) 0333 protocol = m_collection->uidUrlProtocol()+"://"; 0334 if( !newid.startsWith( protocol ) ) 0335 newid.prepend( protocol ); 0336 0337 m_cache.insert( Meta::valUniqueId, newid ); 0338 0339 if( m_batchUpdate == 0 ) 0340 { 0341 debug() << "setting uidUrl manually...did you really mean to do this?"; 0342 commitIfInNonBatchUpdate(); 0343 } 0344 } 0345 0346 QString 0347 SqlTrack::notPlayableReason() const 0348 { 0349 return localFileNotPlayableReason( playableUrl().toLocalFile() ); 0350 } 0351 0352 bool 0353 SqlTrack::isEditable() const 0354 { 0355 QReadLocker locker( &m_lock ); 0356 0357 QFile::Permissions p = QFile::permissions( m_url.path() ); 0358 const bool editable = ( p & QFile::WriteUser ) || ( p & QFile::WriteGroup ) || ( p & QFile::WriteOther ); 0359 return m_collection && QFile::exists( m_url.path() ) && editable; 0360 } 0361 0362 Meta::AlbumPtr 0363 SqlTrack::album() const 0364 { 0365 QReadLocker locker( &m_lock ); 0366 return m_album; 0367 } 0368 0369 void 0370 SqlTrack::setAlbum( const QString &newAlbum ) 0371 { 0372 QWriteLocker locker( &m_lock ); 0373 0374 if( !m_album || m_album->name() != newAlbum ) 0375 commitIfInNonBatchUpdate( Meta::valAlbum, newAlbum ); 0376 } 0377 0378 void 0379 SqlTrack::setAlbum( int albumId ) 0380 { 0381 QWriteLocker locker( &m_lock ); 0382 0383 commitIfInNonBatchUpdate( Meta::valAlbumId, albumId ); 0384 } 0385 0386 Meta::ArtistPtr 0387 SqlTrack::artist() const 0388 { 0389 QReadLocker locker( &m_lock ); 0390 return m_artist; 0391 } 0392 0393 void 0394 SqlTrack::setArtist( const QString &newArtist ) 0395 { 0396 QWriteLocker locker( &m_lock ); 0397 0398 if( !m_artist || m_artist->name() != newArtist ) 0399 commitIfInNonBatchUpdate( Meta::valArtist, newArtist ); 0400 } 0401 0402 void 0403 SqlTrack::setAlbumArtist( const QString &newAlbumArtist ) 0404 { 0405 if( m_album.isNull() ) 0406 return; 0407 0408 if( !newAlbumArtist.compare( "Various Artists", Qt::CaseInsensitive ) || 0409 !newAlbumArtist.compare( i18n( "Various Artists" ), Qt::CaseInsensitive ) ) 0410 { 0411 commitIfInNonBatchUpdate( Meta::valCompilation, true ); 0412 } 0413 else 0414 { 0415 m_cache.insert( Meta::valAlbumArtist, ArtistHelper::realTrackArtist( newAlbumArtist ) ); 0416 m_cache.insert( Meta::valCompilation, false ); 0417 commitIfInNonBatchUpdate(); 0418 } 0419 } 0420 0421 Meta::ComposerPtr 0422 SqlTrack::composer() const 0423 { 0424 QReadLocker locker( &m_lock ); 0425 return m_composer; 0426 } 0427 0428 void 0429 SqlTrack::setComposer( const QString &newComposer ) 0430 { 0431 QWriteLocker locker( &m_lock ); 0432 0433 if( !m_composer || m_composer->name() != newComposer ) 0434 commitIfInNonBatchUpdate( Meta::valComposer, newComposer ); 0435 } 0436 0437 Meta::YearPtr 0438 SqlTrack::year() const 0439 { 0440 QReadLocker locker( &m_lock ); 0441 return m_year; 0442 } 0443 0444 void 0445 SqlTrack::setYear( int newYear ) 0446 { 0447 QWriteLocker locker( &m_lock ); 0448 0449 if( !m_year || m_year->year() != newYear ) 0450 commitIfInNonBatchUpdate( Meta::valYear, newYear ); 0451 } 0452 0453 Meta::GenrePtr 0454 SqlTrack::genre() const 0455 { 0456 QReadLocker locker( &m_lock ); 0457 return m_genre; 0458 } 0459 0460 void 0461 SqlTrack::setGenre( const QString &newGenre ) 0462 { 0463 QWriteLocker locker( &m_lock ); 0464 0465 if( !m_genre || m_genre->name() != newGenre ) 0466 commitIfInNonBatchUpdate( Meta::valGenre, newGenre ); 0467 } 0468 0469 QString 0470 SqlTrack::type() const 0471 { 0472 QReadLocker locker( &m_lock ); 0473 0474 return m_url.isLocalFile() 0475 ? Amarok::FileTypeSupport::toString( m_filetype ) 0476 // don't localize. This is used in different files to identify streams, see EngineController quirks 0477 : "stream"; 0478 } 0479 0480 void 0481 SqlTrack::setType( Amarok::FileType newType ) 0482 { 0483 QWriteLocker locker( &m_lock ); 0484 0485 if ( m_filetype != newType ) 0486 commitIfInNonBatchUpdate( Meta::valFormat, int(newType) ); 0487 } 0488 0489 qreal 0490 SqlTrack::bpm() const 0491 { 0492 QReadLocker locker( &m_lock ); 0493 return m_bpm; 0494 } 0495 0496 void 0497 SqlTrack::setBpm( const qreal newBpm ) 0498 { 0499 QWriteLocker locker( &m_lock ); 0500 0501 if ( m_bpm != newBpm ) 0502 commitIfInNonBatchUpdate( Meta::valBpm, newBpm ); 0503 } 0504 0505 QString 0506 SqlTrack::comment() const 0507 { 0508 QReadLocker locker( &m_lock ); 0509 return m_comment; 0510 } 0511 0512 void 0513 SqlTrack::setComment( const QString &newComment ) 0514 { 0515 QWriteLocker locker( &m_lock ); 0516 0517 if( newComment != m_comment ) 0518 commitIfInNonBatchUpdate( Meta::valComment, newComment ); 0519 } 0520 0521 double 0522 SqlTrack::score() const 0523 { 0524 QReadLocker locker( &m_lock ); 0525 return m_score; 0526 } 0527 0528 void 0529 SqlTrack::setScore( double newScore ) 0530 { 0531 QWriteLocker locker( &m_lock ); 0532 0533 newScore = qBound( double(0), newScore, double(100) ); 0534 if( qAbs( newScore - m_score ) > 0.001 ) // we don't commit for minimal changes 0535 commitIfInNonBatchUpdate( Meta::valScore, newScore ); 0536 } 0537 0538 int 0539 SqlTrack::rating() const 0540 { 0541 QReadLocker locker( &m_lock ); 0542 return m_rating; 0543 } 0544 0545 void 0546 SqlTrack::setRating( int newRating ) 0547 { 0548 QWriteLocker locker( &m_lock ); 0549 0550 newRating = qBound( 0, newRating, 10 ); 0551 if( newRating != m_rating ) 0552 commitIfInNonBatchUpdate( Meta::valRating, newRating ); 0553 } 0554 0555 qint64 0556 SqlTrack::length() const 0557 { 0558 QReadLocker locker( &m_lock ); 0559 return m_length; 0560 } 0561 0562 void 0563 SqlTrack::setLength( qint64 newLength ) 0564 { 0565 QWriteLocker locker( &m_lock ); 0566 0567 if( newLength != m_length ) 0568 commitIfInNonBatchUpdate( Meta::valLength, newLength ); 0569 } 0570 0571 int 0572 SqlTrack::filesize() const 0573 { 0574 QReadLocker locker( &m_lock ); 0575 return m_filesize; 0576 } 0577 0578 int 0579 SqlTrack::sampleRate() const 0580 { 0581 QReadLocker locker( &m_lock ); 0582 return m_sampleRate; 0583 } 0584 0585 void 0586 SqlTrack::setSampleRate( int newSampleRate ) 0587 { 0588 QWriteLocker locker( &m_lock ); 0589 0590 if( newSampleRate != m_sampleRate ) 0591 commitIfInNonBatchUpdate( Meta::valSamplerate, newSampleRate ); 0592 } 0593 0594 int 0595 SqlTrack::bitrate() const 0596 { 0597 QReadLocker locker( &m_lock ); 0598 return m_bitrate; 0599 } 0600 0601 void 0602 SqlTrack::setBitrate( int newBitrate ) 0603 { 0604 QWriteLocker locker( &m_lock ); 0605 0606 if( newBitrate != m_bitrate ) 0607 commitIfInNonBatchUpdate( Meta::valBitrate, newBitrate ); 0608 } 0609 0610 QDateTime 0611 SqlTrack::createDate() const 0612 { 0613 QReadLocker locker( &m_lock ); 0614 return m_createDate; 0615 } 0616 0617 QDateTime 0618 SqlTrack::modifyDate() const 0619 { 0620 QReadLocker locker( &m_lock ); 0621 return m_modifyDate; 0622 } 0623 0624 void 0625 SqlTrack::setModifyDate( const QDateTime &newTime ) 0626 { 0627 QWriteLocker locker( &m_lock ); 0628 0629 if( newTime != m_modifyDate ) 0630 commitIfInNonBatchUpdate( Meta::valModified, newTime ); 0631 } 0632 0633 int 0634 SqlTrack::trackNumber() const 0635 { 0636 QReadLocker locker( &m_lock ); 0637 return m_trackNumber; 0638 } 0639 0640 void 0641 SqlTrack::setTrackNumber( int newTrackNumber ) 0642 { 0643 QWriteLocker locker( &m_lock ); 0644 0645 if( newTrackNumber != m_trackNumber ) 0646 commitIfInNonBatchUpdate( Meta::valTrackNr, newTrackNumber ); 0647 } 0648 0649 int 0650 SqlTrack::discNumber() const 0651 { 0652 QReadLocker locker( &m_lock ); 0653 return m_discNumber; 0654 } 0655 0656 void 0657 SqlTrack::setDiscNumber( int newDiscNumber ) 0658 { 0659 QWriteLocker locker( &m_lock ); 0660 0661 if( newDiscNumber != m_discNumber ) 0662 commitIfInNonBatchUpdate( Meta::valDiscNr, newDiscNumber ); 0663 } 0664 0665 QDateTime 0666 SqlTrack::lastPlayed() const 0667 { 0668 QReadLocker locker( &m_lock ); 0669 return m_lastPlayed; 0670 } 0671 0672 void 0673 SqlTrack::setLastPlayed( const QDateTime &newTime ) 0674 { 0675 QWriteLocker locker( &m_lock ); 0676 0677 if( newTime != m_lastPlayed ) 0678 commitIfInNonBatchUpdate( Meta::valLastPlayed, newTime ); 0679 } 0680 0681 QDateTime 0682 SqlTrack::firstPlayed() const 0683 { 0684 QReadLocker locker( &m_lock ); 0685 return m_firstPlayed; 0686 } 0687 0688 void 0689 SqlTrack::setFirstPlayed( const QDateTime &newTime ) 0690 { 0691 QWriteLocker locker( &m_lock ); 0692 0693 if( newTime != m_firstPlayed ) 0694 commitIfInNonBatchUpdate( Meta::valFirstPlayed, newTime ); 0695 } 0696 0697 int 0698 SqlTrack::playCount() const 0699 { 0700 QReadLocker locker( &m_lock ); 0701 return m_playCount; 0702 } 0703 0704 void 0705 SqlTrack::setPlayCount( const int newCount ) 0706 { 0707 QWriteLocker locker( &m_lock ); 0708 0709 if( newCount != m_playCount ) 0710 commitIfInNonBatchUpdate( Meta::valPlaycount, newCount ); 0711 } 0712 0713 qreal 0714 SqlTrack::replayGain( ReplayGainTag mode ) const 0715 { 0716 QReadLocker locker(&(const_cast<SqlTrack*>(this)->m_lock)); 0717 0718 switch( mode ) 0719 { 0720 case Meta::ReplayGain_Track_Gain: 0721 return m_trackGain; 0722 case Meta::ReplayGain_Track_Peak: 0723 return m_trackPeakGain; 0724 case Meta::ReplayGain_Album_Gain: 0725 return m_albumGain; 0726 case Meta::ReplayGain_Album_Peak: 0727 return m_albumPeakGain; 0728 } 0729 return 0.0; 0730 } 0731 0732 void 0733 SqlTrack::setReplayGain( Meta::ReplayGainTag mode, qreal value ) 0734 { 0735 if( qAbs( value - replayGain( mode ) ) < 0.01 ) 0736 return; 0737 0738 { 0739 QWriteLocker locker( &m_lock ); 0740 0741 switch( mode ) 0742 { 0743 case Meta::ReplayGain_Track_Gain: 0744 m_cache.insert( Meta::valTrackGain, value ); 0745 break; 0746 case Meta::ReplayGain_Track_Peak: 0747 m_cache.insert( Meta::valTrackGainPeak, value ); 0748 break; 0749 case Meta::ReplayGain_Album_Gain: 0750 m_cache.insert( Meta::valAlbumGain, value ); 0751 break; 0752 case Meta::ReplayGain_Album_Peak: 0753 m_cache.insert( Meta::valAlbumGainPeak, value ); 0754 break; 0755 } 0756 0757 commitIfInNonBatchUpdate(); 0758 } 0759 } 0760 0761 0762 void 0763 SqlTrack::beginUpdate() 0764 { 0765 QWriteLocker locker( &m_lock ); 0766 m_batchUpdate++; 0767 } 0768 0769 void 0770 SqlTrack::endUpdate() 0771 { 0772 QWriteLocker locker( &m_lock ); 0773 Q_ASSERT( m_batchUpdate > 0 ); 0774 m_batchUpdate--; 0775 commitIfInNonBatchUpdate(); 0776 } 0777 0778 void 0779 SqlTrack::commitIfInNonBatchUpdate( qint64 field, const QVariant &value ) 0780 { 0781 m_cache.insert( field, value ); 0782 commitIfInNonBatchUpdate(); 0783 } 0784 0785 void 0786 SqlTrack::commitIfInNonBatchUpdate() 0787 { 0788 if( m_batchUpdate > 0 || m_cache.isEmpty() ) 0789 return; // nothing to do 0790 0791 // debug() << "SqlTrack::commitMetaDataChanges " << m_cache; 0792 0793 QString oldUid = m_uid; 0794 0795 // for all the following objects we need to invalidate the cache and 0796 // notify the observers after the update 0797 AmarokSharedPointer<SqlArtist> oldArtist; 0798 AmarokSharedPointer<SqlArtist> newArtist; 0799 AmarokSharedPointer<SqlAlbum> oldAlbum; 0800 AmarokSharedPointer<SqlAlbum> newAlbum; 0801 AmarokSharedPointer<SqlComposer> oldComposer; 0802 AmarokSharedPointer<SqlComposer> newComposer; 0803 AmarokSharedPointer<SqlGenre> oldGenre; 0804 AmarokSharedPointer<SqlGenre> newGenre; 0805 AmarokSharedPointer<SqlYear> oldYear; 0806 AmarokSharedPointer<SqlYear> newYear; 0807 0808 if( m_cache.contains( Meta::valFormat ) ) 0809 m_filetype = Amarok::FileType(m_cache.value( Meta::valFormat ).toInt()); 0810 if( m_cache.contains( Meta::valTitle ) ) 0811 m_title = m_cache.value( Meta::valTitle ).toString(); 0812 if( m_cache.contains( Meta::valComment ) ) 0813 m_comment = m_cache.value( Meta::valComment ).toString(); 0814 if( m_cache.contains( Meta::valScore ) ) 0815 m_score = m_cache.value( Meta::valScore ).toDouble(); 0816 if( m_cache.contains( Meta::valRating ) ) 0817 m_rating = m_cache.value( Meta::valRating ).toInt(); 0818 if( m_cache.contains( Meta::valLength ) ) 0819 m_length = m_cache.value( Meta::valLength ).toLongLong(); 0820 if( m_cache.contains( Meta::valSamplerate ) ) 0821 m_sampleRate = m_cache.value( Meta::valSamplerate ).toInt(); 0822 if( m_cache.contains( Meta::valBitrate ) ) 0823 m_bitrate = m_cache.value( Meta::valBitrate ).toInt(); 0824 if( m_cache.contains( Meta::valFirstPlayed ) ) 0825 m_firstPlayed = m_cache.value( Meta::valFirstPlayed ).toDateTime(); 0826 if( m_cache.contains( Meta::valLastPlayed ) ) 0827 m_lastPlayed = m_cache.value( Meta::valLastPlayed ).toDateTime(); 0828 if( m_cache.contains( Meta::valTrackNr ) ) 0829 m_trackNumber = m_cache.value( Meta::valTrackNr ).toInt(); 0830 if( m_cache.contains( Meta::valDiscNr ) ) 0831 m_discNumber = m_cache.value( Meta::valDiscNr ).toInt(); 0832 if( m_cache.contains( Meta::valPlaycount ) ) 0833 m_playCount = m_cache.value( Meta::valPlaycount ).toInt(); 0834 if( m_cache.contains( Meta::valCreateDate ) ) 0835 m_createDate = m_cache.value( Meta::valCreateDate ).toDateTime(); 0836 if( m_cache.contains( Meta::valModified ) ) 0837 m_modifyDate = m_cache.value( Meta::valModified ).toDateTime(); 0838 if( m_cache.contains( Meta::valTrackGain ) ) 0839 m_trackGain = m_cache.value( Meta::valTrackGain ).toDouble(); 0840 if( m_cache.contains( Meta::valTrackGainPeak ) ) 0841 m_trackPeakGain = m_cache.value( Meta::valTrackGainPeak ).toDouble(); 0842 if( m_cache.contains( Meta::valAlbumGain ) ) 0843 m_albumGain = m_cache.value( Meta::valAlbumGain ).toDouble(); 0844 if( m_cache.contains( Meta::valAlbumGainPeak ) ) 0845 m_albumPeakGain = m_cache.value( Meta::valAlbumGainPeak ).toDouble(); 0846 0847 if( m_cache.contains( Meta::valUrl ) ) 0848 { 0849 // slight problem here: it is possible to set the url to the one of an already 0850 // existing track, which is forbidden by the database 0851 // At least the ScanResultProcessor handles this problem 0852 0853 QUrl oldUrl = m_url; 0854 QUrl newUrl = QUrl::fromUserInput(m_cache.value( Meta::valUrl ).toString()); 0855 if( oldUrl != newUrl ) 0856 m_collection->registry()->updateCachedUrl( oldUrl.path(), newUrl.path() ); 0857 m_url = newUrl; 0858 // debug() << "m_cache contains a new URL, setting m_url to " << m_url << " from " << oldUrl; 0859 } 0860 0861 if( m_cache.contains( Meta::valArtist ) ) 0862 { 0863 //invalidate cache of the old artist... 0864 oldArtist = static_cast<SqlArtist*>(m_artist.data()); 0865 m_artist = m_collection->registry()->getArtist( m_cache.value( Meta::valArtist ).toString() ); 0866 //and the new one 0867 newArtist = static_cast<SqlArtist*>(m_artist.data()); 0868 0869 // if the current album is no compilation and we aren't changing 0870 // the album anyway, then we need to create a new album with the 0871 // new artist. 0872 if( m_album ) 0873 { 0874 bool supp = m_album->suppressImageAutoFetch(); 0875 m_album->setSuppressImageAutoFetch( true ); 0876 0877 if( m_album->hasAlbumArtist() && 0878 m_album->albumArtist() == oldArtist && 0879 !m_cache.contains( Meta::valAlbum ) && 0880 !m_cache.contains( Meta::valAlbumId ) ) 0881 { 0882 m_cache.insert( Meta::valAlbum, m_album->name() ); 0883 } 0884 0885 m_album->setSuppressImageAutoFetch( supp ); 0886 } 0887 } 0888 0889 if( m_cache.contains( Meta::valAlbum ) || 0890 m_cache.contains( Meta::valAlbumId ) || 0891 m_cache.contains( Meta::valAlbumArtist ) ) 0892 { 0893 oldAlbum = static_cast<SqlAlbum*>(m_album.data()); 0894 0895 if( m_cache.contains( Meta::valAlbumId ) ) 0896 m_album = m_collection->registry()->getAlbum( m_cache.value( Meta::valAlbumId ).toInt() ); 0897 else 0898 { 0899 // the album should remain a compilation after renaming it 0900 // TODO: we would need to use the artist helper 0901 QString newArtistName; 0902 if( m_cache.contains( Meta::valAlbumArtist ) ) 0903 newArtistName = m_cache.value( Meta::valAlbumArtist ).toString(); 0904 else if( oldAlbum && oldAlbum->isCompilation() && !oldAlbum->name().isEmpty() ) 0905 newArtistName.clear(); 0906 else if( oldAlbum && oldAlbum->hasAlbumArtist() ) 0907 newArtistName = oldAlbum->albumArtist()->name(); 0908 0909 m_album = m_collection->registry()->getAlbum( m_cache.contains( Meta::valAlbum) 0910 ? m_cache.value( Meta::valAlbum ).toString() 0911 : oldAlbum->name(), 0912 newArtistName ); 0913 } 0914 0915 newAlbum = static_cast<SqlAlbum*>(m_album.data()); 0916 0917 // due to the complex logic with artist and albumId it can happen that 0918 // in the end we have the same album as before. 0919 if( newAlbum == oldAlbum ) 0920 { 0921 m_cache.remove( Meta::valAlbum ); 0922 m_cache.remove( Meta::valAlbumId ); 0923 m_cache.remove( Meta::valAlbumArtist ); 0924 oldAlbum.clear(); 0925 newAlbum.clear(); 0926 } 0927 } 0928 0929 if( m_cache.contains( Meta::valComposer ) ) 0930 { 0931 oldComposer = static_cast<SqlComposer*>(m_composer.data()); 0932 m_composer = m_collection->registry()->getComposer( m_cache.value( Meta::valComposer ).toString() ); 0933 newComposer = static_cast<SqlComposer*>(m_composer.data()); 0934 } 0935 0936 if( m_cache.contains( Meta::valGenre ) ) 0937 { 0938 oldGenre = static_cast<SqlGenre*>(m_genre.data()); 0939 m_genre = m_collection->registry()->getGenre( m_cache.value( Meta::valGenre ).toString() ); 0940 newGenre = static_cast<SqlGenre*>(m_genre.data()); 0941 } 0942 0943 if( m_cache.contains( Meta::valYear ) ) 0944 { 0945 oldYear = static_cast<SqlYear*>(m_year.data()); 0946 m_year = m_collection->registry()->getYear( m_cache.value( Meta::valYear ).toInt() ); 0947 newYear = static_cast<SqlYear*>(m_year.data()); 0948 } 0949 0950 if( m_cache.contains( Meta::valBpm ) ) 0951 m_bpm = m_cache.value( Meta::valBpm ).toDouble(); 0952 0953 // --- write the file 0954 if( m_writeFile && AmarokConfig::writeBack() ) 0955 { 0956 Meta::Tag::writeTags( m_url.path(), m_cache, AmarokConfig::writeBackStatistics() ); 0957 // unique id may have changed 0958 QString uid = Meta::Tag::readTags( m_url.path() ).value( Meta::valUniqueId ).toString(); 0959 if( !uid.isEmpty() ) 0960 m_cache[ Meta::valUniqueId ] = m_collection->generateUidUrl( uid ); 0961 } 0962 0963 // needs to be after writing to file; that may have changed generated uid 0964 if( m_cache.contains( Meta::valUniqueId ) ) 0965 { 0966 QString newUid = m_cache.value( Meta::valUniqueId ).toString(); 0967 if( oldUid != newUid && m_collection->registry()->updateCachedUid( oldUid, newUid ) ) 0968 m_uid = newUid; 0969 } 0970 0971 //updating the fields might have changed the filesize 0972 //read the current filesize so that we can update the db 0973 QFile file( m_url.path() ); 0974 if( file.exists() ) 0975 { 0976 if( m_filesize != file.size() ) 0977 { 0978 m_cache.insert( Meta::valFilesize, file.size() ); 0979 m_filesize = file.size(); 0980 } 0981 } 0982 0983 // --- add to the registry dirty list 0984 SqlRegistry *registry = nullptr; 0985 // prevent writing to the db when we don't know the directory, bug 322474. Note that 0986 // m_urlId is created by registry->commitDirtyTracks() if there is none. 0987 if( m_deviceId != 0 && m_directoryId > 0 ) 0988 { 0989 registry = m_collection->registry(); 0990 QMutexLocker locker2( ®istry->m_blockMutex ); 0991 registry->m_dirtyTracks.insert( Meta::SqlTrackPtr( this ) ); 0992 if( oldArtist ) 0993 registry->m_dirtyArtists.insert( oldArtist ); 0994 if( newArtist ) 0995 registry->m_dirtyArtists.insert( newArtist ); 0996 if( oldAlbum ) 0997 registry->m_dirtyAlbums.insert( oldAlbum ); 0998 if( newAlbum ) 0999 registry->m_dirtyAlbums.insert( newAlbum ); 1000 if( oldComposer ) 1001 registry->m_dirtyComposers.insert( oldComposer ); 1002 if( newComposer ) 1003 registry->m_dirtyComposers.insert( newComposer ); 1004 if( oldGenre ) 1005 registry->m_dirtyGenres.insert( oldGenre ); 1006 if( newGenre ) 1007 registry->m_dirtyGenres.insert( newGenre ); 1008 if( oldYear ) 1009 registry->m_dirtyYears.insert( oldYear ); 1010 if( newYear ) 1011 registry->m_dirtyYears.insert( newYear ); 1012 } 1013 else 1014 error() << Q_FUNC_INFO << "non-positive urlId, zero deviceId or non-positive" 1015 << "directoryId encountered in track" << m_url 1016 << "urlId:" << m_urlId << "deviceId:" << m_deviceId 1017 << "directoryId:" << m_directoryId << "- not writing back metadata" 1018 << "changes to the database."; 1019 1020 m_lock.unlock(); // or else we provoke a deadlock 1021 1022 // copy the image BUG: 203211 (we need to do it here or provoke a dead lock) 1023 if( oldAlbum && newAlbum ) 1024 { 1025 bool oldSupp = oldAlbum->suppressImageAutoFetch(); 1026 bool newSupp = newAlbum->suppressImageAutoFetch(); 1027 oldAlbum->setSuppressImageAutoFetch( true ); 1028 newAlbum->setSuppressImageAutoFetch( true ); 1029 1030 if( oldAlbum->hasImage() && !newAlbum->hasImage() ) 1031 newAlbum->setImage( oldAlbum->imageLocation().path() ); 1032 1033 oldAlbum->setSuppressImageAutoFetch( oldSupp ); 1034 newAlbum->setSuppressImageAutoFetch( newSupp ); 1035 } 1036 1037 if( registry ) 1038 registry->commitDirtyTracks(); // calls notifyObservers() as appropriate 1039 else 1040 notifyObservers(); 1041 m_lock.lockForWrite(); // reset back to state it was during call 1042 1043 if( m_uid != oldUid ) 1044 { 1045 updatePlaylistsToDb( m_cache, oldUid ); 1046 updateEmbeddedCoversToDb( m_cache, oldUid ); 1047 } 1048 1049 // --- clean up 1050 m_cache.clear(); 1051 } 1052 1053 void 1054 SqlTrack::updatePlaylistsToDb( const FieldHash &fields, const QString &oldUid ) 1055 { 1056 if( fields.isEmpty() ) 1057 return; // nothing to do 1058 1059 auto storage = m_collection->sqlStorage(); 1060 QStringList tags; 1061 1062 // keep this in sync with SqlPlaylist::saveTracks()! 1063 if( fields.contains( Meta::valUrl ) ) 1064 tags << QString( "url='%1'" ).arg( storage->escape( m_url.path() ) ); 1065 if( fields.contains( Meta::valTitle ) ) 1066 tags << QString( "title='%1'" ).arg( storage->escape( m_title ) ); 1067 if( fields.contains( Meta::valAlbum ) ) 1068 tags << QString( "album='%1'" ).arg( m_album ? storage->escape( m_album->prettyName() ) : "" ); 1069 if( fields.contains( Meta::valArtist ) ) 1070 tags << QString( "artist='%1'" ).arg( m_artist ? storage->escape( m_artist->prettyName() ) : "" ); 1071 if( fields.contains( Meta::valLength ) ) 1072 tags << QString( "length=%1").arg( QString::number( m_length ) ); 1073 if( fields.contains( Meta::valUniqueId ) ) 1074 { 1075 // SqlPlaylist mirrors uniqueid to url, update it too, bug 312128 1076 tags << QString( "url='%1'" ).arg( storage->escape( m_uid ) ); 1077 tags << QString( "uniqueid='%1'" ).arg( storage->escape( m_uid ) ); 1078 } 1079 1080 if( !tags.isEmpty() ) 1081 { 1082 QString update = "UPDATE playlist_tracks SET %1 WHERE uniqueid = '%2';"; 1083 update = update.arg( tags.join( ", " ), storage->escape( oldUid ) ); 1084 storage->query( update ); 1085 } 1086 } 1087 1088 void 1089 SqlTrack::updateEmbeddedCoversToDb( const FieldHash &fields, const QString &oldUid ) 1090 { 1091 if( fields.isEmpty() ) 1092 return; // nothing to do 1093 1094 auto storage = m_collection->sqlStorage(); 1095 QString tags; 1096 1097 if( fields.contains( Meta::valUniqueId ) ) 1098 tags += QString( ",path='%1'" ).arg( storage->escape( m_uid ) ); 1099 1100 if( !tags.isEmpty() ) 1101 { 1102 tags = tags.remove(0, 1); // the first character is always a ',' 1103 QString update = "UPDATE images SET %1 WHERE path = '%2';"; 1104 update = update.arg( tags, storage->escape( oldUid ) ); 1105 storage->query( update ); 1106 } 1107 } 1108 1109 QString 1110 SqlTrack::prettyTitle( const QString &filename ) //static 1111 { 1112 QString s = filename; //just so the code is more readable 1113 1114 //remove .part extension if it exists 1115 if (s.endsWith( ".part" )) 1116 s = s.left( s.length() - 5 ); 1117 1118 //remove file extension, s/_/ /g and decode %2f-like sequences 1119 s = s.left( s.lastIndexOf( QLatin1Char('.') ) ).replace( '_', ' ' ); 1120 s = QUrl::fromPercentEncoding( s.toLatin1() ); 1121 1122 return s; 1123 } 1124 1125 bool 1126 SqlTrack::inCollection() const 1127 { 1128 QReadLocker locker( &m_lock ); 1129 return m_trackId > 0; 1130 } 1131 1132 Collections::Collection* 1133 SqlTrack::collection() const 1134 { 1135 return m_collection; 1136 } 1137 1138 QString 1139 SqlTrack::cachedLyrics() const 1140 { 1141 /* We don't cache the string as it may be potentially very long */ 1142 QString query = QStringLiteral( "SELECT lyrics FROM lyrics WHERE url = %1" ).arg( m_urlId ); 1143 QStringList result = m_collection->sqlStorage()->query( query ); 1144 if( result.isEmpty() ) 1145 return QString(); 1146 return result.first(); 1147 } 1148 1149 void 1150 SqlTrack::setCachedLyrics( const QString &lyrics ) 1151 { 1152 QString query = QString( "SELECT count(*) FROM lyrics WHERE url = %1").arg( m_urlId ); 1153 const QStringList queryResult = m_collection->sqlStorage()->query( query ); 1154 if( queryResult.isEmpty() ) 1155 return; // error in the query? 1156 1157 if( queryResult.first().toInt() == 0 ) 1158 { 1159 QString insert = QString( "INSERT INTO lyrics( url, lyrics ) VALUES ( %1, '%2' )" ) 1160 .arg( QString::number( m_urlId ), 1161 m_collection->sqlStorage()->escape( lyrics ) ); 1162 m_collection->sqlStorage()->insert( insert, "lyrics" ); 1163 } 1164 else 1165 { 1166 QString update = QString( "UPDATE lyrics SET lyrics = '%1' WHERE url = %2" ) 1167 .arg( m_collection->sqlStorage()->escape( lyrics ), 1168 QString::number( m_urlId ) ); 1169 m_collection->sqlStorage()->query( update ); 1170 } 1171 1172 notifyObservers(); 1173 } 1174 1175 bool 1176 SqlTrack::hasCapabilityInterface( Capabilities::Capability::Type type ) const 1177 { 1178 switch( type ) 1179 { 1180 case Capabilities::Capability::Actions: 1181 case Capabilities::Capability::Organisable: 1182 case Capabilities::Capability::BookmarkThis: 1183 case Capabilities::Capability::WriteTimecode: 1184 case Capabilities::Capability::LoadTimecode: 1185 case Capabilities::Capability::ReadLabel: 1186 case Capabilities::Capability::WriteLabel: 1187 case Capabilities::Capability::FindInSource: 1188 return true; 1189 default: 1190 return Track::hasCapabilityInterface( type ); 1191 } 1192 } 1193 1194 Capabilities::Capability* 1195 SqlTrack::createCapabilityInterface( Capabilities::Capability::Type type ) 1196 { 1197 switch( type ) 1198 { 1199 case Capabilities::Capability::Actions: 1200 { 1201 QList<QAction*> actions; 1202 //TODO These actions will hang around until m_collection is destructed. 1203 // Find a better parent to avoid this memory leak. 1204 //actions.append( new CopyToDeviceAction( m_collection, this ) ); 1205 1206 return new Capabilities::ActionsCapability( actions ); 1207 } 1208 case Capabilities::Capability::Organisable: 1209 return new Capabilities::OrganiseCapabilityImpl( this ); 1210 case Capabilities::Capability::BookmarkThis: 1211 return new Capabilities::BookmarkThisCapability( new BookmarkCurrentTrackPositionAction( nullptr ) ); 1212 case Capabilities::Capability::WriteTimecode: 1213 return new Capabilities::TimecodeWriteCapabilityImpl( this ); 1214 case Capabilities::Capability::LoadTimecode: 1215 return new Capabilities::TimecodeLoadCapabilityImpl( this ); 1216 case Capabilities::Capability::ReadLabel: 1217 return new Capabilities::SqlReadLabelCapability( this, sqlCollection()->sqlStorage() ); 1218 case Capabilities::Capability::WriteLabel: 1219 return new Capabilities::SqlWriteLabelCapability( this, sqlCollection()->sqlStorage() ); 1220 case Capabilities::Capability::FindInSource: 1221 return new Capabilities::FindInSourceCapabilityImpl( this ); 1222 1223 default: 1224 return Track::createCapabilityInterface( type ); 1225 } 1226 1227 } 1228 1229 void 1230 SqlTrack::addLabel( const QString &label ) 1231 { 1232 Meta::LabelPtr realLabel = m_collection->registry()->getLabel( label ); 1233 addLabel( realLabel ); 1234 } 1235 1236 void 1237 SqlTrack::addLabel( const Meta::LabelPtr &label ) 1238 { 1239 AmarokSharedPointer<SqlLabel> sqlLabel = AmarokSharedPointer<SqlLabel>::dynamicCast( label ); 1240 if( !sqlLabel ) 1241 { 1242 Meta::LabelPtr tmp = m_collection->registry()->getLabel( label->name() ); 1243 sqlLabel = AmarokSharedPointer<SqlLabel>::dynamicCast( tmp ); 1244 } 1245 if( sqlLabel ) 1246 { 1247 QWriteLocker locker( &m_lock ); 1248 commitIfInNonBatchUpdate(); // we need to have a up-to-date m_urlId 1249 if( m_urlId <= 0 ) 1250 { 1251 warning() << "Track does not have an urlId."; 1252 return; 1253 } 1254 1255 QString countQuery = "SELECT COUNT(*) FROM urls_labels WHERE url = %1 AND label = %2;"; 1256 QStringList countRs = m_collection->sqlStorage()->query( countQuery.arg( QString::number( m_urlId ), QString::number( sqlLabel->id() ) ) ); 1257 if( !countRs.isEmpty() && countRs.first().toInt() == 0 ) 1258 { 1259 QString insert = "INSERT INTO urls_labels(url,label) VALUES (%1,%2);"; 1260 m_collection->sqlStorage()->insert( insert.arg( QString::number( m_urlId ), QString::number( sqlLabel->id() ) ), "urls_labels" ); 1261 1262 if( m_labelsInCache ) 1263 { 1264 m_labelsCache.append( Meta::LabelPtr::staticCast( sqlLabel ) ); 1265 } 1266 locker.unlock(); 1267 notifyObservers(); 1268 sqlLabel->invalidateCache(); 1269 } 1270 } 1271 } 1272 1273 int 1274 SqlTrack::id() const 1275 { 1276 QReadLocker locker( &m_lock ); 1277 return m_trackId; 1278 } 1279 1280 int 1281 SqlTrack::urlId() const 1282 { 1283 QReadLocker locker( &m_lock ); 1284 return m_urlId; 1285 } 1286 1287 void 1288 SqlTrack::removeLabel( const Meta::LabelPtr &label ) 1289 { 1290 AmarokSharedPointer<SqlLabel> sqlLabel = AmarokSharedPointer<SqlLabel>::dynamicCast( label ); 1291 if( !sqlLabel ) 1292 { 1293 Meta::LabelPtr tmp = m_collection->registry()->getLabel( label->name() ); 1294 sqlLabel = AmarokSharedPointer<SqlLabel>::dynamicCast( tmp ); 1295 } 1296 if( sqlLabel ) 1297 { 1298 QString query = "DELETE FROM urls_labels WHERE label = %2 and url = (SELECT url FROM tracks WHERE id = %1);"; 1299 m_collection->sqlStorage()->query( query.arg( QString::number( m_trackId ), QString::number( sqlLabel->id() ) ) ); 1300 if( m_labelsInCache ) 1301 { 1302 m_labelsCache.removeAll( Meta::LabelPtr::staticCast( sqlLabel ) ); 1303 } 1304 notifyObservers(); 1305 sqlLabel->invalidateCache(); 1306 } 1307 } 1308 1309 Meta::LabelList 1310 SqlTrack::labels() const 1311 { 1312 { 1313 QReadLocker locker( &m_lock ); 1314 if( m_labelsInCache ) 1315 return m_labelsCache; 1316 } 1317 1318 if( !m_collection ) 1319 return Meta::LabelList(); 1320 1321 // when running the query maker don't lock. might lead to deadlock via registry 1322 Collections::SqlQueryMaker *qm = static_cast< Collections::SqlQueryMaker* >( m_collection->queryMaker() ); 1323 qm->setQueryType( Collections::QueryMaker::Label ); 1324 qm->addMatch( Meta::TrackPtr( const_cast<SqlTrack*>(this) ) ); 1325 qm->setBlocking( true ); 1326 qm->run(); 1327 1328 { 1329 QWriteLocker locker( &m_lock ); 1330 m_labelsInCache = true; 1331 m_labelsCache = qm->labels(); 1332 1333 delete qm; 1334 return m_labelsCache; 1335 } 1336 } 1337 1338 TrackEditorPtr 1339 SqlTrack::editor() 1340 { 1341 return TrackEditorPtr( isEditable() ? this : nullptr ); 1342 } 1343 1344 StatisticsPtr 1345 SqlTrack::statistics() 1346 { 1347 return StatisticsPtr( this ); 1348 } 1349 1350 void 1351 SqlTrack::remove() 1352 { 1353 QWriteLocker locker( &m_lock ); 1354 m_cache.clear(); 1355 locker.unlock(); 1356 m_collection->registry()->removeTrack( m_urlId, m_uid ); 1357 1358 // -- inform all albums, artist, years 1359 #undef foreachInvalidateCache 1360 #define INVALIDATE_AND_UPDATE(X) if( X ) \ 1361 { \ 1362 X->invalidateCache(); \ 1363 X->notifyObservers(); \ 1364 } 1365 INVALIDATE_AND_UPDATE(static_cast<Meta::SqlArtist*>(m_artist.data())); 1366 INVALIDATE_AND_UPDATE(static_cast<Meta::SqlAlbum*>(m_album.data())); 1367 INVALIDATE_AND_UPDATE(static_cast<Meta::SqlComposer*>(m_composer.data())); 1368 INVALIDATE_AND_UPDATE(static_cast<Meta::SqlGenre*>(m_genre.data())); 1369 INVALIDATE_AND_UPDATE(static_cast<Meta::SqlYear*>(m_year.data())); 1370 #undef INVALIDATE_AND_UPDATE 1371 m_artist = nullptr; 1372 m_album = nullptr; 1373 m_composer = nullptr; 1374 m_genre = nullptr; 1375 m_year = nullptr; 1376 1377 m_urlId = 0; 1378 m_trackId = 0; 1379 m_statisticsId = 0; 1380 1381 m_collection->collectionUpdated(); 1382 } 1383 1384 //---------------------- class Artist -------------------------- 1385 1386 SqlArtist::SqlArtist( Collections::SqlCollection *collection, int id, const QString &name ) 1387 : Artist() 1388 , m_collection( collection ) 1389 , m_id( id ) 1390 , m_name( name ) 1391 , m_tracksLoaded( false ) 1392 { 1393 Q_ASSERT( m_collection ); 1394 Q_ASSERT( m_id > 0 ); 1395 } 1396 1397 Meta::SqlArtist::~SqlArtist() 1398 { 1399 } 1400 1401 void 1402 SqlArtist::invalidateCache() 1403 { 1404 QMutexLocker locker( &m_mutex ); 1405 m_tracksLoaded = false; 1406 m_tracks.clear(); 1407 } 1408 1409 TrackList 1410 SqlArtist::tracks() 1411 { 1412 { 1413 QMutexLocker locker( &m_mutex ); 1414 if( m_tracksLoaded ) 1415 return m_tracks; 1416 } 1417 1418 // when running the query maker don't lock. might lead to deadlock via registry 1419 Collections::SqlQueryMaker *qm = static_cast< Collections::SqlQueryMaker* >( m_collection->queryMaker() ); 1420 qm->setQueryType( Collections::QueryMaker::Track ); 1421 qm->addMatch( Meta::ArtistPtr( this ) ); 1422 qm->setBlocking( true ); 1423 qm->run(); 1424 1425 { 1426 QMutexLocker locker( &m_mutex ); 1427 m_tracks = qm->tracks(); 1428 m_tracksLoaded = true; 1429 delete qm; 1430 return m_tracks; 1431 } 1432 } 1433 1434 bool 1435 SqlArtist::hasCapabilityInterface( Capabilities::Capability::Type type ) const 1436 { 1437 switch( type ) 1438 { 1439 case Capabilities::Capability::BookmarkThis: 1440 return true; 1441 default: 1442 return Artist::hasCapabilityInterface( type ); 1443 } 1444 } 1445 1446 Capabilities::Capability* 1447 SqlArtist::createCapabilityInterface( Capabilities::Capability::Type type ) 1448 { 1449 switch( type ) 1450 { 1451 case Capabilities::Capability::BookmarkThis: 1452 return new Capabilities::BookmarkThisCapability( new BookmarkArtistAction( nullptr, Meta::ArtistPtr( this ) ) ); 1453 default: 1454 return Artist::createCapabilityInterface( type ); 1455 } 1456 } 1457 1458 1459 //--------------- class Album --------------------------------- 1460 const QString SqlAlbum::AMAROK_UNSET_MAGIC = QString( "AMAROK_UNSET_MAGIC" ); 1461 1462 SqlAlbum::SqlAlbum( Collections::SqlCollection *collection, int id, const QString &name, int artist ) 1463 : Album() 1464 , m_collection( collection ) 1465 , m_name( name ) 1466 , m_id( id ) 1467 , m_artistId( artist ) 1468 , m_imageId( -1 ) 1469 , m_hasImage( false ) 1470 , m_hasImageChecked( false ) 1471 , m_unsetImageId( -1 ) 1472 , m_tracksLoaded( NotLoaded ) 1473 , m_suppressAutoFetch( false ) 1474 , m_mutex( QMutex::Recursive ) 1475 { 1476 Q_ASSERT( m_collection ); 1477 Q_ASSERT( m_id > 0 ); 1478 } 1479 1480 Meta::SqlAlbum::~SqlAlbum() 1481 { 1482 CoverCache::invalidateAlbum( this ); 1483 } 1484 1485 void 1486 SqlAlbum::invalidateCache() 1487 { 1488 QMutexLocker locker( &m_mutex ); 1489 m_tracksLoaded = NotLoaded; 1490 m_hasImage = false; 1491 m_hasImageChecked = false; 1492 m_tracks.clear(); 1493 } 1494 1495 TrackList 1496 SqlAlbum::tracks() 1497 { 1498 bool startQuery = false; 1499 1500 { 1501 QMutexLocker locker( &m_mutex ); 1502 if( m_tracksLoaded == Loaded ) 1503 return m_tracks; 1504 else if( m_tracksLoaded == NotLoaded ) 1505 { 1506 startQuery = true; 1507 m_tracksLoaded = Loading; 1508 } 1509 } 1510 1511 if( startQuery ) 1512 { 1513 // when running the query maker don't lock. might lead to deadlock via registry 1514 Collections::SqlQueryMaker *qm = static_cast< Collections::SqlQueryMaker* >( m_collection->queryMaker() ); 1515 qm->setQueryType( Collections::QueryMaker::Track ); 1516 qm->addMatch( Meta::AlbumPtr( this ) ); 1517 qm->orderBy( Meta::valDiscNr ); 1518 qm->orderBy( Meta::valTrackNr ); 1519 qm->orderBy( Meta::valTitle ); 1520 qm->setBlocking( true ); 1521 qm->run(); 1522 1523 { 1524 QMutexLocker locker( &m_mutex ); 1525 m_tracks = qm->tracks(); 1526 m_tracksLoaded = Loaded; 1527 delete qm; 1528 return m_tracks; 1529 } 1530 } 1531 else 1532 { 1533 // Wait for tracks to be loaded 1534 forever 1535 { 1536 QMutexLocker locker( &m_mutex ); 1537 if( m_tracksLoaded == Loaded ) 1538 return m_tracks; 1539 else 1540 QThread::yieldCurrentThread(); 1541 } 1542 } 1543 } 1544 1545 // note for internal implementation: 1546 // if hasImage returns true then m_imagePath is set 1547 bool 1548 SqlAlbum::hasImage( int size ) const 1549 { 1550 Q_UNUSED(size); // we have every size if we have an image at all 1551 QMutexLocker locker( &m_mutex ); 1552 1553 if( m_name.isEmpty() ) 1554 return false; 1555 1556 if( !m_hasImageChecked ) 1557 { 1558 m_hasImageChecked = true; 1559 1560 const_cast<SqlAlbum*>( this )->largeImagePath(); 1561 1562 // The user has explicitly set no cover 1563 if( m_imagePath == AMAROK_UNSET_MAGIC ) 1564 m_hasImage = false; 1565 1566 // if we don't have an image but it was not explicitly blocked 1567 else if( m_imagePath.isEmpty() ) 1568 { 1569 // Cover fetching runs in another thread. If there is a retrieved cover 1570 // then updateImage() gets called which updates the cache and alerts the 1571 // subscribers. We use queueAlbum() because this runs the fetch as a 1572 // background job and doesn't give an intruding popup asking for confirmation 1573 if( !m_suppressAutoFetch && !m_name.isEmpty() && AmarokConfig::autoGetCoverArt() ) 1574 CoverFetcher::instance()->queueAlbum( AlbumPtr(const_cast<SqlAlbum *>(this)) ); 1575 1576 m_hasImage = false; 1577 } 1578 else 1579 m_hasImage = true; 1580 } 1581 1582 return m_hasImage; 1583 } 1584 1585 QImage 1586 SqlAlbum::image( int size ) const 1587 { 1588 QMutexLocker locker( &m_mutex ); 1589 1590 if( !hasImage() ) 1591 return Meta::Album::image( size ); 1592 1593 // findCachedImage looks for a scaled version of the fullsize image 1594 // which may have been saved on a previous lookup 1595 QString cachedImagePath; 1596 if( size <= 1 ) 1597 cachedImagePath = m_imagePath; 1598 else 1599 cachedImagePath = scaledDiskCachePath( size ); 1600 1601 //FIXME this cache doesn't differentiate between shadowed/unshadowed 1602 // a image exists. just load it. 1603 if( !cachedImagePath.isEmpty() && QFile( cachedImagePath ).exists() ) 1604 { 1605 QImage image( cachedImagePath ); 1606 if( image.isNull() ) 1607 return Meta::Album::image( size ); 1608 return image; 1609 } 1610 1611 // no cached scaled image exists. Have to create it 1612 QImage image; 1613 1614 // --- embedded cover 1615 if( m_collection && m_imagePath.startsWith( m_collection->uidUrlProtocol() ) ) 1616 { 1617 // -- check if we have a track with the given path as uid 1618 Meta::TrackPtr track = m_collection->getTrackFromUid( m_imagePath ); 1619 if( track ) 1620 image = Meta::Tag::embeddedCover( track->playableUrl().path() ); 1621 } 1622 1623 // --- a normal path 1624 if( image.isNull() ) 1625 image = QImage( m_imagePath ); 1626 1627 if( image.isNull() ) 1628 return Meta::Album::image( size ); 1629 1630 if( size > 1 && size < 1000 ) 1631 { 1632 image = image.scaled( size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation ); 1633 image.save( cachedImagePath, "PNG", -1 ); 1634 } 1635 1636 return image; 1637 } 1638 1639 QUrl 1640 SqlAlbum::imageLocation( int size ) 1641 { 1642 if( !hasImage() ) 1643 return QUrl(); 1644 1645 // findCachedImage looks for a scaled version of the fullsize image 1646 // which may have been saved on a previous lookup 1647 if( size <= 1 ) 1648 return QUrl::fromLocalFile( m_imagePath ); 1649 1650 QString cachedImagePath = scaledDiskCachePath( size ); 1651 1652 if( cachedImagePath.isEmpty() ) 1653 return QUrl(); 1654 1655 if( !QFile( cachedImagePath ).exists() ) 1656 { 1657 // If we don't have the location, it's possible that we haven't tried to find the image yet 1658 // So, let's look for it and just ignore the result 1659 QImage i = image( size ); 1660 Q_UNUSED( i ) 1661 } 1662 1663 if( !QFile( cachedImagePath ).exists() ) 1664 return QUrl(); 1665 1666 return QUrl::fromLocalFile(cachedImagePath); 1667 } 1668 1669 void 1670 SqlAlbum::setImage( const QImage &image ) 1671 { 1672 // the unnamed album is special. it will never have an image 1673 if( m_name.isEmpty() ) 1674 return; 1675 1676 if( image.isNull() ) 1677 return; 1678 1679 QMutexLocker locker( &m_mutex ); 1680 1681 // removeImage() will destroy all scaled cached versions of the artwork 1682 // and remove references from the database if required. 1683 removeImage(); 1684 1685 QString path = largeDiskCachePath(); 1686 // make sure not to overwrite existing images 1687 while( QFile(path).exists() ) 1688 path += '_'; // not that nice but it shouldn't happen that often. 1689 1690 image.save( path, "JPG", -1 ); 1691 setImage( path ); 1692 1693 locker.unlock(); 1694 notifyObservers(); 1695 1696 // -- write back the album cover if allowed 1697 if( AmarokConfig::writeBackCover() ) 1698 { 1699 // - scale to cover to a sensible size 1700 QImage scaledImage( image ); 1701 if( scaledImage.width() > AmarokConfig::writeBackCoverDimensions() || scaledImage.height() > AmarokConfig::writeBackCoverDimensions() ) 1702 scaledImage = scaledImage.scaled( AmarokConfig::writeBackCoverDimensions(), AmarokConfig::writeBackCoverDimensions(), Qt::KeepAspectRatio, Qt::SmoothTransformation ); 1703 1704 // - set the image for each track 1705 Meta::TrackList myTracks = tracks(); 1706 foreach( Meta::TrackPtr metaTrack, myTracks ) 1707 { 1708 // the song needs to be at least one mb big or we won't set an image 1709 // that means that the new image will increase the file size by less than 2% 1710 if( metaTrack->filesize() > 1024l * 1024l ) 1711 { 1712 Meta::FieldHash fields; 1713 fields.insert( Meta::valImage, scaledImage ); 1714 WriteTagsJob *job = new WriteTagsJob( metaTrack->playableUrl().path(), fields ); 1715 QObject::connect( job, &WriteTagsJob::done, job, &WriteTagsJob::deleteLater ); 1716 ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(job) ); 1717 } 1718 // note: we might want to update the track file size after writing the image 1719 } 1720 } 1721 } 1722 1723 void 1724 SqlAlbum::removeImage() 1725 { 1726 QMutexLocker locker( &m_mutex ); 1727 if( !hasImage() ) 1728 return; 1729 1730 // Update the database image path 1731 // Set the album image to a magic value which will tell Amarok not to fetch it automatically 1732 const int unsetId = unsetImageId(); 1733 QString query = "UPDATE albums SET image = %1 WHERE id = %2"; 1734 m_collection->sqlStorage()->query( query.arg( QString::number( unsetId ), QString::number( m_id ) ) ); 1735 1736 // From here on we check if there are any remaining references to that particular image in the database 1737 // If there aren't, then we should remove the image path from the database ( and possibly delete the file? ) 1738 // If there are, we need to leave it since other albums will reference this particular image path. 1739 // 1740 query = "SELECT count( albums.id ) FROM albums " 1741 "WHERE albums.image = %1"; 1742 QStringList res = m_collection->sqlStorage()->query( query.arg( QString::number( m_imageId ) ) ); 1743 1744 if( !res.isEmpty() ) 1745 { 1746 int references = res.first().toInt(); 1747 1748 // If there are no more references to this particular image, then we should clean up 1749 if( references <= 0 ) 1750 { 1751 query = "DELETE FROM images WHERE id = %1"; 1752 m_collection->sqlStorage()->query( query.arg( QString::number( m_imageId ) ) ); 1753 1754 // remove the large cover only if it was cached. 1755 QDir largeCoverDir( Amarok::saveLocation( "albumcovers/large/" ) ); 1756 if( QFileInfo(m_imagePath).absoluteDir() == largeCoverDir ) 1757 QFile::remove( m_imagePath ); 1758 1759 // remove all cache images 1760 QString key = md5sum( QString(), QString(), m_imagePath ); 1761 QDir cacheDir( Amarok::saveLocation( "albumcovers/cache/" ) ); 1762 QStringList cacheFilter; 1763 cacheFilter << QString( "*@" ) + key; 1764 QStringList cachedImages = cacheDir.entryList( cacheFilter ); 1765 1766 foreach( const QString &image, cachedImages ) 1767 { 1768 bool r = QFile::remove( cacheDir.filePath( image ) ); 1769 debug() << "deleting cached image: " << image << " : " + ( r ? QStringLiteral("ok") : QStringLiteral("fail") ); 1770 } 1771 1772 CoverCache::invalidateAlbum( this ); 1773 } 1774 } 1775 1776 m_imageId = -1; 1777 m_imagePath.clear(); 1778 m_hasImage = false; 1779 m_hasImageChecked = true; 1780 1781 locker.unlock(); 1782 notifyObservers(); 1783 } 1784 1785 int 1786 SqlAlbum::unsetImageId() const 1787 { 1788 // Return the cached value if we have already done the lookup before 1789 if( m_unsetImageId >= 0 ) 1790 return m_unsetImageId; 1791 1792 QString query = "SELECT id FROM images WHERE path = '%1'"; 1793 QStringList res = m_collection->sqlStorage()->query( query.arg( AMAROK_UNSET_MAGIC ) ); 1794 1795 // We already have the AMAROK_UNSET_MAGIC variable in the database 1796 if( !res.isEmpty() ) 1797 { 1798 m_unsetImageId = res.first().toInt(); 1799 } 1800 else 1801 { 1802 // We need to create this value 1803 query = QString( "INSERT INTO images( path ) VALUES ( '%1' )" ) 1804 .arg( m_collection->sqlStorage()->escape( AMAROK_UNSET_MAGIC ) ); 1805 m_unsetImageId = m_collection->sqlStorage()->insert( query, "images" ); 1806 } 1807 return m_unsetImageId; 1808 } 1809 1810 bool 1811 SqlAlbum::isCompilation() const 1812 { 1813 return !hasAlbumArtist(); 1814 } 1815 1816 bool 1817 SqlAlbum::hasAlbumArtist() const 1818 { 1819 return !albumArtist().isNull(); 1820 } 1821 1822 Meta::ArtistPtr 1823 SqlAlbum::albumArtist() const 1824 { 1825 if( m_artistId > 0 && !m_artist ) 1826 { 1827 const_cast<SqlAlbum*>( this )->m_artist = 1828 m_collection->registry()->getArtist( m_artistId ); 1829 } 1830 return m_artist; 1831 } 1832 1833 QByteArray 1834 SqlAlbum::md5sum( const QString& artist, const QString& album, const QString& file ) const 1835 { 1836 // FIXME: All existing image stores have been invalidated. 1837 return QCryptographicHash::hash( artist.toLower().toUtf8() + QByteArray( "#" ) + 1838 album.toLower().toUtf8() + QByteArray( "?" ) + 1839 file.toUtf8(), 1840 QCryptographicHash::Md5 1841 ).toHex(); 1842 } 1843 1844 QString 1845 SqlAlbum::largeDiskCachePath() const 1846 { 1847 // IMPROVEMENT: the large disk cache path could be human readable 1848 const QString artist = hasAlbumArtist() ? albumArtist()->name() : QString(); 1849 if( artist.isEmpty() && m_name.isEmpty() ) 1850 return QString(); 1851 1852 QDir largeCoverDir( Amarok::saveLocation( "albumcovers/large/" ) ); 1853 const QString key = md5sum( artist, m_name, QString() ); 1854 1855 if( !key.isEmpty() ) 1856 return largeCoverDir.filePath( key ); 1857 1858 return QString(); 1859 } 1860 1861 QString 1862 SqlAlbum::scaledDiskCachePath( int size ) const 1863 { 1864 const QByteArray widthKey = QByteArray::number( size ) + '@'; 1865 QDir cacheCoverDir( Amarok::saveLocation( "albumcovers/cache/" ) ); 1866 QString key = md5sum( QString(), QString(), m_imagePath ); 1867 1868 if( !cacheCoverDir.exists( widthKey + key ) ) 1869 { 1870 // the correct location is empty 1871 // check deprecated locations for the image cache and delete them 1872 // (deleting the scaled image cache is fine) 1873 1874 const QString artist = hasAlbumArtist() ? albumArtist()->name() : QString(); 1875 if( artist.isEmpty() && m_name.isEmpty() ) 1876 ; // do nothing special 1877 else 1878 { 1879 QString oldKey; 1880 oldKey = md5sum( artist, m_name, m_imagePath ); 1881 if( cacheCoverDir.exists( widthKey + oldKey ) ) 1882 cacheCoverDir.remove( widthKey + oldKey ); 1883 1884 oldKey = md5sum( artist, m_name, QString() ); 1885 if( cacheCoverDir.exists( widthKey + oldKey ) ) 1886 cacheCoverDir.remove( widthKey + oldKey ); 1887 } 1888 } 1889 1890 return cacheCoverDir.filePath( widthKey + key ); 1891 } 1892 1893 QString 1894 SqlAlbum::largeImagePath() 1895 { 1896 if( !m_collection ) 1897 return m_imagePath; 1898 1899 // Look up in the database 1900 QString query = "SELECT images.id, images.path FROM images, albums WHERE albums.image = images.id AND albums.id = %1;"; // TODO: shouldn't we do a JOIN here? 1901 QStringList res = m_collection->sqlStorage()->query( query.arg( m_id ) ); 1902 if( !res.isEmpty() ) 1903 { 1904 m_imageId = res.at(0).toInt(); 1905 m_imagePath = res.at(1); 1906 1907 // explicitly deleted image 1908 if( m_imagePath == AMAROK_UNSET_MAGIC ) 1909 return AMAROK_UNSET_MAGIC; 1910 1911 // embedded image (e.g. id3v2 APIC 1912 // We store embedded images as unique ids in the database 1913 // we will get the real image later on from the track. 1914 if( m_imagePath.startsWith( m_collection->uidUrlProtocol()+"://" ) ) 1915 return m_imagePath; 1916 1917 // normal file 1918 if( !m_imagePath.isEmpty() && QFile::exists( m_imagePath ) ) 1919 return m_imagePath; 1920 } 1921 1922 // After a rescan we currently lose all image information, so we need 1923 // to check that we haven't already downloaded this image before. 1924 m_imagePath = largeDiskCachePath(); 1925 if( !m_imagePath.isEmpty() && QFile::exists( m_imagePath ) ) { 1926 setImage(m_imagePath); 1927 return m_imagePath; 1928 } 1929 1930 m_imageId = -1; 1931 m_imagePath.clear(); 1932 return m_imagePath; 1933 } 1934 1935 // note: we won't notify the observers. we are a private function. the caller must do that. 1936 void 1937 SqlAlbum::setImage( const QString &path ) 1938 { 1939 if( m_name.isEmpty() ) // the empty album never has an image 1940 return; 1941 1942 QMutexLocker locker( &m_mutex ); 1943 1944 if( m_imagePath == path ) 1945 return; 1946 1947 QString query = "SELECT id FROM images WHERE path = '%1'"; 1948 query = query.arg( m_collection->sqlStorage()->escape( path ) ); 1949 QStringList res = m_collection->sqlStorage()->query( query ); 1950 1951 if( res.isEmpty() ) 1952 { 1953 QString insert = QString( "INSERT INTO images( path ) VALUES ( '%1' )" ) 1954 .arg( m_collection->sqlStorage()->escape( path ) ); 1955 m_imageId = m_collection->sqlStorage()->insert( insert, "images" ); 1956 } 1957 else 1958 m_imageId = res.first().toInt(); 1959 1960 if( m_imageId >= 0 ) 1961 { 1962 query = QStringLiteral("UPDATE albums SET image = %1 WHERE albums.id = %2" ) 1963 .arg( QString::number( m_imageId ), QString::number( m_id ) ); 1964 m_collection->sqlStorage()->query( query ); 1965 1966 m_imagePath = path; 1967 m_hasImage = true; 1968 m_hasImageChecked = true; 1969 CoverCache::invalidateAlbum( this ); 1970 } 1971 } 1972 1973 /** Set the compilation flag. 1974 * Actually it does not change this album but instead moves 1975 * the tracks to other albums (e.g. one with the same name which is a 1976 * compilation) 1977 * If the compilation flag is set to "false" then all songs 1978 * with different artists will be moved to other albums, possibly even 1979 * creating them. 1980 */ 1981 void 1982 SqlAlbum::setCompilation( bool compilation ) 1983 { 1984 if( m_name.isEmpty() ) 1985 return; 1986 1987 if( isCompilation() == compilation ) 1988 { 1989 return; 1990 } 1991 else 1992 { 1993 m_collection->blockUpdatedSignal(); 1994 1995 if( compilation ) 1996 { 1997 // get the new compilation album 1998 Meta::AlbumPtr metaAlbum = m_collection->registry()->getAlbum( name(), QString() ); 1999 AmarokSharedPointer<SqlAlbum> sqlAlbum = AmarokSharedPointer<SqlAlbum>::dynamicCast( metaAlbum ); 2000 2001 Meta::FieldHash changes; 2002 changes.insert( Meta::valCompilation, 1); 2003 2004 Meta::TrackList myTracks = tracks(); 2005 foreach( Meta::TrackPtr metaTrack, myTracks ) 2006 { 2007 SqlTrack* sqlTrack = static_cast<SqlTrack*>(metaTrack.data()); 2008 2009 // copy over the cover image 2010 if( sqlTrack->album()->hasImage() && !sqlAlbum->hasImage() ) 2011 sqlAlbum->setImage( sqlTrack->album()->imageLocation().path() ); 2012 2013 // move the track 2014 sqlTrack->setAlbum( sqlAlbum->id() ); 2015 if( AmarokConfig::writeBack() ) 2016 Meta::Tag::writeTags( sqlTrack->playableUrl().path(), changes, 2017 AmarokConfig::writeBackStatistics() ); 2018 } 2019 /* TODO: delete all old tracks albums */ 2020 } 2021 else 2022 { 2023 Meta::FieldHash changes; 2024 changes.insert( Meta::valCompilation, 0); 2025 2026 Meta::TrackList myTracks = tracks(); 2027 foreach( Meta::TrackPtr metaTrack, myTracks ) 2028 { 2029 SqlTrack* sqlTrack = static_cast<SqlTrack*>(metaTrack.data()); 2030 Meta::ArtistPtr trackArtist = sqlTrack->artist(); 2031 2032 // get the new album 2033 Meta::AlbumPtr metaAlbum = m_collection->registry()->getAlbum( 2034 sqlTrack->album()->name(), 2035 trackArtist ? ArtistHelper::realTrackArtist( trackArtist->name() ) : QString() ); 2036 AmarokSharedPointer<SqlAlbum> sqlAlbum = AmarokSharedPointer<SqlAlbum>::dynamicCast( metaAlbum ); 2037 2038 // copy over the cover image 2039 if( sqlTrack->album()->hasImage() && !sqlAlbum->hasImage() ) 2040 sqlAlbum->setImage( sqlTrack->album()->imageLocation().path() ); 2041 2042 // move the track 2043 sqlTrack->setAlbum( sqlAlbum->id() ); 2044 if( AmarokConfig::writeBack() ) 2045 Meta::Tag::writeTags( sqlTrack->playableUrl().path(), changes, 2046 AmarokConfig::writeBackStatistics() ); 2047 } 2048 /* TODO //step 5: delete the original album, if necessary */ 2049 } 2050 2051 m_collection->unblockUpdatedSignal(); 2052 } 2053 } 2054 2055 bool 2056 SqlAlbum::hasCapabilityInterface( Capabilities::Capability::Type type ) const 2057 { 2058 if( m_name.isEmpty() ) 2059 return false; 2060 2061 switch( type ) 2062 { 2063 case Capabilities::Capability::Actions: 2064 case Capabilities::Capability::BookmarkThis: 2065 return true; 2066 default: 2067 return Album::hasCapabilityInterface( type ); 2068 } 2069 } 2070 2071 Capabilities::Capability* 2072 SqlAlbum::createCapabilityInterface( Capabilities::Capability::Type type ) 2073 { 2074 if( m_name.isEmpty() ) 2075 return nullptr; 2076 2077 switch( type ) 2078 { 2079 case Capabilities::Capability::Actions: 2080 return new Capabilities::AlbumActionsCapability( Meta::AlbumPtr( this ) ); 2081 case Capabilities::Capability::BookmarkThis: 2082 return new Capabilities::BookmarkThisCapability( new BookmarkAlbumAction( nullptr, Meta::AlbumPtr( this ) ) ); 2083 default: 2084 return Album::createCapabilityInterface( type ); 2085 } 2086 } 2087 2088 //---------------SqlComposer--------------------------------- 2089 2090 SqlComposer::SqlComposer( Collections::SqlCollection *collection, int id, const QString &name ) 2091 : Composer() 2092 , m_collection( collection ) 2093 , m_id( id ) 2094 , m_name( name ) 2095 , m_tracksLoaded( false ) 2096 { 2097 Q_ASSERT( m_collection ); 2098 Q_ASSERT( m_id > 0 ); 2099 } 2100 2101 void 2102 SqlComposer::invalidateCache() 2103 { 2104 QMutexLocker locker( &m_mutex ); 2105 m_tracksLoaded = false; 2106 m_tracks.clear(); 2107 } 2108 2109 TrackList 2110 SqlComposer::tracks() 2111 { 2112 { 2113 QMutexLocker locker( &m_mutex ); 2114 if( m_tracksLoaded ) 2115 return m_tracks; 2116 } 2117 2118 Collections::SqlQueryMaker *qm = static_cast< Collections::SqlQueryMaker* >( m_collection->queryMaker() ); 2119 qm->setQueryType( Collections::QueryMaker::Track ); 2120 qm->addMatch( Meta::ComposerPtr( this ) ); 2121 qm->setBlocking( true ); 2122 qm->run(); 2123 2124 { 2125 QMutexLocker locker( &m_mutex ); 2126 m_tracks = qm->tracks(); 2127 m_tracksLoaded = true; 2128 delete qm; 2129 return m_tracks; 2130 } 2131 } 2132 2133 //---------------SqlGenre--------------------------------- 2134 2135 SqlGenre::SqlGenre( Collections::SqlCollection *collection, int id, const QString &name ) 2136 : Genre() 2137 , m_collection( collection ) 2138 , m_id( id ) 2139 , m_name( name ) 2140 , m_tracksLoaded( false ) 2141 { 2142 Q_ASSERT( m_collection ); 2143 Q_ASSERT( m_id > 0 ); 2144 } 2145 2146 void 2147 SqlGenre::invalidateCache() 2148 { 2149 QMutexLocker locker( &m_mutex ); 2150 m_tracksLoaded = false; 2151 m_tracks.clear(); 2152 } 2153 2154 TrackList 2155 SqlGenre::tracks() 2156 { 2157 { 2158 QMutexLocker locker( &m_mutex ); 2159 if( m_tracksLoaded ) 2160 return m_tracks; 2161 } 2162 2163 // when running the query maker don't lock. might lead to deadlock via registry 2164 Collections::SqlQueryMaker *qm = static_cast< Collections::SqlQueryMaker* >( m_collection->queryMaker() ); 2165 qm->setQueryType( Collections::QueryMaker::Track ); 2166 qm->addMatch( Meta::GenrePtr( this ) ); 2167 qm->setBlocking( true ); 2168 qm->run(); 2169 2170 { 2171 QMutexLocker locker( &m_mutex ); 2172 m_tracks = qm->tracks(); 2173 m_tracksLoaded = true; 2174 delete qm; 2175 return m_tracks; 2176 } 2177 } 2178 2179 //---------------SqlYear--------------------------------- 2180 2181 SqlYear::SqlYear( Collections::SqlCollection *collection, int id, int year) 2182 : Year() 2183 , m_collection( collection ) 2184 , m_id( id ) 2185 , m_year( year ) 2186 , m_tracksLoaded( false ) 2187 { 2188 Q_ASSERT( m_collection ); 2189 Q_ASSERT( m_id > 0 ); 2190 } 2191 2192 void 2193 SqlYear::invalidateCache() 2194 { 2195 QMutexLocker locker( &m_mutex ); 2196 m_tracksLoaded = false; 2197 m_tracks.clear(); 2198 } 2199 2200 TrackList 2201 SqlYear::tracks() 2202 { 2203 { 2204 QMutexLocker locker( &m_mutex ); 2205 if( m_tracksLoaded ) 2206 return m_tracks; 2207 } 2208 2209 // when running the query maker don't lock. might lead to deadlock via registry 2210 Collections::SqlQueryMaker *qm = static_cast< Collections::SqlQueryMaker* >( m_collection->queryMaker() ); 2211 qm->setQueryType( Collections::QueryMaker::Track ); 2212 qm->addMatch( Meta::YearPtr( this ) ); 2213 qm->setBlocking( true ); 2214 qm->run(); 2215 2216 { 2217 QMutexLocker locker( &m_mutex ); 2218 m_tracks = qm->tracks(); 2219 m_tracksLoaded = true; 2220 delete qm; 2221 return m_tracks; 2222 } 2223 } 2224 2225 //---------------SqlLabel--------------------------------- 2226 2227 SqlLabel::SqlLabel( Collections::SqlCollection *collection, int id, const QString &name ) 2228 : Label() 2229 , m_collection( collection ) 2230 , m_id( id ) 2231 , m_name( name ) 2232 , m_tracksLoaded( false ) 2233 { 2234 Q_ASSERT( m_collection ); 2235 Q_ASSERT( m_id > 0 ); 2236 } 2237 2238 void 2239 SqlLabel::invalidateCache() 2240 { 2241 QMutexLocker locker( &m_mutex ); 2242 m_tracksLoaded = false; 2243 m_tracks.clear(); 2244 } 2245 2246 TrackList 2247 SqlLabel::tracks() 2248 { 2249 { 2250 QMutexLocker locker( &m_mutex ); 2251 if( m_tracksLoaded ) 2252 return m_tracks; 2253 } 2254 2255 // when running the query maker don't lock. might lead to deadlock via registry 2256 Collections::SqlQueryMaker *qm = static_cast< Collections::SqlQueryMaker* >( m_collection->queryMaker() ); 2257 qm->setQueryType( Collections::QueryMaker::Track ); 2258 qm->addMatch( Meta::LabelPtr( this ) ); 2259 qm->setBlocking( true ); 2260 qm->run(); 2261 2262 { 2263 QMutexLocker locker( &m_mutex ); 2264 m_tracks = qm->tracks(); 2265 m_tracksLoaded = true; 2266 delete qm; 2267 return m_tracks; 2268 } 2269 } 2270