From 5f9c21120d6b01860fdd32aacc78c44e5f475e16 Mon Sep 17 00:00:00 2001
From: Leo Franchi <lfranchi@kde.org>
Date: Fri, 30 Sep 2011 14:45:55 -0400
Subject: [PATCH] Add keep-in-synch support to EchonestCatalogSynchronizer

be extra safe
---
 src/libtomahawk/CMakeLists.txt                |   4 +
 .../EchonestCatalogSynchronizer.cpp           | 175 ++++++++++++++++--
 src/libtomahawk/EchonestCatalogSynchronizer.h |  13 +-
 src/libtomahawk/collection.cpp                |   1 +
 src/libtomahawk/database/databasecommand.cpp  |   8 +
 .../databasecommand_collectionattributes.cpp  |   1 -
 .../databasecommand_collectionattributes.h    |   9 +-
 .../database/databasecommand_deletefiles.cpp  |   1 +
 ...atabasecommand_setcollectionattributes.cpp |  20 +-
 .../databasecommand_setcollectionattributes.h |  12 +-
 .../databasecommand_settrackattributes.cpp    |  97 ++++++++++
 .../databasecommand_settrackattributes.h      |  57 ++++++
 .../databasecommand_trackattributes.cpp       |  71 +++++++
 .../databasecommand_trackattributes.h         |  51 +++++
 src/libtomahawk/typedefs.h                    |   2 +
 src/tomahawkapp.cpp                           |   1 +
 src/tomahawkapp.h                             |   3 +-
 17 files changed, 490 insertions(+), 36 deletions(-)
 create mode 100644 src/libtomahawk/database/databasecommand_settrackattributes.cpp
 create mode 100644 src/libtomahawk/database/databasecommand_settrackattributes.h
 create mode 100644 src/libtomahawk/database/databasecommand_trackattributes.cpp
 create mode 100644 src/libtomahawk/database/databasecommand_trackattributes.h

diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt
index ef331994b..bb21cf564 100644
--- a/src/libtomahawk/CMakeLists.txt
+++ b/src/libtomahawk/CMakeLists.txt
@@ -99,6 +99,8 @@ set( libSources
     database/databasecommand_genericselect.cpp
     database/databasecommand_setcollectionattributes.cpp
     database/databasecommand_collectionattributes.cpp
+    database/databasecommand_trackattributes.cpp
+    database/databasecommand_settrackattributes.cpp
     database/database.cpp
 
     infobar/infobar.cpp
@@ -322,6 +324,8 @@ set( libHeaders
     database/databasecommand_genericselect.h
     database/databasecommand_setcollectionattributes.h
     database/databasecommand_collectionattributes.h
+    database/databasecommand_trackattributes.h
+    database/databasecommand_settrackattributes.h
 
     infobar/infobar.h
 
diff --git a/src/libtomahawk/EchonestCatalogSynchronizer.cpp b/src/libtomahawk/EchonestCatalogSynchronizer.cpp
index e8d001d3f..d21fa915b 100644
--- a/src/libtomahawk/EchonestCatalogSynchronizer.cpp
+++ b/src/libtomahawk/EchonestCatalogSynchronizer.cpp
@@ -18,14 +18,18 @@
 
 #include "EchonestCatalogSynchronizer.h"
 
+#include "collection.h"
 #include "database/database.h"
 #include "database/databasecommand_genericselect.h"
 #include "database/databasecommand_setcollectionattributes.h"
 #include "tomahawksettings.h"
 #include "sourcelist.h"
+#include "query.h"
 
 #include <echonest/CatalogUpdateEntry.h>
 #include <echonest/Config.h>
+#include "database/databasecommand_settrackattributes.h"
+#include "database/databasecommand_trackattributes.h"
 
 using namespace Tomahawk;
 
@@ -39,6 +43,8 @@ EchonestCatalogSynchronizer::EchonestCatalogSynchronizer( QObject *parent )
     qRegisterMetaType<QList<QStringList> >("QList<QStringList>");
 
     connect( TomahawkSettings::instance(), SIGNAL( changed() ), this, SLOT( checkSettingsChanged() ) );
+    connect( SourceList::instance()->getLocal()->collection().data(), SIGNAL( tracksAdded( QList<Tomahawk::query_ptr> ) ), this, SLOT( tracksAdded( QList<Tomahawk::query_ptr> ) ), Qt::QueuedConnection );
+    connect( SourceList::instance()->getLocal()->collection().data(), SIGNAL( tracksRemoved( QList<Tomahawk::query_ptr> ) ), this, SLOT( tracksRemoved( QList<Tomahawk::query_ptr> ) ), Qt::QueuedConnection );
 
     const QByteArray artist = TomahawkSettings::instance()->value( "collection/artistCatalog" ).toByteArray();
     const QByteArray song = TomahawkSettings::instance()->value( "collection/songCatalog" ).toByteArray();
@@ -46,6 +52,21 @@ EchonestCatalogSynchronizer::EchonestCatalogSynchronizer( QObject *parent )
         m_artistCatalog.setId( artist );
     if ( !song.isEmpty() )
         m_songCatalog.setId( song );
+
+    // Sanity check
+    if ( !m_songCatalog.id().isEmpty() && !m_syncing )
+    {
+        // Not syncing but have a catalog id... lets fix this
+        QNetworkReply* r = m_songCatalog.deleteCatalog();
+        connect( r, SIGNAL( finished() ), this, SLOT( catalogDeleted() ) );
+        r->setProperty( "type", "song" );
+    }
+    if ( !m_artistCatalog.id().isEmpty() && !m_syncing )
+    {
+        QNetworkReply* r = m_artistCatalog.deleteCatalog();
+        connect( r, SIGNAL( finished() ), this, SLOT( catalogDeleted() ) );
+        r->setProperty( "type", "artist" );
+    }
 }
 
 void
@@ -60,12 +81,52 @@ EchonestCatalogSynchronizer::checkSettingsChanged()
         uploadDb();
     } else if ( !TomahawkSettings::instance()->enableEchonestCatalogs() && m_syncing )
     {
-        m_songCatalog.deleteCatalog();
-        m_artistCatalog.deleteCatalog();
+
+        // delete all track nums and catalog ids from our peers
+        {
+            DatabaseCommand_SetTrackAttributes* cmd = new DatabaseCommand_SetTrackAttributes( DatabaseCommand_SetTrackAttributes::EchonestCatalogId );
+            Database::instance()->enqueue( QSharedPointer< DatabaseCommand >( cmd ) );
+        }
+        {
+            DatabaseCommand_SetCollectionAttributes* cmd = new DatabaseCommand_SetCollectionAttributes( DatabaseCommand_SetCollectionAttributes::EchonestSongCatalog, true );
+            Database::instance()->enqueue( QSharedPointer< DatabaseCommand >( cmd ) );
+        }
+
+        if ( !m_songCatalog.id().isEmpty() )
+        {
+            QNetworkReply* r = m_songCatalog.deleteCatalog();
+            connect( r, SIGNAL( finished() ), this, SLOT( catalogDeleted() ) );
+            r->setProperty( "type", "song" );
+        }
+        if ( !m_artistCatalog.id().isEmpty() )
+        {
+            QNetworkReply* r = m_artistCatalog.deleteCatalog();
+            connect( r, SIGNAL( finished() ), this, SLOT( catalogDeleted() ) );
+            r->setProperty( "type", "artist" );
+        }
         m_syncing = false;
     }
 }
 
