1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-07-31 11:20:22 +02:00

Initial last.fm loved track syncing

This commit is contained in:
Leo Franchi
2012-07-30 20:53:18 -04:00
parent a70f14e7ab
commit 26509493a2
8 changed files with 284 additions and 54 deletions

View File

@@ -459,14 +459,19 @@ Query::checkResults()
bool bool
Query::equals( const Tomahawk::query_ptr& other ) const Query::equals( const Tomahawk::query_ptr& other, bool ignoreCase ) const
{ {
if ( other.isNull() ) if ( other.isNull() )
return false; return false;
return ( artist() == other->artist() && if ( ignoreCase )
album() == other->album() && return ( artist().toLower() == other->artist().toLower() &&
track() == other->track() ); 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() ); Q_ASSERT( !r->album().isNull() );
if ( r->artist().isNull() || r->album().isNull() ) if ( r->artist().isNull() || r->album().isNull() )
return 0.0; return 0.0;
// result values // result values
const QString rArtistname = r->artist()->sortname(); const QString rArtistname = r->artist()->sortname();
const QString rAlbumname = r->album()->sortname(); const QString rAlbumname = r->album()->sortname();

View File

@@ -122,7 +122,7 @@ public:
void setAlbumPos( unsigned int albumpos ) { m_albumpos = albumpos; } void setAlbumPos( unsigned int albumpos ) { m_albumpos = albumpos; }
void setDiscNumber( unsigned int discnumber ) { m_discnumber = discnumber; } 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; QVariant toVariant() const;
QString toString() const; QString toString() const;

View File

@@ -26,6 +26,7 @@
#include <attica/content.h> #include <attica/content.h>
#include <QObject> #include <QObject>
#include <QSet>
namespace Tomahawk { namespace Tomahawk {
class ExternalResolverGui; class ExternalResolverGui;

View File

@@ -1,6 +1,6 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> === /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
* *
* Copyright 2010-2011, Leo Franchi <lfranchi@kde.org> * Copyright 2010-2012, Leo Franchi <lfranchi@kde.org>
* *
* Tomahawk is free software: you can redistribute it and/or modify * Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -22,11 +22,16 @@
#include "LastFmAccount.h" #include "LastFmAccount.h"
#include "database/Database.h" #include "database/Database.h"
#include "database/DatabaseCommand_LogPlayback.h" #include "database/DatabaseCommand_LogPlayback.h"
#include <database/DatabaseCommand_LoadSocialActions.h>
#include <database/DatabaseCommand_SocialAction.h>
#include "utils/TomahawkUtils.h" #include "utils/TomahawkUtils.h"
#include "utils/Logger.h" #include "utils/Logger.h"
#include "lastfm/ws.h" #include "utils/Closure.h"
#include "lastfm/User.h"
#include "lastfm/XmlQuery.h" #include <lastfm/ws.h>
#include <lastfm/User.h>
#include <lastfm/XmlQuery.h>
#include <lastfm/Track.h>
using namespace Tomahawk::Accounts; using namespace Tomahawk::Accounts;
@@ -36,6 +41,8 @@ LastFmConfig::LastFmConfig( LastFmAccount* account )
, m_account( account ) , m_account( account )
, m_page( 1 ) , m_page( 1 )
, m_lastTimeStamp( 0 ) , m_lastTimeStamp( 0 )
, m_totalLovedPages( -1 )
, m_doneFetchingLoved( false )
{ {
m_ui = new Ui_LastFmConfig; m_ui = new Ui_LastFmConfig;
m_ui->setupUi( this ); m_ui->setupUi( this );
@@ -48,6 +55,7 @@ LastFmConfig::LastFmConfig( LastFmAccount* account )
connect( m_ui->testLogin, SIGNAL( clicked( bool ) ), SLOT( testLogin() ) ); connect( m_ui->testLogin, SIGNAL( clicked( bool ) ), SLOT( testLogin() ) );
connect( m_ui->importHistory, SIGNAL( clicked( bool ) ), SLOT( loadHistory() ) ); 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->username, SIGNAL( textChanged( QString ) ), SLOT( enableButton() ) );
connect( m_ui->password, 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" ) ) foreach ( lastfm::XmlQuery e, lfm.children( "track" ) )
{ {
// tDebug() << "Found:" << e.children( "artist" ).first()["name"].text() << e["name"].text() << e["date"].attribute( "uts" ).toUInt(); // 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() ) if ( query.isNull() )
continue; 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<DatabaseCommand>(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();
}
}

View File

@@ -1,6 +1,6 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> === /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
* *
* Copyright 2010-2011, Leo Franchi <lfranchi@kde.org> * Copyright 2010-2012, Leo Franchi <lfranchi@kde.org>
* *
* Tomahawk is free software: you can redistribute it and/or modify * Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -19,7 +19,12 @@
#ifndef LASTFMCONFIG_H #ifndef LASTFMCONFIG_H
#define LASTFMCONFIG_H #define LASTFMCONFIG_H
#include "Query.h"
#include "database/DatabaseCommand_LoadSocialActions.h"
#include <QWidget> #include <QWidget>
#include <QSet>
#include <QNetworkReply>
class Ui_LastFmConfig; class Ui_LastFmConfig;
@@ -45,19 +50,31 @@ public slots:
private slots: private slots:
void enableButton(); void enableButton();
void loadHistory(); void loadHistory();
void onHistoryLoaded(); void onHistoryLoaded();
void syncLovedTracks() { syncLovedTracks( 1 ); }
void syncLovedTracks( uint page );
void onLovedFinished( QNetworkReply* reply );
void localLovedLoaded( DatabaseCommand_LoadSocialActions::TrackActions );
signals: signals:
void sizeHintChanged(); void sizeHintChanged();
private: private:
void syncLoved();
LastFmAccount* m_account; LastFmAccount* m_account;
Ui_LastFmConfig* m_ui; Ui_LastFmConfig* m_ui;
unsigned int m_page; unsigned int m_page;
unsigned int m_lastTimeStamp; unsigned int m_lastTimeStamp;
int m_totalLovedPages;
bool m_doneFetchingLoved;
QSet< Tomahawk::query_ptr > m_lastfmLoved;
DatabaseCommand_LoadSocialActions::TrackActions m_localLoved;
}; };
} }

View File

@@ -23,7 +23,7 @@
<string/> <string/>
</property> </property>
<property name="pixmap"> <property name="pixmap">
<pixmap resource="../../../resources.qrc">:/data/images/lastfm-icon.png</pixmap> <pixmap>:/data/images/lastfm-icon.png</pixmap>
</property> </property>
<property name="alignment"> <property name="alignment">
<set>Qt::AlignCenter</set> <set>Qt::AlignCenter</set>
@@ -84,6 +84,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QPushButton" name="syncLovedTracks">
<property name="text">
<string>Synchronize Loved Tracks</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QProgressBar" name="progressBar"> <widget class="QProgressBar" name="progressBar">
<property name="value"> <property name="value">

View File

@@ -37,40 +37,74 @@ DatabaseCommand_LoadSocialActions::exec( DatabaseImpl* dbi )
TomahawkSqlQuery query = dbi->newquery(); TomahawkSqlQuery query = dbi->newquery();
QVariant srcid = source()->isLocal() ? QVariant( QVariant::Int ) : source()->id(); if ( m_actionOnly.isNull() )
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() )
{ {
Tomahawk::SocialAction action; // Load for just specified track
action.action = query.value( 0 ); // action int artid = dbi->artistId( m_artist, false );
action.value = query.value( 1 ); // comment if( artid < 1 )
action.timestamp = query.value( 2 ); // timestamp return;
action.source = SourceList::instance()->get( query.value( 3 ).toInt() ); // source
if ( !action.source.isNull() )
allSocialActions.append( action );
}
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 );
}
} }

