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( &registry->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