void
DatabaseCommand_UpdateSearchIndex::exec( DatabaseImpl* db )
{
    db->m_fuzzyIndex->beginIndexing();

    QMap< unsigned int, QMap< QString, QString > > data;
    TomahawkSqlQuery q = db->newquery();

    q.exec( "SELECT track.id, track.name, artist.name, artist.id FROM track, artist WHERE artist.id = track.artist" );
    while ( q.next() )
    {
        QMap< QString, QString > track;
        track.insert( "track", q.value( 1 ).toString() );
        track.insert( "artist", q.value( 2 ).toString() );
        track.insert( "artistid", q.value( 3 ).toString() );

        data.insert( q.value( 0 ).toUInt(), track );
    }

    db->m_fuzzyIndex->appendFields( data );
    data.clear();

    q.exec( "SELECT album.id, album.name FROM album" );
    while ( q.next() )
    {
        QMap< QString, QString > album;
        album.insert( "album", q.value( 1 ).toString() );

        data.insert( q.value( 0 ).toUInt(), album );
    }

    db->m_fuzzyIndex->appendFields( data );

    qDebug() << "Building index finished.";

    db->m_fuzzyIndex->endIndexing();
}
void
DatabaseCommand_SetPlaylistRevision::exec( DatabaseImpl* lib )
{
    // get the current revision for this playlist
    // this also serves to check the playlist exists.
    TomahawkSqlQuery chkq = lib->newquery();
    chkq.prepare( "SELECT currentrevision FROM playlist WHERE guid = ?" );
    chkq.addBindValue( m_playlistguid );
    if( chkq.exec() && chkq.next() )
    {
        m_currentRevision = chkq.value( 0 ).toString();
        qDebug() << Q_FUNC_INFO << "pl guid" << m_playlistguid << "- curr rev" << m_currentRevision;
    }
    else
    {
        tDebug() << "Playlist:" << m_playlistguid << m_currentRevision << source()->friendlyName() << source()->id();
        Q_ASSERT_X( false, "DatabaseCommand_SetPlaylistRevision::exec", "No such playlist, WTF?" );
        return;
    }

    QVariantList vlist = m_orderedguids;
    QJson::Serializer ser;
    const QByteArray entries = ser.serialize( vlist );

    // add any new items:
    TomahawkSqlQuery adde = lib->newquery();
    if ( m_localOnly )
    {
        QString sql = "UPDATE playlist_item SET result_hint = ? WHERE guid = ?";
        adde.prepare( sql );

        foreach( const plentry_ptr& e, m_entries )
        {
            if ( e->query()->results().isEmpty() )
                continue;

            adde.bindValue( 0, e->query()->results().first()->url() );
            adde.bindValue( 1, e->guid() );
            adde.exec();
        }

        return;
    }
    else if ( m_metadataUpdate )
void
DatabaseCommand_DeleteFiles::exec( DatabaseImpl* dbi )
{
    qDebug() << Q_FUNC_INFO;
    Q_ASSERT( !source().isNull() );

    int deleted = 0;
    QVariant srcid = source()->isLocal() ? QVariant( QVariant::Int ) : source()->id();
    TomahawkSqlQuery delquery = dbi->newquery();
    QString lastPath;

    if ( !m_dir.path().isEmpty() && source()->isLocal() )
    {
        qDebug() << "Deleting" << m_dir.path() << "from db for localsource" << srcid;
        TomahawkSqlQuery dirquery = dbi->newquery();

        dirquery.prepare( QString( "SELECT id, url FROM file WHERE source %1 AND url LIKE ?" )
                             .arg( source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( source()->id() ) ) );
        delquery.prepare( QString( "DELETE FROM file WHERE source %1 AND id = ?" )
                             .arg( source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( source()->id() ) ) );

        dirquery.bindValue( 0, "file://" + m_dir.canonicalPath() + "/%" );
        dirquery.exec();

        while ( dirquery.next() )
        {
            QFileInfo fi( dirquery.value( 1 ).toString().mid( 7 ) ); // remove file://
            if ( fi.canonicalPath() != m_dir.canonicalPath() )
            {
                if ( lastPath != fi.canonicalPath() )
                    qDebug() << "Skipping subdir:" << fi.canonicalPath();

                lastPath = fi.canonicalPath();
                continue;
            }

            m_ids << dirquery.value( 0 ).toUInt();
            m_files << dirquery.value( 1 ).toString();
        }

        foreach ( const QVariant& id, m_ids )
        {
            delquery.bindValue( 0, id.toUInt() );
            if( !delquery.exec() )
            {
                qDebug() << "Failed to delete file:"
                    << delquery.lastError().databaseText()
                    << delquery.lastError().driverText()
                    << delquery.boundValues();
                continue;
            }

            deleted++;
        }
void
DatabaseCommand_UpdateSearchIndex::exec( DatabaseImpl* db )
{
    db->m_fuzzyIndex->beginIndexing();

    TomahawkSqlQuery q = db->newquery();
    q.exec( "SELECT track.id, track.name, artist.name, artist.id FROM track, artist WHERE artist.id = track.artist" );
    while ( q.next() )
    {
        IndexData ida;
        ida.id = q.value( 0 ).toUInt();
        ida.artistId = q.value( 3 ).toUInt();
        ida.track = q.value( 1 ).toString();
        ida.artist = q.value( 2 ).toString();

        db->m_fuzzyIndex->appendFields( ida );
    }

    q.exec( "SELECT album.id, album.name FROM album" );
    while ( q.next() )
    {
        IndexData ida;
        ida.id = q.value( 0 ).toUInt();
        ida.album = q.value( 1 ).toString();

        db->m_fuzzyIndex->appendFields( ida );
    }

    tDebug( LOGVERBOSE ) << "Building index finished.";

    db->m_fuzzyIndex->endIndexing();
}
void
DatabaseCommand_SocialAction::exec( DatabaseImpl* dbi )
{
    qDebug() << Q_FUNC_INFO;
    Q_ASSERT( !source().isNull() );

    TomahawkSqlQuery query = dbi->newquery();

    QVariant srcid = source()->isLocal() ? QVariant( QVariant::Int ) : source()->id();

    if ( m_artist.isNull() || m_title.isEmpty() || m_action.isEmpty() )
        return;

    int artid = dbi->artistId( m_artist, true );
    if ( artid < 1 )
        return;
    int trkid = dbi->trackId( artid, m_title, true );
    if ( trkid < 1 )
        return;

    // update if it already exists
    TomahawkSqlQuery find = dbi->newquery();
    find.prepare( QString( "SELECT id, k, v FROM social_attributes WHERE social_attributes.id = ? AND social_attributes.source %1 AND social_attributes.k = ?" ).arg( source()->isLocal() ? "IS NULL" : QString( "=%1" ).arg( source()->id() ) ) );
    find.addBindValue( trkid );
    find.addBindValue( m_action );
    if ( find.exec() && find.next() )
    {
        // update
        query.prepare( QString( "UPDATE social_attributes SET v = '%1', timestamp = %2 WHERE social_attributes.id = %3 AND social_attributes.source %4 AND social_attributes.k = '%5'" )
                               .arg( m_comment )
                               .arg( m_timestamp )
                               .arg( trkid )
                               .arg( source()->isLocal() ? "IS NULL" : QString( "=%1" ).arg( source()->id() ) )
                               .arg( m_action ) );
    }
    else
    {
        query.prepare( "INSERT INTO social_attributes(id, source, k, v, timestamp) "
                       "VALUES (?, ?, ?, ?, ?)" );

        query.bindValue( 0, trkid );
        query.bindValue( 1, srcid );
        query.bindValue( 2, m_action );
        query.bindValue( 3, m_comment );
        query.bindValue( 4, m_timestamp );
    }

    query.exec();
}
void
DatabaseCommand_LoadAllPlaylists::exec( DatabaseImpl* dbi )
{
    Q_D( DatabaseCommand_LoadAllPlaylists );

    TomahawkSqlQuery query = dbi->newquery();
    QString orderToken, sourceToken;

    switch ( d->sortOrder )
    {
    case 0:
        break;

    case ModificationTime:
            orderToken = "ORDER BY playlist.createdOn";
    }

    if ( !source().isNull() )
        sourceToken = QString( "AND source %1 " ).arg( source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( source()->id() ) );

    QString trackIdJoin;
    QString trackIdFields;
    if ( d->returnPlEntryIds )
    {
        trackIdFields = ", pr.entries";
        trackIdJoin = "JOIN playlist_revision pr ON pr.playlist = p.guid AND pr.guid = p.currentrevision";
    }

    query.exec( QString( " SELECT p.guid, p.title, p.info, p.creator, p.lastmodified, p.shared, p.currentrevision, p.createdOn %6 "
                         " FROM playlist p "
                         " %5 "
                         " WHERE ( ( dynplaylist = 'false' ) OR ( dynplaylist = 0 ) ) "
                         " %1 "
                         " %2 %3 %4 "
                         )
                .arg( sourceToken )
                .arg( orderToken )
                .arg( d->sortDescending ? "DESC" : QString() )
                .arg( d->limitAmount > 0 ? QString( "LIMIT 0, %1" ).arg( d->limitAmount ) : QString() )
                .arg( trackIdJoin )
                .arg( trackIdFields )
                );

    QList<playlist_ptr> plists;
    QHash<playlist_ptr, QStringList> phash;
    while ( query.next() )
    {
        playlist_ptr p( new Playlist( source(),                  //src
                                      query.value(6).toString(), //current rev
                                      query.value(1).toString(), //title
                                      query.value(2).toString(), //info
                                      query.value(3).toString(), //creator
                                      query.value(7).toInt(),    //lastmod / createdOn
                                      query.value(5).toBool(),   //shared
                                      query.value(4).toInt(),    //lastmod
                                      query.value(0).toString()  //GUID
                                    ), &QObject::deleteLater );
        p->setWeakSelf( p.toWeakRef() );
        plists.append( p );

        if ( d->returnPlEntryIds )
        {
            QStringList trackIds = TomahawkUtils::parseJson( query.value( 8 ).toByteArray() ).toStringList();
            phash.insert( p, trackIds );
        }
    }

    emit done( plists );

    if ( d->returnPlEntryIds )
    {
        emit done( phash );
    }
}
void
Tomahawk::DatabaseCommand_LoadDynamicPlaylist::exec( DatabaseImpl* dbi )
{
    TomahawkSqlQuery query = dbi->newquery();

    query.exec( QString( "SELECT playlist.guid as guid, title, info, creator, createdOn, lastmodified, shared, currentrevision, dynamic_playlist.pltype, dynamic_playlist.plmode "
                         "FROM playlist, dynamic_playlist WHERE source %1 AND dynplaylist = 'true' AND playlist.guid = dynamic_playlist.guid AND playlist.guid = '%2'" )
    .arg( source()->isLocal() ? "IS NULL" : QString( "=%1" ).arg( source()->id() ) ).arg( m_plid ) );

    QList<dynplaylist_ptr> plists;
    if( query.next() )
    {
        dynplaylist_ptr p( new DynamicPlaylist( source(),
                                                query.value(7).toString(),  //current rev
                                                query.value(1).toString(),  //title
                                                query.value(2).toString(),  //info
                                                query.value(3).toString(),  //creator
                                                query.value(4).toUInt(),  //createdOn
                                                query.value(8).toString(),  // dynamic type
                                                static_cast<GeneratorMode>(query.value(9).toInt()),  // dynamic mode
                                                query.value(6).toBool(),    //shared
                                                query.value(5).toInt(),     //lastmod
                                                query.value(0).toString() ) );  //GUID

        tLog() << "Loaded individual dynamic playlist:" <<      query.value(7).toString()  //current rev
        <<      query.value(1).toString()  //title
        <<      query.value(2).toString()  //info
        <<      query.value(3).toString()  //creator
        <<      query.value(4).toString()  //createdOn
        <<      query.value(8).toString()  // dynamic type
        <<      static_cast<GeneratorMode>(query.value(9).toInt())  // dynamic mode
        <<      query.value(6).toBool()    //shared
        <<      query.value(5).toInt()     //lastmod
        <<      query.value(0).toString();  //GUID

        emit dynamicPlaylistLoaded( p );
    }

    emit done();
}
void
DatabaseCommand_AllTracks::exec( DatabaseImpl* dbi )
{
    TomahawkSqlQuery query = dbi->newquery();
    QList<Tomahawk::query_ptr> ql;

    QString m_orderToken, sourceToken;
    switch ( m_sortOrder )
    {
        case 0:
            break;

        case Album:
            m_orderToken = "album.name, file_join.discnumber, file_join.albumpos";
            break;

        case ModificationTime:
            m_orderToken = "file.mtime";
            break;

        case AlbumPosition:
            m_orderToken = "file_join.discnumber, file_join.albumpos";
            break;
    }

    if ( !m_collection.isNull() )
        sourceToken = QString( "AND file.source %1" ).arg( m_collection->source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( m_collection->source()->id() ) );

    QString albumToken;
    if ( m_album )
    {
        if ( m_album->id() == 0 )
        {
            m_artist = m_album->artist();
            albumToken = QString( "AND album.id IS NULL" );
        }
        else
            albumToken = QString( "AND album.id = %1" ).arg( m_album->id() );
    }

    QString sql = QString(
            "SELECT file.id, artist.name, album.name, track.name, composer.name, file.size, "   //0
                   "file.duration, file.bitrate, file.url, file.source, file.mtime, "           //6
                   "file.mimetype, file_join.discnumber, file_join.albumpos, artist.id, "       //11
                   "album.id, track.id, composer.id "                                           //15
            "FROM file, artist, track, file_join "
            "LEFT OUTER JOIN album "
            "ON file_join.album = album.id "
            "LEFT OUTER JOIN artist AS composer "
            "ON file_join.composer = composer.id "
            "WHERE file.id = file_join.file "
            "AND file_join.artist = artist.id "
            "AND file_join.track = track.id "
            "%1 "
            "%2 %3 "
            "%4 %5 %6"
            ).arg( sourceToken )
             .arg( !m_artist ? QString() : QString( "AND artist.id = %1" ).arg( m_artist->id() ) )
             .arg( !m_album ? QString() : albumToken )
             .arg( m_sortOrder > 0 ? QString( "ORDER BY %1" ).arg( m_orderToken ) : QString() )
             .arg( m_sortDescending ? "DESC" : QString() )
             .arg( m_amount > 0 ? QString( "LIMIT 0, %1" ).arg( m_amount ) : QString() );

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

    while( query.next() )
    {
        QString url = query.value( 8 ).toString();
        Tomahawk::source_ptr s = SourceList::instance()->get( query.value( 9 ).toUInt() );
        if ( !s )
        {
            Q_ASSERT( false );
            continue;
        }
        if ( !s->isLocal() )
            url = QString( "servent://%1\t%2" ).arg( s->nodeId() ).arg( url );

        QString artist, track, album, composer;
        artist = query.value( 1 ).toString();
        album = query.value( 2 ).toString();
        track = query.value( 3 ).toString();
        composer = query.value( 4 ).toString();

        Tomahawk::result_ptr result = Tomahawk::Result::get( url );
        Tomahawk::query_ptr qry = Tomahawk::Query::get( artist, track, album );

        Tomahawk::track_ptr t = Tomahawk::Track::get( query.value( 16 ).toUInt(), artist, track, album, query.value( 6 ).toUInt(), composer, query.value( 13 ).toUInt(), query.value( 12 ).toUInt() );
        t->loadAttributes();
        result->setTrack( t );

        result->setSize( query.value( 5 ).toUInt() );
        result->setBitrate( query.value( 7 ).toUInt() );
        result->setModificationTime( query.value( 10 ).toUInt() );
        result->setMimetype( query.value( 11 ).toString() );
        result->setScore( 1.0 );
        result->setCollection( s->dbCollection() );

        QList<Tomahawk::result_ptr> results;
        results << result;
        qry->addResults( results );
        qry->setResolveFinished( true );

        ql << qry;
    }

    emit tracks( ql, data() );
    emit tracks( ql );
    emit done( m_collection );
}
void
DatabaseCommand_PlaybackHistory::exec( DatabaseImpl* dbi )
{
    TomahawkSqlQuery query = dbi->newquery();
    QString whereToken( "WHERE 1" );

    if ( !source().isNull() )
    {
        whereToken += QString( " AND source %1" ).arg( source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( source()->id() ) );
    }
    if ( m_dateFrom.year() > 1900 && m_dateTo.year() > 1900 )
    {
        whereToken += QString( " AND playtime >= %1 AND playtime <= %2" )
                         .arg( QDateTime( m_dateFrom ).toUTC().toTime_t() )
                         .arg( QDateTime( m_dateTo.addDays( 1 ) ).toUTC().toTime_t() );
    }

    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();

    QList<Tomahawk::track_ptr> tl;
    QList<Tomahawk::PlaybackLog> logs;
    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::track_ptr track = Tomahawk::Track::get( query_track.value( 1 ).toString(), query_track.value( 0 ).toString(), QString() );
            if ( !track )
                continue;

            Tomahawk::PlaybackLog log;
            log.timestamp = query.value( 1 ).toUInt();
            log.secsPlayed = query.value( 2 ).toUInt();
            log.source = SourceList::instance()->get( query.value( 3 ).toUInt() );

            logs << log;
            tl << track;
        }
    }

    emit tracks( tl, logs );
}
void
DatabaseCommand_loadOps::exec( DatabaseImpl* dbi )
{
    QList< dbop_ptr > ops;

    if ( !m_since.isEmpty() )
    {
        TomahawkSqlQuery query = dbi->newquery();
        query.prepare( QString( "SELECT id FROM oplog WHERE guid = ?" ) );
        query.addBindValue( m_since );
        query.exec();

        if ( !query.next() )
        {
            tLog() << "Unknown oplog guid, requested, not replying:" << m_since;
            Q_ASSERT( false );
            emit done( m_since, m_since, ops );
            return;
        }
    }

    TomahawkSqlQuery query = dbi->newquery();
    query.prepare( QString(
                   "SELECT guid, command, json, compressed, singleton "
                   "FROM oplog "
                   "WHERE source %1 "
                   "AND id > coalesce((SELECT id FROM oplog WHERE guid = ?),0) "
                   "ORDER BY id ASC"
                   ).arg( source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( source()->id() ) )
                  );
    query.addBindValue( m_since );
    query.exec();

    QString lastguid = m_since;
    while( query.next() )
    {
        dbop_ptr op( new DBOp );
        op->guid = query.value( 0 ).toString();
        op->command = query.value( 1 ).toString();
        op->payload = query.value( 2 ).toByteArray();
        op->compressed = query.value( 3 ).toBool();
        op->singleton = query.value( 4 ).toBool();

        lastguid = op->guid;
        ops << op;
    }

//    qDebug() << "Loaded" << ops.length() << "ops from db";
    emit done( m_since, lastguid, ops );
}