1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-03-25 02:09:48 +01: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
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();

View File

@ -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;

View File

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

View File

@ -1,6 +1,6 @@
/* === 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
* 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 <database/DatabaseCommand_LoadSocialActions.h>
#include <database/DatabaseCommand_SocialAction.h>
#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 <lastfm/ws.h>
#include <lastfm/User.h>
#include <lastfm/XmlQuery.h>
#include <lastfm/Track.h>
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<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> ===
*
* 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
* 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 <QWidget>
#include <QSet>
#include <QNetworkReply>
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;
};
}

View File

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

View File

@ -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 );
}
}

View File

@ -1,6 +1,7 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* 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
* 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<Tomahawk::query_ptr,Tomahawk::SocialAction> 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<TrackActions>( "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