From 625dc0304ba17189e5578a27c4112027e9f391f1 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Fri, 30 Sep 2011 15:35:38 -0400 Subject: [PATCH] Handle catalogs more gracefully in dynamic playlists Standardize some code Propertify Misc catalog fixes Clean up by hacking --- .../EchonestCatalogSynchronizer.cpp | 9 +- src/libtomahawk/EchonestCatalogSynchronizer.h | 3 + ...atabasecommand_setcollectionattributes.cpp | 8 +- .../databasecommand_setcollectionattributes.h | 6 +- .../dynamic/echonest/EchonestControl.cpp | 51 +++++++++- .../dynamic/echonest/EchonestControl.h | 1 + .../dynamic/echonest/EchonestGenerator.cpp | 99 ++++++++++++------- .../dynamic/echonest/EchonestGenerator.h | 24 ++++- 8 files changed, 156 insertions(+), 45 deletions(-) diff --git a/src/libtomahawk/EchonestCatalogSynchronizer.cpp b/src/libtomahawk/EchonestCatalogSynchronizer.cpp index d21fa915b..5601e474d 100644 --- a/src/libtomahawk/EchonestCatalogSynchronizer.cpp +++ b/src/libtomahawk/EchonestCatalogSynchronizer.cpp @@ -369,5 +369,12 @@ EchonestCatalogSynchronizer::trackAttributes( PairList attributes ) QByteArray EchonestCatalogSynchronizer::escape( const QString &in ) const { - return QUrl::toPercentEncoding( in ); + // TODO echonest chokes on some chars in the output. But if we percent-encode those chars it works + // We can't percent-encode the whole string, because then any UTF-8 chars that have been url-encoded, fail. + // God this sucks. It's going to break... + QString clean = in; + clean.replace( "&", "%25" ); + clean.replace( ";", "%3B" ); + return clean.toUtf8(); + //return QUrl::toPercentEncoding( in. ); } diff --git a/src/libtomahawk/EchonestCatalogSynchronizer.h b/src/libtomahawk/EchonestCatalogSynchronizer.h index 5eda0ddcd..f76ba2088 100644 --- a/src/libtomahawk/EchonestCatalogSynchronizer.h +++ b/src/libtomahawk/EchonestCatalogSynchronizer.h @@ -50,6 +50,7 @@ public: Echonest::Catalog artistCatalog() const { return m_artistCatalog; } signals: + void knownCatalogsChanged(); private slots: void checkSettingsChanged(); @@ -83,6 +84,8 @@ private: QQueue< QList< QPair< QID, QString > > > m_queuedTrackInfo; static EchonestCatalogSynchronizer* s_instance; + + friend class ::DatabaseCommand_SetCollectionAttributes; }; } diff --git a/src/libtomahawk/database/databasecommand_setcollectionattributes.cpp b/src/libtomahawk/database/databasecommand_setcollectionattributes.cpp index 9e930397f..844dbcc89 100644 --- a/src/libtomahawk/database/databasecommand_setcollectionattributes.cpp +++ b/src/libtomahawk/database/databasecommand_setcollectionattributes.cpp @@ -21,6 +21,7 @@ #include "source.h" #include "network/servent.h" #include "sourcelist.h" +#include "EchonestCatalogSynchronizer.h" DatabaseCommand_SetCollectionAttributes::DatabaseCommand_SetCollectionAttributes( AttributeType type, const QByteArray& id ) : DatabaseCommandLoggable( ) @@ -72,5 +73,10 @@ DatabaseCommand_SetCollectionAttributes::exec( DatabaseImpl *lib ) void DatabaseCommand_SetCollectionAttributes::postCommitHook() { - Servent::instance()->triggerDBSync(); + if ( m_type == EchonestSongCatalog || + m_type == EchonestArtistCatalog ) + Tomahawk::EchonestCatalogSynchronizer::instance()->knownCatalogsChanged(); + + if ( source()->isLocal() ) + Servent::instance()->triggerDBSync(); } diff --git a/src/libtomahawk/database/databasecommand_setcollectionattributes.h b/src/libtomahawk/database/databasecommand_setcollectionattributes.h index 41de403e9..c7f5b7ab8 100644 --- a/src/libtomahawk/database/databasecommand_setcollectionattributes.h +++ b/src/libtomahawk/database/databasecommand_setcollectionattributes.h @@ -28,6 +28,7 @@ class DatabaseCommand_SetCollectionAttributes : public DatabaseCommandLoggable Q_OBJECT Q_PROPERTY( QByteArray id READ id WRITE setId ) Q_PROPERTY( int type READ type WRITE setType ) + Q_PROPERTY( bool del READ del WRITE setDel ) public: enum AttributeType { @@ -38,8 +39,7 @@ public: DatabaseCommand_SetCollectionAttributes( AttributeType type, const QByteArray& id ); // Delete all attributes for the source+type DatabaseCommand_SetCollectionAttributes( AttributeType type, bool toDelete ); - - DatabaseCommand_SetCollectionAttributes() {} // JSON + DatabaseCommand_SetCollectionAttributes() : m_delete( false ) {} // JSON virtual void exec( DatabaseImpl* lib ); virtual bool doesMutates() const { return true; } virtual void postCommitHook(); @@ -52,6 +52,8 @@ public: void setType( int type ) { m_type = (AttributeType)type; } int type() const { return (int)m_type; } + void setDel( bool del ) { m_delete = del; } + bool del() const { return m_delete; } private: bool m_delete; AttributeType m_type; diff --git a/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.cpp b/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.cpp index 732919005..b677cf753 100644 --- a/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.cpp +++ b/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.cpp @@ -29,6 +29,7 @@ #include "EchonestGenerator.h" #include "utils/logger.h" +#include QHash< QString, QStringList > Tomahawk::EchonestControl::s_suggestCache = QHash< QString, QStringList >(); @@ -198,7 +199,7 @@ Tomahawk::EchonestControl::updateWidgets() input->hide(); m_match = QWeakPointer< QWidget >( match ); m_input = QWeakPointer< QWidget >( input ); - } else if( selectedType() == "Catalog Radio" ) { + } else if( selectedType() == "User Radio" ) { m_currentType = Echonest::DynamicPlaylist::SourceCatalog; QLabel* match = new QLabel( tr( "from user" ) ); @@ -209,6 +210,12 @@ Tomahawk::EchonestControl::updateWidgets() combo->addItem( str, EchonestGenerator::catalogId( str ) ); } + if ( EchonestGenerator::userCatalogs().isEmpty() ) + combo->addItem( tr( "No users with Echo Nest Catalogs enabled. Try enabling option in Collection settings" ) ); + + if ( combo->findData( m_data.second ) < 0 ) + combo->setCurrentIndex( 0 ); + m_matchString = match->text(); m_matchData = match->text(); @@ -493,7 +500,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" || selectedType() == "Catalog Radio" ) { + } else if( selectedType() == "Mode" || selectedType() == "Key" || selectedType() == "Mood" || selectedType() == "Style" || selectedType() == "User Radio" ) { updateFromLabelAndCombo(); } else if( selectedType() == "Sorting" ) { QComboBox* match = qobject_cast( m_match.data() ); @@ -556,6 +563,26 @@ Tomahawk::EchonestControl::updateWidgetsFromData() QLineEdit* edit = qobject_cast( m_input.data() ); if( edit ) edit->setText( m_data.second.toString() ); + } else if ( selectedType() == "User Radio" ) + { + QComboBox* combo = qobject_cast< QComboBox* >( m_input.data() ); + if ( combo ) + { + combo->clear(); + + foreach( const QString& str, EchonestGenerator::userCatalogs() ) + { + combo->addItem( str, EchonestGenerator::catalogId( str ) ); + } + + if ( EchonestGenerator::userCatalogs().isEmpty() ) + combo->addItem( tr( "No users with Echo Nest Catalogs enabled. Try enabling option in Collection settings" ) ); + + if ( combo->findData( m_data.second ) < 0 ) + combo->setCurrentIndex( 0 ); + + combo->setCurrentIndex( combo->findData( m_data.second ) ); + } } else if( selectedType() == "Variety" || selectedType() == "Adventurousness" ) { LabeledSlider* s = qobject_cast( m_input.data() ); if( s ) @@ -564,7 +591,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" || selectedType() == "Catalog Radio" ) { + } else if( selectedType() == "Mode" || selectedType() == "Key" || selectedType() == "Mood" || selectedType() == "Style") { updateToLabelAndCombo(); } else if( selectedType() == "Sorting" ) { QComboBox* match = qobject_cast( m_match.data() ); @@ -714,6 +741,24 @@ Tomahawk::EchonestControl::calculateSummary() summary = QString( "similar to ~%1" ).arg( m_data.second.toString() ); } else if( selectedType() == "Artist Description" ) { summary = QString( "with genre ~%1" ).arg( m_data.second.toString() ); + } else if( selectedType() == "User Radio" ) { + QComboBox* b = qobject_cast< QComboBox* >( m_input.data() ); + if ( b ) + { + if ( b->currentText().isEmpty() || b->itemData( b->currentIndex() ).isNull() ) + summary = "from no one"; + else + { + QString subSum; + if ( b->currentText() == "My Collection" ) + subSum = "my"; + else + subSum = b->currentText(); + summary = QString( "from %1 radio" ).arg( subSum ); + } + } + else + summary = "from no one"; } else if( selectedType() == "Artist Description" || selectedType() == "Song" ) { summary = QString( "similar to ~%1" ).arg( m_data.second.toString() ); } else if( selectedType() == "Variety" || selectedType() == "Danceability" || selectedType() == "Artist Hotttnesss" || selectedType() == "Energy" || selectedType() == "Artist Familiarity" || selectedType() == "Song Hotttnesss" ) { diff --git a/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.h b/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.h index 03a06114a..f4df5eb61 100644 --- a/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.h +++ b/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.h @@ -22,6 +22,7 @@ #include #include "dynamic/DynamicControl.h" + #include namespace Tomahawk diff --git a/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.cpp b/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.cpp index 24efe9d87..388b6a602 100644 --- a/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.cpp +++ b/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.cpp @@ -28,6 +28,7 @@ #include "sourcelist.h" #include #include +#include using namespace Tomahawk; @@ -37,8 +38,7 @@ 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 >(); +CatalogManager* EchonestGenerator::s_catalogs = 0; EchonestFactory::EchonestFactory() @@ -63,18 +63,54 @@ EchonestFactory::createControl( const QString& controlType ) QStringList EchonestFactory::typeSelectors() const { - QStringList types = QStringList() << "Artist" << "Artist Description" << "Song" << "Mood" << "Style" << "Variety" << "Tempo" << "Duration" << "Loudness" + QStringList types = QStringList() << "Artist" << "Artist Description" << "User Radio" << "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; } +CatalogManager::CatalogManager( QObject* parent ) + : QObject( parent ) +{ + connect( EchonestCatalogSynchronizer::instance(), SIGNAL( knownCatalogsChanged() ), this, SLOT( doCatalogUpdate() ) ); + connect( SourceList::instance(), SIGNAL( ready() ), this, SLOT( doCatalogUpdate() ) ); + + doCatalogUpdate(); +} + +void +CatalogManager::collectionAttributes( const PairList& data ) +{ + QPair part; + m_catalogs.clear(); + + foreach ( part, data ) + { + if ( SourceList::instance()->get( part.first.toInt() ).isNull() ) + continue; + + const QString name = SourceList::instance()->get( part.first.toInt() )->friendlyName(); + m_catalogs.insert( name, part.second ); + } + + emit catalogsUpdated(); +} + +void +CatalogManager::doCatalogUpdate() +{ + QSharedPointer< DatabaseCommand > cmd( new DatabaseCommand_CollectionAttributes( DatabaseCommand_SetCollectionAttributes::EchonestSongCatalog ) ); + connect( cmd.data(), SIGNAL( collectionAttributes( PairList ) ), this, SLOT( collectionAttributes( PairList ) ) ); + Database::instance()->enqueue( cmd ); +} + +QHash< QString, QString > +CatalogManager::catalogs() const +{ + return m_catalogs; +} + EchonestGenerator::EchonestGenerator ( QObject* parent ) : GeneratorInterface ( parent ) @@ -86,17 +122,13 @@ 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; - } - } + + // TODO Yes this is a race condition. If multiple threads initialize echonestgenerator at the exact same time we could run into some issues. + // not dealing with that right now. + if ( s_catalogs == 0 ) + s_catalogs = new CatalogManager( this ); + + connect( s_catalogs, SIGNAL( catalogsUpdated() ), this, SLOT( knownCatalogsChanged() ) ); // qDebug() << "ECHONEST:" << m_logo.size(); } @@ -120,6 +152,16 @@ QPixmap EchonestGenerator::logo() return m_logo; } +void +EchonestGenerator::knownCatalogsChanged() +{ + // Refresh all contrls + foreach( const dyncontrol_ptr& control, m_controls ) + { + control.staticCast< EchonestControl >()->updateWidgetsFromData(); + } +} + void EchonestGenerator::generate( int number ) @@ -381,30 +423,17 @@ EchonestGenerator::resetSteering() m_steerData.second = QString(); } -void -EchonestGenerator::collectionAttributes(PairList data) -{ - QPair part; - foreach ( part, data ) - { - if ( SourceList::instance()->get( part.first.toInt() ).isNull() ) - continue; - - const QString name = SourceList::instance()->get( part.first.toInt() )->friendlyName(); - s_catalogs.insert( name, part.second ); - } -} QByteArray EchonestGenerator::catalogId(const QString &collectionId) { - return s_catalogs.value( collectionId ).toUtf8(); + return s_catalogs->catalogs().value( collectionId ).toUtf8(); } QStringList EchonestGenerator::userCatalogs() { - return s_catalogs.keys(); + return s_catalogs->catalogs().keys(); } bool @@ -446,7 +475,7 @@ EchonestGenerator::appendRadioType( Echonest::DynamicPlaylist::PlaylistParams& p /// 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 ) { - if ( control->selectedType() == "Catalog Radio" ) + if ( control->selectedType() == "User Radio" ) someCatalog = true; } if( someCatalog ) diff --git a/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.h b/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.h index 90824ad78..b4152b56d 100644 --- a/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.h +++ b/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.h @@ -34,6 +34,25 @@ namespace Tomahawk class EchonestSteerer; +class CatalogManager : public QObject +{ + Q_OBJECT +public: + CatalogManager( QObject* parent ); + + QHash< QString, QString > catalogs() const; + +signals: + void catalogsUpdated(); + +private slots: + void doCatalogUpdate(); + void collectionAttributes( const PairList& ); + +private: + QHash< QString, QString > m_catalogs; +}; + class DLLEXPORT EchonestFactory : public GeneratorFactoryInterface { public: @@ -83,7 +102,7 @@ private slots: void stylesReceived(); void moodsReceived(); - void collectionAttributes(PairList); + void knownCatalogsChanged(); void songLookupFinished(); private: @@ -105,8 +124,7 @@ private: static QNetworkReply* s_stylesJob; static QNetworkReply* s_moodsJob; - static bool s_catalogsFetched; - static QHash< QString, QString > s_catalogs; + static CatalogManager* s_catalogs; // used for the intermediary song id lookup QSet< QNetworkReply* > m_waiting;