Esempio n. 1
0
void
DynamicModel::newTrackGenerated( const Tomahawk::query_ptr& query )
{
    if ( m_onDemandRunning )
    {
        bool isDuplicate = false;
        for ( int i = 0; i < m_deduper.size(); i++ )
        {
            if ( m_deduper[ i ].first == query->track()->track() && m_deduper[ i ].second == query->track()->artist() )
                isDuplicate = true;
        }
        if ( isDuplicate )
        {
            m_playlist->generator()->fetchNext();

            return;
        }
        else
        {
            m_deduper.append( QPair< QString, QString >( query->track()->track(), query->track()->artist() ) );
        }

        connect( query.data(), SIGNAL( resolvingFinished( bool ) ), this, SLOT( trackResolveFinished( bool ) ) );

        m_waitingFor << query.data();
        appendQuery( query );
    }
}
void
DatabaseCommand_LogPlayback::postCommitHook()
{
    connect( this, SIGNAL( trackPlaying( Tomahawk::query_ptr ) ),
             source().data(), SLOT( onPlaybackStarted( Tomahawk::query_ptr ) ), Qt::QueuedConnection );
    connect( this, SIGNAL( trackPlayed( Tomahawk::query_ptr ) ),
             source().data(), SLOT( onPlaybackFinished( Tomahawk::query_ptr ) ), Qt::QueuedConnection );

    Tomahawk::query_ptr q;
    if ( !m_result.isNull() )
    {
        q = m_result->toQuery();
    }
    else
    {
        // do not auto resolve this track
        q = Tomahawk::Query::get( m_artist, m_track, QString() );
    }
    q->setPlayedBy( source(), m_playtime );

    if ( m_action == Finished )
    {
        emit trackPlayed( q );
    }
    // if the play time is more than 10 minutes in the past, ignore
    else if ( m_action == Started && QDateTime::fromTime_t( playtime() ).secsTo( QDateTime::currentDateTime() ) < STARTED_THRESHOLD )
    {
        emit trackPlaying( q );
    }

    if ( source()->isLocal() )
    {
        Servent::instance()->triggerDBSync();
    }
}
Esempio n. 3
0
void
M3uLoader::getTags( const QFileInfo& info )
{
    QByteArray fileName = QFile::encodeName( info.canonicalFilePath() );
    const char *encodedName = fileName.constData();

    TagLib::FileRef f( encodedName );
    TagLib::Tag *tag = f.tag();

    QString artist = TStringToQString( tag->artist() ).trimmed();
    QString album  = TStringToQString( tag->album() ).trimmed();
    QString track  = TStringToQString( tag->title() ).trimmed();

    if ( artist.isEmpty() || track.isEmpty() )
    {
        qDebug() << "Error parsing" << info.fileName();
        return;
    }
    else
    {
        qDebug() << Q_FUNC_INFO << artist << track << album;
        Tomahawk::query_ptr q = Tomahawk::Query::get( artist, track, album, uuid(), !m_createNewPlaylist );
        if ( !q.isNull() )
            m_tracks << q;
    }
}
Esempio n. 4
0
void
AudioEngine::playItem( Tomahawk::playlistinterface_ptr playlist, const Tomahawk::query_ptr& query )
{
    if ( query->resolvingFinished() )
    {
        if ( query->numResults() && query->results().first()->isOnline() )
        {
            playItem( playlist, query->results().first() );
            return;
        }

        JobStatusView::instance()->model()->addJob(
            new ErrorStatusMessage( tr( "Sorry, Tomahawk couldn't find the track '%1' by %2" ).arg( query->track() ).arg( query->artist() ), 15 ) );

        if ( isStopped() )
            emit stopped(); // we do this so the original caller knows we couldn't find this track
    }
    else
    {
        Pipeline::instance()->resolve( query );

        NewClosure( query.data(), SIGNAL( resolvingFinished( bool ) ),
                    const_cast<AudioEngine*>(this), SLOT( playItem( Tomahawk::playlistinterface_ptr, Tomahawk::query_ptr ) ), playlist, query );
    }
}
Esempio n. 5
0
void
TrackModelItem::setupItem( const Tomahawk::query_ptr& query, TrackModelItem* parent, int row )
{
    this->parent = parent;
    if ( parent )
    {
        if ( row < 0 )
        {
            parent->children.append( this );
            row = parent->children.count() - 1;
        }
        else
        {
            parent->children.insert( row, this );
        }

        this->model = parent->model;
    }

    m_isPlaying = false;
    toberemoved = false;
    m_query = query;
    if ( !query->numResults() )
    {
        connect( query.data(), SIGNAL( resultsAdded( QList<Tomahawk::result_ptr> ) ),
                               SIGNAL( dataChanged() ) );

        connect( query.data(), SIGNAL( resultsRemoved( Tomahawk::result_ptr ) ),
                               SIGNAL( dataChanged() ) );

        connect( query.data(), SIGNAL( resultsChanged() ),
                               SIGNAL( dataChanged() ) );
    }
}
Esempio n. 6
0
TreeModelItem::TreeModelItem( const Tomahawk::query_ptr& query, TreeModelItem* parent, int row )
    : QObject( parent )
    , m_query( query )
{
    this->parent = parent;
    fetchingMore = false;
    m_isPlaying = false;

    if ( parent )
    {
        if ( row < 0 )
        {
            parent->children.append( this );
            row = parent->children.count() - 1;
        }
        else
        {
            parent->children.insert( row, this );
        }

        this->model = parent->model;
    }

    toberemoved = false;
    onResultsChanged();

    connect( query.data(), SIGNAL( resultsAdded( QList<Tomahawk::result_ptr> ) ),
                             SLOT( onResultsChanged() ) );

    connect( query.data(), SIGNAL( resultsRemoved( Tomahawk::result_ptr ) ),
                             SLOT( onResultsChanged() ) );

    connect( query.data(), SIGNAL( resultsChanged() ),
                             SLOT( onResultsChanged() ) );
}
void
DatabaseCommand_PlaybackHistory::exec( DatabaseImpl* dbi )
{
    TomahawkSqlQuery query = dbi->newquery();
    QList<Tomahawk::query_ptr> ql;

    QString whereToken;
    if ( !source().isNull() )
    {
        whereToken = QString( "WHERE source %1" ).arg( source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( source()->id() ) );
    }

    QString sql = QString(
            "SELECT track, playtime, secs_played, source "
            "FROM playback_log "
            "%1 "
            "ORDER BY playtime DESC "
            "%2" ).arg( whereToken )
                  .arg( m_amount > 0 ? QString( "LIMIT 0, %1" ).arg( m_amount ) : QString() );

    query.prepare( sql );
    query.exec();

    while( query.next() )
    {
        TomahawkSqlQuery query_track = dbi->newquery();

        QString sql = QString(
                "SELECT track.name, artist.name "
                "FROM track, artist "
                "WHERE artist.id = track.artist "
                "AND track.id = %1"
                ).arg( query.value( 0 ).toUInt() );

        query_track.prepare( sql );
        query_track.exec();

        if ( query_track.next() )
        {
            Tomahawk::query_ptr q = Tomahawk::Query::get( query_track.value( 1 ).toString(), query_track.value( 0 ).toString(), QString() );

            if ( query.value( 3 ).toUInt() == 0 )
            {
                q->setPlayedBy( SourceList::instance()->getLocal(), query.value( 1 ).toUInt() );
            }
            else
            {
                q->setPlayedBy( SourceList::instance()->get( query.value( 3 ).toUInt() ), query.value( 1 ).toUInt() );
            }

            ql << q;
        }
    }

    if ( ql.count() )
        emit tracks( ql );
}
Esempio n. 8
0
void
DynamicModel::newTrackGenerated( const Tomahawk::query_ptr& query )
{
    if( m_onDemandRunning ) {
        connect( query.data(), SIGNAL( resolvingFinished( bool ) ), this, SLOT( trackResolveFinished( bool ) ) );

        m_waitingFor << query.data();
        append( query );
    }
}
Esempio n. 9
0
void
PlaylistModel::append( const Tomahawk::query_ptr& query )
{
    if ( query.isNull() )
        return;

    if ( !query->resolvingFinished() )
        Pipeline::instance()->resolve( query );

    TrackModel::append( query );
}
Esempio n. 10
0
void
PipelineStatusItem::resolving( const Tomahawk::query_ptr& query )
{
    if ( query->isFullTextQuery() )
        m_latestQuery = query->fullTextQuery();
    else
        m_latestQuery = QString( "%1 - %2" ).arg( query->queryTrack()->artist() ).arg( query->queryTrack()->track() );

    Q_ASSERT( !m_latestQuery.isEmpty() );

    emit statusChanged();
}
Esempio n. 11
0
QMap< int, float >
FuzzyIndex::searchAlbum( const Tomahawk::query_ptr& query )
{
    Q_ASSERT( query->isFullTextQuery() );

    QMutexLocker lock( &m_mutex );

    QMap< int, float > resultsmap;
    try
    {
        if ( !m_luceneReader )
        {
            if ( !IndexReader::indexExists( m_luceneDir ) )
            {
                tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "index didn't exist.";
                return resultsmap;
            }

            m_luceneReader = IndexReader::open( m_luceneDir );
            m_luceneSearcher = newLucene<IndexSearcher>( m_luceneReader );
        }

        QueryParserPtr parser = newLucene<QueryParser>( LuceneVersion::LUCENE_CURRENT, L"album", m_analyzer );
        QString q = Tomahawk::DatabaseImpl::sortname( query->fullTextQuery() );

        FuzzyQueryPtr qry = newLucene<FuzzyQuery>( newLucene<Term>( L"album", q.toStdWString() ) );
        TopScoreDocCollectorPtr collector = TopScoreDocCollector::create( 99999, false );
        m_luceneSearcher->search( boost::dynamic_pointer_cast<Query>( qry ), collector );
        Collection<ScoreDocPtr> hits = collector->topDocs()->scoreDocs;

        for ( int i = 0; i < collector->getTotalHits(); i++ )
        {
            DocumentPtr d = m_luceneSearcher->doc( hits[i]->doc );
            float score = hits[i]->score;
            int id = QString::fromStdWString( d->get( L"albumid" ) ).toInt();

            if ( score > 0.30 )
            {
                resultsmap.insert( id, score );
//                tDebug() << "Index hit:" << id << score;
            }
        }
    }
    catch( LuceneException& error )
    {
        tDebug() << "Caught Lucene error:" << error.what();

        QTimer::singleShot( 0, this, SLOT( wipeIndex() ) );
    }

    return resultsmap;
}
Esempio n. 12
0
PlayableItem::PlayableItem( const Tomahawk::query_ptr& query, PlayableItem* parent, int row )
    : QObject( parent )
    , m_query( query )
{
    init( parent, row );

    connect( query->track().data(), SIGNAL( socialActionsLoaded() ),
                                    SIGNAL( dataChanged() ) );

    connect( query->track().data(), SIGNAL( updated() ),
                                    SIGNAL( dataChanged() ) );

    connect( query.data(), SIGNAL( resultsChanged() ),
                             SLOT( onResultsChanged() ) );
}
Esempio n. 13
0
void
SourceTreeView::dropEvent( QDropEvent* event )
{
    bool accept = false;
    const QPoint pos = event->pos();
    const QModelIndex index = indexAt( pos );

    if ( event->mimeData()->hasFormat( "application/tomahawk.query.list" ) )
    {
        const QPoint pos = event->pos();
        const QModelIndex index = indexAt( pos );

        if ( index.isValid() )
        {
            if ( SourcesModel::indexType( index ) == SourcesModel::PlaylistSource )
            {
                playlist_ptr playlist = SourcesModel::indexToPlaylist( index );
                if ( !playlist.isNull() && playlist->author()->isLocal() )
                {
                    accept = true;
                    QByteArray itemData = event->mimeData()->data( "application/tomahawk.query.list" );
                    QDataStream stream( &itemData, QIODevice::ReadOnly );
                    QList<Tomahawk::query_ptr> queries;

                    while ( !stream.atEnd() )
                    {
                        qlonglong qptr;
                        stream >> qptr;

                        Tomahawk::query_ptr* query = reinterpret_cast<Tomahawk::query_ptr*>(qptr);
                        if ( query && !query->isNull() )
                        {
                            qDebug() << "Dropped query item:" << query->data()->artist() << "-" << query->data()->track();
                            queries << *query;
                        }
                    }

                    qDebug() << "on playlist:" << playlist->title() << playlist->guid();

                    SourceTreeItem* treeItem = SourcesModel::indexToTreeItem( index );
                    if ( treeItem )
                    {
                        QString rev = treeItem->currentlyLoadedPlaylistRevision( playlist->guid() );
                        playlist->addEntries( queries, rev );
                    }
                }
            }
        }
Esempio n. 14
0
void
AudioEngine::playItem( Tomahawk::playlistinterface_ptr playlist, const Tomahawk::result_ptr& result, const Tomahawk::query_ptr& fromQuery )
{
    tDebug( LOGEXTRA ) << Q_FUNC_INFO << ( result.isNull() ? QString() : result->url() );

    if ( !m_playlist.isNull() )
        m_playlist.data()->reset();

    setPlaylist( playlist );

    if ( playlist.isNull() && !fromQuery.isNull() )
        m_currentTrackPlaylist = playlistinterface_ptr( new SingleTrackPlaylistInterface( fromQuery ) );
    else
        m_currentTrackPlaylist = playlist;

    if ( !result.isNull() )
    {
        loadTrack( result );
    }
    else if ( !m_playlist.isNull() && m_playlist.data()->retryMode() == PlaylistModes::Retry )
    {
        m_waitingOnNewTrack = true;
        if ( isStopped() )
            emit sendWaitingNotification();
        else
            stop();
    }
}
Esempio n. 15
0
void
Source::onPlaybackStarted( const Tomahawk::query_ptr& query )
{
    qDebug() << Q_FUNC_INFO << query->toString();
    m_currentTrack = query;
    emit playbackStarted( query );
}
Esempio n. 16
0
void
Source::onPlaybackFinished( const Tomahawk::query_ptr& query )
{
    qDebug() << Q_FUNC_INFO << query->toString();
    emit playbackFinished( query );

    m_currentTrackTimer.start();
}
Esempio n. 17
0
void
PlaylistEntry::setQuery( const Tomahawk::query_ptr& q )
{
    Q_D( PlaylistEntry );
    d->query = q;

    connect( q.data(), SIGNAL( resolvingFinished( bool ) ), SLOT( onQueryResolved( bool ) ) );
}
Esempio n. 18
0
void
QueryLabel::setQuery( const Tomahawk::query_ptr& query )
{
    if ( query.isNull() )
        return;

    setContentsMargins( BOXMARGIN * 2, BOXMARGIN / 2, BOXMARGIN * 2, BOXMARGIN / 2 );

    if ( m_query.isNull() || m_query.data() != query.data() )
    {
        m_query = query;
        m_result.clear();
        updateLabel();

        emit textChanged( text() );
        emit queryChanged( m_query );
    }
}
Esempio n. 19
0
void
Source::onPlaybackFinished( const Tomahawk::query_ptr& query )
{
    tDebug() << Q_FUNC_INFO << query->toString();
    emit playbackFinished( query );

    m_currentTrack.clear();
    emit stateChanged();
}
Esempio n. 20
0
PipelineStatusItem::PipelineStatusItem( const Tomahawk::query_ptr& q )
    : JobStatusItem()
{
    connect( Tomahawk::Pipeline::instance(), SIGNAL( resolving( Tomahawk::query_ptr ) ), this, SLOT( resolving( Tomahawk::query_ptr ) ) );
    connect( Tomahawk::Pipeline::instance(), SIGNAL( idle() ), this, SLOT( idle() ) );

    if ( !q.isNull() )
        resolving( q );
}
Esempio n. 21
0
void
TopTracksContext::setQuery( const Tomahawk::query_ptr& query )
{
    if ( !m_query.isNull() && query->artist() == m_query->artist() )
        return;

    m_query = query;

    Tomahawk::InfoSystem::InfoStringHash artistInfo;
    artistInfo["artist"] = query->artist();

    Tomahawk::InfoSystem::InfoRequestData requestData;
    requestData.caller = m_infoId;
    requestData.customData = QVariantMap();
    requestData.input = QVariant::fromValue< Tomahawk::InfoSystem::InfoStringHash >( artistInfo );

    requestData.type = Tomahawk::InfoSystem::InfoArtistSongs;
    Tomahawk::InfoSystem::InfoSystem::instance()->getInfo( requestData );
}
Esempio n. 22
0
void
Source::onPlaybackStarted( const Tomahawk::query_ptr& query, unsigned int duration )
{
    qDebug() << Q_FUNC_INFO << query->toString();
    m_currentTrack = query;
    m_currentTrackTimer.start( duration * 1000 + 900000 ); // duration comes in seconds

    if ( m_playlistInterface.isNull() )
        getPlaylistInterface();
    emit playbackStarted( query );
}
Esempio n. 23
0
void
TrackModel::insert( const Tomahawk::query_ptr& query, int row )
{
    if ( query.isNull() )
        return;

    QList< Tomahawk::query_ptr > ql;
    ql << query;

    insert( ql, row );
}
Esempio n. 24
0
void
SocialWidget::setQuery( const Tomahawk::query_ptr& query )
{
    m_query = query;
    ui->coverImage->setPixmap( TomahawkUtils::addDropShadow(
                    query->cover( ui->coverImage->size() ), ui->coverImage->size() ) );
    onShortLinkReady( QString(), QString(), QVariant() );
    onChanged();

    QUrl longUrl = GlobalActionManager::instance()->openLinkFromQuery( query );
    GlobalActionManager::instance()->shortenLink( longUrl );
}
Esempio n. 25
0
QMap< int, float >
FuzzyIndex::searchAlbum( const Tomahawk::query_ptr& query )
{
    Q_ASSERT( query->isFullTextQuery() );

//    QMutexLocker lock( &m_mutex );
    QMap< int, float > resultsmap;
    if ( !m_luceneReader || !m_luceneSearcher )
        return resultsmap;

    try
    {
        QueryParserPtr parser = newLucene<QueryParser>( LuceneVersion::LUCENE_CURRENT, L"album", m_analyzer );
        const QString q = Tomahawk::DatabaseImpl::sortname( query->fullTextQuery() );

        FuzzyQueryPtr qry = newLucene<FuzzyQuery>( newLucene<Term>( L"album", q.toStdWString() ) );
        TopScoreDocCollectorPtr collector = TopScoreDocCollector::create( 99999, false );
        m_luceneSearcher->search( boost::dynamic_pointer_cast<Query>( qry ), collector );
        Collection<ScoreDocPtr> hits = collector->topDocs()->scoreDocs;

        for ( int i = 0; i < collector->getTotalHits(); i++ )
        {
            DocumentPtr d = m_luceneSearcher->doc( hits[i]->doc );
            float score = hits[i]->score;
            int id = QString::fromStdWString( d->get( L"albumid" ) ).toInt();

            if ( score > 0.30 )
            {
                resultsmap.insert( id, score );
//                tDebug() << "Index hit:" << id << score;
            }
        }
    }
    catch( LuceneException& error )
    {
        tDebug() << "Caught Lucene error:" << QString::fromWCharArray( error.getError().c_str() );
    }

    return resultsmap;
}
Esempio n. 26
0
QMap< int, float >
FuzzyIndex::searchAlbum( const Tomahawk::query_ptr& query )
{
    Q_ASSERT( query->isFullTextQuery() );

    QMutexLocker lock( &m_mutex );

    QMap< int, float > resultsmap;
    try
    {
        if ( !m_luceneReader )
        {
            if ( !IndexReader::indexExists( TomahawkUtils::appDataDir().absoluteFilePath( "tomahawk.lucene" ).toStdString().c_str() ) )
            {
                qDebug() << Q_FUNC_INFO << "index didn't exist.";
                return resultsmap;
            }

            m_luceneReader = IndexReader::open( m_luceneDir );
            m_luceneSearcher = _CLNEW IndexSearcher( m_luceneReader );
        }

        QueryParser parser( _T( "album" ), m_analyzer );
        QString escapedName = QString::fromWCharArray( parser.escape( DatabaseImpl::sortname( query->fullTextQuery() ).toStdWString().c_str() ) );

        Query* qry = _CLNEW FuzzyQuery( _CLNEW Term( _T( "album" ), escapedName.toStdWString().c_str() ) );
        Hits* hits = m_luceneSearcher->search( qry );
        for ( uint i = 0; i < hits->length(); i++ )
        {
            Document* d = &hits->doc( i );

            float score = hits->score( i );
            int id = QString::fromWCharArray( d->get( _T( "albumid" ) ) ).toInt();

            if ( score > 0.30 )
            {
                resultsmap.insert( id, score );
//                tDebug() << "Index hit:" << id << score;
            }
        }

        delete hits;
        delete qry;
    }
    catch( CLuceneError& error )
    {
        tDebug() << "Caught CLucene error:" << error.what();
        Q_ASSERT( false );
    }

    return resultsmap;
}
Esempio n. 27
0
void
ScriptEngine::resolve( const Tomahawk::query_ptr& query )
{
    qDebug() << Q_FUNC_INFO << query->toString();
    QString eval = QString( "resolve( '%1', '%2', '%3', '%4' );" )
                      .arg( query->id().replace( "'", "\\'" ) )
                      .arg( query->artist().replace( "'", "\\'" ) )
                      .arg( query->album().replace( "'", "\\'" ) )
                      .arg( query->track().replace( "'", "\\'" ) );

    QList< Tomahawk::result_ptr > results;

    QVariantMap m = mainFrame()->evaluateJavaScript( eval ).toMap();
    qDebug() << "JavaScript Result:" << m;

    const QString qid = query->id();
    const QVariantList reslist = m.value( "results" ).toList();

    foreach( const QVariant& rv, reslist )
    {
        QVariantMap m = rv.toMap();
        qDebug() << "RES" << m;

        Tomahawk::result_ptr rp( new Tomahawk::Result() );
        Tomahawk::artist_ptr ap = Tomahawk::Artist::get( 0, m.value( "artist" ).toString() );
        rp->setArtist( ap );
        rp->setAlbum( Tomahawk::Album::get( 0, m.value( "album" ).toString(), ap ) );
        rp->setTrack( m.value( "track" ).toString() );
        rp->setBitrate( m.value( "bitrate" ).toUInt() );
        rp->setUrl( m.value( "url" ).toString() );
        rp->setSize( m.value( "size" ).toUInt() );
        rp->setScore( m.value( "score" ).toFloat() * ( (float)m_parent->weight() / 100.0 ) );
        rp->setRID( uuid() );
        rp->setFriendlySource( m_parent->name() );

        if ( m.contains( "year" ) )
        {
            QVariantMap attr;
            attr[ "releaseyear" ] = m.value( "year" );
            rp->setAttributes( attr );
        }

        rp->setDuration( m.value( "duration", 0 ).toUInt() );
        if ( rp->duration() <= 0 && m.contains( "durationString" ) )
        {
            QTime time = QTime::fromString( m.value( "durationString" ).toString(), "hh:mm:ss" );
            rp->setDuration( time.secsTo( QTime( 0, 0 ) ) * -1 );
        }

        rp->setMimetype( m.value( "mimetype" ).toString() );
        if ( rp->mimetype().isEmpty() )
        {
            rp->setMimetype( TomahawkUtils::extensionToMimetype( m.value( "extension" ).toString() ) );
            Q_ASSERT( !rp->mimetype().isEmpty() );
        }

        results << rp;
    }
Esempio n. 28
0
Tomahawk::result_ptr
PlaylistInterface::siblingResult( int itemsAway, qint64 rootIndex ) const
{
    qint64 idx = siblingIndex( itemsAway, rootIndex );
    QList< qint64 > safetyCheck;

    // If safetyCheck contains idx, this means the interface keeps returning the same items and we won't discover anything new - abort
    // This can happen in repeat / random mode e.g.
    while ( idx >= 0 && !safetyCheck.contains( idx ) )
    {
        safetyCheck << idx;
        Tomahawk::query_ptr query = queryAt( idx );

        if ( query && query->playable() )
        {
            return query->results().first();
        }

        idx = siblingIndex( itemsAway < 0 ? -1 : 1, idx );
    }

    return Tomahawk::result_ptr();
}
Esempio n. 29
0
void
QtScriptResolver::resolve( const Tomahawk::query_ptr& query )
{
    if ( QThread::currentThread() != thread() )
    {
        QMetaObject::invokeMethod( this, "resolve", Qt::QueuedConnection, Q_ARG(Tomahawk::query_ptr, query) );
        return;
    }

    QString eval;
    if ( !query->isFullTextQuery() )
    {
        eval = QString( RESOLVER_LEGACY_CODE2 "resolver.resolve( '%1', '%2', '%3', '%4' );" )
               .arg( query->id().replace( "'", "\\'" ) )
               .arg( query->artist().replace( "'", "\\'" ) )
               .arg( query->album().replace( "'", "\\'" ) )
               .arg( query->track().replace( "'", "\\'" ) );
    }
    else
    {
        eval = QString( "if(Tomahawk.resolver.instance !== undefined) {"
                        "   resolver.search( '%1', '%2' );"
                        "} else {"
                        "   resolve( '%1', '', '', '%2' );"
                        "}"
                      )
               .arg( query->id().replace( "'", "\\'" ) )
               .arg( query->fullTextQuery().replace( "'", "\\'" ) );
    }

    QVariantMap m = m_engine->mainFrame()->evaluateJavaScript( eval ).toMap();

    if( m.isEmpty() )
    {
        // if the resolver doesn't return anything, async api is used
        return;
    }

    qDebug() << "JavaScript Result:" << m;

    const QString qid = query->id();
    const QVariantList reslist = m.value( "results" ).toList();

    QList< Tomahawk::result_ptr > results = parseResultVariantList( reslist );

    Tomahawk::Pipeline::instance()->reportResults( qid, results );
}
// TODO make clever (ft. featuring live (stuff) etc)
float
DatabaseCommand_Resolve::how_similar( const Tomahawk::query_ptr& q, const Tomahawk::result_ptr& r )
{
    // query values
    const QString qArtistname = DatabaseImpl::sortname( q->artist() );
    const QString qAlbumname  = DatabaseImpl::sortname( q->album() );
    const QString qTrackname  = DatabaseImpl::sortname( q->track() );

    // result values
    const QString rArtistname = DatabaseImpl::sortname( r->artist()->name() );
    const QString rAlbumname  = DatabaseImpl::sortname( r->album()->name() );
    const QString rTrackname  = DatabaseImpl::sortname( r->track() );

    // normal edit distance
    int artdist = levenshtein( qArtistname, rArtistname );
    int albdist = levenshtein( qAlbumname, rAlbumname );
    int trkdist = levenshtein( qTrackname, rTrackname );

    // max length of name
    int mlart = qMax( qArtistname.length(), rArtistname.length() );
    int mlalb = qMax( qAlbumname.length(), rAlbumname.length() );
    int mltrk = qMax( qTrackname.length(), rTrackname.length() );

    // distance scores
    float dcart = (float)( mlart - artdist ) / mlart;
    float dcalb = (float)( mlalb - albdist ) / mlalb;
    float dctrk = (float)( mltrk - trkdist ) / mltrk;

    // don't penalize for missing album name
    if( qAlbumname.length() == 0 )
        dcalb = 1.0;

    // weighted, so album match is worth less than track title
    float combined = ( dcart*4 + dcalb + dctrk*5 ) / 10;
    return combined;
}