+void
+EchonestCatalogSynchronizer::catalogDeleted()
+{
+    QNetworkReply* r = qobject_cast< QNetworkReply* >( sender() );
+    Q_ASSERT( r );
+
+    QString toDel = QString( "collection/%1Catalog" ).arg( r->property( "type" ).toString() );
+
+    try
+    {
+        // HACK libechonest bug, should be a static method but it's not. Doesn't actually use any instance vars though
+        m_songCatalog.parseDelete( r );
+        // If we didn't throw, no errors, so clear our config
+        TomahawkSettings::instance()->setValue( toDel, QString() );
+    } catch ( const Echonest::ParseError& e )
+    {}
+}
+
+
 void
 EchonestCatalogSynchronizer::uploadDb()
 {
@@ -73,8 +134,8 @@ EchonestCatalogSynchronizer::uploadDb()
     QNetworkReply* r =  Echonest::Catalog::create( QString( "%1_song" ).arg( Database::instance()->dbid() ), Echonest::CatalogTypes::Song );
     connect( r, SIGNAL( finished() ), this, SLOT( songCreateFinished() ) );
 
-    r =  Echonest::Catalog::create( QString( "%1_artist" ).arg( Database::instance()->dbid() ), Echonest::CatalogTypes::Artist );
-    connect( r, SIGNAL( finished() ), this, SLOT( artistCreateFinished() ) );
+//     r =  Echonest::Catalog::create( QString( "%1_artist" ).arg( Database::instance()->dbid() ), Echonest::CatalogTypes::Artist );
+//     connect( r, SIGNAL( finished() ), this, SLOT( artistCreateFinished() ) );
 }
 
 void
@@ -88,9 +149,8 @@ EchonestCatalogSynchronizer::songCreateFinished()
     {
         m_songCatalog = Echonest::Catalog::parseCreate( r );
         TomahawkSettings::instance()->setValue( "collection/songCatalog", m_songCatalog.id() );
-        QSharedPointer< DatabaseCommand > cmd( new DatabaseCommand_SetCollectionAttributes( SourceList::instance()->getLocal(),
-                                                                                                    DatabaseCommand_SetCollectionAttributes::EchonestSongCatalog,
-                                                                                                    m_songCatalog.id() ) );
+        QSharedPointer< DatabaseCommand > cmd( new DatabaseCommand_SetCollectionAttributes( DatabaseCommand_SetCollectionAttributes::EchonestSongCatalog,
+                                                                                            m_songCatalog.id() ) );
         Database::instance()->enqueue( cmd );
     } catch ( const Echonest::ParseError& e )
     {
@@ -98,7 +158,7 @@ EchonestCatalogSynchronizer::songCreateFinished()
         return;
     }
 
