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