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