-    QString sql( "SELECT track.name, artist.name, album.name "
+    QString sql( "SELECT track.id, track.name, artist.name, album.name "
                  "FROM file, artist, track, file_join "
                  "LEFT OUTER JOIN album "
                  "ON file_join.album = album.id "
@@ -107,7 +167,7 @@ EchonestCatalogSynchronizer::songCreateFinished()
                  "AND file_join.track = track.id "
                  "AND file.source IS NULL");
     DatabaseCommand_GenericSelect* cmd = new DatabaseCommand_GenericSelect( sql, DatabaseCommand_GenericSelect::Track, true );
-    connect( cmd, SIGNAL( rawData( QList< QStringList > ) ), this, SLOT( rawTracks( QList< QStringList > ) ) );
+    connect( cmd, SIGNAL( rawData( QList< QStringList > ) ), this, SLOT( rawTracksAdd( QList< QStringList > ) ) );
     Database::instance()->enqueue( QSharedPointer< DatabaseCommand >( cmd ) );
 }
 
@@ -138,7 +198,7 @@ EchonestCatalogSynchronizer::artistCreateFinished()
 }
 
 void
-EchonestCatalogSynchronizer::rawTracks( const QList< QStringList >& tracks )
+EchonestCatalogSynchronizer::rawTracksAdd( const QList< QStringList >& tracks )
 {
     tDebug() << "Got raw tracks, num:" << tracks.size();
 
@@ -151,14 +211,18 @@ EchonestCatalogSynchronizer::rawTracks( const QList< QStringList >& tracks )
         cur = ( cur + 2000 > tracks.size() ) ? tracks.size() : cur + 2000;
 
         tDebug() << "Enqueueing a batch of tracks to upload to echonest catalog:" << cur - prev;
-        Echonest::CatalogUpdateEntries entries( cur - prev );
+        Echonest::CatalogUpdateEntries entries;
+        QList< QPair< QID, QString > > inserted;
         for ( int i = prev; i < cur; i++ )
         {
-            if ( tracks[i][0].isEmpty() || tracks[i][1].isEmpty() )
+            if ( tracks[i][1].isEmpty() || tracks[i][2].isEmpty() )
                 continue;
-            entries.append( entryFromTrack( tracks[i] ) );
+            entries.append( entryFromTrack( tracks[i], Echonest::CatalogTypes::Update ) );
+            inserted << QPair< QID, QString >( tracks[i][0], entries.last().itemId() );
         }
+        tDebug() << "Done queuing:" << entries.size() << "tracks";
         m_queuedUpdates.enqueue( entries );
+        m_queuedTrackInfo.enqueue( inserted );
     }
 
     doUploadJob();
@@ -172,6 +236,7 @@ EchonestCatalogSynchronizer::doUploadJob()
         return;
 
     Echonest::CatalogUpdateEntries entries = m_queuedUpdates.dequeue();
+    tDebug() << "Updating number of entries:" << entries.count();
 
     QNetworkReply* updateJob = m_songCatalog.update( entries );
     connect( updateJob, SIGNAL( finished() ), this, SLOT( songUpdateFinished() ) );
@@ -179,14 +244,14 @@ EchonestCatalogSynchronizer::doUploadJob()
 
 
 Echonest::CatalogUpdateEntry
-EchonestCatalogSynchronizer::entryFromTrack( const QStringList& track ) const
+EchonestCatalogSynchronizer::entryFromTrack( const QStringList& track, Echonest::CatalogTypes::Action action ) const
 {
     //qDebug() << "UPLOADING:" << track[0] << track[1] << track[2];
     Echonest::CatalogUpdateEntry entry;
-    entry.setAction( Echonest::CatalogTypes::Update );
-    entry.setSongName( escape( track[ 0 ] ) );
-    entry.setArtistName( escape( track[ 1 ] ) );
-    entry.setRelease( escape( track[ 2 ] ) );
+    entry.setAction( action );
+    entry.setSongName( escape( track[ 1 ] ) );
+    entry.setArtistName( escape( track[ 2 ] ) );
+    entry.setRelease( escape( track[ 3 ] ) );
     entry.setItemId( uuid().toUtf8() );
 
     return entry;
@@ -199,7 +264,13 @@ EchonestCatalogSynchronizer::songUpdateFinished()
     QNetworkReply* r = qobject_cast< QNetworkReply* >( sender() );
     Q_ASSERT( r );
 
-    doUploadJob();
+    QList< QPair< QID, QString > > ids = m_queuedTrackInfo.dequeue();
+    if ( r->error() == QNetworkReply::NoError )
+    {
+        // Save the ids of each track in the echonest catalog to our db, so we can keep track of them
+        DatabaseCommand_SetTrackAttributes* cmd = new DatabaseCommand_SetTrackAttributes( DatabaseCommand_SetTrackAttributes::EchonestCatalogId, ids );
+        Database::instance()->enqueue( QSharedPointer< DatabaseCommand >( cmd ) );
+    }
 
     try
     {
@@ -209,8 +280,9 @@ EchonestCatalogSynchronizer::songUpdateFinished()
     } catch ( const Echonest::ParseError& e )
     {
         tLog() << "Echonest threw an exception parsing catalog update finished:" << e.what();
-        return;
     }
+
+    doUploadJob();
 }
 
 void