View File

@@ -1,6 +1,7 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> === /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
* *
* Copyright 2011, Christopher Reichert <creichert07@gmail.com> * Copyright 2011, Christopher Reichert <creichert07@gmail.com>
* Copyright 2012, Leo Franchi <lfranchi@kde.org>
* *
* Tomahawk is free software: you can redistribute it and/or modify * Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * 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 Q_OBJECT
public: public:
typedef QMap<Tomahawk::query_ptr,Tomahawk::SocialAction> TrackActions;
/** /**
* \brief Default constructor for DatabaseCommand_LoadSocialActions. * \brief Default constructor for DatabaseCommand_LoadSocialActions.
* *
@@ -69,6 +71,16 @@ public:
setTrack( query->track() ); 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<TrackActions>( "DatabaseCommand_LoadSocialAction::TrackActions" );
}
/** /**
* \brief Returns the name of this database command. * \brief Returns the name of this database command.
* \return QString containing the database command name 'loadsocialaction'. * \return QString containing the database command name 'loadsocialaction'.
@@ -115,20 +127,20 @@ public:
void setTrack( const QString& s ) { m_track = s; } void setTrack( const QString& s ) { m_track = s; }
signals: signals:
/** /**
* \brief Emitted when the database command has finished the Query successfully * All loaded social actions for each track found, for queries that generate all tracks
* * with matching actions.
* \param QList of all social actions
* \see QList
*/ */
void done( QList< Tomahawk::SocialAction >& allSocialActions ); void done( DatabaseCommand_LoadSocialActions::TrackActions actionsForTracks );
private: private:
Tomahawk::query_ptr m_query; Tomahawk::query_ptr m_query;
QString m_artist; QString m_artist;
QString m_track; QString m_track;
QString m_actionOnly;
}; };
Q_DECLARE_METATYPE( DatabaseCommand_LoadSocialActions::TrackActions )
#endif // DATABASECOMMAND_LOADSOCIALACTIONS_H #endif // DATABASECOMMAND_LOADSOCIALACTIONS_H