File indexing completed on 2024-05-05 04:49:21
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 "TrackTuple.h" 0018 0019 #include "MetaValues.h" 0020 #include "core/support/Debug.h" 0021 #include "statsyncing/Options.h" 0022 #include "statsyncing/Provider.h" 0023 0024 Q_DECLARE_METATYPE(QSet<QString>) 0025 0026 using namespace StatSyncing; 0027 0028 const QList<qint64> TrackTuple::s_fields = QList<qint64>() << Meta::valRating 0029 << Meta::valFirstPlayed << Meta::valLastPlayed << Meta::valPlaycount << Meta::valLabel; 0030 0031 TrackTuple::TrackTuple() 0032 { 0033 } 0034 0035 void 0036 TrackTuple::insert( ProviderPtr provider, const TrackPtr &track ) 0037 { 0038 m_map.insert( provider, track ); 0039 } 0040 0041 ProviderPtrList 0042 TrackTuple::providers() const 0043 { 0044 return m_map.keys(); 0045 } 0046 0047 ProviderPtr 0048 TrackTuple::provider( int i ) const 0049 { 0050 return m_map.keys().value( i ); 0051 } 0052 0053 TrackPtr 0054 TrackTuple::track( const ProviderPtr &provider ) const 0055 { 0056 Q_ASSERT( m_map.contains( provider ) ); 0057 return m_map.value( provider ); 0058 } 0059 0060 int 0061 TrackTuple::count() const 0062 { 0063 return m_map.count(); 0064 } 0065 0066 bool 0067 TrackTuple::isEmpty() const 0068 { 0069 return m_map.isEmpty(); 0070 } 0071 0072 bool 0073 TrackTuple::fieldUpdated( qint64 field, const Options &options, ProviderPtr provider ) const 0074 { 0075 if( isEmpty() || 0076 !(options.syncedFields() & field) || 0077 ( provider && !m_map.contains( provider ) ) || 0078 ( provider && !(provider->writableTrackStatsData() & field) ) ) 0079 { 0080 return false; 0081 } 0082 0083 switch( field ) 0084 { 0085 case Meta::valRating: 0086 { 0087 int rating = syncedRating( options ); 0088 if( rating < 0 ) 0089 return false; // unresolved conflict, not going to write that 0090 if( provider ) 0091 return track( provider )->rating() != rating; 0092 0093 foreach( const ProviderPtr &prov, m_map.keys() ) 0094 { 0095 if( !(prov->writableTrackStatsData() & field ) ) 0096 continue; // this provider doesn't even know how to write this field 0097 if( track( prov )->rating() != rating ) 0098 return true; 0099 } 0100 return false; 0101 } 0102 0103 case Meta::valFirstPlayed: 0104 { 0105 QDateTime firstPlayed = syncedFirstPlayed( options ); 0106 if( provider ) 0107 return track( provider )->firstPlayed() != firstPlayed; 0108 0109 foreach( const ProviderPtr &prov, m_map.keys() ) 0110 { 0111 if( !(prov->writableTrackStatsData() & field ) ) 0112 continue; // this provider doesn't even know how to write this field 0113 if( track( prov )->firstPlayed() != firstPlayed ) 0114 return true; 0115 } 0116 return false; 0117 } 0118 0119 case Meta::valLastPlayed: 0120 { 0121 QDateTime lastPlayed = syncedLastPlayed( options ); 0122 if( provider ) 0123 return track( provider )->lastPlayed() != lastPlayed; 0124 0125 foreach( const ProviderPtr &prov, m_map.keys() ) 0126 { 0127 if( !(prov->writableTrackStatsData() & field ) ) 0128 continue; // this provider doesn't even know how to write this field 0129 if( track( prov )->lastPlayed() != lastPlayed ) 0130 return true; 0131 } 0132 return false; 0133 } 0134 0135 case Meta::valPlaycount: 0136 { 0137 int playcount = syncedPlaycount( options ); 0138 if( provider ) 0139 return track( provider )->playCount() != playcount; 0140 0141 foreach( const ProviderPtr &prov, m_map.keys() ) 0142 { 0143 if( !(prov->writableTrackStatsData() & field ) ) 0144 continue; // this provider doesn't even know how to write this field 0145 if( track( prov )->playCount() != playcount ) 0146 return true; 0147 } 0148 return false; 0149 } 0150 0151 case Meta::valLabel: 0152 { 0153 bool hasConflict = true; 0154 QSet<QString> labels = syncedLabels( options, m_labelProviders, hasConflict ); 0155 if( hasConflict ) 0156 return false; // unresolved conflict, not going to write that 0157 if( provider ) 0158 return track( provider )->labels() - options.excludedLabels() != labels; 0159 0160 foreach( const ProviderPtr &prov, m_map.keys() ) 0161 { 0162 if( !(prov->writableTrackStatsData() & field ) ) 0163 continue; // this provider doesn't even know how to write this field 0164 if( track( prov )->labels() - options.excludedLabels() != labels ) 0165 return true; 0166 } 0167 return false; 0168 } 0169 } 0170 return false; 0171 } 0172 0173 bool 0174 TrackTuple::hasUpdate( const Options &options ) const 0175 { 0176 foreach( qint64 field, s_fields ) 0177 { 0178 if( fieldUpdated( field, options ) ) 0179 return true; 0180 } 0181 return false; 0182 } 0183 0184 bool 0185 TrackTuple::fieldHasConflict( qint64 field, const Options& options, bool includeResolved ) const 0186 { 0187 switch( field ) 0188 { 0189 case Meta::valRating: 0190 // we must disregard currently selected rating provider for includeResolved = true 0191 return syncedRating( options, includeResolved ? ProviderPtr() : m_ratingProvider ) < 0; 0192 case Meta::valLabel: 0193 { 0194 bool hasConflict = false; 0195 // we must disregard currently selected label providers for includeResolved = true 0196 syncedLabels( options, includeResolved ? ProviderPtrSet() : m_labelProviders , hasConflict ); 0197 return hasConflict; 0198 } 0199 } 0200 return false; 0201 } 0202 0203 bool 0204 TrackTuple::hasConflict( const Options &options ) const 0205 { 0206 return fieldHasConflict( Meta::valRating, options ) 0207 || fieldHasConflict( Meta::valLabel, options ); 0208 } 0209 0210 ProviderPtr 0211 TrackTuple::ratingProvider() const 0212 { 0213 return m_ratingProvider; 0214 } 0215 0216 void 0217 TrackTuple::setRatingProvider( const ProviderPtr &provider ) 0218 { 0219 if( !provider || m_map.contains( provider ) ) 0220 m_ratingProvider = provider; 0221 } 0222 0223 ProviderPtrSet 0224 TrackTuple::labelProviders() const 0225 { 0226 return m_labelProviders; 0227 } 0228 0229 void 0230 TrackTuple::setLabelProviders( const ProviderPtrSet &providers ) 0231 { 0232 m_labelProviders.clear(); 0233 foreach( const ProviderPtr &provider, providers ) 0234 { 0235 if( m_map.contains( provider ) ) 0236 m_labelProviders.insert( provider ); 0237 } 0238 } 0239 0240 int 0241 TrackTuple::syncedRating( const Options &options ) const 0242 { 0243 return syncedRating( options, m_ratingProvider ); 0244 } 0245 0246 int 0247 TrackTuple::syncedRating( const Options &options, ProviderPtr ratingProvider ) const 0248 { 0249 if( isEmpty() || !(options.syncedFields() & Meta::valRating) ) 0250 return 0; 0251 if( ratingProvider ) // a provider has been chosen 0252 return track( ratingProvider )->rating(); 0253 0254 // look for conflict: 0255 int candidate = -1; // rating candidate 0256 QMapIterator<ProviderPtr, TrackPtr> it( m_map ); 0257 while( it.hasNext() ) 0258 { 0259 it.next(); 0260 int rating = it.value()->rating(); 0261 0262 // take rating candidate only from rated tracks or from rating-writable collections 0263 bool canWriteRating = it.key()->writableTrackStatsData() & Meta::valRating; 0264 if( candidate < 0 ) 0265 { 0266 if( rating > 0 || canWriteRating ) 0267 candidate = rating; 0268 continue; // nothing to do in this loop iteration in either case 0269 } 0270 0271 if( rating <= 0 && !canWriteRating ) 0272 // skip unrated songs from colls with not-writable rating 0273 continue; 0274 0275 if( rating != candidate ) 0276 return -1; 0277 } 0278 // if candidate == -1, it means there are no colls with writable or non-zero rating 0279 return qMax( 0, candidate ); 0280 } 0281 0282 QDateTime 0283 TrackTuple::syncedFirstPlayed( const Options &options ) const 0284 { 0285 QDateTime first; 0286 if( isEmpty() || !(options.syncedFields() & Meta::valFirstPlayed) ) 0287 return first; 0288 foreach( TrackPtr track, m_map ) 0289 { 0290 QDateTime trackFirstPlayed = track->firstPlayed(); 0291 if( !trackFirstPlayed.isValid() ) 0292 continue; 0293 if( !first.isValid() || trackFirstPlayed < first ) 0294 first = trackFirstPlayed; 0295 } 0296 return first; 0297 } 0298 0299 QDateTime 0300 TrackTuple::syncedLastPlayed( const Options &options ) const 0301 { 0302 QDateTime last; 0303 if( isEmpty() || !(options.syncedFields() & Meta::valLastPlayed) ) 0304 return last; 0305 foreach( TrackPtr track, m_map ) 0306 { 0307 QDateTime trackLastPlayed = track->lastPlayed(); 0308 if( !trackLastPlayed.isValid() ) 0309 continue; 0310 if( !last.isValid() || trackLastPlayed > last ) 0311 last = trackLastPlayed; 0312 } 0313 return last; 0314 } 0315 0316 int 0317 TrackTuple::syncedPlaycount( const Options &options ) const 0318 { 0319 if( isEmpty() || !(options.syncedFields() & Meta::valPlaycount) ) 0320 return 0; 0321 int max = 0; 0322 int sumRecent = 0; 0323 foreach( TrackPtr track, m_map ) 0324 { 0325 int recent = track->recentPlayCount(); 0326 sumRecent += recent; 0327 max = qMax( max, track->playCount() - recent ); 0328 } 0329 return max + sumRecent; 0330 } 0331 0332 QSet<QString> 0333 TrackTuple::syncedLabels( const Options &options ) const 0334 { 0335 bool dummy = false; 0336 return syncedLabels( options, m_labelProviders, dummy ); 0337 } 0338 0339 QSet<QString> 0340 TrackTuple::syncedLabels( const Options &options, const ProviderPtrSet &labelProviders, bool &hasConflict ) const 0341 { 0342 hasConflict = false; 0343 QSet<QString> labelsCandidate; 0344 if( isEmpty() || !(options.syncedFields() & Meta::valLabel) ) 0345 return labelsCandidate; 0346 if( !labelProviders.isEmpty() ) // providers have been chosen 0347 { 0348 foreach( const ProviderPtr &provider, labelProviders ) 0349 labelsCandidate |= track( provider )->labels(); 0350 return labelsCandidate - options.excludedLabels(); 0351 } 0352 0353 // look for conflict: 0354 bool labelsCandidateAlreadySet = false; 0355 QMapIterator<ProviderPtr, TrackPtr> it( m_map ); 0356 while( it.hasNext() ) 0357 { 0358 it.next(); 0359 QSet<QString> labels = it.value()->labels() - options.excludedLabels(); 0360 0361 // take labels candidate only from labelled tracks or from label-writable collections 0362 bool canWriteLabels = it.key()->writableTrackStatsData() & Meta::valLabel; 0363 if( !labelsCandidateAlreadySet ) 0364 { 0365 if( !labels.isEmpty() || canWriteLabels ) 0366 { 0367 labelsCandidate = labels; 0368 labelsCandidateAlreadySet = true; 0369 } 0370 continue; // nothing to do in this loop iteration in either case 0371 } 0372 0373 if( labels.isEmpty() && !canWriteLabels ) 0374 // skip unlabelled songs from colls with not-writable labels 0375 continue; 0376 0377 if( labels != labelsCandidate ) 0378 { 0379 hasConflict = true; 0380 return QSet<QString>(); 0381 } 0382 } 0383 return labelsCandidate; 0384 } 0385 0386 ProviderPtrSet 0387 TrackTuple::synchronize( const Options &options ) const 0388 { 0389 ProviderPtrSet updatedProviders; 0390 foreach( qint64 field, s_fields ) 0391 { 0392 // catches if field should not be at all updated (either no change or not in options ) 0393 if( !fieldUpdated( field, options ) ) 0394 continue; 0395 0396 QVariant synced; 0397 switch( field ) 0398 { 0399 case Meta::valRating: 0400 synced = syncedRating( options ); break; 0401 case Meta::valFirstPlayed: 0402 synced = syncedFirstPlayed( options ); break; 0403 case Meta::valLastPlayed: 0404 synced = syncedLastPlayed( options ); break; 0405 case Meta::valPlaycount: 0406 synced = syncedPlaycount( options ); break; 0407 case Meta::valLabel: 0408 synced.setValue<QSet<QString> >( syncedLabels( options ) ); break; 0409 default: 0410 warning() << __PRETTY_FUNCTION__ << "unhandled first switch"; 0411 } 0412 0413 QMapIterator<ProviderPtr, TrackPtr> it( m_map ); 0414 while( it.hasNext() ) 0415 { 0416 it.next(); 0417 ProviderPtr provider = it.key(); 0418 // we have special case for playcount because it needs to we written even if 0419 // apparently unchanged to reset possible nonzero recentPlayCount 0420 if( field != Meta::valPlaycount && !fieldUpdated( field, options, provider ) ) 0421 continue; // nothing to do for this field and provider 0422 0423 updatedProviders.insert( provider ); 0424 TrackPtr track = it.value(); 0425 switch( field ) 0426 { 0427 case Meta::valRating: 0428 track->setRating( synced.toInt() ); break; 0429 case Meta::valFirstPlayed: 0430 track->setFirstPlayed( synced.toDateTime() ); break; 0431 case Meta::valLastPlayed: 0432 track->setLastPlayed( synced.toDateTime() ); break; 0433 case Meta::valPlaycount: 0434 track->setPlayCount( synced.toInt() ); break; 0435 case Meta::valLabel: 0436 { 0437 QSet<QString> desiredLabels = synced.value<QSet<QString> >(); 0438 /* add back blacklisted labels; we say we don't touch them, so we 0439 * should neither add them (handled in syncedLabels()) nor remove them 0440 * (handled here) 0441 */ 0442 desiredLabels |= track->labels() & options.excludedLabels(); 0443 track->setLabels( desiredLabels ); 0444 break; 0445 } 0446 default: 0447 warning() << __PRETTY_FUNCTION__ << "unhandled second switch"; 0448 } 0449 } 0450 } 0451 0452 foreach( const ProviderPtr &provider, updatedProviders ) 0453 track( provider )->commit(); 0454 return updatedProviders; 0455 }