@@ -231,6 +303,69 @@ EchonestCatalogSynchronizer::checkTicket()
     }
 }
 
+void
+EchonestCatalogSynchronizer::tracksAdded( const QList< query_ptr >& tracks )
+{
+    if ( !m_syncing || m_songCatalog.id().isEmpty() || tracks.isEmpty() )
+        return;
+
+    QList< QStringList > rawTracks;
+    foreach( const query_ptr& track, tracks )
+    {
+        // DatabaseCommand_AddFiles sets the track id on the result
+        int id = -1;
+        if ( track->results().size() == 1 )
+            id = track->results().first()->dbid();
+        else
+        {
+            tLog() << Q_FUNC_INFO << "No dbid for track we got in tracksAdded()!";
+            continue;
+        }
+        rawTracks << ( QStringList() << QString::number( id ) << track->track() << track->artist() << track->album() );
+    }
+    rawTracksAdd( rawTracks );
+}
+
+void
+EchonestCatalogSynchronizer::tracksRemoved( const QList< query_ptr >& tracks )
+{
+
+    if ( !m_syncing || m_songCatalog.id().isEmpty() || tracks.isEmpty() )
+        return;
+
+    // get the catalog ids, if they exist, otherwise we can't do anything with them.
+    QList< QID > qids;
+    foreach ( const query_ptr& q, tracks )
+    {
+        qids << q->id();
+    }
+
+    DatabaseCommand_TrackAttributes* cmd = new DatabaseCommand_TrackAttributes( DatabaseCommand_SetTrackAttributes::EchonestCatalogId, qids );
+    connect( cmd, SIGNAL( trackAttributes( PairList ) ), this, SLOT( trackAttributes( PairList ) ) );
+    Database::instance()->enqueue( QSharedPointer< DatabaseCommand >( cmd ) );
+}
+
+void
+EchonestCatalogSynchronizer::trackAttributes( PairList attributes )
+{
+//     QString actionStr = cmd->property( "action" ).toString();
+    Echonest::CatalogTypes::Action action;
+//     if ( actionStr == "delete" )
+        action = Echonest::CatalogTypes::Delete;
+
+    Echonest::CatalogUpdateEntries entries( attributes.size() );
+    QPair< QID, QString > track;
+    foreach ( track, attributes )
+    {
+        Echonest::CatalogUpdateEntry e( action );
+        e.setItemId( track.second.toUtf8() );
+        entries.append( e );
+    }
+
+    m_songCatalog.update( entries );
+}
+
+
 QByteArray
 EchonestCatalogSynchronizer::escape( const QString &in ) const
 {
diff --git a/src/libtomahawk/EchonestCatalogSynchronizer.h b/src/libtomahawk/EchonestCatalogSynchronizer.h
index 393e3bc86..5eda0ddcd 100644
--- a/src/libtomahawk/EchonestCatalogSynchronizer.h
+++ b/src/libtomahawk/EchonestCatalogSynchronizer.h
@@ -20,6 +20,8 @@
 #define ECHONESTCATALOGSYNCHRONIZER_H
 
 #include "dllmacro.h"
+#include "query.h"
+#include "database/databasecommand_trackattributes.h"
 
 #include <echonest/Catalog.h>
 
@@ -51,19 +53,25 @@ signals:
 
 private slots:
     void checkSettingsChanged();
+    void tracksAdded( const QList<Tomahawk::query_ptr>& );
+    void tracksRemoved( const QList<Tomahawk::query_ptr>& );
 
+    // Echonest slots
     void songCreateFinished();
     void artistCreateFinished();
     void songUpdateFinished();
+    void catalogDeleted();
 
     void checkTicket();
 
-    void rawTracks( const QList< QStringList >& tracks );
+    void rawTracksAdd( const QList< QStringList >& tracks );
+
+    void trackAttributes( PairList );
 private:
     void uploadDb();
     QByteArray escape( const QString& in ) const;
 
-    Echonest::CatalogUpdateEntry entryFromTrack( const QStringList& ) const;
+    Echonest::CatalogUpdateEntry entryFromTrack( const QStringList&, Echonest::CatalogTypes::Action action ) const;
     void doUploadJob();
 
     bool m_syncing;
@@ -72,6 +80,7 @@ private:
     Echonest::Catalog m_artistCatalog;
 
     QQueue< Echonest::CatalogUpdateEntries > m_queuedUpdates;
+    QQueue< QList< QPair< QID, QString > > > m_queuedTrackInfo;
 
     static EchonestCatalogSynchronizer* s_instance;
 };
diff --git a/src/libtomahawk/collection.cpp b/src/libtomahawk/collection.cpp
index aa02a285d..bce7851c0 100644
--- a/src/libtomahawk/collection.cpp
+++ b/src/libtomahawk/collection.cpp
@@ -259,6 +259,7 @@ Collection::delTracks( const QStringList& files )
         i++;
     }
 
