File indexing completed on 2025-01-05 04:26:01

0001 /****************************************************************************************
0002  * Copyright (c) 2012 Matěj Laitl <matej@laitl.cz                                       *
0003  *                                                                                      *
0004  * This program is free software; you can redistribute it and/or modify it under        *
0005  * the terms of the GNU General Public License as published by the Free Software        *
0006  * Foundation; either version 2 of the License, or (at your option) any later           *
0007  * version.                                                                             *
0008  *                                                                                      *
0009  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0010  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0011  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0012  *                                                                                      *
0013  * You should have received a copy of the GNU General Public License along with         *
0014  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0015  ****************************************************************************************/
0016 
0017 #include "IpodMeta.h"
0018 
0019 #include "amarokconfig.h"
0020 #include "core/support/Amarok.h"
0021 #include "core/support/Debug.h"
0022 #include "core-impl/collections/ipodcollection/IpodCollection.h"
0023 #include "core-impl/collections/ipodcollection/config-ipodcollection.h"
0024 #include "core-impl/collections/support/jobs/WriteTagsJob.h"
0025 #include "core-impl/collections/support/ArtistHelper.h"
0026 #include "covermanager/CoverCache.h"
0027 #include "FileType.h"
0028 
0029 #include <QTemporaryFile>
0030 
0031 #include <ThreadWeaver/Queue>
0032 
0033 #include <cmath>
0034 #include <gpod/itdb.h>
0035 #ifdef GDKPIXBUF_FOUND
0036 #undef signals  // gdbusintrospection.h uses a member named signals, prevent build fail
0037 #include <gdk-pixbuf/gdk-pixbuf.h>
0038 #include <QBuffer>
0039 #endif
0040 
0041 
0042 using namespace IpodMeta;
0043 
0044 gpointer AmarokItdbUserDataDuplicateFunc( gpointer userdata )
0045 {
0046     Q_UNUSED( userdata )
0047     return nullptr; // we never copy our userdata
0048 }
0049 
0050 Track::Track( Itdb_Track *ipodTrack )
0051     : m_track( ipodTrack )
0052     , m_batch( 0 )
0053 {
0054     Q_ASSERT( m_track != nullptr );
0055     m_track->usertype = m_gpodTrackUserTypeAmarokTrackPtr;
0056     m_track->userdata = this;
0057     m_track->userdata_duplicate = AmarokItdbUserDataDuplicateFunc;
0058 }
0059 
0060 Track::Track( const Meta::TrackPtr &origTrack )
0061     : m_track( itdb_track_new() )
0062     , m_batch( 0 )
0063 {
0064     Q_ASSERT( m_track != nullptr );
0065     m_track->usertype = m_gpodTrackUserTypeAmarokTrackPtr;
0066     m_track->userdata = this;
0067     m_track->userdata_duplicate = AmarokItdbUserDataDuplicateFunc;
0068 
0069     Meta::AlbumPtr origAlbum = origTrack->album();
0070     Meta::ArtistPtr origArtist = origTrack->artist();
0071 
0072     beginUpdate();
0073     setTitle( origTrack->name() );
0074     // url is set in setCollection()
0075     setAlbum( origAlbum ? origAlbum->name() : QString() );
0076     setArtist( origArtist ? origArtist->name() : QString() );
0077     setComposer( origTrack->composer() ? origTrack->composer()->name() : QString() );
0078     setGenre( origTrack->genre() ? origTrack->genre()->name() : QString() );
0079     setYear( origTrack->year() ? origTrack->year()->year() : 0 );
0080 
0081     QString albumArtist;
0082     bool isCompilation = false;
0083     if ( origAlbum )
0084     {
0085         isCompilation = origAlbum->isCompilation();
0086         if( origAlbum->hasAlbumArtist() && origAlbum->albumArtist() )
0087             albumArtist = origAlbum->albumArtist()->name();
0088 
0089         if( origAlbum->hasImage() )
0090             setImage( origAlbum->image() );
0091     }
0092     /* iPod doesn't handle empty album artist well for compilation albums (splits these
0093      * albums). Ensure that we have something in albumArtist. We filter it for Amarok for
0094      * compilation albums in IpodMeta::Album::albumArtist() */
0095     if( albumArtist.isEmpty() && origArtist )
0096         albumArtist = origArtist->name();
0097     if( albumArtist.isEmpty() )
0098         albumArtist = i18n( "Various Artists" );
0099 
0100     Meta::ConstStatisticsPtr origStats = origTrack->statistics();
0101 
0102     setAlbumArtist( albumArtist );
0103     setIsCompilation( isCompilation );
0104 
0105     setBpm( origTrack->bpm() );
0106     setComment( origTrack->comment() );
0107 
0108     setScore( origStats->score() );
0109     setRating( origStats->rating() );
0110 
0111     setLength( origTrack->length() );
0112     // filesize is set in finalizeCopying(), which could be more accurate
0113     setSampleRate( origTrack->sampleRate() );
0114     setBitrate( origTrack->bitrate() );
0115 
0116     setCreateDate( QDateTime::currentDateTime() );  // createDate == added to collection
0117     setModifyDate( origTrack->modifyDate() );
0118 
0119     setTrackNumber( origTrack->trackNumber() );
0120     setDiscNumber( origTrack->discNumber() );
0121 
0122     setLastPlayed( origStats->lastPlayed() );
0123     setFirstPlayed( origStats->firstPlayed() );
0124     setPlayCount( origStats->playCount() );
0125 
0126     setReplayGain( Meta::ReplayGain_Track_Gain, origTrack->replayGain( Meta::ReplayGain_Track_Gain ) );
0127     setReplayGain( Meta::ReplayGain_Track_Peak, origTrack->replayGain( Meta::ReplayGain_Track_Peak ) );
0128     setReplayGain( Meta::ReplayGain_Album_Gain, origTrack->replayGain( Meta::ReplayGain_Album_Gain ) );
0129     setReplayGain( Meta::ReplayGain_Album_Peak, origTrack->replayGain( Meta::ReplayGain_Album_Peak ) );
0130 
0131     setType( origTrack->type() );
0132     m_changedFields.clear();  // some of the set{Something} insert to m_changedFields, not
0133                               // desirable for constructor
0134     endUpdate();
0135 }
0136 
0137 Track::~Track()
0138 {
0139     itdb_track_free( m_track );
0140     if( !m_tempImageFilePath.isEmpty() )
0141         QFile::remove( m_tempImageFilePath );
0142 }
0143 
0144 QString
0145 Track::name() const
0146 {
0147     QReadLocker locker( &m_trackLock );
0148     return QString::fromUtf8( m_track->title );
0149 }
0150 
0151 void
0152 Track::setTitle( const QString &newTitle )
0153 {
0154     QWriteLocker locker( &m_trackLock );
0155     g_free( m_track->title );
0156     m_track->title = g_strdup( newTitle.toUtf8() );
0157     commitIfInNonBatchUpdate( Meta::valTitle, newTitle );
0158 }
0159 
0160 QUrl
0161 Track::playableUrl() const
0162 {
0163     if( m_mountPoint.isEmpty() || !m_track->ipod_path || m_track->ipod_path[0] == '\0' )
0164         return QUrl();
0165     QReadLocker locker( &m_trackLock );
0166     gchar *relPathChar = g_strdup( m_track->ipod_path );
0167     locker.unlock();
0168     itdb_filename_ipod2fs( relPathChar ); // in-place
0169     // relPath begins with a slash
0170     QString relPath = QFile::decodeName( relPathChar );
0171     g_free( relPathChar );
0172     return QUrl::fromLocalFile( m_mountPoint + relPath );
0173 }
0174 
0175 QString
0176 Track::prettyUrl() const
0177 {
0178     const QUrl &url = playableUrl();
0179     if( url.isLocalFile() )
0180         return url.toLocalFile();
0181 
0182     QString collName = m_coll ? m_coll->prettyName() : i18n( "Unknown Collection" );
0183     QString artistName = artist() ? artist()->prettyName() : i18n( "Unknown Artist" );
0184     QString trackName = !name().isEmpty() ? name() : i18n( "Unknown track" );
0185 
0186     return  QString( "%1: %2 - %3" ).arg( collName, artistName, trackName );
0187 }
0188 
0189 QString
0190 Track::uidUrl() const
0191 {
0192     QReadLocker locker( &m_trackLock );
0193     gchar *relPathChar = g_strdup( m_track->ipod_path );
0194     locker.unlock();
0195     itdb_filename_ipod2fs( relPathChar ); // in-place
0196     // relPath begins with a slash
0197     QString relPath = QFile::decodeName( relPathChar );
0198     g_free( relPathChar );
0199 
0200     if( m_coll )
0201         return m_coll->collectionId() + relPath;
0202     else
0203         return m_mountPoint + relPath;
0204 }
0205 
0206 QString
0207 Track::notPlayableReason() const
0208 {
0209     return localFileNotPlayableReason( playableUrl().toLocalFile() );
0210 }
0211 
0212 Meta::AlbumPtr
0213 Track::album() const
0214 {
0215     // we may not store AmarokSharedPointer to Album because it would create circular reference
0216     return Meta::AlbumPtr( new Album( const_cast<Track*>( this ) ) );
0217 }
0218 
0219 void
0220 Track::setAlbum( const QString &newAlbum )
0221 {
0222     QWriteLocker locker( &m_trackLock );
0223     g_free( m_track->album );
0224     m_track->album = g_strdup( newAlbum.toUtf8() );
0225     commitIfInNonBatchUpdate( Meta::valAlbum, newAlbum );
0226 }
0227 
0228 void
0229 Track::setAlbumArtist( const QString &newAlbumArtist )
0230 {
0231     QWriteLocker locker( &m_trackLock );
0232     g_free( m_track->albumartist );
0233     m_track->albumartist = g_strdup( newAlbumArtist.toUtf8() );
0234     commitIfInNonBatchUpdate( Meta::valAlbumArtist, newAlbumArtist );
0235 }
0236 
0237 void
0238 Track::setIsCompilation( bool newIsCompilation )
0239 {
0240     // libgpod says: m_track->combination: True if set to 0x1, false if set to 0x0.
0241     if( m_track->compilation == newIsCompilation )
0242         return;  // nothing to do
0243 
0244     QWriteLocker locker( &m_trackLock );
0245     m_track->compilation = newIsCompilation ? 0x1 : 0x0;
0246     commitIfInNonBatchUpdate( Meta::valCompilation, newIsCompilation );
0247 }
0248 
0249 void
0250 Track::setImage( const QImage &newImage )
0251 {
0252     QWriteLocker locker( &m_trackLock );
0253     if( !m_tempImageFilePath.isEmpty() )
0254         QFile::remove( m_tempImageFilePath );
0255     m_tempImageFilePath.clear();
0256     if( newImage.isNull() )
0257         itdb_track_remove_thumbnails( m_track );
0258     else
0259     {
0260         // we set artwork even for devices that don't support it, everyone has new-enough iPod nowadays
0261         const int maxSize = AmarokConfig::writeBackCoverDimensions();
0262         QImage image;
0263         if( newImage.width() > maxSize || newImage.height() > maxSize )
0264             image = newImage.scaled( maxSize, maxSize, Qt::KeepAspectRatio, Qt::SmoothTransformation );
0265         else
0266             image = newImage;
0267 
0268         QTemporaryFile tempImageFile;
0269         tempImageFile.setAutoRemove( false ); // file will be removed in ~Track()
0270         tempImageFile.setFileTemplate( QDir::tempPath() + "/XXXXXX.png" );
0271         // we save the file to disk rather than pass image data to save several megabytes of RAM
0272         if( tempImageFile.open() )
0273             m_tempImageFilePath = tempImageFile.fileName();
0274         if( tempImageFile.isOpen() && image.save( &tempImageFile, "PNG" ) )
0275             /* this function remembers image path, it also forgets previous images (if any)
0276              * and sets artwork_size, artwork_count and has_artwork m_track fields */
0277             itdb_track_set_thumbnails( m_track, QFile::encodeName( m_tempImageFilePath ) );
0278     }
0279     commitIfInNonBatchUpdate( Meta::valImage, newImage );
0280 }
0281 
0282 Meta::ArtistPtr
0283 Track::artist() const
0284 {
0285     QReadLocker locker( &m_trackLock );
0286     return Meta::ArtistPtr( new Artist( QString::fromUtf8( m_track->artist ) ) );
0287 }
0288 
0289 void
0290 Track::setArtist( const QString &newArtist )
0291 {
0292     QWriteLocker locker( &m_trackLock );
0293     g_free( m_track->artist );
0294     m_track->artist = g_strdup( newArtist.toUtf8() );
0295     commitIfInNonBatchUpdate( Meta::valArtist, newArtist );
0296 }
0297 
0298 Meta::ComposerPtr
0299 Track::composer() const
0300 {
0301     QReadLocker locker( &m_trackLock );
0302     return Meta::ComposerPtr( new Composer( QString::fromUtf8( m_track->composer ) ) );
0303 }
0304 
0305 void
0306 Track::setComposer( const QString &newComposer )
0307 {
0308     QWriteLocker locker( &m_trackLock );
0309     g_free( m_track->composer );
0310     m_track->composer = g_strdup( newComposer.toUtf8() );
0311     commitIfInNonBatchUpdate( Meta::valComposer, newComposer );
0312 }
0313 
0314 Meta::GenrePtr
0315 Track::genre() const
0316 {
0317     QReadLocker locker( &m_trackLock );
0318     return Meta::GenrePtr( new Genre( QString::fromUtf8( m_track->genre ) ) );
0319 }
0320 
0321 void
0322 Track::setGenre( const QString &newGenre )
0323 {
0324     QWriteLocker locker( &m_trackLock );
0325     g_free( m_track->genre );
0326     m_track->genre = g_strdup( newGenre.toUtf8() );
0327     commitIfInNonBatchUpdate( Meta::valGenre, newGenre );
0328 }
0329 
0330 Meta::YearPtr
0331 Track::year() const
0332 {
0333     // no need for lock here, reading integer should be atomic
0334     return Meta::YearPtr( new Year( QString::number( m_track->year ) ) );
0335 }
0336 
0337 void Track::setYear( int newYear )
0338 {
0339     QWriteLocker locker( &m_trackLock );
0340     m_track->year = newYear;
0341     commitIfInNonBatchUpdate( Meta::valYear, newYear );
0342 }
0343 
0344 qreal
0345 Track::bpm() const
0346 {
0347     // no need for lock here, integer read
0348     return m_track->BPM;
0349 }
0350 
0351 void Track::setBpm( const qreal newBpm )
0352 {
0353     QWriteLocker locker( &m_trackLock );
0354     m_track->BPM = newBpm;
0355     commitIfInNonBatchUpdate( Meta::valBpm, newBpm );
0356 }
0357 
0358 QString
0359 Track::comment() const
0360 {
0361     QReadLocker locker( &m_trackLock );
0362     return QString::fromUtf8( m_track->comment );
0363 }
0364 
0365 void Track::setComment( const QString &newComment )
0366 {
0367     QWriteLocker locker( &m_trackLock );
0368     g_free( m_track->comment );
0369     m_track->comment = g_strdup( newComment.toUtf8() );
0370     commitIfInNonBatchUpdate( Meta::valComment, newComment );
0371 }
0372 
0373 int
0374 Track::rating() const
0375 {
0376     /* (rating/RATING_STEP) is a number of stars, Amarok uses number of half-stars.
0377      * the order of multiply and divide operations is significant because of rounding */
0378     return ( ( m_track->rating * 2 ) / ITDB_RATING_STEP );
0379 }
0380 
0381 void
0382 Track::setRating( int newRating )
0383 {
0384     newRating = ( newRating * ITDB_RATING_STEP ) / 2;
0385     if( newRating == (int) m_track->rating ) // casting prevents compiler warning about signedness
0386         return; // nothing to do, do not notify observers
0387 
0388     QWriteLocker locker( &m_trackLock );
0389     m_track->rating = newRating;
0390     commitIfInNonBatchUpdate( Meta::valRating, newRating );
0391 }
0392 
0393 qint64
0394 Track::length() const
0395 {
0396     return m_track->tracklen;
0397 }
0398 
0399 void
0400 Track::setLength( qint64 newLength )
0401 {
0402     QWriteLocker locker( &m_trackLock );
0403     m_track->tracklen = newLength;
0404     commitIfInNonBatchUpdate( Meta::valLength, newLength );
0405 }
0406 
0407 int
0408 Track::filesize() const
0409 {
0410     return m_track->size;
0411 }
0412 
0413 int
0414 Track::sampleRate() const
0415 {
0416     return m_track->samplerate;
0417 }
0418 
0419 void
0420 Track::setSampleRate( int newSampleRate )
0421 {
0422     QWriteLocker locker( &m_trackLock );
0423     m_track->samplerate = newSampleRate;
0424     commitIfInNonBatchUpdate( Meta::valSamplerate, newSampleRate );
0425 }
0426 
0427 int
0428 Track::bitrate() const
0429 {
0430     return m_track->bitrate;
0431 }
0432 
0433 void
0434 Track::setBitrate( int newBitrate )
0435 {
0436     QWriteLocker locker( &m_trackLock );
0437     m_track->bitrate = newBitrate;
0438     commitIfInNonBatchUpdate( Meta::valBitrate, newBitrate );
0439 }
0440 
0441 QDateTime
0442 Track::createDate() const
0443 {
0444     time_t time = m_track->time_added;
0445     if( time == 0 )
0446         return QDateTime();  // 0 means "no reasonable time", so return invalid QDateTime
0447     return QDateTime::fromSecsSinceEpoch( time );
0448 }
0449 
0450 void
0451 Track::setCreateDate( const QDateTime &newDate )
0452 {
0453     QWriteLocker locker( &m_trackLock );
0454     m_track->time_added = newDate.isValid() ? newDate.toSecsSinceEpoch() : 0;
0455     commitIfInNonBatchUpdate( Meta::valCreateDate, newDate );
0456 }
0457 
0458 QDateTime
0459 Track::modifyDate() const
0460 {
0461     time_t time = m_track->time_modified;
0462     if( time == 0 )
0463         return QDateTime();  // 0 means "no reasonable time", so return invalid QDateTime
0464     return QDateTime::fromSecsSinceEpoch( time );
0465 }
0466 
0467 void
0468 Track::setModifyDate( const QDateTime &newDate )
0469 {
0470     // this method _cannot_ lock m_trackLock or deadlock will occur in commitChanges()
0471     m_track->time_modified = newDate.isValid() ? newDate.toSecsSinceEpoch() : 0;
0472 }
0473 
0474 int
0475 Track::trackNumber() const
0476 {
0477     // no need for lock here, integer read
0478     return m_track->track_nr;
0479 }
0480 
0481 void
0482 Track::setTrackNumber( int newTrackNumber )
0483 {
0484     QWriteLocker locker( &m_trackLock );
0485     m_track->track_nr = newTrackNumber;
0486     commitIfInNonBatchUpdate( Meta::valTrackNr, newTrackNumber );
0487 }
0488 
0489 int
0490 Track::discNumber() const
0491 {
0492     // no need for lock here, integer read
0493     return m_track->cd_nr;
0494 }
0495 
0496 void
0497 Track::setDiscNumber( int newDiscNumber )
0498 {
0499     QWriteLocker locker( &m_trackLock );
0500     m_track->cd_nr = newDiscNumber;
0501     commitIfInNonBatchUpdate( Meta::valDiscNr, newDiscNumber );
0502 }
0503 
0504 QDateTime
0505 Track::lastPlayed() const
0506 {
0507     return m_track->time_played ? QDateTime::fromSecsSinceEpoch( m_track->time_played ) : QDateTime();
0508 }
0509 
0510 void
0511 Track::setLastPlayed( const QDateTime &time )
0512 {
0513     QWriteLocker locker( &m_trackLock );
0514     m_track->time_played = time.isValid() ? time.toSecsSinceEpoch() : 0;
0515     commitIfInNonBatchUpdate( Meta::valLastPlayed, time );
0516 }
0517 
0518 QDateTime
0519 Track::firstPlayed() const
0520 {
0521     // we abuse time_released for this, which should be okay for non-podcast tracks
0522     // TODO: return QDateTime for podcast tracks
0523     return m_track->time_released ? QDateTime::fromSecsSinceEpoch( m_track->time_released ) : QDateTime();
0524 }
0525 
0526 void
0527 Track::setFirstPlayed( const QDateTime &time )
0528 {
0529     QWriteLocker locker( &m_trackLock );
0530     m_track->time_released = time.isValid() ? time.toSecsSinceEpoch() : 0;
0531     commitIfInNonBatchUpdate( Meta::valFirstPlayed, time );
0532 }
0533 
0534 int
0535 Track::playCount() const
0536 {
0537     return m_track->playcount;
0538 }
0539 
0540 int
0541 Track::recentPlayCount() const
0542 {
0543     if( !m_coll || !m_coll->isWritable() )
0544         return 0; // we must be able to reset recent playcount if we return nonzero
0545     return m_track->recent_playcount;
0546 }
0547 
0548 void
0549 Track::setPlayCount( const int playcount )
0550 {
0551     QWriteLocker locker( &m_trackLock );
0552     m_track->playcount = playcount;
0553     m_track->recent_playcount = 0;
0554     commitIfInNonBatchUpdate( Meta::valLastPlayed, playcount );
0555 }
0556 
0557 qreal
0558 Track::replayGain( Meta::ReplayGainTag mode ) const
0559 {
0560     // iPods are not able to differentiante between different replay gain modes (track & album)
0561     switch( mode )
0562     {
0563         case Meta::ReplayGain_Track_Gain:
0564         case Meta::ReplayGain_Album_Gain:
0565             break; // fall to the computation
0566         case Meta::ReplayGain_Track_Peak:
0567         case Meta::ReplayGain_Album_Peak:
0568             return 0.0; // perhaps return -replayGain assuming there was enough headroom
0569     }
0570 
0571     if( m_track->soundcheck == 0 )
0572         return 0.0; // libgpod: The value 0 is special, treated as "no Soundcheck"
0573     // libgpod: X = 1000 * 10 ^ (-.1 * Y)
0574     // where Y is the adjustment value in dB and X is the value that goes into the SoundCheck field
0575     return 30.0 - 10.0 * std::log10( m_track->soundcheck );
0576 }
0577 
0578 void
0579 Track::setReplayGain( Meta::ReplayGainTag mode, qreal newReplayGain )
0580 {
0581     guint32 soundcheck;
0582     switch( mode )
0583     {
0584         case Meta::ReplayGain_Track_Gain:
0585             if( newReplayGain == 0.0 )
0586                 // libgpod: The value 0 is special, treated as "no Soundcheck"
0587                 soundcheck = 0;
0588             else
0589                 // libgpod: X = 1000 * 10 ^ (-.1 * Y)
0590                 // where Y is the adjustment value in dB and X is the value that goes into the SoundCheck field
0591                 soundcheck = 1000 * std::pow( 10.0, -0.1 * newReplayGain );
0592             m_track->soundcheck = soundcheck;
0593             break;
0594         case Meta::ReplayGain_Album_Gain:
0595         case Meta::ReplayGain_Track_Peak:
0596             // we should somehow abuse Itdb_Track to store this, it is really needed
0597         case Meta::ReplayGain_Album_Peak:
0598             break;
0599     }
0600 }
0601 
0602 QString
0603 Track::type() const
0604 {
0605     QReadLocker locker( &m_trackLock );
0606     return QString::fromUtf8( m_track->filetype );
0607 }
0608 
0609 void
0610 Track::setType( const QString &newType )
0611 {
0612     QWriteLocker locker( &m_trackLock );
0613     g_free( m_track->filetype );
0614     m_track->filetype = g_strdup( newType.toUtf8() );
0615     commitIfInNonBatchUpdate( Meta::valFormat, newType );
0616 }
0617 
0618 bool
0619 Track::inCollection() const
0620 {
0621     return m_coll; // converts to bool nicely
0622 }
0623 
0624 Collections::Collection*
0625 Track::collection() const
0626 {
0627     return m_coll.data();
0628 }
0629 
0630 Meta::TrackEditorPtr
0631 Track::editor()
0632 {
0633     return Meta::TrackEditorPtr( isEditable() ? this : nullptr );
0634 }
0635 
0636 Meta::StatisticsPtr
0637 Track::statistics()
0638 {
0639     return Meta::StatisticsPtr( this );
0640 }
0641 
0642 Meta::TrackPtr
0643 Track::fromIpodTrack( const Itdb_Track *ipodTrack )
0644 {
0645     if( !ipodTrack )
0646         return Meta::TrackPtr();
0647     if( ipodTrack->usertype != m_gpodTrackUserTypeAmarokTrackPtr )
0648         return Meta::TrackPtr();
0649     if( !ipodTrack->userdata )
0650         return Meta::TrackPtr();
0651     return Meta::TrackPtr( static_cast<Track *>( ipodTrack->userdata ) );
0652 }
0653 
0654 Itdb_Track*
0655 Track::itdbTrack() const
0656 {
0657     return m_track;
0658 }
0659 
0660 bool
0661 Track::finalizeCopying( const gchar *mountPoint, const gchar *filePath )
0662 {
0663     GError *error = nullptr;
0664     // we cannot use m_mountPoint, we are not yet in collection
0665     Itdb_Track *res = itdb_cp_finalize( m_track, mountPoint, filePath, &error );
0666     if( error )
0667     {
0668         warning() << "Failed to finalize copying of iPod track:" << error->message;
0669         g_error_free( error );
0670         error = nullptr;
0671     }
0672     return res == m_track;
0673 }
0674 
0675 void
0676 Track::setCollection( QPointer<IpodCollection> collection )
0677 {
0678     m_coll = collection;
0679     if( !collection )
0680         return;
0681     { // scope for locker
0682         QWriteLocker locker( &m_trackLock );
0683         // paranoia: collection may become null while we were waiting for lock...
0684         m_mountPoint = collection ? collection->mountPoint() : QString();
0685     }
0686 
0687     // m_track->filetype field may have been set by someone else, rather check it (if set
0688     // by us, it can be more accurate than file extension, so we prefer it)
0689     if( !Amarok::FileTypeSupport::possibleFileTypes().contains( type() ) )
0690         setType( Amarok::extension( playableUrl().path() ) );
0691         // we don't make the datbase dirty, this can be recomputed every time
0692 }
0693 
0694 void Track::beginUpdate()
0695 {
0696     QWriteLocker locker( &m_trackLock );
0697     m_batch++;
0698 }
0699 
0700 void Track::endUpdate()
0701 {
0702     QWriteLocker locker( &m_trackLock );
0703     Q_ASSERT( m_batch > 0 );
0704     m_batch--;
0705     commitIfInNonBatchUpdate();
0706 }
0707 
0708 bool
0709 Track::isEditable() const
0710 {
0711     if( !inCollection() )
0712         return false;
0713     return collection()->isWritable(); // IpodCollection implements this nicely
0714 }
0715 
0716 void
0717 Track::commitIfInNonBatchUpdate( qint64 field, const QVariant &value )
0718 {
0719     m_changedFields.insert( field, value );
0720     commitIfInNonBatchUpdate();
0721 }
0722 
0723 void
0724 Track::commitIfInNonBatchUpdate()
0725 {
0726     static const QSet<qint64> statFields = ( QSet<qint64>() << Meta::valFirstPlayed <<
0727         Meta::valLastPlayed << Meta::valPlaycount << Meta::valScore << Meta::valRating );
0728 
0729     if( m_batch > 0 || m_changedFields.isEmpty() )
0730         return;
0731 
0732     // we block changing the track meta-data of read-only iPod Collections;
0733     // it would only be confusing to the user as the changes would get discarded.
0734     if( !m_coll || !m_coll->isWritable() )
0735         return;
0736     const QList<long long int> changedfieldkeys=m_changedFields.keys();
0737     if( AmarokConfig::writeBackStatistics() ||
0738         !(QSet<qint64>( changedfieldkeys.begin(), changedfieldkeys.end() ) - statFields).isEmpty() )
0739     {
0740         setModifyDate( QDateTime::currentDateTime() );
0741     }
0742 
0743     m_trackLock.unlock(); // playableUrl() locks it too, notifyObservers() better without lock
0744     QString path = playableUrl().path(); // needs to be here because it locks m_trackLock too
0745 
0746     // write tags to file in a thread in order not to block
0747     WriteTagsJob *job = new WriteTagsJob( path, m_changedFields );
0748     job->connect( job, &WriteTagsJob::done, job, &QObject::deleteLater );
0749     ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(job) );
0750 
0751     notifyObservers();
0752     m_trackLock.lockForWrite(); // reset to original state when this was called
0753     m_changedFields.clear();
0754 }
0755 
0756 // IpodMeta:Album
0757 
0758 Album::Album( Track *track )
0759     : m_track( track )
0760 {
0761 }
0762 
0763 QString Album::name() const
0764 {
0765     QReadLocker locker( &m_track->m_trackLock );
0766     return QString::fromUtf8( m_track->m_track->album );
0767 }
0768 
0769 bool
0770 Album::isCompilation() const
0771 {
0772     return m_track->m_track->compilation;
0773 }
0774 
0775 bool
0776 Album::canUpdateCompilation() const
0777 {
0778     Collections::Collection *coll = m_track->collection();
0779     return coll ? coll->isWritable() : false;
0780 }
0781 
0782 void
0783 Album::setCompilation( bool isCompilation )
0784 {
0785     m_track->setIsCompilation( isCompilation );
0786 }
0787 
0788 bool
0789 Album::hasAlbumArtist() const
0790 {
0791     return !isCompilation();
0792 }
0793 
0794 Meta::ArtistPtr
0795 Album::albumArtist() const
0796 {
0797     if( isCompilation() )
0798         return Meta::ArtistPtr();
0799     QReadLocker locker( &m_track->m_trackLock );
0800     QString albumArtistName = QString::fromUtf8( m_track->m_track->albumartist );
0801     if( albumArtistName.isEmpty() )
0802         albumArtistName = QString::fromUtf8( m_track->m_track->artist );
0803     return Meta::ArtistPtr( new Artist( albumArtistName ) );
0804 }
0805 
0806 bool
0807 Album::hasImage( int size ) const
0808 {
0809     Q_UNUSED(size)
0810     if( m_track->m_track->has_artwork != 0x01 )
0811         return false; // libgpod: has_artwork: True if set to 0x01, false if set to 0x02.
0812     return itdb_track_has_thumbnails( m_track->m_track ); // should be false if GdbPixBuf is not available
0813 }
0814 
0815 QImage
0816 Album::image( int size ) const
0817 {
0818     Q_UNUSED(size) // MemoryMeta does scaling for us
0819 
0820     QImage albumImage;
0821 #ifdef GDKPIXBUF_FOUND
0822     do {
0823         if( m_track->m_track->has_artwork != 0x01 )
0824             break; // libgpod: has_artwork: True if set to 0x01, false if set to 0x02.
0825 
0826         // it reads "thumbnail", but this is the correct function to call
0827         GdkPixbuf *pixbuf = (GdkPixbuf*) itdb_track_get_thumbnail( m_track->m_track, -1, -1 );
0828         if( !pixbuf )
0829             break;
0830         if( gdk_pixbuf_get_colorspace( pixbuf ) != GDK_COLORSPACE_RGB )
0831         {
0832             warning() << __PRETTY_FUNCTION__ << "Unsupported GTK colorspace.";
0833             g_object_unref( pixbuf );
0834             break;
0835         }
0836         if( gdk_pixbuf_get_bits_per_sample( pixbuf ) != 8 )
0837         {
0838             warning() << __PRETTY_FUNCTION__ << "Unsupported number of bits per sample.";
0839             g_object_unref( pixbuf );
0840             break;
0841         }
0842         int n_channels = gdk_pixbuf_get_n_channels( pixbuf );
0843         bool has_alpha = gdk_pixbuf_get_has_alpha( pixbuf );
0844         QImage::Format format;
0845         if( n_channels == 4 && has_alpha )
0846             format = QImage::Format_ARGB32;
0847         else if( n_channels == 3 && !has_alpha )
0848             format = QImage::Format_RGB888;
0849         else
0850         {
0851             warning() << __PRETTY_FUNCTION__ << "Unsupported n_channels / has_alpha combination.";
0852             g_object_unref( pixbuf );
0853             break;
0854         }
0855 
0856         // cost cast needed to choose QImage constructor that takes read-only image data
0857         albumImage = QImage( const_cast<const uchar *>( gdk_pixbuf_get_pixels( pixbuf ) ),
0858                              gdk_pixbuf_get_width( pixbuf ),
0859                              gdk_pixbuf_get_height( pixbuf ),
0860                              gdk_pixbuf_get_rowstride( pixbuf ),
0861                              format );
0862         // force deep copy so that memory from gdk pixbuf can be unreferenced:
0863         albumImage.setDotsPerMeterX( 2835 );
0864         g_object_unref( pixbuf );
0865     } while( false );
0866 #endif
0867     return albumImage;
0868 }
0869 
0870 bool Album::canUpdateImage() const
0871 {
0872 #ifdef GDKPIXBUF_FOUND
0873     Collections::Collection *coll = m_track->collection();
0874     return coll ? coll->isWritable() : false;
0875 #else
0876     return false;
0877 #endif
0878 }
0879 
0880 void Album::setImage( const QImage &image )
0881 {
0882     m_track->setImage( image );
0883     CoverCache::invalidateAlbum( this );
0884 }
0885 
0886 void Album::removeImage()
0887 {
0888     setImage( QImage() );
0889 }