diff --git a/src/libtomahawk/Query.cpp b/src/libtomahawk/Query.cpp index 24b219d7d..444bace2e 100644 --- a/src/libtomahawk/Query.cpp +++ b/src/libtomahawk/Query.cpp @@ -459,14 +459,19 @@ Query::checkResults() bool -Query::equals( const Tomahawk::query_ptr& other ) const +Query::equals( const Tomahawk::query_ptr& other, bool ignoreCase ) const { if ( other.isNull() ) return false; - return ( artist() == other->artist() && - album() == other->album() && - track() == other->track() ); + if ( ignoreCase ) + return ( artist().toLower() == other->artist().toLower() && + album().toLower() == other->album().toLower() && + track().toLower() == other->track().toLower() ); + else + return ( artist() == other->artist() && + album() == other->album() && + track() == other->track() ); } @@ -502,7 +507,7 @@ Query::howSimilar( const Tomahawk::result_ptr& r ) Q_ASSERT( !r->album().isNull() ); if ( r->artist().isNull() || r->album().isNull() ) return 0.0; - + // result values const QString rArtistname = r->artist()->sortname(); const QString rAlbumname = r->album()->sortname(); diff --git a/src/libtomahawk/Query.h b/src/libtomahawk/Query.h index 62ee4e6ac..be8f3d613 100644 --- a/src/libtomahawk/Query.h +++ b/src/libtomahawk/Query.h @@ -122,7 +122,7 @@ public: void setAlbumPos( unsigned int albumpos ) { m_albumpos = albumpos; } void setDiscNumber( unsigned int discnumber ) { m_discnumber = discnumber; } - bool equals( const Tomahawk::query_ptr& other ) const; + bool equals( const Tomahawk::query_ptr& other, bool ignoreCase = false ) const; QVariant toVariant() const; QString toString() const; diff --git a/src/libtomahawk/accounts/lastfm/LastFmAccount.h b/src/libtomahawk/accounts/lastfm/LastFmAccount.h index f1b7ffc0c..ef4bbea3b 100644 --- a/src/libtomahawk/accounts/lastfm/LastFmAccount.h +++ b/src/libtomahawk/accounts/lastfm/LastFmAccount.h @@ -26,6 +26,7 @@ #include #include +#include namespace Tomahawk { class ExternalResolverGui; diff --git a/src/libtomahawk/accounts/lastfm/LastFmConfig.cpp b/src/libtomahawk/accounts/lastfm/LastFmConfig.cpp index aceea6bff..f6151d2c4 100644 --- a/src/libtomahawk/accounts/lastfm/LastFmConfig.cpp +++ b/src/libtomahawk/accounts/lastfm/LastFmConfig.cpp @@ -1,6 +1,6 @@ /* === This file is part of Tomahawk Player - === * - * Copyright 2010-2011, Leo Franchi + * Copyright 2010-2012, 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 @@ -22,11 +22,16 @@ #include "LastFmAccount.h" #include "database/Database.h" #include "database/DatabaseCommand_LogPlayback.h" +#include +#include #include "utils/TomahawkUtils.h" #include "utils/Logger.h" -#include "lastfm/ws.h" -#include "lastfm/User.h" -#include "lastfm/XmlQuery.h" +#include "utils/Closure.h" + +#include +#include +#include +#include using namespace Tomahawk::Accounts; @@ -36,6 +41,8 @@ LastFmConfig::LastFmConfig( LastFmAccount* account ) , m_account( account ) , m_page( 1 ) , m_lastTimeStamp( 0 ) + , m_totalLovedPages( -1 ) + , m_doneFetchingLoved( false ) { m_ui = new Ui_LastFmConfig; m_ui->setupUi( this ); @@ -48,6 +55,7 @@ LastFmConfig::LastFmConfig( LastFmAccount* account ) connect( m_ui->testLogin, SIGNAL( clicked( bool ) ), SLOT( testLogin() ) ); connect( m_ui->importHistory, SIGNAL( clicked( bool ) ), SLOT( loadHistory() ) ); + connect( m_ui->syncLovedTracks, SIGNAL( clicked( bool ) ), SLOT( syncLovedTracks() ) ); connect( m_ui->username, SIGNAL( textChanged( QString ) ), SLOT( enableButton() ) ); connect( m_ui->password, SIGNAL( textChanged( QString ) ), SLOT( enableButton() ) ); @@ -141,7 +149,7 @@ LastFmConfig::onHistoryLoaded() foreach ( lastfm::XmlQuery e, lfm.children( "track" ) ) { // tDebug() << "Found:" << e.children( "artist" ).first()["name"].text() << e["name"].text() << e["date"].attribute( "uts" ).toUInt(); - Tomahawk::query_ptr query = Query::get( e.children( "artist" ).first()["name"].text(), e["name"].text(), QString(), QString(), false ); + Tomahawk::query_ptr query = Tomahawk::Query::get( e.children( "artist" ).first()["name"].text(), e["name"].text(), QString(), QString(), false ); if ( query.isNull() ) continue; @@ -237,3 +245,149 @@ LastFmConfig::onLastFmFinished() } } } + + +void +LastFmConfig::syncLovedTracks( uint page ) +{ + QNetworkReply* reply = lastfm::User( username() ).getLovedTracks( 200, page ); + + m_ui->progressBar->show(); + + NewClosure( reply, SIGNAL( finished() ), this, SLOT( onLovedFinished( QNetworkReply* ) ), reply ); + + DatabaseCommand_LoadSocialActions* cmd = new DatabaseCommand_LoadSocialActions( "Love", SourceList::instance()->getLocal() ); + connect( cmd, SIGNAL( done( DatabaseCommand_LoadSocialActions::TrackActions ) ), this, SLOT( localLovedLoaded( DatabaseCommand_LoadSocialActions::TrackActions ) ) ); + + Database::instance()->enqueue( QSharedPointer< DatabaseCommand >( cmd ) ); + +} + + +void +LastFmConfig::onLovedFinished( QNetworkReply* reply ) +{ + Q_ASSERT( reply ); + + try + { + lastfm::XmlQuery lfm; + lfm.parse( reply->readAll() ); + + if ( !lfm.children( "lovedtracks" ).isEmpty() ) + { + lastfm::XmlQuery loved = lfm.children( "lovedtracks" ).first(); + + const int thisPage = loved.attribute( "page" ).toInt(); + + if ( m_totalLovedPages < 0 ) + { + m_totalLovedPages = loved.attribute( "totalPages" ).toInt(); + m_ui->progressBar->setMaximum( m_totalLovedPages ); + } + + m_ui->progressBar->setValue( thisPage ); + foreach ( lastfm::XmlQuery e, loved.children( "track" ) ) + { +// tDebug() << "Found:" << e.children( "artist" ).first()["name"].text() << e["name"].text() << e["date"].attribute( "uts" ).toUInt(); + Tomahawk::query_ptr query = Tomahawk::Query::get( e.children( "artist" ).first()["name"].text(), e["name"].text(), QString(), QString(), false ); + if ( query.isNull() ) + continue; + + m_lastfmLoved.insert( query ); + } + + + if ( thisPage == m_totalLovedPages ) + { + m_doneFetchingLoved = true; + + if ( !m_localLoved.isEmpty() ) + syncLoved(); + + return; + } + else + { + syncLovedTracks( thisPage + 1 ); + } + } + } + catch( lastfm::ws::ParseError e ) + { + tDebug() << "XmlQuery error:" << e.message(); + } +} + + +bool trackEquality( const Tomahawk::query_ptr& first, const Tomahawk::query_ptr& second ) +{ + return first->equals( second, true ); +} + + +void +LastFmConfig::localLovedLoaded( DatabaseCommand_LoadSocialActions::TrackActions tracks ) +{ + m_localLoved = tracks; + + if ( m_doneFetchingLoved ) + syncLoved(); +} + + +void +LastFmConfig::syncLoved() +{ + QSet< Tomahawk::query_ptr > localToLove, lastFmToLove, lastFmToUnlove; + + const QSet< Tomahawk::query_ptr > myLoved = m_localLoved.keys().toSet(); + + foreach ( const Tomahawk::query_ptr& lastfmLoved, m_lastfmLoved ) + { + QSet< Tomahawk::query_ptr >::const_iterator iter = std::find_if( myLoved.begin(), myLoved.end(), boost::bind( &trackEquality, _1, boost::ref( lastfmLoved ) ) ); + if ( iter == myLoved.constEnd() ) + { +// qDebug() << "Found last.fm loved track that we didn't have loved locally:" << lastfmLoved->track() << lastfmLoved->artist(); + localToLove << lastfmLoved; + } + } + + foreach ( const Tomahawk::query_ptr& localLoved, myLoved ) + { + QSet< Tomahawk::query_ptr >::const_iterator iter = std::find_if( m_lastfmLoved.begin(), m_lastfmLoved.end(), boost::bind( &trackEquality, _1, boost::ref( localLoved ) ) ); + + // If we unloved it locally, but it's still loved on last.fm, unlove it + if ( m_localLoved[ localLoved ].value.toString() == "false" && iter != m_lastfmLoved.constEnd() ) + lastFmToUnlove << localLoved; + + // If we loved it locally but not loved on last.fm, love it + if ( m_localLoved[ localLoved ].value.toString() == "true" && iter == m_lastfmLoved.constEnd() ) + lastFmToLove << localLoved; + } + + foreach ( const Tomahawk::query_ptr& track, localToLove ) + { + // Don't use the infosystem as we don't want to tweet a few hundred times :) + DatabaseCommand_SocialAction* cmd = new DatabaseCommand_SocialAction( track, QString( "Love" ), QString( "true" ) ); + Database::instance()->enqueue( QSharedPointer(cmd) ); + } + + lastFmToLove.unite( lastFmToUnlove ); + + foreach ( const Tomahawk::query_ptr& track, lastFmToLove ) + { + lastfm::MutableTrack lfmTrack; + lfmTrack.stamp(); + + lfmTrack.setTitle( track->track() ); + lfmTrack.setArtist( track->artist() ); + lfmTrack.setSource( lastfm::Track::Player ); + + if ( lastFmToUnlove.contains( track ) ) + lfmTrack.unlove(); + else + lfmTrack.love(); + } +} + diff --git a/src/libtomahawk/accounts/lastfm/LastFmConfig.h b/src/libtomahawk/accounts/lastfm/LastFmConfig.h index c7c53f2b9..704eb58ad 100644 --- a/src/libtomahawk/accounts/lastfm/LastFmConfig.h +++ b/src/libtomahawk/accounts/lastfm/LastFmConfig.h @@ -1,6 +1,6 @@ /* === This file is part of Tomahawk Player - === * - * Copyright 2010-2011, Leo Franchi + * Copyright 2010-2012, 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 @@ -19,7 +19,12 @@ #ifndef LASTFMCONFIG_H #define LASTFMCONFIG_H +#include "Query.h" +#include "database/DatabaseCommand_LoadSocialActions.h" + #include +#include +#include class Ui_LastFmConfig; @@ -45,19 +50,31 @@ public slots: private slots: void enableButton(); - + void loadHistory(); void onHistoryLoaded(); + void syncLovedTracks() { syncLovedTracks( 1 ); } + void syncLovedTracks( uint page ); + void onLovedFinished( QNetworkReply* reply ); + void localLovedLoaded( DatabaseCommand_LoadSocialActions::TrackActions ); + signals: void sizeHintChanged(); private: + void syncLoved(); + LastFmAccount* m_account; Ui_LastFmConfig* m_ui; - + unsigned int m_page; unsigned int m_lastTimeStamp; + + int m_totalLovedPages; + bool m_doneFetchingLoved; + QSet< Tomahawk::query_ptr > m_lastfmLoved; + DatabaseCommand_LoadSocialActions::TrackActions m_localLoved; }; } diff --git a/src/libtomahawk/accounts/lastfm/LastFmConfig.ui b/src/libtomahawk/accounts/lastfm/LastFmConfig.ui index 1a75ae970..20a07ced7 100644 --- a/src/libtomahawk/accounts/lastfm/LastFmConfig.ui +++ b/src/libtomahawk/accounts/lastfm/LastFmConfig.ui @@ -23,7 +23,7 @@ - :/data/images/lastfm-icon.png + :/data/images/lastfm-icon.png Qt::AlignCenter @@ -84,6 +84,13 @@ + + + + Synchronize Loved Tracks + + + diff --git a/src/libtomahawk/database/DatabaseCommand_LoadSocialActions.cpp b/src/libtomahawk/database/DatabaseCommand_LoadSocialActions.cpp index 49e6b7e25..cc53e781f 100644 --- a/src/libtomahawk/database/DatabaseCommand_LoadSocialActions.cpp +++ b/src/libtomahawk/database/DatabaseCommand_LoadSocialActions.cpp @@ -37,40 +37,74 @@ DatabaseCommand_LoadSocialActions::exec( DatabaseImpl* dbi ) TomahawkSqlQuery query = dbi->newquery(); - QVariant srcid = source()->isLocal() ? QVariant( QVariant::Int ) : source()->id(); - - int artid = dbi->artistId( m_artist, false ); - if( artid < 1 ) - return; - - int trkid = dbi->trackId( artid, m_track, false ); - if( trkid < 1 ) - return; - - QString whereToken; - whereToken = QString( "WHERE id IS %1" ).arg( trkid ); - - QString sql = QString( - "SELECT k, v, timestamp, source " - "FROM social_attributes %1 " - "ORDER BY timestamp ASC" ).arg( whereToken ); - - query.prepare( sql ); - query.exec(); - - QList< Tomahawk::SocialAction > allSocialActions; - while ( query.next() ) + if ( m_actionOnly.isNull() ) { - Tomahawk::SocialAction action; - action.action = query.value( 0 ); // action - action.value = query.value( 1 ); // comment - action.timestamp = query.value( 2 ); // timestamp - action.source = SourceList::instance()->get( query.value( 3 ).toInt() ); // source - - if ( !action.source.isNull() ) - allSocialActions.append( action ); - } + // Load for just specified track + int artid = dbi->artistId( m_artist, false ); + if( artid < 1 ) + return; - m_query->setAllSocialActions( allSocialActions ); + int trkid = dbi->trackId( artid, m_track, false ); + if( trkid < 1 ) + return; + + QString whereToken; + whereToken = QString( "WHERE id IS %1" ).arg( trkid ); + + QString sql = QString( + "SELECT k, v, timestamp, source " + "FROM social_attributes %1 " + "ORDER BY timestamp ASC" ).arg( whereToken ); + + query.prepare( sql ); + query.exec(); + + QList< Tomahawk::SocialAction > allSocialActions; + while ( query.next() ) + { + Tomahawk::SocialAction action; + action.action = query.value( 0 ); // action + action.value = query.value( 1 ); // comment + action.timestamp = query.value( 2 ); // timestamp + action.source = SourceList::instance()->get( query.value( 3 ).toInt() ); // source + + if ( !action.source.isNull() ) + allSocialActions.append( action ); + } + + m_query->setAllSocialActions( allSocialActions ); + } + else + { + // Load all tracks with this social action + const QString srcStr = source()->isLocal() ? "IS NULL" : QString( "=%1" ).arg( source()->id() ); + + query.prepare( QString( "SELECT id, v, timestamp FROM social_attributes WHERE source %1 AND k = ? " ).arg( srcStr ) ); + query.addBindValue( m_actionOnly ); + + query.exec(); + + DatabaseCommand_LoadSocialActions::TrackActions trackActions; + while ( query.next() ) + { + const QVariantMap track = dbi->track( query.value( 0 ).toInt() ); + if ( track.value( "artist" ).toString().isEmpty() || track.value( "name" ).toString().isEmpty() ) + continue; + + const QVariantMap artist = dbi->artist( track.value( "artist" ).toInt() ); + + const query_ptr trackQuery = Query::get( artist.value( "name" ).toString(), track.value( "name" ).toString(), QString(), QString(), false ); + + Tomahawk::SocialAction action; + action.action = m_actionOnly; // action + action.value = query.value( 1 ); // comment + action.timestamp = query.value( 2 ); // timestamp + action.source = source(); // source + + trackActions[ trackQuery ] = action; + } + + emit done( trackActions ); + } } diff --git a/src/libtomahawk/database/DatabaseCommand_LoadSocialActions.h b/src/libtomahawk/database/DatabaseCommand_LoadSocialActions.h index 8656823b3..2eca2c3a8 100644 --- a/src/libtomahawk/database/DatabaseCommand_LoadSocialActions.h +++ b/src/libtomahawk/database/DatabaseCommand_LoadSocialActions.h @@ -1,6 +1,7 @@ /* === This file is part of Tomahawk Player - === * * Copyright 2011, Christopher Reichert + * Copyright 2012, 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 @@ -45,6 +46,7 @@ class DLLEXPORT DatabaseCommand_LoadSocialActions : public DatabaseCommand Q_OBJECT public: + typedef QMap TrackActions; /** * \brief Default constructor for DatabaseCommand_LoadSocialActions. * @@ -69,6 +71,16 @@ public: setTrack( query->track() ); } + /** + * Load all tracks with a specific social action + */ + explicit DatabaseCommand_LoadSocialActions( const QString& action, const Tomahawk::source_ptr& source, QObject* parent = 0 ) + : DatabaseCommand( parent ), m_actionOnly( action ) + { + setSource( source ); + qRegisterMetaType( "DatabaseCommand_LoadSocialAction::TrackActions" ); + } + /** * \brief Returns the name of this database command. * \return QString containing the database command name 'loadsocialaction'. @@ -115,20 +127,20 @@ public: void setTrack( const QString& s ) { m_track = s; } signals: - /** - * \brief Emitted when the database command has finished the Query successfully - * - * \param QList of all social actions - * \see QList + * All loaded social actions for each track found, for queries that generate all tracks + * with matching actions. */ - void done( QList< Tomahawk::SocialAction >& allSocialActions ); + void done( DatabaseCommand_LoadSocialActions::TrackActions actionsForTracks ); private: Tomahawk::query_ptr m_query; QString m_artist; QString m_track; + QString m_actionOnly; }; +Q_DECLARE_METATYPE( DatabaseCommand_LoadSocialActions::TrackActions ) + #endif // DATABASECOMMAND_LOADSOCIALACTIONS_H