+    tDebug() << "Emitting tracks removed:" << tracks.size();
     emit tracksRemoved( tracks );
 }
 
diff --git a/src/libtomahawk/database/databasecommand.cpp b/src/libtomahawk/database/databasecommand.cpp
index 9397ba853..1ee2123df 100644
--- a/src/libtomahawk/database/databasecommand.cpp
+++ b/src/libtomahawk/database/databasecommand.cpp
@@ -32,6 +32,7 @@
 
 #include "utils/logger.h"
 #include "databasecommand_setcollectionattributes.h"
+#include "databasecommand_settrackattributes.h"
 
 
 DatabaseCommand::DatabaseCommand( QObject* parent )
@@ -174,6 +175,13 @@ DatabaseCommand::factory( const QVariant& op, const source_ptr& source )
         QJson::QObjectHelper::qvariant2qobject( op.toMap(), cmd );
         return cmd;
     }
+    else if( name == "settrackattributes" )
+    {
+        DatabaseCommand_SetTrackAttributes * cmd = new DatabaseCommand_SetTrackAttributes;
+        cmd->setSource( source );
+        QJson::QObjectHelper::qvariant2qobject( op.toMap(), cmd );
+        return cmd;
+    }
 
     qDebug() << "ERROR in" << Q_FUNC_INFO << name;
 //    Q_ASSERT( false );
diff --git a/src/libtomahawk/database/databasecommand_collectionattributes.cpp b/src/libtomahawk/database/databasecommand_collectionattributes.cpp
index 9454d5e3d..dd950a871 100644
--- a/src/libtomahawk/database/databasecommand_collectionattributes.cpp
+++ b/src/libtomahawk/database/databasecommand_collectionattributes.cpp
@@ -24,7 +24,6 @@ DatabaseCommand_CollectionAttributes::DatabaseCommand_CollectionAttributes( Data
     : DatabaseCommand()
     , m_type( type )
 {
-    qRegisterMetaType< PairList >("PairList");
 }
 
 void
diff --git a/src/libtomahawk/database/databasecommand_collectionattributes.h b/src/libtomahawk/database/databasecommand_collectionattributes.h
index 0de1beeab..b6ccd5093 100644
--- a/src/libtomahawk/database/databasecommand_collectionattributes.h
+++ b/src/libtomahawk/database/databasecommand_collectionattributes.h
@@ -16,16 +16,14 @@
  *   along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef DATABASECOMMAND_ECHONESTCATALOG_H
-#define DATABASECOMMAND_ECHONESTCATALOG_H
+#ifndef DATABASECOMMAND_COLLECTIONATTRIBUTES_H
+#define DATABASECOMMAND_COLLECTIONATTRIBUTES_H
 
 #include "typedefs.h"
 #include "databasecommand.h"
 #include "databasecommand_setcollectionattributes.h"
 #include <QByteArray>
 
-typedef QList< QPair< QString, QString > > PairList;
-
 class DatabaseCommand_CollectionAttributes : public DatabaseCommand
 {
     Q_OBJECT
@@ -35,7 +33,7 @@ public:
     virtual void exec( DatabaseImpl* lib );
     virtual bool doesMutates() const { return false; }
 
-    virtual QString commandname() const { return "setcollectionattributes"; }
+    virtual QString commandname() const { return "collectionattributes"; }
 
 signals:
     void collectionAttributes( PairList );
@@ -44,5 +42,4 @@ private:
     DatabaseCommand_SetCollectionAttributes::AttributeType m_type;
 };
 
-Q_DECLARE_METATYPE(PairList)
 #endif
diff --git a/src/libtomahawk/database/databasecommand_deletefiles.cpp b/src/libtomahawk/database/databasecommand_deletefiles.cpp
index afc7362ea..8f7779499 100644
--- a/src/libtomahawk/database/databasecommand_deletefiles.cpp
+++ b/src/libtomahawk/database/databasecommand_deletefiles.cpp
@@ -47,6 +47,7 @@ DatabaseCommand_DeleteFiles::postCommitHook()
     connect( this, SIGNAL( notify( QStringList ) ),
              coll,   SLOT( delTracks( QStringList ) ), Qt::QueuedConnection );
 
+    tDebug() << "Notifying of deleted tracks:" << m_files.size();
     emit notify( m_files );
 
     if( source()->isLocal() )
diff --git a/src/libtomahawk/database/databasecommand_setcollectionattributes.cpp b/src/libtomahawk/database/databasecommand_setcollectionattributes.cpp
index e021cb5a6..9e930397f 100644
--- a/src/libtomahawk/database/databasecommand_setcollectionattributes.cpp
+++ b/src/libtomahawk/database/databasecommand_setcollectionattributes.cpp
@@ -20,20 +20,33 @@
 #include "databaseimpl.h"
 #include "source.h"
 #include "network/servent.h"
+#include "sourcelist.h"
 
-DatabaseCommand_SetCollectionAttributes::DatabaseCommand_SetCollectionAttributes( const Tomahawk::source_ptr& source, AttributeType type, const QByteArray& id )
-    : DatabaseCommandLoggable( source )
+DatabaseCommand_SetCollectionAttributes::DatabaseCommand_SetCollectionAttributes( AttributeType type, const QByteArray& id )
+    : DatabaseCommandLoggable( )
+    , m_delete( false )
     , m_type( type )
     , m_id( id )
 {
 }
 
+DatabaseCommand_SetCollectionAttributes::DatabaseCommand_SetCollectionAttributes( DatabaseCommand_SetCollectionAttributes::AttributeType type, bool toDelete )
+    : DatabaseCommandLoggable()
+    , m_delete( toDelete )
+    , m_type( type )
+{
+}
+
+
 void
 DatabaseCommand_SetCollectionAttributes::exec( DatabaseImpl *lib )
 {
     TomahawkSqlQuery query = lib->newquery();
 
     QString sourceStr;
+    if ( source().isNull() )
+        setSource( SourceList::instance()->getLocal() );
+
     if ( source().isNull() || source()->isLocal() )
         sourceStr = "NULL";
     else
@@ -48,6 +61,9 @@ DatabaseCommand_SetCollectionAttributes::exec( DatabaseImpl *lib )
     TomahawkSqlQuery delQuery = lib->newquery();
     delQuery.exec( QString( "DELETE FROM collection_attributes WHERE id %1" ).arg( source()->isLocal() ? QString("IS NULL") : QString( "= %1" ).arg( source()->id() )));
 
+    if ( m_delete )
+        return;
+
     QString queryStr = QString( "INSERT INTO collection_attributes ( id, k, v ) VALUES( %1, \"%2\", \"%3\" )" ).arg( sourceStr ).arg( typeStr ).arg( QString::fromUtf8( m_id ) );
     qDebug() << "Doing query:" << queryStr;
     query.exec( queryStr );
diff --git a/src/libtomahawk/database/databasecommand_setcollectionattributes.h b/src/libtomahawk/database/databasecommand_setcollectionattributes.h
index be07a3672..41de403e9 100644
--- a/src/libtomahawk/database/databasecommand_setcollectionattributes.h
+++ b/src/libtomahawk/database/databasecommand_setcollectionattributes.h
@@ -16,8 +16,8 @@
  *   along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef DATABASECOMMAND_SAVEECHONESTCATALOG_H
-#define DATABASECOMMAND_SAVEECHONESTCATALOG_H
+#ifndef DATABASECOMMAND_SETCOLLECTIONATTRIBUTES
+#define DATABASECOMMAND_SETCOLLECTIONATTRIBUTES
 
 #include "typedefs.h"
 #include "databasecommandloggable.h"
@@ -35,7 +35,10 @@ public:
         EchonestArtistCatalog = 1
     };
 
-    DatabaseCommand_SetCollectionAttributes( const Tomahawk::source_ptr& source, AttributeType type, const QByteArray& id );
+    DatabaseCommand_SetCollectionAttributes( AttributeType type, const QByteArray& id );
+    // Delete all attributes for the source+type
+    DatabaseCommand_SetCollectionAttributes( AttributeType type, bool toDelete );
+
     DatabaseCommand_SetCollectionAttributes() {} // JSON
     virtual void exec( DatabaseImpl* lib );
     virtual bool doesMutates() const { return true; }
@@ -50,8 +53,9 @@ public:
     int type() const { return (int)m_type; }
 
 private:
+    bool m_delete;
     AttributeType m_type;
     QByteArray m_id;
 };
 
