diff --git a/data/sql/dbmigrate-26_to_27.sql b/data/sql/dbmigrate-26_to_27.sql
new file mode 100644
index 000000000..1b8ff87c7
--- /dev/null
+++ b/data/sql/dbmigrate-26_to_27.sql
@@ -0,0 +1,10 @@
+-- Script to migate from db version 6 to 27
+-- Nothing to do
+
+CREATE TABLE IF NOT EXISTS collection_attributes (
+ id INTEGER REFERENCES source(id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, -- source id, null for local source
+ k TEXT NOT NULL,
+ v TEXT NOT NULL
+);
+UPDATE settings SET v = '27' WHERE k == 'schema_version';
+
diff --git a/resources.qrc b/resources.qrc
index 3221fbcb3..567b89a86 100644
--- a/resources.qrc
+++ b/resources.qrc
@@ -108,6 +108,7 @@
data/sql/dbmigrate-23_to_24.sql
data/sql/dbmigrate-24_to_25.sql
data/sql/dbmigrate-25_to_26.sql
+ data/sql/dbmigrate-26_to_27.sql
data/js/tomahawk.js
data/images/avatar_frame.png
data/images/drop-all-songs.png
diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt
index cb0640e6a..bc7b4c170 100644
--- a/src/libtomahawk/CMakeLists.txt
+++ b/src/libtomahawk/CMakeLists.txt
@@ -35,6 +35,8 @@ set( libSources
playlistinterface.cpp
LatchManager.cpp
+ EchonestCatalogSynchronizer.cpp
+
sip/SipPlugin.cpp
sip/SipHandler.cpp
sip/SipModel.cpp
@@ -95,6 +97,8 @@ set( libSources
database/databasecommand_socialaction.cpp
database/databasecommand_loadsocialactions.cpp
database/databasecommand_genericselect.cpp
+ database/databasecommand_setcollectionattributes.cpp
+ database/databasecommand_collectionattributes.cpp
database/database.cpp
infobar/infobar.cpp
@@ -253,6 +257,8 @@ set( libHeaders
album.h
playlist.h
+ EchonestCatalogSynchronizer.h
+
sip/SipPlugin.h
sip/SipHandler.h
sip/SipModel.h
@@ -313,6 +319,8 @@ set( libHeaders
database/databasecommand_socialaction.h
database/databasecommand_loadsocialactions.h
database/databasecommand_genericselect.h
+ database/databasecommand_setcollectionattributes.h
+ database/databasecommand_collectionattributes.h
infobar/infobar.h
diff --git a/src/libtomahawk/EchonestCatalogSynchronizer.cpp b/src/libtomahawk/EchonestCatalogSynchronizer.cpp
new file mode 100644
index 000000000..ba8c6dabe
--- /dev/null
+++ b/src/libtomahawk/EchonestCatalogSynchronizer.cpp
@@ -0,0 +1,195 @@
+/* === This file is part of Tomahawk Player - ===
+ *
+ * Copyright 2010-2011, Leo Franchi
+ *
+ * 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 .
+ */
+
+#include "EchonestCatalogSynchronizer.h"
+
+#include "database/database.h"
+#include "database/databasecommand_genericselect.h"
+#include "database/databasecommand_setcollectionattributes.h"
+#include "tomahawksettings.h"
+#include "sourcelist.h"
+
+#include
+#include
+
+using namespace Tomahawk;
+
+EchonestCatalogSynchronizer* EchonestCatalogSynchronizer::s_instance = 0;
+
+EchonestCatalogSynchronizer::EchonestCatalogSynchronizer( QObject *parent )
+ : QObject( parent )
+{
+ m_syncing = TomahawkSettings::instance()->enableEchonestCatalogs();
+
+ qRegisterMetaType >("QList");
+
+ connect( TomahawkSettings::instance(), SIGNAL( changed() ), this, SLOT( checkSettingsChanged() ) );
+
+ const QByteArray artist = TomahawkSettings::instance()->value( "collection/artistCatalog" ).toByteArray();
+ const QByteArray song = TomahawkSettings::instance()->value( "collection/songCatalog" ).toByteArray();
+ if ( !artist.isEmpty() )
+ m_artistCatalog.setId( artist );
+ if ( !song.isEmpty() )
+ m_songCatalog.setId( song );
+}
+
+void
+EchonestCatalogSynchronizer::checkSettingsChanged()
+{
+ if ( TomahawkSettings::instance()->enableEchonestCatalogs() && !m_syncing )
+ {
+ // enable, and upload whole db
+ m_syncing = true;
+
+ tDebug() << "Echonest Catalog sync pref changed, uploading!!";
+ uploadDb();
+ } else if ( !TomahawkSettings::instance()->enableEchonestCatalogs() && m_syncing )
+ {
+ m_songCatalog.deleteCatalog();
+ m_artistCatalog.deleteCatalog();
+ m_syncing = false;
+ }
+}
+
+void
+EchonestCatalogSynchronizer::uploadDb()
+{
+ // create two catalogs: uuid_song, and uuid_artist.
+ 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() ) );
+}
+
+void
+EchonestCatalogSynchronizer::songCreateFinished()
+{
+ QNetworkReply* r = qobject_cast< QNetworkReply* >( sender() );
+ Q_ASSERT( r );
+
+ tDebug() << "Finished creating song catalog, updating data now!!";
+ try
+ {
+ 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() ) );
+ Database::instance()->enqueue( cmd );
+ } catch ( const Echonest::ParseError& e )
+ {
+ tLog() << "Echonest threw an exception parsing song catalog create:" << e.what();
+ return;
+ }
+
+ QString sql( "SELECT track.name, artist.name, album.name "
+ "FROM file, artist, track, file_join "
+ "LEFT OUTER JOIN album "
+ "ON file_join.album = album.id "
+ "WHERE file.id = file_join.file "
+ "AND file_join.artist = artist.id "
+ "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 > ) ) );
+ Database::instance()->enqueue( QSharedPointer< DatabaseCommand >( cmd ) );
+}
+
+
+void
+EchonestCatalogSynchronizer::artistCreateFinished()
+{
+ QNetworkReply* r = qobject_cast< QNetworkReply* >( sender() );
+ Q_ASSERT( r );
+
+ try
+ {
+ m_artistCatalog = Echonest::Catalog::parseCreate( r );
+ TomahawkSettings::instance()->setValue( "collection/artistCatalog", m_artistCatalog.id() );
+
+// QSharedPointer< DatabaseCommand > cmd( new DatabaseCommand_SetCollectionAttributes( SourceList::instance()->getLocal(),
+// DatabaseCommand_SetCollectionAttributes::EchonestSongCatalog,
+// m_songCatalog.id() ) );
+// Database::instance()->enqueue( cmd );
+ } catch ( const Echonest::ParseError& e )
+ {
+ tLog() << "Echonest threw an exception parsing artist catalog create:" << e.what();
+ return;
+ }
+}
+
+void
+EchonestCatalogSynchronizer::rawTracks( const QList< QStringList >& tracks )
+{
+ tDebug() << "Got raw tracks, num:" << tracks.size();
+ Echonest::CatalogUpdateEntries entries( tracks.size() );
+ for ( int i = 0; i < tracks.size(); i++ )
+ {
+ if ( tracks[ i ][ 0 ].isEmpty() || tracks[ i ][ 1 ].isEmpty() )
+ continue;
+
+ Echonest::CatalogUpdateEntry entry;
+ entry.setAction( Echonest::CatalogTypes::Update );
+ entry.setSongName( tracks[ i ][ 0 ] );
+ entry.setArtistName( tracks[ i ][ 1 ] );
+ entry.setRelease( tracks[ i ][ 2 ] );
+ entry.setItemId( uuid().toUtf8() );
+
+ entries.append( entry );
+ }
+
+ QNetworkReply* updateJob = m_songCatalog.update( entries );
+ connect( updateJob, SIGNAL( finished() ), this, SLOT( songUpdateFinished() ) );
+}
+
+void
+EchonestCatalogSynchronizer::songUpdateFinished()
+{
+ QNetworkReply* r = qobject_cast< QNetworkReply* >( sender() );
+ Q_ASSERT( r );
+
+ try
+ {
+ QByteArray ticket = m_songCatalog.parseTicket( r );
+ QNetworkReply* tJob = m_songCatalog.status( ticket );
+ connect( tJob, SIGNAL( finished() ), this, SLOT( checkTicket() ) );
+ } catch ( const Echonest::ParseError& e )
+ {
+ tLog() << "Echonest threw an exception parsing catalog update finished:" << e.what();
+ return;
+ }
+}
+
+void
+EchonestCatalogSynchronizer::checkTicket()
+{
+ QNetworkReply* r = qobject_cast< QNetworkReply* >( sender() );
+ Q_ASSERT( r );
+
+ try
+ {
+ Echonest::CatalogStatus status = m_songCatalog.parseStatus( r );
+
+ tLog() << "Catalog status update:" << status.status << status.details << status.items;
+ } catch ( const Echonest::ParseError& e )
+ {
+ tLog() << "Echonest threw an exception parsing catalog create:" << e.what();
+ return;
+ }
+}
diff --git a/src/libtomahawk/EchonestCatalogSynchronizer.h b/src/libtomahawk/EchonestCatalogSynchronizer.h
new file mode 100644
index 000000000..766b90856
--- /dev/null
+++ b/src/libtomahawk/EchonestCatalogSynchronizer.h
@@ -0,0 +1,71 @@
+/* === This file is part of Tomahawk Player - ===
+ *
+ * Copyright 2010-2011, Leo Franchi
+ *
+ * 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 .
+ */
+
+#ifndef ECHONESTCATALOGSYNCHRONIZER_H
+#define ECHONESTCATALOGSYNCHRONIZER_H
+
+#include
+
+#include
+
+namespace Tomahawk
+{
+
+class EchonestCatalogSynchronizer : public QObject
+{
+ Q_OBJECT
+public:
+ static EchonestCatalogSynchronizer* instance() {
+ if ( !s_instance )
+ {
+ s_instance = new EchonestCatalogSynchronizer;
+ }
+
+ return s_instance;
+ }
+
+ explicit EchonestCatalogSynchronizer(QObject *parent = 0);
+
+ Echonest::Catalog songCatalog() const { return m_songCatalog; }
+ Echonest::Catalog artistCatalog() const { return m_artistCatalog; }
+
+signals:
+
+private slots:
+ void checkSettingsChanged();
+
+ void songCreateFinished();
+ void artistCreateFinished();
+ void songUpdateFinished();
+
+ void checkTicket();
+
+ void rawTracks( const QList< QStringList >& tracks );
+private:
+ void uploadDb();
+
+ bool m_syncing;
+
+ Echonest::Catalog m_songCatalog;
+ Echonest::Catalog m_artistCatalog;
+
+ static EchonestCatalogSynchronizer* s_instance;
+};
+
+}
+#endif // ECHONESTCATALOGSYNCHRONIZER_H
diff --git a/src/libtomahawk/database/databasecommand_collectionattributes.cpp b/src/libtomahawk/database/databasecommand_collectionattributes.cpp
new file mode 100644
index 000000000..b1f7493ec
--- /dev/null
+++ b/src/libtomahawk/database/databasecommand_collectionattributes.cpp
@@ -0,0 +1,59 @@
+/* === This file is part of Tomahawk Player - ===
+ *
+ * Copyright 2010-2011, Leo Franchi
+ *
+ * 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 .
+ */
+#include "DatabaseCommand_CollectionAttributes.h"
+
+#include "databaseimpl.h"
+#include "source.h"
+
+DatabaseCommand_CollectionAttributes::DatabaseCommand_CollectionAttributes( DatabaseCommand_SetCollectionAttributes::AttributeType type )
+ : DatabaseCommand()
+ , m_type( type )
+{
+ qRegisterMetaType< PairList >("PairList");
+}
+
+void
+DatabaseCommand_CollectionAttributes::exec( DatabaseImpl *lib )
+{
+ TomahawkSqlQuery query = lib->newquery();
+
+// QString sourceStr;
+// if ( source().isNull() )
+// sourceStr = "id IS NULL";
+// else
+// sourceStr = QString( "id == %1" ).arg( source()->id() );
+
+ QString typeStr;
+ if ( m_type == DatabaseCommand_SetCollectionAttributes::EchonestSongCatalog )
+ typeStr = "echonest_song";
+ else if ( m_type == DatabaseCommand_SetCollectionAttributes::EchonestArtistCatalog )
+ typeStr = "echonest_artist";
+
+ QString queryStr = QString( "SELECT k, v FROM collection_attributes WHERE k = \"%1\"" ).arg( typeStr );
+ qDebug() << "Doing queryL" << queryStr;
+ query.exec( queryStr );
+ PairList data;
+ while ( query.next() )
+ {
+ QPair< QString, QString > part;
+ part.first = query.value( 0 ).toString();
+ part.second = query.value( 1 ).toString();
+ data << part;
+ }
+ emit data;
+}
diff --git a/src/libtomahawk/database/databasecommand_collectionattributes.h b/src/libtomahawk/database/databasecommand_collectionattributes.h
new file mode 100644
index 000000000..0de1beeab
--- /dev/null
+++ b/src/libtomahawk/database/databasecommand_collectionattributes.h
@@ -0,0 +1,48 @@
+/* === This file is part of Tomahawk Player - ===
+ *
+ * Copyright 2010-2011, Leo Franchi
+ *
+ * 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 .
+ */
+
+#ifndef DATABASECOMMAND_ECHONESTCATALOG_H
+#define DATABASECOMMAND_ECHONESTCATALOG_H
+
+#include "typedefs.h"
+#include "databasecommand.h"
+#include "databasecommand_setcollectionattributes.h"
+#include
+
+typedef QList< QPair< QString, QString > > PairList;
+
+class DatabaseCommand_CollectionAttributes : public DatabaseCommand
+{
+ Q_OBJECT
+public:
+
+ DatabaseCommand_CollectionAttributes( DatabaseCommand_SetCollectionAttributes::AttributeType type );
+ virtual void exec( DatabaseImpl* lib );
+ virtual bool doesMutates() const { return false; }
+
+ virtual QString commandname() const { return "setcollectionattributes"; }
+
+signals:
+ void collectionAttributes( PairList );
+
+private:
+ DatabaseCommand_SetCollectionAttributes::AttributeType m_type;
+};
+
+Q_DECLARE_METATYPE(PairList)
+#endif
diff --git a/src/libtomahawk/database/databasecommand_genericselect.cpp b/src/libtomahawk/database/databasecommand_genericselect.cpp
index 8b151928c..9dddf783c 100644
--- a/src/libtomahawk/database/databasecommand_genericselect.cpp
+++ b/src/libtomahawk/database/databasecommand_genericselect.cpp
@@ -32,9 +32,18 @@ DatabaseCommand_GenericSelect::DatabaseCommand_GenericSelect( const QString& sql
, m_sqlSelect( sqlSelect )
, m_queryType( type )
, m_limit( limit )
+ , m_raw( false )
{
}
+DatabaseCommand_GenericSelect::DatabaseCommand_GenericSelect( const QString& sqlSelect, QueryType type, bool rawData, QObject* parent )
+ : DatabaseCommand( parent )
+ , m_sqlSelect( sqlSelect )
+ , m_queryType( type )
+ , m_limit( -1 )
+ , m_raw( rawData )
+{
+}
void
DatabaseCommand_GenericSelect::exec( DatabaseImpl* dbi )
@@ -48,6 +57,26 @@ DatabaseCommand_GenericSelect::exec( DatabaseImpl* dbi )
QList< artist_ptr > arts;
QList< album_ptr > albs;
+ if ( m_raw )
+ {
+ QList< QStringList > rawDataItems;
+
+ while( query.next() )
+ {
+
+ QStringList rawRow;
+ int count = 0;
+ while ( query.value( count ).isValid() )
+ {
+ rawRow << query.value( count ).toString();
+ ++count;
+ }
+ rawDataItems << rawRow;
+ }
+ emit rawData( rawDataItems );
+ return;
+ }
+
// Expecting
while ( query.next() )
{
diff --git a/src/libtomahawk/database/databasecommand_genericselect.h b/src/libtomahawk/database/databasecommand_genericselect.h
index 1a4f4d2f3..63d61fa6b 100644
--- a/src/libtomahawk/database/databasecommand_genericselect.h
+++ b/src/libtomahawk/database/databasecommand_genericselect.h
@@ -24,6 +24,9 @@
#include "databasecommand.h"
#include "typedefs.h"
+#include
+#include
+
#include "dllmacro.h"
/**
@@ -60,6 +63,7 @@ public:
};
explicit DatabaseCommand_GenericSelect( const QString& sqlSelect, QueryType type, int limitResults = -1, QObject* parent = 0 );
+ explicit DatabaseCommand_GenericSelect( const QString& sqlSelect, QueryType type, bool rawData, QObject* parent = 0 );
virtual void exec( DatabaseImpl* lib );
virtual bool doesMutates() const { return false; }
@@ -70,10 +74,14 @@ signals:
void artists( const QList< Tomahawk::artist_ptr >& artists );
void albums( const QList< Tomahawk::album_ptr >& albums );
+ void rawData( const QList< QStringList >& data );
private:
QString m_sqlSelect;
QueryType m_queryType;
int m_limit;
+ bool m_raw;
};
+Q_DECLARE_METATYPE(QList);
+
#endif // DATABASECOMMAND_GENERICSELECT_H
diff --git a/src/libtomahawk/database/databasecommand_setcollectionattributes.cpp b/src/libtomahawk/database/databasecommand_setcollectionattributes.cpp
new file mode 100644
index 000000000..5b13baf5b
--- /dev/null
+++ b/src/libtomahawk/database/databasecommand_setcollectionattributes.cpp
@@ -0,0 +1,50 @@
+/* === This file is part of Tomahawk Player - ===
+ *
+ * Copyright 2010-2011, Leo Franchi
+ *
+ * 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 .
+ */
+#include "DatabaseCommand_SetCollectionAttributes.h"
+
+#include "databaseimpl.h"
+#include "source.h"
+
+DatabaseCommand_SetCollectionAttributes::DatabaseCommand_SetCollectionAttributes( const Tomahawk::source_ptr& source, AttributeType type, const QByteArray& id )
+ : DatabaseCommandLoggable( source )
+ , m_id( id )
+ , m_type( type )
+{
+}
+
+void
+DatabaseCommand_SetCollectionAttributes::exec( DatabaseImpl *lib )
+{
+ TomahawkSqlQuery query = lib->newquery();
+
+ QString sourceStr;
+ if ( source().isNull() || source()->isLocal() )
+ sourceStr = "NULL";
+ else
+ sourceStr = QString( "%1" ).arg( source()->id() );
+
+ QString typeStr;
+ if ( m_type == EchonestSongCatalog )
+ typeStr = "echonest_song";
+ else if ( m_type == EchonestArtistCatalog )
+ typeStr = "echonest_artist";
+
+ QString queryStr = QString( "REPLACE 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
new file mode 100644
index 000000000..34d3c6016
--- /dev/null
+++ b/src/libtomahawk/database/databasecommand_setcollectionattributes.h
@@ -0,0 +1,45 @@
+/* === This file is part of Tomahawk Player - ===
+ *
+ * Copyright 2010-2011, Leo Franchi
+ *
+ * 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 .
+ */
+
+#ifndef DATABASECOMMAND_SAVEECHONESTCATALOG_H
+#define DATABASECOMMAND_SAVEECHONESTCATALOG_H
+
+#include "typedefs.h"
+#include "databasecommandloggable.h"
+#include
+
+class DatabaseCommand_SetCollectionAttributes : public DatabaseCommandLoggable
+{
+public:
+ enum AttributeType {
+ EchonestSongCatalog = 0,
+ EchonestArtistCatalog = 1
+ };
+
+ DatabaseCommand_SetCollectionAttributes( const Tomahawk::source_ptr& source, AttributeType type, const QByteArray& id );
+ virtual void exec( DatabaseImpl* lib );
+ virtual bool doesMutates() const { return true; }
+
+ virtual QString commandname() const { return "saveechonestcatalog"; }
+
+private:
+ AttributeType m_type;
+ QByteArray m_id;
+};
+
+#endif // DATABASECOMMAND_SAVEECHONESTCATALOG_H
diff --git a/src/libtomahawk/database/databaseimpl.cpp b/src/libtomahawk/database/databaseimpl.cpp
index 4a0983783..9064ccaa0 100644
--- a/src/libtomahawk/database/databaseimpl.cpp
+++ b/src/libtomahawk/database/databaseimpl.cpp
@@ -39,7 +39,7 @@
*/
#include "schema.sql.h"
-#define CURRENT_SCHEMA_VERSION 26
+#define CURRENT_SCHEMA_VERSION 27
DatabaseImpl::DatabaseImpl( const QString& dbname, Database* parent )
diff --git a/src/libtomahawk/database/schema.sql b/src/libtomahawk/database/schema.sql
index 7e2ff8daa..acef5cfd5 100644
--- a/src/libtomahawk/database/schema.sql
+++ b/src/libtomahawk/database/schema.sql
@@ -228,6 +228,14 @@ CREATE TABLE IF NOT EXISTS track_attributes (
CREATE INDEX track_attrib_id ON track_attributes(id);
CREATE INDEX track_attrib_k ON track_attributes(k);
+-- Collection attributes, tied to a source. An example might be an echonest song catalog
+
+CREATE TABLE IF NOT EXISTS collection_attributes (
+ id INTEGER REFERENCES source(id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, -- source id, null for local source
+ k TEXT NOT NULL,
+ v TEXT NOT NULL
+);
+
-- social attributes connected to the track.
-- like love, hate, comments, recommendations
-- love=[comment], hate=[comment], comment=Some text
@@ -283,4 +291,4 @@ CREATE TABLE IF NOT EXISTS settings (
v TEXT NOT NULL DEFAULT ''
);
-INSERT INTO settings(k,v) VALUES('schema_version', '26');
+INSERT INTO settings(k,v) VALUES('schema_version', '27');
diff --git a/src/libtomahawk/database/schema.sql.h b/src/libtomahawk/database/schema.sql.h
index 22168f55e..5b223db46 100644
--- a/src/libtomahawk/database/schema.sql.h
+++ b/src/libtomahawk/database/schema.sql.h
@@ -1,5 +1,5 @@
/*
- This file was automatically generated from schema.sql on Mon Jul 25 20:38:55 EDT 2011.
+ This file was automatically generated from ./schema.sql on Sun Sep 25 10:36:01 EDT 2011.
*/
static const char * tomahawk_schema_sql =
@@ -152,6 +152,11 @@ static const char * tomahawk_schema_sql =
");"
"CREATE INDEX track_attrib_id ON track_attributes(id);"
"CREATE INDEX track_attrib_k ON track_attributes(k);"
+"CREATE TABLE IF NOT EXISTS collection_attributes ("
+" id INTEGER REFERENCES source(id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, "
+" k TEXT NOT NULL,"
+" v TEXT NOT NULL"
+"};"
"CREATE TABLE IF NOT EXISTS social_attributes ("
" id INTEGER REFERENCES track(id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, "
" source INTEGER REFERENCES source(id) ON DELETE CASCADE ON UPDATE CASCADE, "
@@ -184,7 +189,7 @@ static const char * tomahawk_schema_sql =
" k TEXT NOT NULL PRIMARY KEY,"
" v TEXT NOT NULL DEFAULT ''"
");"
-"INSERT INTO settings(k,v) VALUES('schema_version', '26');"
+"INSERT INTO settings(k,v) VALUES('schema_version', '27');"
;
const char * get_tomahawk_sql()
diff --git a/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.cpp b/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.cpp
index ad828f0de..a6bd7be79 100644
--- a/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.cpp
+++ b/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.cpp
@@ -198,6 +198,29 @@ Tomahawk::EchonestControl::updateWidgets()
input->hide();
m_match = QWeakPointer< QWidget >( match );
m_input = QWeakPointer< QWidget >( input );
+ } else if( selectedType() == "Catalog Radio" ) {
+ m_currentType = Echonest::DynamicPlaylist::SourceCatalog;
+
+ QLabel* match = new QLabel( tr( "from user" ) );
+ QComboBox* combo = new QComboBox();
+
+ foreach( const QString& str, EchonestGenerator::userCatalogs() )
+ {
+ combo->addItem( str, EchonestGenerator::catalogId( str ) );
+ }
+ combo->addItem( "lfranchi@gmail.com", "CATNEQO132A15A6C7A" );
+
+ m_matchString = match->text();
+ m_matchData = match->text();
+
+
+ connect( combo, SIGNAL( activated( int ) ), this, SLOT( updateData() ) );
+ connect( combo, SIGNAL( activated( int ) ), this, SLOT( editingFinished() ) );
+
+ match->hide();
+ combo->hide();
+ m_match = QWeakPointer< QWidget >( match );
+ m_input = QWeakPointer< QWidget >( combo );
} else if( selectedType() == "Song" ) {
m_currentType = Echonest::DynamicPlaylist::SongId;
@@ -230,6 +253,27 @@ Tomahawk::EchonestControl::updateWidgets()
m_matchData = match->text();
+ connect( input->slider(), SIGNAL( valueChanged( int ) ), this, SLOT( updateData() ) );
+ connect( input->slider(), SIGNAL( valueChanged( int ) ), this, SLOT( editingFinished() ) );
+
+ match->hide();
+ input->hide();
+ m_match = QWeakPointer< QWidget >( match );
+ m_input = QWeakPointer< QWidget >( input );
+ } else if( selectedType() == "Adventurousness" ) {
+ m_currentType = Echonest::DynamicPlaylist::Adventurousness;
+
+ QLabel* match = new QLabel( tr( "is" ) );
+ LabeledSlider* input = new LabeledSlider( tr( "Less" ), tr( "More" ) );
+ input->slider()->setRange( 0, 10000 );
+ input->slider()->setTickInterval( 1 );
+ input->slider()->setTracking( false );
+ input->slider()->setValue( 10000 * .2 );
+
+ m_matchString = match->text();
+ m_matchData = match->text();
+
+
connect( input->slider(), SIGNAL( valueChanged( int ) ), this, SLOT( updateData() ) );
connect( input->slider(), SIGNAL( valueChanged( int ) ), this, SLOT( editingFinished() ) );
@@ -440,7 +484,7 @@ Tomahawk::EchonestControl::updateData()
m_data.first = m_currentType;
m_data.second = edit->text();
}
- } else if( selectedType() == "Variety" ) {
+ } else if( selectedType() == "Variety" || selectedType() == "Adventurousness" ) {
LabeledSlider* s = qobject_cast( m_input.data() );
if( s ) {
m_data.first = m_currentType;
@@ -450,7 +494,7 @@ Tomahawk::EchonestControl::updateData()
updateFromComboAndSlider();
} else if( selectedType() == "Danceability" || selectedType() == "Energy" || selectedType() == "Artist Familiarity" || selectedType() == "Artist Hotttnesss" || selectedType() == "Song Hotttnesss" ) {
updateFromComboAndSlider( true );
- } else if( selectedType() == "Mode" || selectedType() == "Key" || selectedType() == "Mood" || selectedType() == "Style" ) {
+ } else if( selectedType() == "Mode" || selectedType() == "Key" || selectedType() == "Mood" || selectedType() == "Style" || selectedType() == "Catalog Radio" ) {
updateFromLabelAndCombo();
} else if( selectedType() == "Sorting" ) {
QComboBox* match = qobject_cast( m_match.data() );
@@ -513,7 +557,7 @@ Tomahawk::EchonestControl::updateWidgetsFromData()
QLineEdit* edit = qobject_cast( m_input.data() );
if( edit )
edit->setText( m_data.second.toString() );
- } else if( selectedType() == "Variety" ) {
+ } else if( selectedType() == "Variety" || selectedType() == "Adventurousness" ) {
LabeledSlider* s = qobject_cast( m_input.data() );
if( s )
s->slider()->setValue( m_data.second.toDouble() * 10000 );
@@ -521,7 +565,7 @@ Tomahawk::EchonestControl::updateWidgetsFromData()
updateToComboAndSlider();
} else if( selectedType() == "Danceability" || selectedType() == "Energy" || selectedType() == "Artist Familiarity" || selectedType() == "Artist Hotttnesss" || selectedType() == "Song Hotttnesss" ) {
updateToComboAndSlider( true );
- } else if( selectedType() == "Mode" || selectedType() == "Key" || selectedType() == "Mood" || selectedType() == "Style" ) {
+ } else if( selectedType() == "Mode" || selectedType() == "Key" || selectedType() == "Mood" || selectedType() == "Style" || selectedType() == "Catalog Radio" ) {
updateToLabelAndCombo();
} else if( selectedType() == "Sorting" ) {
QComboBox* match = qobject_cast( m_match.data() );
diff --git a/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.cpp b/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.cpp
index 4fa8eab12..2a59c439b 100644
--- a/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.cpp
+++ b/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.cpp
@@ -21,6 +21,9 @@
#include "dynamic/echonest/EchonestSteerer.h"
#include "query.h"
#include "utils/tomahawkutils.h"
+#include "tomahawksettings.h"
+#include "database/databasecommand_collectionattributes.h"
+#include "database/database.h"
#include "utils/logger.h"
#include
#include
@@ -33,6 +36,9 @@ QStringList EchonestGenerator::s_styles = QStringList();
QNetworkReply* EchonestGenerator::s_moodsJob = 0;
QNetworkReply* EchonestGenerator::s_stylesJob = 0;
+bool EchonestGenerator::s_catalogsFetched = false;
+QHash< QString, QString > EchonestGenerator::s_catalogs = QHash< QString, QString >();
+
EchonestFactory::EchonestFactory()
{
@@ -56,9 +62,16 @@ EchonestFactory::createControl( const QString& controlType )
QStringList
EchonestFactory::typeSelectors() const
{
- return QStringList() << "Artist" << "Artist Description" << "Song" << "Mood" << "Style" << "Variety" << "Tempo" << "Duration" << "Loudness"
+ QStringList types = QStringList() << "Artist" << "Artist Description" << "Song" << "Mood" << "Style" << "Variety" << "Tempo" << "Duration" << "Loudness"
<< "Danceability" << "Energy" << "Artist Familiarity" << "Artist Hotttnesss" << "Song Hotttnesss"
<< "Longitude" << "Latitude" << "Mode" << "Key" << "Sorting";
+
+ if ( TomahawkSettings::instance()->enableEchonestCatalogs() )
+ {
+ types.insert( 2, "Catalog Radio" );
+ types.insert( 3, "Adventurousness" );
+ }
+ return types;
}
@@ -72,6 +85,17 @@ EchonestGenerator::EchonestGenerator ( QObject* parent )
m_logo.load( RESPATH "/images/echonest_logo.png" );
loadStylesAndMoods();
+ if ( s_catalogs.isEmpty() && TomahawkSettings::instance()->enableEchonestCatalogs() )
+ {
+ if ( !s_catalogsFetched )
+ {
+ QSharedPointer< DatabaseCommand > cmd( new DatabaseCommand_CollectionAttributes( DatabaseCommand_SetCollectionAttributes::EchonestSongCatalog ) );
+ connect( cmd.data(), SIGNAL(collectionAttributes(PairList)),
+ this, SLOT(collectionAttributes(PairList) ) );
+ Database::instance()->enqueue( cmd );
+ s_catalogsFetched = true;
+ }
+ }
// qDebug() << "ECHONEST:" << m_logo.size();
}
@@ -356,6 +380,27 @@ EchonestGenerator::resetSteering()
m_steerData.second = QString();
}
+void
+EchonestGenerator::collectionAttributes(PairList data)
+{
+ QPair part;
+ foreach ( part, data )
+ {
+ s_catalogs.insert( part.first, part.second );
+ }
+}
+
+QByteArray
+EchonestGenerator::catalogId(const QString &collectionId)
+{
+ return s_catalogs.value( collectionId ).toUtf8();
+}
+
+QStringList
+EchonestGenerator::userCatalogs()
+{
+ return s_catalogs.keys();
+}
bool
EchonestGenerator::onlyThisArtistType( Echonest::DynamicPlaylist::ArtistTypeEnum type ) const throw( std::runtime_error )
@@ -389,12 +434,17 @@ EchonestGenerator::appendRadioType( Echonest::DynamicPlaylist::PlaylistParams& p
*
*/
- /// 1. artist: If all the artist controls are Limit-To. If some were but not all, error out.
- /// 2. artist-description: If all the artist entries are Description. If some were but not all, error out.
- /// 3. artist-radio: If all the artist entries are Similar To. If some were but not all, error out.
- /// 4. song-radio: If all the artist entries are Similar To. If some were but not all, error out.
- if( onlyThisArtistType( Echonest::DynamicPlaylist::ArtistType ) )
- params.append( Echonest::DynamicPlaylist::PlaylistParamData( Echonest::DynamicPlaylist::Type, Echonest::DynamicPlaylist::ArtistType ) );
+ /// 1. catalog-radio: If any the entries are catalog types.
+ /// 2. artist: If all the artist controls are Limit-To. If some were but not all, error out.
+ /// 3. artist-description: If all the artist entries are Description. If some were but not all, error out.
+ /// 4. artist-radio: If all the artist entries are Similar To. If some were but not all, error out.
+ /// 5. song-radio: If all the artist entries are Similar To. If some were but not all, error out.
+ bool someCatalog = false;
+ foreach( const dyncontrol_ptr& control, m_controls ) {
+ someCatalog = true;
+ }
+ if( someCatalog )
+ params.append( Echonest::DynamicPlaylist::PlaylistParamData( Echonest::DynamicPlaylist::Type, Echonest::DynamicPlaylist::CatalogRadioType ) );
else if( onlyThisArtistType( Echonest::DynamicPlaylist::ArtistDescriptionType ) )
params.append( Echonest::DynamicPlaylist::PlaylistParamData( Echonest::DynamicPlaylist::Type, Echonest::DynamicPlaylist::ArtistDescriptionType ) );
else if( onlyThisArtistType( Echonest::DynamicPlaylist::ArtistRadioType ) )
diff --git a/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.h b/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.h
index 50d6b1963..90824ad78 100644
--- a/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.h
+++ b/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.h
@@ -25,6 +25,7 @@
#include "playlist/dynamic/GeneratorInterface.h"
#include "playlist/dynamic/GeneratorFactory.h"
#include "playlist/dynamic/DynamicControl.h"
+#include "database/databasecommand_collectionattributes.h"
#include "dllmacro.h"
@@ -61,6 +62,8 @@ public:
static QStringList styles();
static QStringList moods();
+ static QStringList userCatalogs();
+ static QByteArray catalogId( const QString& collectionId );
signals:
void paramsGenerated( const Echonest::DynamicPlaylist::PlaylistParams& );
@@ -80,6 +83,7 @@ private slots:
void stylesReceived();
void moodsReceived();
+ void collectionAttributes(PairList);
void songLookupFinished();
private:
@@ -101,6 +105,9 @@ private:
static QNetworkReply* s_stylesJob;
static QNetworkReply* s_moodsJob;
+ static bool s_catalogsFetched;
+ static QHash< QString, QString > s_catalogs;
+
// used for the intermediary song id lookup
QSet< QNetworkReply* > m_waiting;
Echonest::DynamicPlaylist::PlaylistParams m_storedParams;
diff --git a/src/libtomahawk/tomahawksettings.cpp b/src/libtomahawk/tomahawksettings.cpp
index 1aa88bcbb..48060e544 100644
--- a/src/libtomahawk/tomahawksettings.cpp
+++ b/src/libtomahawk/tomahawksettings.cpp
@@ -473,6 +473,20 @@ TomahawkSettings::setShowOfflineSources( bool show )
setValue( "collection/sources/showoffline", show );
}
+bool
+TomahawkSettings::enableEchonestCatalogs() const
+{
+ return value( "collection/enable_catalogs", false ).toBool();
+}
+
+void
+TomahawkSettings::setEnableEchonestCatalogs( bool enable )
+{
+ setValue( "collection/enable_catalogs", enable );
+
+ emit changed();
+}
+
QByteArray
TomahawkSettings::playlistColumnSizes( const QString& playlistid ) const
{
diff --git a/src/libtomahawk/tomahawksettings.h b/src/libtomahawk/tomahawksettings.h
index 9f518b21a..7d760d338 100644
--- a/src/libtomahawk/tomahawksettings.h
+++ b/src/libtomahawk/tomahawksettings.h
@@ -76,6 +76,9 @@ public:
bool showOfflineSources() const;
void setShowOfflineSources( bool show );
+ bool enableEchonestCatalogs() const;
+ void setEnableEchonestCatalogs( bool enable );
+
/// Playlist stuff
QByteArray playlistColumnSizes( const QString& playlistid ) const;
void setPlaylistColumnSizes( const QString& playlistid, const QByteArray& state );
diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp
index 7f2883973..14be34bf8 100644
--- a/src/settingsdialog.cpp
+++ b/src/settingsdialog.cpp
@@ -136,6 +136,8 @@ SettingsDialog::SettingsDialog( QWidget *parent )
ui->checkBoxWatchForChanges->setChecked( s->watchForChanges() );
ui->scannerTimeSpinBox->setValue( s->scannerTime() );
+ ui->enableEchonestCatalog->setChecked( s->enableEchonestCatalogs() );
+
connect( ui->checkBoxWatchForChanges, SIGNAL( clicked( bool ) ), SLOT( updateScanOptionsView() ) );
connect( ui->scannerDirModeButton, SIGNAL( clicked( bool ) ), SLOT( updateScanOptionsView() ) );
connect( ui->scannerFileModeButton, SIGNAL( clicked( bool ) ), SLOT( updateScanOptionsView() ) );
@@ -239,6 +241,7 @@ SettingsDialog::~SettingsDialog()
s->setWatchForChanges( ui->checkBoxWatchForChanges->isChecked() );
s->setScannerTime( ui->scannerTimeSpinBox->value() );
s->setScannerMode( ui->scannerFileModeButton->isChecked() ? TomahawkSettings::Files : TomahawkSettings::Dirs );
+ s->setEnableEchonestCatalogs( ui->enableEchonestCatalog->isChecked() );
s->setNowPlayingEnabled( ui->checkBoxEnableAdium->isChecked() );
@@ -510,7 +513,6 @@ SettingsDialog::onLastFmFinished()
#endif
}
-
void
SettingsDialog::addScriptResolver()
{
diff --git a/src/settingsdialog.h b/src/settingsdialog.h
index 384a90194..834ef3880 100644
--- a/src/settingsdialog.h
+++ b/src/settingsdialog.h
@@ -66,9 +66,6 @@ public:
explicit SettingsDialog( QWidget* parent = 0 );
~SettingsDialog();
-signals:
- void settingsChanged();
-
protected:
void changeEvent( QEvent* e );
diff --git a/src/stackedsettingsdialog.ui b/src/stackedsettingsdialog.ui
index 659166c75..09c16f832 100644
--- a/src/stackedsettingsdialog.ui
+++ b/src/stackedsettingsdialog.ui
@@ -211,6 +211,13 @@
-
+
-
+
+
+ Upload catalog to The Echo Nest to enable recommended radios.
+
+
+
-
diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp
index 2b89bda95..75b0fe6bc 100644
--- a/src/tomahawkapp.cpp
+++ b/src/tomahawkapp.cpp
@@ -56,6 +56,7 @@
#include "pipeline.h"
#include "utils/spotifyparser.h"
#include "dropjob.h"
+#include "EchonestCatalogSynchronizer.h"
#include "audio/audioengine.h"
#include "utils/xspfloader.h"
@@ -264,6 +265,8 @@ TomahawkApp::init()
// Make sure to init GAM in the gui thread
GlobalActionManager::instance();
+ // Set up echonest catalog synchronizer
+ Tomahawk::EchonestCatalogSynchronizer::instance();
// check if our spotify playlist api server is up and running, and enable spotify playlist drops if so
QNetworkReply* r = TomahawkUtils::nam()->get( QNetworkRequest( QUrl( SPOTIFY_PLAYLIST_API_URL "/playlist/test" ) ) );
connect( r, SIGNAL( finished() ), this, SLOT( spotifyApiCheckFinished() ) );