File indexing completed on 2024-05-19 04:50:17
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 "SynchronizationTrack.h" 0018 0019 #include "core/support/Debug.h" 0020 #include "core/support/SemaphoreReleaser.h" 0021 0022 #include <QCoreApplication> 0023 #include <QNetworkReply> 0024 0025 #include <Track.h> 0026 #include <XmlQuery.h> 0027 0028 SynchronizationTrack::SynchronizationTrack( QString artist, QString album, QString name, 0029 int playCount, bool useFancyRatingTags ) 0030 : m_artist( artist ) 0031 , m_album( album ) 0032 , m_name( name ) 0033 , m_rating( 0 ) 0034 , m_newRating( 0 ) 0035 , m_playCount( playCount ) 0036 , m_useFancyRatingTags( useFancyRatingTags ) 0037 { 0038 // ensure this object is created in a main thread 0039 Q_ASSERT( thread() == QCoreApplication::instance()->thread() ); 0040 0041 connect( this, &SynchronizationTrack::startTagAddition, 0042 this, &SynchronizationTrack::slotStartTagAddition ); 0043 connect( this, &SynchronizationTrack::startTagRemoval, 0044 this, &SynchronizationTrack::slotStartTagRemoval ); 0045 } 0046 0047 QString 0048 SynchronizationTrack::name() const 0049 { 0050 return m_name; 0051 } 0052 0053 QString 0054 SynchronizationTrack::album() const 0055 { 0056 return m_album; 0057 } 0058 0059 QString 0060 SynchronizationTrack::artist() const 0061 { 0062 return m_artist; 0063 } 0064 0065 int 0066 SynchronizationTrack::rating() const 0067 { 0068 return m_rating; 0069 } 0070 0071 void 0072 SynchronizationTrack::setRating( int rating ) 0073 { 0074 m_newRating = rating; 0075 } 0076 0077 QDateTime 0078 SynchronizationTrack::firstPlayed() const 0079 { 0080 return QDateTime(); // no support on Last.fm side yet 0081 } 0082 0083 QDateTime 0084 SynchronizationTrack::lastPlayed() const 0085 { 0086 return QDateTime(); // no support on Last.fm side yet 0087 } 0088 0089 int 0090 SynchronizationTrack::playCount() const 0091 { 0092 return m_playCount; 0093 } 0094 0095 QSet<QString> 0096 SynchronizationTrack::labels() const 0097 { 0098 return m_labels; 0099 } 0100 0101 void 0102 SynchronizationTrack::setLabels( const QSet<QString> &labels ) 0103 { 0104 m_newLabels = labels; 0105 } 0106 0107 void 0108 SynchronizationTrack::commit() 0109 { 0110 if( m_newRating == m_rating && m_newLabels == m_labels ) 0111 return; 0112 0113 const QSet<QString> existingLabels = m_labels | m_ratingLabels; 0114 if( m_useFancyRatingTags ) 0115 { 0116 // implicitly we remove all ratingLabels here by not including them in m_newLabels 0117 if( m_newRating > 0 ) 0118 { 0119 QString ratingLabel = QString( "%1 of 10 stars" ).arg( m_newRating ); 0120 m_newLabels.insert( ratingLabel ); 0121 m_ratingLabels = QSet<QString>() << ratingLabel; 0122 } 0123 } 0124 else 0125 m_newLabels |= m_ratingLabels; // preserve all rating labels 0126 0127 QSet<QString> toAdd = m_newLabels - existingLabels; 0128 QSet<QString> toRemove = existingLabels - m_newLabels; 0129 0130 // first remove, than add Last.fm may limit number of track tags 0131 if( !toRemove.isEmpty() ) 0132 { 0133 Q_ASSERT( m_semaphore.available() == 0 ); 0134 m_tagsToRemove = toRemove.values(); 0135 emit startTagRemoval(); 0136 m_semaphore.acquire(); // wait for the job to complete 0137 m_tagsToRemove.clear(); 0138 } 0139 if( !toAdd.isEmpty() ) 0140 { 0141 Q_ASSERT( m_semaphore.available() == 0 ); 0142 emit startTagAddition( toAdd.values() ); 0143 m_semaphore.acquire(); // wait for the job to complete 0144 } 0145 0146 m_rating = m_newRating; 0147 m_labels = m_newLabels - m_ratingLabels; 0148 } 0149 0150 void 0151 SynchronizationTrack::parseAndSaveLastFmTags( const QSet<QString> &tags ) 0152 { 0153 m_labels.clear(); 0154 m_ratingLabels.clear(); 0155 m_rating = 0; 0156 0157 // we still match and explicitly ignore rating tags even in m_useFancyRatingTags is false 0158 QRegExp rx( "([0-9]{1,3}) of ([0-9]{1,3}) stars", Qt::CaseInsensitive ); 0159 foreach( const QString &tag, tags ) 0160 { 0161 if( rx.exactMatch( tag ) ) 0162 { 0163 m_ratingLabels.insert( tag ); 0164 QStringList texts = rx.capturedTexts(); 0165 if( texts.count() != 3 ) 0166 continue; 0167 qreal numerator = texts.at( 1 ).toDouble(); 0168 qreal denominator = texts.at( 2 ).toDouble(); 0169 if( denominator == 0.0 ) 0170 continue; 0171 m_rating = qBound( 0, qRound( 10.0 * numerator / denominator ), 10 ); 0172 } 0173 else 0174 m_labels.insert( tag ); 0175 } 0176 if( !m_useFancyRatingTags || m_ratingLabels.count() > 1 ) 0177 m_rating = 0; // ambiguous or not requested 0178 0179 m_newLabels = m_labels; 0180 m_newRating = m_rating; 0181 } 0182 0183 void 0184 SynchronizationTrack::slotStartTagAddition( QStringList tags ) 0185 { 0186 lastfm::MutableTrack track; 0187 track.setArtist( m_artist ); 0188 track.setAlbum( m_album ); 0189 track.setTitle( m_name ); 0190 0191 if( tags.count() > 10 ) 0192 tags = tags.mid( 0, 10 ); // Last.fm says 10 tags is max 0193 QNetworkReply *reply = track.addTags( tags ); 0194 connect( reply, &QNetworkReply::finished, this, &SynchronizationTrack::slotTagsAdded ); 0195 } 0196 0197 void 0198 SynchronizationTrack::slotStartTagRemoval() 0199 { 0200 Q_ASSERT( m_tagsToRemove.count() ); 0201 lastfm::MutableTrack track; 0202 track.setArtist( m_artist ); 0203 track.setAlbum( m_album ); 0204 track.setTitle( m_name ); 0205 0206 QNetworkReply *reply = track.removeTag( m_tagsToRemove.takeFirst() ); 0207 connect( reply, &QNetworkReply::finished, this, &SynchronizationTrack::slotTagRemoved ); 0208 } 0209 0210 void 0211 SynchronizationTrack::slotTagsAdded() 0212 { 0213 SemaphoreReleaser releaser( &m_semaphore ); 0214 QNetworkReply *reply = qobject_cast<QNetworkReply *>( sender() ); 0215 if( !reply ) 0216 { 0217 warning() << __PRETTY_FUNCTION__ << "cannot cast sender to QNetworkReply. (?)"; 0218 return; 0219 } 0220 reply->deleteLater(); 0221 0222 lastfm::XmlQuery lfm; 0223 if( !lfm.parse( reply->readAll() ) ) 0224 { 0225 warning() << __PRETTY_FUNCTION__ << "error adding tags:" << lfm.parseError().message(); 0226 return; 0227 } 0228 } 0229 0230 void 0231 SynchronizationTrack::slotTagRemoved() 0232 { 0233 SemaphoreReleaser releaser( &m_semaphore ); 0234 QNetworkReply *reply = qobject_cast<QNetworkReply *>( sender() ); 0235 if( !reply ) 0236 { 0237 warning() << __PRETTY_FUNCTION__ << "cannot cast sender to QNetworkReply. (?)"; 0238 return; 0239 } 0240 reply->deleteLater(); 0241 0242 lastfm::XmlQuery lfm; 0243 if( !lfm.parse( reply->readAll() ) ) 0244 { 0245 warning() << __PRETTY_FUNCTION__ << "error removing a tag:" << lfm.parseError().message(); 0246 return; 0247 } 0248 0249 // remove the next one, sadly only one at a time can be removed 0250 if( !m_tagsToRemove.isEmpty() ) 0251 { 0252 releaser.dontRelease(); 0253 emit startTagRemoval(); 0254 } 0255 }