-#endif // DATABASECOMMAND_SAVEECHONESTCATALOG_H
+#endif // DATABASECOMMAND_SETCOLLECTIONATTRIBUTES
diff --git a/src/libtomahawk/database/databasecommand_settrackattributes.cpp b/src/libtomahawk/database/databasecommand_settrackattributes.cpp
new file mode 100644
index 000000000..173e78689
--- /dev/null
+++ b/src/libtomahawk/database/databasecommand_settrackattributes.cpp
@@ -0,0 +1,97 @@
+/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
+ *
+ *   Copyright 2010-2011, Leo Franchi <lfranchi@kde.org>
+ *
+ *   Tomahawk is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   Tomahawk is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "databasecommand_settrackattributes.h"
+#include "tomahawksqlquery.h"
+#include "databaseimpl.h"
+
+using namespace Tomahawk;
+
+DatabaseCommand_SetTrackAttributes::DatabaseCommand_SetTrackAttributes( DatabaseCommand_SetTrackAttributes::AttributeType type, QList< QPair< QID, QString > > ids, bool toDelete )
+    : DatabaseCommandLoggable()
+    , m_loggable( false )
+    , m_delete( toDelete )
+    , m_type( type )
+    , m_tracks( ids )
+{
+}
+
+DatabaseCommand_SetTrackAttributes::DatabaseCommand_SetTrackAttributes( DatabaseCommand_SetTrackAttributes::AttributeType type )
+    : DatabaseCommandLoggable()
+    , m_loggable( false )
+    , m_delete( true )
+    , m_type( type )
+{
+
+}
+
+
+void
+DatabaseCommand_SetTrackAttributes::exec( DatabaseImpl* dbi )
+{
+    TomahawkSqlQuery checkquery = dbi->newquery();
+    TomahawkSqlQuery delquery = dbi->newquery();
+    TomahawkSqlQuery insertquery = dbi->newquery();
+
+    QString k;
+    switch ( m_type )
+    {
+    case EchonestCatalogId:
+        k = "echonestcatalogid";
+        break;
+    }
+
+    if ( m_delete && m_tracks.isEmpty() )
+    {
+        //delete all
+        TomahawkSqlQuery delAll = dbi->newquery();
+        delAll.prepare( "DELETE FROM track_attributes WHERE k = ?" );
+        delAll.bindValue( 0, k );
+        delAll.exec();
+        return;
+    }
+
+    checkquery.prepare( "SELECT id, sortname FROM track WHERE id = ?" );
+    delquery.prepare( "DELETE FROM track_attributes WHERE id = ? AND k = ?" );
+    insertquery.prepare( "INSERT INTO track_attributes ( id, k, v ) VALUES( ?, ?, ? )" );
+
+    QPair< QID, QString > track;
+    foreach ( track, m_tracks )
+    {
+        checkquery.bindValue( 0, track.first );
+        if ( !checkquery.exec() )
+        {
+            tLog() << "No track in track table for set track attribute command...aborting:" << track.first;
+            continue;
+        }
+
+        delquery.bindValue( 0, track.first );
+        delquery.bindValue( 1, k );
+        delquery.exec();
+
+        if ( m_delete )
+            continue; // stop at deleting, don't insert
+
+        insertquery.bindValue( 0, track.first );
+        insertquery.bindValue( 1, k );
+        insertquery.bindValue( 2, track.second );
+        if ( !insertquery.exec() )
+            tLog() << "Failed to insert track attribute:" << k << track.first << track.second;
+
+    }
+}
diff --git a/src/libtomahawk/database/databasecommand_settrackattributes.h b/src/libtomahawk/database/databasecommand_settrackattributes.h
new file mode 100644
index 000000000..e05619b74
--- /dev/null
+++ b/src/libtomahawk/database/databasecommand_settrackattributes.h
@@ -0,0 +1,57 @@
+/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
+ *
+ *   Copyright 2010-2011, Leo Franchi <lfranchi@kde.org>
+ *
+ *   Tomahawk is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   Tomahawk is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef DATABASECOMMAND_SETTRACKATTRIBUTES
+#define DATABASECOMMAND_SETTRACKATTRIBUTES
+
+#include "typedefs.h"
+#include "databasecommandloggable.h"
+
+#include <QByteArray>
+
+class DatabaseCommand_SetTrackAttributes : public DatabaseCommandLoggable
+{
+    Q_OBJECT
+public:
+    enum AttributeType {
+        EchonestCatalogId = 0,
+    };
+
+    // Takes a list of <track_id, value> pairs. key is always type
+    DatabaseCommand_SetTrackAttributes( AttributeType type, QList< QPair< Tomahawk::QID, QString > > ids, bool toDelete = false );
+    // Deletes *all tracks with attribute*
+    DatabaseCommand_SetTrackAttributes( AttributeType type );
+    DatabaseCommand_SetTrackAttributes() {} // JSON
+
+    virtual void exec( DatabaseImpl* lib );
+    virtual bool doesMutates() const { return true; }
+    virtual bool loggable() const { return m_loggable; }
+
+    virtual QString commandname() const { return "settrackattributes"; }
+
+    void setType( int type ) { m_type = (AttributeType)type; }
+    int type() const { return (int)m_type; }
+
+private:
+    bool m_loggable, m_delete;
+
+    AttributeType m_type;
+    QList< QPair< Tomahawk::QID, QString > > m_tracks;
+};
+
+#endif // DATABASECOMMAND_SETTRACKATTRIBUTES
diff --git a/src/libtomahawk/database/databasecommand_trackattributes.cpp b/src/libtomahawk/database/databasecommand_trackattributes.cpp
new file mode 100644
index 000000000..48fddaded
--- /dev/null
+++ b/src/libtomahawk/database/databasecommand_trackattributes.cpp
@@ -0,0 +1,71 @@
+/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
+ *
+ *   Copyright 2010-2011, Leo Franchi <lfranchi@kde.org>
+ *
+ *   Tomahawk is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   Tomahawk is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "databasecommand_trackattributes.h"
+#include "databaseimpl.h"
+
+using namespace Tomahawk;
+DatabaseCommand_TrackAttributes::DatabaseCommand_TrackAttributes( DatabaseCommand_SetTrackAttributes::AttributeType type, const QList< Tomahawk::QID > ids )
+    : DatabaseCommand()
+    , m_type( type )
+    , m_ids( ids )
+{
+}
+
+DatabaseCommand_TrackAttributes::DatabaseCommand_TrackAttributes( DatabaseCommand_SetTrackAttributes::AttributeType type )
+    : DatabaseCommand()
+    , m_type( type )
+{
+}
+
+void DatabaseCommand_TrackAttributes::exec( DatabaseImpl* lib )
+{
+    TomahawkSqlQuery query = lib->newquery();
+
+    QString k;
+    switch ( m_type )
+    {
+        case DatabaseCommand_SetTrackAttributes::EchonestCatalogId:
+            k = "echonestcatalogid";
+            break;
+    }
+
+    PairList results;
+    if ( !m_ids.isEmpty() )
+    {
+
+        foreach ( const QID id, m_ids )
+        {
+            query.prepare( "SELECT v FROM track_attributes WHERE id = ? AND k = ?" );
+            query.bindValue( 0, id );
+            query.bindValue( 1, k );
+            if ( query.exec() )
+                results.append( QPair< QID, QString >( id, query.value( 0 ).toString() ) );
+        }
+    } else {
+        query.prepare( "SELECT id, v FROM track_attributes WHERE k = ?" );
+        query.bindValue( 0, k );
+        query.exec();
+        while ( !query.next() )
+        {
+            results.append( QPair< QID, QString >( query.value( 0 ).toString(), query.value( 1 ).toString() ) );
+        }
+    }
+
+    emit trackAttributes( results );
+}
diff --git a/src/libtomahawk/database/databasecommand_trackattributes.h b/src/libtomahawk/database/databasecommand_trackattributes.h
new file mode 100644
index 000000000..261ce0f79
--- /dev/null
+++ b/src/libtomahawk/database/databasecommand_trackattributes.h
@@ -0,0 +1,51 @@
+/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
+ *
+ *   Copyright 2010-2011, Leo Franchi <lfranchi@kde.org>
+ *
+ *   Tomahawk is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   Tomahawk is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef DATABASECOMMAND_TRACKATTRIBUTES_H
+#define DATABASECOMMAND_TRACKATTRIBUTES_H
+
+#include "typedefs.h"
+#include "databasecommand.h"
+#include "databasecommand_collectionattributes.h"
+#include "databasecommand_settrackattributes.h"
+#include <QByteArray>
+
+class DatabaseCommand_TrackAttributes : public DatabaseCommand
+{
+    Q_OBJECT
+public:
+
+    // Get all tracks with this attribute
+    DatabaseCommand_TrackAttributes( DatabaseCommand_SetTrackAttributes::AttributeType type );
+    // Get the specific tracks with this attribute
+    DatabaseCommand_TrackAttributes( DatabaseCommand_SetTrackAttributes::AttributeType type, const QList< Tomahawk::QID > ids );
+
+    virtual void exec( DatabaseImpl* lib );
+    virtual bool doesMutates() const { return false; }
+
+    virtual QString commandname() const { return "trackattributes"; }
+
+signals:
+    void trackAttributes( PairList );
+
+private:
+    DatabaseCommand_SetTrackAttributes::AttributeType m_type;
+    QList< Tomahawk::QID > m_ids;
+};
+
+#endif
diff --git a/src/libtomahawk/typedefs.h b/src/libtomahawk/typedefs.h
index 4ef47641b..b3b98232e 100644
--- a/src/libtomahawk/typedefs.h
+++ b/src/libtomahawk/typedefs.h
@@ -21,6 +21,7 @@
 
 #include <QSharedPointer>
 #include <QUuid>
