1 /****************************************************************************************
2  * Copyright (c) 2010 Sergey Ivanov <123kash@gmail.com>                                 *
3  * Copyright (c) 2013 Alberto Villa <avilla@FreeBSD.org>                                *
4  *                                                                                      *
5  * This program is free software; you can redistribute it and/or modify it under        *
6  * the terms of the GNU General Public License as published by the Free Software        *
7  * Foundation; either version 2 of the License, or (at your option) any later           *
8  * version.                                                                             *
9  *                                                                                      *
10  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
11  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
12  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
13  *                                                                                      *
14  * You should have received a copy of the GNU General Public License along with         *
15  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
16  ****************************************************************************************/
17 
18 #define DEBUG_PREFIX "MusicBrainzTagsItem"
19 
20 #include "MusicBrainzTagsItem.h"
21 
22 #include "AmarokMimeData.h"
23 #include "MusicBrainzMeta.h"
24 #include "core/support/Debug.h"
25 #include "core/collections/QueryMaker.h"
26 
MusicBrainzTagsItem(MusicBrainzTagsItem * parent,const Meta::TrackPtr & track,const QVariantMap & tags)27 MusicBrainzTagsItem::MusicBrainzTagsItem( MusicBrainzTagsItem *parent,
28                                           const Meta::TrackPtr &track,
29                                           const QVariantMap &tags )
30     : m_parent( parent )
31     , m_track( track )
32     , m_data( tags )
33     , m_chosen( false )
34     , m_dataLock( QReadWriteLock::Recursive )
35     , m_parentLock( QReadWriteLock::Recursive )
36     , m_childrenLock( QReadWriteLock::Recursive )
37 {
38 }
39 
~MusicBrainzTagsItem()40 MusicBrainzTagsItem::~MusicBrainzTagsItem()
41 {
42     qDeleteAll( m_childItems );
43 }
44 
45 MusicBrainzTagsItem *
parent() const46 MusicBrainzTagsItem::parent() const
47 {
48     QReadLocker lock( &m_parentLock );
49     return m_parent;
50 }
51 
52 void
setParent(MusicBrainzTagsItem * parent)53 MusicBrainzTagsItem::setParent( MusicBrainzTagsItem *parent )
54 {
55     QWriteLocker lock( &m_parentLock );
56     m_parent = parent;
57 }
58 
59 MusicBrainzTagsItem *
child(const int row) const60 MusicBrainzTagsItem::child( const int row ) const
61 {
62     QReadLocker lock( &m_childrenLock );
63     return m_childItems.value( row );
64 }
65 
66 void
appendChild(MusicBrainzTagsItem * newItem)67 MusicBrainzTagsItem::appendChild( MusicBrainzTagsItem *newItem )
68 {
69     QWriteLocker lock( &m_childrenLock );
70     m_childItems.append( newItem );
71     newItem->setParent( this );
72 
73     if( !newItem->data().isEmpty() )
74     {
75         newItem->recalcSimilarityRate();
76 
77 #define MAKE_DATA_LIST( k ) { QVariantList list; if( newItem->dataContains( k ) ) list.append( newItem->dataValue( k ) ); newItem->dataInsert( k, list ); }
78 
79         MAKE_DATA_LIST( MusicBrainz::TRACKID );
80         MAKE_DATA_LIST( MusicBrainz::ARTISTID );
81         MAKE_DATA_LIST( MusicBrainz::RELEASEID );
82 
83 #undef MAKE_DATA_LIST
84     }
85 }
86 
87 void
mergeData(const QVariantMap & tags)88 MusicBrainzTagsItem::mergeData( const QVariantMap &tags )
89 {
90     if( tags.isEmpty() )
91         return;
92 
93     MusicBrainzTagsItem fakeItem( this, m_track, tags );
94     // Calculate the future score of the result when merged.
95     if( !fakeItem.dataContains( MusicBrainz::MUSICBRAINZ ) && dataContains( MusicBrainz::MUSICBRAINZ ) )
96         fakeItem.dataInsert( MusicBrainz::MUSICBRAINZ, dataValue( MusicBrainz::MUSICBRAINZ ) );
97 
98     if( !fakeItem.dataContains( MusicBrainz::MUSICDNS ) && dataContains( MusicBrainz::MUSICDNS ) )
99         fakeItem.dataInsert( MusicBrainz::MUSICDNS, dataValue( MusicBrainz::MUSICDNS ) );
100 
101     fakeItem.recalcSimilarityRate();
102 
103     QVariantList trackList = dataValue( MusicBrainz::TRACKID ).toList();
104     QVariantList artistList = dataValue( MusicBrainz::ARTISTID ).toList();
105     QVariantList releaseList = dataValue( MusicBrainz::RELEASEID ).toList();
106     if( fakeItem.score() > score() )
107     {
108         // Update the score.
109         if( fakeItem.dataContains( MusicBrainz::MUSICBRAINZ ) )
110             dataInsert( MusicBrainz::MUSICBRAINZ, fakeItem.dataValue( MusicBrainz::MUSICBRAINZ ) );
111 
112         if( fakeItem.dataContains( MusicBrainz::MUSICDNS ) )
113             dataInsert( MusicBrainz::MUSICDNS, fakeItem.dataValue( MusicBrainz::MUSICDNS ) );
114 
115         recalcSimilarityRate();
116 
117         if( fakeItem.dataContains( MusicBrainz::TRACKID ) )
118             trackList.prepend( fakeItem.dataValue( MusicBrainz::TRACKID ) );
119 
120         if( fakeItem.dataContains( MusicBrainz::ARTISTID ) )
121             artistList.prepend( fakeItem.dataValue( MusicBrainz::ARTISTID ) );
122 
123         if( fakeItem.dataContains( MusicBrainz::RELEASEID ) )
124             releaseList.prepend( fakeItem.dataValue( MusicBrainz::RELEASEID ) );
125     }
126     else
127     {
128         if( fakeItem.dataContains( MusicBrainz::TRACKID ) )
129             trackList.append( fakeItem.dataValue( MusicBrainz::TRACKID ) );
130 
131         if( fakeItem.dataContains( MusicBrainz::ARTISTID ) )
132             artistList.append( fakeItem.dataValue( MusicBrainz::ARTISTID ) );
133 
134         if( fakeItem.dataContains( MusicBrainz::RELEASEID ) )
135             releaseList.append( fakeItem.dataValue( MusicBrainz::RELEASEID ) );
136     }
137 
138     dataInsert( MusicBrainz::TRACKID, trackList );
139     dataInsert( MusicBrainz::ARTISTID, artistList );
140     dataInsert( MusicBrainz::RELEASEID, releaseList );
141 }
142 
143 int
childCount() const144 MusicBrainzTagsItem::childCount() const
145 {
146     QReadLocker lock( &m_childrenLock );
147     return m_childItems.count();
148 }
149 
150 int
row() const151 MusicBrainzTagsItem::row() const
152 {
153     if( parent() )
154     {
155         QReadLocker lock( &m_childrenLock );
156         return m_parent->m_childItems.indexOf( const_cast<MusicBrainzTagsItem *>( this ) );
157     }
158 
159     return 0;
160 }
161 
162 Meta::TrackPtr
track() const163 MusicBrainzTagsItem::track() const
164 {
165     QReadLocker lock( &m_dataLock );
166     return m_track;
167 }
168 
169 float
score() const170 MusicBrainzTagsItem::score() const
171 {
172     QReadLocker lock( &m_dataLock );
173     float score = dataValue( MusicBrainz::SIMILARITY ).toFloat();
174 
175     /*
176      * Results of fingerprint-only lookup go on bottom as they are weak matches (only
177      * their length is compared).
178      */
179     if( !dataContains( MusicBrainz::MUSICBRAINZ ) )
180         score -= 1.0;
181 
182     return score;
183 }
184 
185 QVariantMap
data() const186 MusicBrainzTagsItem::data() const
187 {
188     QReadLocker lock( &m_dataLock );
189     return m_data;
190 }
191 
192 QVariant
data(const int column) const193 MusicBrainzTagsItem::data( const int column ) const
194 {
195     if( m_data.isEmpty() )
196     {
197         switch( column )
198         {
199         case 0:
200             {
201                 QString title;
202                 int trackNumber = m_track->trackNumber();
203                 if( trackNumber > 0 )
204                     title += QString( "%1 - " ).arg( trackNumber );
205                 title += m_track->prettyName();
206                 return title;
207             }
208         case 1:
209             return ( !m_track->artist().isNull() )? m_track->artist()->name() : QVariant();
210         case 2:
211             {
212                 if( m_track->album().isNull() )
213                     return QVariant();
214                 QString album = m_track->album()->name();
215                 int discNumber = m_track->discNumber();
216                 if( discNumber > 0 )
217                     album += QString( " (disc %1)" ).arg( discNumber );
218                 return album;
219             }
220         case 3:
221             return ( !m_track->album().isNull() && m_track->album()->hasAlbumArtist() )?
222                    m_track->album()->albumArtist()->name() : QVariant();
223         case 4:
224             return ( m_track->year()->year() > 0 )? m_track->year()->year() : QVariant();
225         }
226 
227         return QVariant();
228     }
229 
230     switch( column )
231     {
232     case 0:
233         {
234             QString title;
235             QVariant trackNumber = dataValue( Meta::Field::TRACKNUMBER );
236             if( trackNumber.toInt() > 0 )
237             {
238                 title += trackNumber.toString();
239                 int trackCount = dataValue( MusicBrainz::TRACKCOUNT ).toInt();
240                 if ( trackCount > 0 )
241                     title += QString( "/%1" ).arg( trackCount );
242                 title += " - ";
243             }
244             title += dataValue( Meta::Field::TITLE ).toString();
245             return title;
246         }
247     case 1:
248         return dataValue( Meta::Field::ARTIST );
249     case 2:
250         {
251             QString album = dataValue( Meta::Field::ALBUM ).toString();
252             int discNumber = dataValue( Meta::Field::DISCNUMBER ).toInt();
253             if( discNumber > 0 )
254                 album += QString( " (disc %1)" ).arg( discNumber );
255             return album;
256         }
257     case 3:
258         return dataValue( Meta::Field::ALBUMARTIST );
259     case 4:
260         return dataValue( Meta::Field::YEAR );
261     }
262 
263     return QVariant();
264 }
265 
266 void
setData(const QVariantMap & tags)267 MusicBrainzTagsItem::setData( const QVariantMap &tags )
268 {
269     QWriteLocker lock( &m_dataLock );
270     m_data = tags;
271 }
272 
273 bool
dataContains(const QString & key) const274 MusicBrainzTagsItem::dataContains( const QString &key ) const
275 {
276     QReadLocker lock( &m_dataLock );
277     return m_data.contains( key );
278 }
279 
280 QVariant
dataValue(const QString & key) const281 MusicBrainzTagsItem::dataValue( const QString &key ) const
282 {
283     QReadLocker lock( &m_dataLock );
284     if( m_data.contains( key ) )
285         return m_data.value( key );
286 
287     return QVariant();
288 }
289 
290 void
dataInsert(const QString & key,const QVariant & value)291 MusicBrainzTagsItem::dataInsert( const QString &key, const QVariant &value )
292 {
293     QWriteLocker lock( &m_dataLock );
294     m_data.insert( key, value );
295 }
296 
297 bool
isChosen() const298 MusicBrainzTagsItem::isChosen() const
299 {
300     QReadLocker lock( &m_dataLock );
301     if( m_data.isEmpty() )
302     {
303         foreach( MusicBrainzTagsItem *item, m_childItems )
304             if( item->isChosen() )
305                 return true;
306         return false;
307     }
308 
309     return m_chosen;
310 }
311 
312 void
setChosen(bool chosen)313 MusicBrainzTagsItem::setChosen( bool chosen )
314 {
315     if( m_data.isEmpty() )
316         return;
317 
318     QWriteLocker lock( &m_dataLock );
319     m_chosen = chosen;
320 }
321 
322 MusicBrainzTagsItem *
chosenItem() const323 MusicBrainzTagsItem::chosenItem() const
324 {
325     if( m_data.isEmpty() )
326     {
327         QReadLocker lock( &m_childrenLock );
328         foreach( MusicBrainzTagsItem *item, m_childItems )
329             if( item->isChosen() )
330                 return item;
331     }
332 
333     return 0;
334 }
335 
336 bool
chooseBestMatch()337 MusicBrainzTagsItem::chooseBestMatch()
338 {
339     if( !m_data.isEmpty() || isChosen() )
340         return false;
341 
342     QReadLocker lock( &m_childrenLock );
343     MusicBrainzTagsItem *bestMatch = 0;
344     float maxScore = 0;
345     foreach( MusicBrainzTagsItem *item, m_childItems )
346     {
347         if( item->score() > maxScore )
348         {
349             bestMatch = item;
350             maxScore = item->score();
351         }
352     }
353     if( !bestMatch )
354         return false;
355 
356     bestMatch->setChosen( true );
357     return true;
358 }
359 
360 bool
chooseBestMatchFromRelease(const QStringList & releases)361 MusicBrainzTagsItem::chooseBestMatchFromRelease( const QStringList &releases )
362 {
363     if( !m_data.isEmpty() )
364         return false;
365 
366     QReadLocker lock( &m_childrenLock );
367     if( !childCount() || isChosen() )
368         return false;
369 
370     MusicBrainzTagsItem *bestMatch = 0;
371     float maxScore = 0;
372     QSet<QString> idList = releases.toSet();
373     foreach( MusicBrainzTagsItem *item, m_childItems )
374     {
375         /*
376          * Match any of the releases referenced by selected entry. This should guarantee
377          * that best results are always chosen when available.
378          */
379         if( item->score() > maxScore &&
380             !item->dataValue( MusicBrainz::RELEASEID ).toStringList().toSet().intersect( idList ).isEmpty() )
381         {
382             bestMatch = item;
383             maxScore = item->score();
384         }
385     }
386 
387     if( bestMatch )
388     {
389         bestMatch->setChosen( true );
390         return true;
391     }
392 
393     return false;
394 }
395 
396 void
clearChoices()397 MusicBrainzTagsItem::clearChoices()
398 {
399     QReadLocker lock( &m_childrenLock );
400     if( !parent() )
401         foreach( MusicBrainzTagsItem *item, m_childItems )
402             item->clearChoices();
403     else if( m_data.isEmpty() )
404         foreach( MusicBrainzTagsItem *item, m_childItems )
405             item->setChosen( false );
406 }
407 
408 bool
isSimilar(const QVariantMap & tags) const409 MusicBrainzTagsItem::isSimilar( const QVariantMap &tags ) const
410 {
411     QReadLocker lock( &m_dataLock );
412     QVariant empty;
413 #define MATCH( k, t ) ( dataValue( k ).t() == ( tags.contains( k ) ? tags.value( k ) : empty ).t() )
414     /*
415      * This is the information shown to the user: he will never be able to
416      * distinguish between two tracks with the same information.
417      */
418     return MATCH( Meta::Field::TITLE, toString ) &&
419            MATCH( Meta::Field::ARTIST, toString ) &&
420            MATCH( Meta::Field::ALBUM, toString ) &&
421            MATCH( Meta::Field::ALBUMARTIST, toString ) &&
422            MATCH( Meta::Field::YEAR, toInt ) &&
423            MATCH( MusicBrainz::TRACKCOUNT, toInt ) &&
424            MATCH( Meta::Field::DISCNUMBER, toInt ) &&
425            MATCH( Meta::Field::TRACKNUMBER, toInt );
426 #undef MATCH
427 }
428 
429 bool
operator ==(const MusicBrainzTagsItem * item) const430 MusicBrainzTagsItem::operator==( const MusicBrainzTagsItem* item ) const
431 {
432     return isSimilar( item->data() );
433 }
434 
435 bool
operator ==(const Meta::TrackPtr & track) const436 MusicBrainzTagsItem::operator==( const Meta::TrackPtr &track) const
437 {
438     return m_track == track;
439 }
440 
441 void
recalcSimilarityRate()442 MusicBrainzTagsItem::recalcSimilarityRate()
443 {
444     dataInsert( MusicBrainz::SIMILARITY, dataValue( MusicBrainz::MUSICBRAINZ ).toFloat() + dataValue( MusicBrainz::MUSICDNS ).toFloat() );
445 }
446