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 }