+#include <QPair>
 
 //template <typename T> class QSharedPointer;
 
@@ -66,6 +67,7 @@ namespace Tomahawk
 
 typedef int AudioErrorCode;
 typedef int AudioState;
+typedef QList< QPair< QString, QString > > PairList;
 
 // creates 36char ascii guid without {} around it
 inline static QString uuid()
diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp
index 75b0fe6bc..22f36aeb8 100644
--- a/src/tomahawkapp.cpp
+++ b/src/tomahawkapp.cpp
@@ -367,6 +367,7 @@ TomahawkApp::registerMetaTypes()
     qRegisterMetaType< QMap< QString, plentry_ptr > >("QMap< QString, plentry_ptr >");
     qRegisterMetaType< QHash< QString, QMap<quint32, quint16> > >("QHash< QString, QMap<quint32, quint16> >");
     qRegisterMetaType< QMap< QString, QMap< unsigned int, unsigned int > > >("QMap< QString, QMap< unsigned int, unsigned int > >");
+    qRegisterMetaType< PairList >("PairList");
 
     qRegisterMetaType< GeneratorMode>("GeneratorMode");
     qRegisterMetaType<Tomahawk::GeneratorMode>("Tomahawk::GeneratorMode");
diff --git a/src/tomahawkapp.h b/src/tomahawkapp.h
index 0b3a0b1a8..00a7749ed 100644
--- a/src/tomahawkapp.h
+++ b/src/tomahawkapp.h
@@ -135,7 +135,8 @@ private:
     QxtHttpSessionManager m_session;
 };
 
-Q_DECLARE_METATYPE( QPersistentModelIndex );
+Q_DECLARE_METATYPE( QPersistentModelIndex )
+Q_DECLARE_METATYPE(PairList)
 
 #endif // TOMAHAWKAPP_H