PageRenderTime 249ms CodeModel.GetById 60ms app.highlight 143ms RepoModel.GetById 38ms app.codeStats 1ms

/src/libtomahawk/Query.cpp

http://github.com/tomahawk-player/tomahawk
C++ | 792 lines | 587 code | 166 blank | 39 comment | 64 complexity | 64a2e7f87f55d9d8b0f69e72a3302fb6 MD5 | raw file
  1/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
  2 *
  3 *   Copyright 2010-2015, Christian Muehlhaeuser <muesli@tomahawk-player.org>
  4 *   Copyright 2010-2012, Jeff Mitchell <jeff@tomahawk-player.org>
  5 *   Copyright 2013,      Uwe L. Korn <uwelk@xhochy.com>
  6 *
  7 *   Tomahawk is free software: you can redistribute it and/or modify
  8 *   it under the terms of the GNU General Public License as published by
  9 *   the Free Software Foundation, either version 3 of the License, or
 10 *   (at your option) any later version.
 11 *
 12 *   Tomahawk is distributed in the hope that it will be useful,
 13 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 14 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 15 *   GNU General Public License for more details.
 16 *
 17 *   You should have received a copy of the GNU General Public License
 18 *   along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
 19 */
 20
 21#include "Query_p.h"
 22
 23#include "audio/AudioEngine.h"
 24#include "collection/Collection.h"
 25#include "database/Database.h"
 26#include "database/DatabaseImpl.h"
 27#include "resolvers/Resolver.h"
 28#include "utils/Logger.h"
 29
 30#include "Album.h"
 31#include "Pipeline.h"
 32#include "Result.h"
 33
 34#include <QtAlgorithms>
 35#include <QDebug>
 36#include <QCoreApplication>
 37
 38using namespace Tomahawk;
 39
 40
 41query_ptr
 42Query::get( const QString& artist, const QString& track, const QString& album, const QID& qid, bool autoResolve )
 43{
 44    if ( artist.trimmed().isEmpty() || track.trimmed().isEmpty() )
 45        return query_ptr();
 46
 47    if ( qid.isEmpty() )
 48        autoResolve = false;
 49
 50    query_ptr q = query_ptr( new Query( Track::get( artist, track, album ), qid, autoResolve ), &QObject::deleteLater );
 51    q->moveToThread( QCoreApplication::instance()->thread() );
 52    q->setWeakRef( q.toWeakRef() );
 53
 54    if ( autoResolve )
 55        Pipeline::instance()->resolve( q );
 56
 57    return q;
 58}
 59
 60
 61query_ptr
 62Query::get( const Tomahawk::track_ptr& track, const QID& qid )
 63{
 64    query_ptr q = query_ptr( new Query( track, qid, false ), &QObject::deleteLater );
 65    q->setWeakRef( q.toWeakRef() );
 66
 67    return q;
 68}
 69
 70
 71query_ptr
 72Query::get( const QString& query, const QID& qid )
 73{
 74    Q_ASSERT( !query.trimmed().isEmpty() );
 75
 76    query_ptr q = query_ptr( new Query( query, qid ), &QObject::deleteLater );
 77    q->setWeakRef( q.toWeakRef() );
 78
 79    if ( !qid.isEmpty() )
 80        Pipeline::instance()->resolve( q );
 81
 82    return q;
 83}
 84
 85
 86query_ptr
 87Query::getFixed( const track_ptr& track, const result_ptr& result )
 88{
 89    query_ptr q = query_ptr( new Query( track, result ), &QObject::deleteLater );
 90    q->setWeakRef( q.toWeakRef() );
 91
 92    return q;
 93}
 94
 95
 96Query::Query( const track_ptr& track, const QID& qid, bool autoResolve )
 97    : d_ptr( new QueryPrivate( this, track, qid ) )
 98{
 99    init();
100
101    if ( autoResolve )
102    {
103        connect( Database::instance(), SIGNAL( indexReady() ), SLOT( refreshResults() ), Qt::QueuedConnection );
104    }
105
106    connect( Pipeline::instance(), SIGNAL( resolverAdded( Tomahawk::Resolver* ) ), SLOT( onResolverAdded() ), Qt::QueuedConnection );
107}
108
109
110Query::Query( const track_ptr& track, const result_ptr& result )
111    : d_ptr( new QueryPrivate( this, track, QString() ) )
112{
113    Q_D( Query );
114
115    init();
116    d->allowReresolve = false;
117    d->resolveFinished = true;
118    d->results << result;
119    d->playable = result->playable();
120    d->solved = true;
121    d->score = 1.0;
122    connect( result.data(), SIGNAL( statusChanged() ), SLOT( onResultStatusChanged() ) );
123}
124
125
126Query::Query( const QString& query, const QID& qid )
127    : d_ptr( new QueryPrivate( this, query, qid ) )
128{
129    init();
130
131    if ( !qid.isEmpty() )
132    {
133        connect( Database::instance(), SIGNAL( indexReady() ), SLOT( refreshResults() ), Qt::QueuedConnection );
134    }
135}
136
137
138Query::~Query()
139{
140}
141
142
143void
144Query::init()
145{
146    Q_D( Query );
147    d->resolveFinished = false;
148    d->solved = false;
149    d->playable = false;
150    d->saveResultHint = false;
151    d->score = 0.0;
152}
153
154
155track_ptr
156Query::queryTrack() const
157{
158    Q_D( const Query );
159    return d->queryTrack;
160}
161
162
163track_ptr
164Query::track() const
165{
166    Q_D( const Query );
167
168    {
169        QMutexLocker lock( &d->mutex );
170        if ( !d->results.isEmpty() )
171            return d->results.first()->track();
172    }
173
174    return d->queryTrack;
175}
176
177
178void
179Query::addResults( const QList< Tomahawk::result_ptr >& newresults )
180{
181    Q_D( Query );
182    {
183        QMutexLocker lock( &d->mutex );
184
185/*        const QStringList smt = AudioEngine::instance()->supportedMimeTypes();
186        foreach ( const Tomahawk::result_ptr& result, newresults )
187        {
188            if ( !smt.contains( result->mimetype() ) )
189            {
190                tDebug() << "Won't accept result, unsupported mimetype" << result->toString() << result->mimetype();
191            }
192            else
193                m_results.append( result );
194        }*/
195
196        d->results << newresults;
197        sortResults();
198
199        // hook up signals, and check solved status
200        foreach( const result_ptr& rp, newresults )
201        {
202            connect( rp.data(), SIGNAL( statusChanged() ), SLOT( onResultStatusChanged() ) );
203        }
204    }
205
206    checkResults();
207    emit resultsAdded( newresults );
208    emit resultsChanged();
209}
210
211
212void
213Query::addAlbums( const QList< Tomahawk::album_ptr >& newalbums )
214{
215    {
216        Q_D( Query );
217        QMutexLocker lock( &d->mutex );
218        d->albums << newalbums;
219    }
220
221    emit albumsAdded( newalbums );
222}
223
224
225void
226Query::addArtists( const QList< Tomahawk::artist_ptr >& newartists )
227{
228    {
229        Q_D( Query );
230        QMutexLocker lock( &d->mutex );
231        d->artists << newartists;
232    }
233
234    emit artistsAdded( newartists );
235}
236
237
238void
239Query::refreshResults()
240{
241    Q_D( Query );
242
243    clearResults();
244    if ( d->resolveFinished && d->allowReresolve )
245    {
246        d->resolveFinished = false;
247        query_ptr q = d->ownRef.toStrongRef();
248        if ( q )
249            Pipeline::instance()->resolve( q );
250    }
251}
252
253
254void
255Query::onResultStatusChanged()
256{
257    {
258        Q_D( Query );
259        QMutexLocker lock( &d->mutex );
260        if ( !d->results.isEmpty() )
261            sortResults();
262    }
263
264    checkResults();
265    emit resultsChanged();
266}
267
268
269void
270Query::removeResult( const Tomahawk::result_ptr& result )
271{
272    {
273        Q_D( Query );
274        QMutexLocker lock( &d->mutex );
275        d->results.removeAll( result );
276        if ( d->preferredResult == result )
277        {
278            d->preferredResult.clear();
279        }
280        sortResults();
281    }
282
283    emit resultsRemoved( result );
284    checkResults();
285    emit resultsChanged();
286}
287
288
289void
290Query::clearResults()
291{
292    Q_D( Query );
293
294    d->solved = false;
295    d->playable = false;
296
297    {
298        QMutexLocker lock( &d->mutex );
299        d->results.clear();
300    }
301
302    emit playableStateChanged( false );
303    emit solvedStateChanged( false );
304    emit resultsChanged();
305}
306
307
308void
309Query::onResolvingFinished()
310{
311    Q_D( Query );
312    tDebug( LOGVERBOSE ) << "Finished resolving:" << toString();
313    if ( !d->resolveFinished )
314    {
315        d->resolveFinished = true;
316        d->resolvers.clear();
317
318        emit resolvingFinished( d->playable );
319    }
320}
321
322
323void
324Query::onResolverAdded()
325{
326    if ( !solved() )
327    {
328        refreshResults();
329    }
330}
331
332
333QList< result_ptr >
334Query::results() const
335{
336    Q_D( const Query );
337    QMutexLocker lock( &d->mutex );
338    return d->results;
339}
340
341
342unsigned int
343Query::numResults( bool onlyPlayableResults ) const
344{
345    Q_D( const Query );
346    QMutexLocker lock( &d->mutex );
347
348    if ( onlyPlayableResults )
349    {
350        unsigned int c = 0;
351        foreach ( const result_ptr& result, d->results )
352        {
353            if ( result->isOnline() )
354                c++;
355        }
356
357        return c;
358    }
359
360    return d->results.length();
361}
362
363
364bool
365Query::resolvingFinished() const
366{
367    Q_D( const Query );
368    return d->resolveFinished;
369}
370
371
372bool
373Query::solved() const
374{
375    Q_D( const Query );
376    return d->solved;
377}
378
379
380bool
381Query::playable() const
382{
383    Q_D( const Query );
384    return d->playable;
385}
386
387
388QID
389Query::id() const
390{
391    Q_D( const Query );
392    if ( d->qid.isEmpty() )
393    {
394        d->qid = uuid();
395    }
396
397    return d->qid;
398}
399
400
401bool
402Query::resultSorter( const result_ptr& left, const result_ptr& right )
403{
404    Q_D( Query );
405    if ( !d->preferredResult.isNull() )
406    {
407        if ( d->preferredResult == left )
408            return true;
409        if ( d->preferredResult == right )
410            return false;
411    }
412
413    const float ls = left->isOnline() ? howSimilar( left ) : 0.0;
414    const float rs = right->isOnline() ? howSimilar( right ) : 0.0;
415
416    if ( ls == rs )
417    {
418        if ( right->isLocal() )
419        {
420            return false;
421        }
422        if ( left->isPreview() != right->isPreview() )
423        {
424            return !left->isPreview();
425        }
426
427        if ( left->resolvedBy() != nullptr && right->resolvedBy() != nullptr )
428        {
429            return left->resolvedBy()->weight() > right->resolvedBy()->weight();
430        }
431
432        return left->id() > right->id();
433    }
434
435    if ( left->isPreview() != right->isPreview() )
436    {
437        return !left->isPreview();
438    }
439
440    return ls > rs;
441}
442
443
444result_ptr
445Query::preferredResult() const
446{
447    Q_D( const Query );
448    return d->preferredResult;
449}
450
451
452void
453Query::setPreferredResult( const result_ptr& result )
454{
455    {
456        Q_D( Query );
457        QMutexLocker lock( &d->mutex );
458
459        Q_ASSERT( d->results.contains( result ) );
460        d->preferredResult = result;
461        sortResults();
462    }
463
464    emit resultsChanged();
465}
466
467
468void
469Query::setCurrentResolver( Tomahawk::Resolver* resolver )
470{
471    Q_D( Query );
472    d->resolvers << resolver;
473}
474
475
476Tomahawk::Resolver*
477Query::currentResolver() const
478{
479    Q_D( const Query );
480    int x = d->resolvers.count();
481    while ( --x )
482    {
483        QPointer< Resolver > r = d->resolvers.at( x );
484        if ( r.isNull() )
485            continue;
486
487        return r.data();
488    }
489
490    return 0;
491}
492
493
494QList< QPointer<Resolver> >
495Query::resolvedBy() const
496{
497    Q_D( const Query );
498    return d->resolvers;
499}
500
501
502QString
503Query::fullTextQuery() const
504{
505    Q_D( const Query );
506    return d->fullTextQuery;
507}
508
509
510bool
511Query::isFullTextQuery() const
512{
513    Q_D( const Query );
514    return !d->fullTextQuery.isEmpty();
515}
516
517
518void
519Query::setResolveFinished( bool resolved )
520{
521    Q_D( Query );
522    d->resolveFinished = resolved;
523}
524
525
526void
527Query::allowReresolve()
528{
529    Q_D( Query );
530    d->allowReresolve = true;
531}
532
533
534void
535Query::disallowReresolve()
536{
537    Q_D( Query );
538    d->allowReresolve = false;
539}
540
541
542void
543Query::checkResults()
544{
545    Q_D( Query );
546    if ( !d->results.isEmpty() )
547    {
548        d->score = howSimilar( d->results.first() );
549    }
550    else
551    {
552        d->score = 0.0;
553    }
554
555    bool playable = false;
556    bool solved = false;
557
558    {
559        QMutexLocker lock( &d->mutex );
560
561        // hook up signals, and check solved status
562        foreach( const result_ptr& rp, d->results )
563        {
564            if ( rp->playable() )
565                playable = true;
566
567            if ( rp->isOnline() && howSimilar( rp ) > 0.99 )
568            {
569                solved = true;
570            }
571
572            if ( playable )
573                break;
574        }
575    }
576
577    if ( d->solved && !solved )
578    {
579        refreshResults();
580    }
581    else
582    {
583        if ( d->playable != playable )
584        {
585            d->playable = playable;
586            emit playableStateChanged( d->playable );
587        }
588        if ( d->solved != solved )
589        {
590            d->solved = solved;
591            emit solvedStateChanged( d->solved );
592        }
593    }
594}
595
596
597bool
598Query::equals( const Tomahawk::query_ptr& other, bool ignoreCase, bool ignoreAlbum ) const
599{
600    if ( !other )
601        return false;
602
603    if ( ignoreCase )
604    {
605        return ( queryTrack()->artist().toLower() == other->queryTrack()->artist().toLower() &&
606                 ( ignoreAlbum || queryTrack()->album().toLower() == other->queryTrack()->album().toLower() ) &&
607                   queryTrack()->track().toLower() == other->queryTrack()->track().toLower() );
608    }
609
610    return ( queryTrack()->artist() == other->queryTrack()->artist() &&
611             ( ignoreAlbum || queryTrack()->album() == other->queryTrack()->album() ) &&
612               queryTrack()->track() == other->queryTrack()->track() );
613}
614
615
616QVariant
617Query::toVariant() const
618{
619    QVariantMap m;
620    m.insert( "artist", queryTrack()->artist() );
621    m.insert( "album", queryTrack()->album() );
622    m.insert( "track", queryTrack()->track() );
623    m.insert( "duration", queryTrack()->duration() );
624    m.insert( "qid", id() );
625
626    return m;
627}
628
629
630QString
631Query::toString() const
632{
633    if ( !isFullTextQuery() )
634    {
635        return QString( "Query(%1, %2 - %3%4)" )
636                  .arg( id() )
637                  .arg( queryTrack()->artist() )
638                  .arg( queryTrack()->track() )
639                  .arg( queryTrack()->album().isEmpty() ? "" : QString( " on %1" ).arg( queryTrack()->album() ) );
640    }
641    else
642    {
643        return QString( "Query(%1, Fulltext: %2)" )
644                  .arg( id() )
645                  .arg( fullTextQuery() );
646    }
647}
648
649
650float
651Query::score() const
652{
653    Q_D( const Query );
654    return d->score;
655}
656
657
658// TODO make clever (ft. featuring live (stuff) etc)
659float
660Query::howSimilar( const Tomahawk::result_ptr& r )
661{
662    Q_D( Query );
663    if (d->howSimilarCache.find(r->id()) != d->howSimilarCache.end())
664    {
665        return d->howSimilarCache[r->id()];
666    }
667    // result values
668    const QString& rArtistname = r->track()->artistSortname();
669    const QString& rAlbumname  = r->track()->albumSortname();
670    const QString& rTrackname  = r->track()->trackSortname();
671    QString qArtistname;
672    QString qAlbumname;
673    QString qTrackname;
674
675    if ( isFullTextQuery() )
676    {
677        qArtistname = DatabaseImpl::sortname( d->fullTextQuery, true );
678        qAlbumname = DatabaseImpl::sortname( d->fullTextQuery );
679        qTrackname = qAlbumname;
680    }
681    else
682    {
683        qArtistname = queryTrack()->artistSortname();
684        qAlbumname  = queryTrack()->albumSortname();
685        qTrackname  = queryTrack()->trackSortname();
686    }
687
688    static const QRegExp filterOutChars = QRegExp(QString::fromUtf8("[-`´~!@#$%^&*()_—+=|:;<>«»,.?/{}\'\"\\[\\]\\\\]"));
689
690    //Cleanup symbols for minor naming differences
691    qArtistname.remove(filterOutChars);
692    qTrackname.remove(filterOutChars);
693    qAlbumname.remove(filterOutChars);
694
695    // normal edit distance
696    const int artdist = TomahawkUtils::levenshtein( qArtistname, rArtistname );
697    const int trkdist = TomahawkUtils::levenshtein( qTrackname, rTrackname );
698
699    // max length of name
700    const int mlart = qMax( qArtistname.length(), rArtistname.length() );
701    const int mltrk = qMax( qTrackname.length(), rTrackname.length() );
702
703    // distance scores
704    const float dcart = (float)( mlart - artdist ) / mlart;
705    const float dctrk = (float)( mltrk - trkdist ) / mltrk;
706
707    // don't penalize for missing album name
708    float dcalb = 1.0;
709    if ( !qAlbumname.isEmpty() )
710    {
711        const int albdist = TomahawkUtils::levenshtein( qAlbumname, rAlbumname );
712        const int mlalb = qMax( qAlbumname.length(), rAlbumname.length() );
713        dcalb = (float)( mlalb - albdist ) / mlalb;
714    }
715
716    if ( isFullTextQuery() )
717    {
718        const QString artistTrackname = DatabaseImpl::sortname( fullTextQuery() );
719        const QString rArtistTrackname = DatabaseImpl::sortname( r->track()->artist() + " " + r->track()->track() );
720
721        const int atrdist = TomahawkUtils::levenshtein( artistTrackname, rArtistTrackname );
722        const int mlatr = qMax( artistTrackname.length(), rArtistTrackname.length() );
723        const float dcatr = (float)( mlatr - atrdist ) / mlatr;
724
725        float resultScore = qMax( dctrk, qMax( dcatr, qMax( dcart, dcalb ) ) );
726        d->howSimilarCache[r->id()] =  resultScore;
727        return resultScore;
728    }
729    else
730    {
731        // weighted, so album match is worth less than track title
732        float resultScore = ( dcart * 4 + dcalb + dctrk * 5 ) / 10;
733        d->howSimilarCache[r->id()] =  resultScore;
734        return resultScore;
735    }
736}
737
738
739void
740Query::setSaveHTTPResultHint( bool saveResultHint )
741{
742    Q_D( Query );
743    d->saveResultHint = saveResultHint;
744}
745
746
747bool
748Query::saveHTTPResultHint() const
749{
750    Q_D( const Query );
751    return d->saveResultHint;
752}
753
754
755QString
756Query::resultHint() const
757{
758    Q_D( const Query );
759    return d->resultHint;
760}
761
762
763void
764Query::setResultHint( const QString& resultHint )
765{
766    Q_D( Query );
767    d->resultHint = resultHint;
768}
769
770
771QWeakPointer<Query>
772Query::weakRef()
773{
774    Q_D( Query );
775    return d->ownRef;
776}
777
778
779void
780Query::setWeakRef( QWeakPointer<Query> weakRef )
781{
782    Q_D( Query );
783    d->ownRef = weakRef;
784}
785
786
787void
788Query::sortResults()
789{
790    Q_D( Query );
791    qStableSort( d->results.begin(), d->results.end(), std::bind( &Query::resultSorter, this, std::placeholders::_1, std::placeholders::_2 ) );
792}