1
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
3
* Copyright 2010-2012, Leo Franchi <lfranchi@kde.org>
5
* Tomahawk is free software: you can redistribute it and/or modify
6
* it under the terms of the GNU General Public License as published by
7
* the Free Software Foundation, either version 3 of the License, or
8
* (at your option) any later version.
10
* Tomahawk is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details.
15
* You should have received a copy of the GNU General Public License
16
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
19
#include "LastFmConfig.h"
20
#include "ui_LastFmConfig.h"
22
#include <boost/bind.hpp>
24
#include "LastFmAccount.h"
25
#include "database/Database.h"
26
#include "database/DatabaseCommand_LogPlayback.h"
27
#include <database/DatabaseCommand_LoadSocialActions.h>
28
#include <database/DatabaseCommand_SocialAction.h>
29
#include "utils/TomahawkUtils.h"
30
#include "utils/Logger.h"
31
#include "utils/Closure.h"
33
#include <lastfm/ws.h>
34
#include <lastfm/User.h>
35
#include <lastfm/XmlQuery.h>
36
#include <lastfm/Track.h>
38
using namespace Tomahawk::Accounts;
41
LastFmConfig::LastFmConfig( LastFmAccount* account )
43
, m_account( account )
45
, m_lastTimeStamp( 0 )
46
, m_totalLovedPages( -1 )
47
, m_doneFetchingLoved( false )
48
, m_doneFetchingLocal( false )
50
m_ui = new Ui_LastFmConfig;
51
m_ui->setupUi( this );
53
m_ui->progressBar->hide();
55
m_ui->username->setText( m_account->username() );
56
m_ui->password->setText( m_account->password() );
57
m_ui->enable->setChecked( m_account->scrobble() );
59
connect( m_ui->testLogin, SIGNAL( clicked( bool ) ), SLOT( testLogin() ) );
60
connect( m_ui->importHistory, SIGNAL( clicked( bool ) ), SLOT( loadHistory() ) );
61
connect( m_ui->syncLovedTracks, SIGNAL( clicked( bool ) ), SLOT( syncLovedTracks() ) );
63
connect( m_ui->username, SIGNAL( textChanged( QString ) ), SLOT( enableButton() ) );
64
connect( m_ui->password, SIGNAL( textChanged( QString ) ), SLOT( enableButton() ) );
69
LastFmConfig::password() const
71
return m_ui->password->text();
76
LastFmConfig::scrobble() const
78
return m_ui->enable->isChecked();
83
LastFmConfig::username() const
85
return m_ui->username->text().trimmed();
90
LastFmConfig::testLogin()
92
m_ui->testLogin->setEnabled( false );
93
m_ui->testLogin->setText( tr( "Testing..." ) );
95
QString authToken = TomahawkUtils::md5( ( m_ui->username->text().toLower() + TomahawkUtils::md5( m_ui->password->text().toUtf8() ) ).toUtf8() );
97
// now authenticate w/ last.fm and get our session key
98
QMap<QString, QString> query;
99
query[ "method" ] = "auth.getMobileSession";
100
query[ "username" ] = m_ui->username->text().toLower();
101
query[ "authToken" ] = authToken;
103
// ensure they have up-to-date settings
104
lastfm::setNetworkAccessManager( TomahawkUtils::nam() );
106
QNetworkReply* authJob = lastfm::ws::post( query );
108
connect( authJob, SIGNAL( finished() ), SLOT( onLastFmFinished() ) );
113
LastFmConfig::enableButton()
115
m_ui->testLogin->setText( tr( "Test Login" ) );
116
m_ui->testLogin->setEnabled( true );
121
LastFmConfig::loadHistory()
123
if ( m_lastTimeStamp )
125
m_ui->importHistory->setText( tr( "Importing %1", "e.g. Importing 2012/01/01" ).arg( QDateTime::fromTime_t( m_lastTimeStamp ).toString( "MMMM d yyyy" ) ) );
128
m_ui->importHistory->setText( tr( "Importing History..." ) );
130
m_ui->importHistory->setEnabled( false );
131
m_ui->progressBar->show();
133
emit sizeHintChanged();
135
QNetworkReply* reply = lastfm::User( m_ui->username->text().toLower() ).getRecentTracks( 200, m_page );
136
connect( reply, SIGNAL( finished() ), SLOT( onHistoryLoaded() ) );
141
LastFmConfig::onHistoryLoaded()
144
bool finished = false;
145
QNetworkReply* reply = qobject_cast< QNetworkReply* >( sender() );
149
lastfm::XmlQuery lfm;
150
lfm.parse( reply->readAll() );
152
foreach ( lastfm::XmlQuery e, lfm.children( "track" ) )
154
// tDebug() << "Found:" << e.children( "artist" ).first()["name"].text() << e["name"].text() << e["date"].attribute( "uts" ).toUInt();
155
Tomahawk::query_ptr query = Tomahawk::Query::get( e.children( "artist" ).first()["name"].text(), e["name"].text(), QString(), QString(), false );
156
if ( query.isNull() )
159
m_lastTimeStamp = e["date"].attribute( "uts" ).toUInt();
161
DatabaseCommand_LogPlayback* cmd = new DatabaseCommand_LogPlayback( query, DatabaseCommand_LogPlayback::Finished, m_lastTimeStamp );
162
Database::instance()->enqueue( QSharedPointer<DatabaseCommand>(cmd) );
165
if ( !lfm.children( "recenttracks" ).isEmpty() )
167
lastfm::XmlQuery stats = lfm.children( "recenttracks" ).first();
169
uint page = stats.attribute( "page" ).toUInt();
170
total = stats.attribute( "totalPages" ).toUInt();
172
m_ui->progressBar->setMaximum( total );
173
m_ui->progressBar->setValue( page );
186
catch( lastfm::ws::ParseError e )
188
tDebug() << "XmlQuery error:" << e.message();
194
if ( m_page != total )
196
//: Text on a button that resumes import
197
m_ui->importHistory->setText( tr( "History Incomplete. Resume" ) );
198
m_ui->importHistory->setEnabled( true );
202
m_ui->importHistory->setText( tr( "Playback History Imported" ) );
209
LastFmConfig::onLastFmFinished()
211
QNetworkReply* authJob = dynamic_cast<QNetworkReply*>( sender() );
214
qDebug() << Q_FUNC_INFO << "No auth job returned!";
217
if( authJob->error() == QNetworkReply::NoError )
219
lastfm::XmlQuery lfm;
220
lfm.parse( authJob->readAll() );
222
if( lfm.children( "error" ).size() > 0 )
224
qDebug() << "ERROR from last.fm:" << lfm.text();
225
m_ui->testLogin->setText( tr( "Failed" ) );
226
m_ui->testLogin->setEnabled( true );
230
m_ui->testLogin->setText( tr( "Success" ) );
231
m_ui->testLogin->setEnabled( false );
232
m_ui->syncLovedTracks->setEnabled( true );
237
switch( authJob->error() )
239
case QNetworkReply::ContentOperationNotPermittedError:
240
case QNetworkReply::AuthenticationRequiredError:
241
m_ui->testLogin->setText( tr( "Failed" ) );
242
m_ui->testLogin->setEnabled( true );
246
qDebug() << "Couldn't get last.fm auth result";
247
m_ui->testLogin->setText( tr( "Could not contact server" ) );
248
m_ui->testLogin->setEnabled( true );
256
LastFmConfig::syncLovedTracks( uint page )
258
QNetworkReply* reply = lastfm::User( username() ).getLovedTracks( 200, page );
260
m_ui->syncLovedTracks->setEnabled( false );
261
m_ui->syncLovedTracks->setText( tr( "Synchronizing..." ) );
262
m_ui->progressBar->show();
263
emit sizeHintChanged();
265
NewClosure( reply, SIGNAL( finished() ), this, SLOT( onLovedFinished( QNetworkReply* ) ), reply );
267
DatabaseCommand_LoadSocialActions* cmd = new DatabaseCommand_LoadSocialActions( "Love", SourceList::instance()->getLocal() );
268
connect( cmd, SIGNAL( done( DatabaseCommand_LoadSocialActions::TrackActions ) ), this, SLOT( localLovedLoaded( DatabaseCommand_LoadSocialActions::TrackActions ) ) );
270
Database::instance()->enqueue( QSharedPointer< DatabaseCommand >( cmd ) );
275
LastFmConfig::onLovedFinished( QNetworkReply* reply )
281
lastfm::XmlQuery lfm;
282
lfm.parse( reply->readAll() );
284
if ( !lfm.children( "lovedtracks" ).isEmpty() )
286
lastfm::XmlQuery loved = lfm.children( "lovedtracks" ).first();
288
const int thisPage = loved.attribute( "page" ).toInt();
290
if ( m_totalLovedPages < 0 )
292
m_totalLovedPages = loved.attribute( "totalPages" ).toInt();
293
m_ui->progressBar->setMaximum( m_totalLovedPages + 2 );
296
m_ui->progressBar->setValue( thisPage );
297
foreach ( lastfm::XmlQuery e, loved.children( "track" ) )
299
tDebug() << "Found:" << e.children( "artist" ).first()["name"].text() << e["name"].text() << e["date"].attribute( "uts" ).toUInt();
300
Tomahawk::query_ptr query = Tomahawk::Query::get( e.children( "artist" ).first()["name"].text(), e["name"].text(), QString(), QString(), false );
301
if ( query.isNull() )
304
m_lastfmLoved.insert( query );
307
if ( thisPage == m_totalLovedPages )
309
m_doneFetchingLoved = true;
311
if ( m_doneFetchingLocal )
318
syncLovedTracks( thisPage + 1 );
323
m_ui->syncLovedTracks->setText( "Failed" );
324
m_ui->progressBar->hide();
325
emit sizeHintChanged();
328
catch( lastfm::ws::ParseError e )
330
m_ui->syncLovedTracks->setText( "Failed" );
331
m_ui->progressBar->hide();
332
emit sizeHintChanged();
333
tDebug() << "XmlQuery error:" << e.message();
338
bool trackEquality( const Tomahawk::query_ptr& first, const Tomahawk::query_ptr& second )
340
qDebug() << "Comparing:" << first->track() << second->track();
341
qDebug() << "==========" << first->artist() << second->artist();
342
return first->equals( second, true );
347
LastFmConfig::localLovedLoaded( DatabaseCommand_LoadSocialActions::TrackActions tracks )
349
m_localLoved = tracks;
350
m_doneFetchingLocal = true;
352
if ( m_doneFetchingLoved )
358
LastFmConfig::syncLoved()
360
QSet< Tomahawk::query_ptr > localToLove, lastFmToLove, lastFmToUnlove;
362
const QSet< Tomahawk::query_ptr > myLoved = m_localLoved.keys().toSet();
364
m_ui->progressBar->setValue( m_ui->progressBar->value() + 1 );
366
foreach ( const Tomahawk::query_ptr& lastfmLoved, m_lastfmLoved )
368
QSet< Tomahawk::query_ptr >::const_iterator iter = std::find_if( myLoved.begin(), myLoved.end(), boost::bind( &trackEquality, _1, boost::ref( lastfmLoved ) ) );
369
if ( iter == myLoved.constEnd() )
371
// qDebug() << "Found last.fm loved track that we didn't have loved locally:" << lastfmLoved->track() << lastfmLoved->artist();
372
localToLove << lastfmLoved;
376
foreach ( const Tomahawk::query_ptr& localLoved, myLoved )
378
qDebug() << "CHECKING FOR LOCAL LOVED ON LAST.FM TOO:" << m_localLoved[ localLoved ].value.toString() << localLoved->track() << localLoved->artist();
379
QSet< Tomahawk::query_ptr >::const_iterator iter = std::find_if( m_lastfmLoved.begin(), m_lastfmLoved.end(), boost::bind( &trackEquality, _1, boost::ref( localLoved ) ) );
381
qDebug() << "Result:" << (iter == m_lastfmLoved.constEnd());
382
// If we unloved it locally, but it's still loved on last.fm, unlove it
383
if ( m_localLoved[ localLoved ].value.toString() == "false" && iter != m_lastfmLoved.constEnd() )
384
lastFmToUnlove << localLoved;
386
// If we loved it locally but not loved on last.fm, love it
387
if ( m_localLoved[ localLoved ].value.toString() == "true" && iter == m_lastfmLoved.constEnd() )
389
qDebug() << "Found Local loved track but not on last.fm!:" << localLoved->track() << localLoved->artist();
390
lastFmToLove << localLoved;
394
foreach ( const Tomahawk::query_ptr& track, localToLove )
396
// Don't use the infosystem as we don't want to tweet a few hundred times :)
397
DatabaseCommand_SocialAction* cmd = new DatabaseCommand_SocialAction( track, QString( "Love" ), QString( "true" ) );
398
Database::instance()->enqueue( QSharedPointer<DatabaseCommand>(cmd) );
401
lastFmToLove.unite( lastFmToUnlove );
403
foreach ( const Tomahawk::query_ptr& track, lastFmToLove )
405
lastfm::MutableTrack lfmTrack;
408
lfmTrack.setTitle( track->track() );
409
lfmTrack.setArtist( track->artist() );
410
lfmTrack.setSource( lastfm::Track::Player );
412
if ( lastFmToUnlove.contains( track ) )
418
m_ui->progressBar->setValue( m_ui->progressBar->value() + 1 );
419
m_ui->syncLovedTracks->setText( tr( "Synchronization Finished" ) );