|
|
|
// (c) 2004 Christian Muehlhaeuser <chris@chris.de>
|
|
|
|
// (c) 2004 Sami Nieminen <sami.nieminen@iki.fi>
|
|
|
|
// (c) 2006 Shane King <kde@dontletsstart.com>
|
|
|
|
// (c) 2006 Iain Benson <iain@arctos.me.uk>
|
|
|
|
// (c) 2006 Alexandre Oliveira <aleprj@gmail.com>
|
|
|
|
// (c) 2006 Andy Kelk <andy@mopoke.co.uk>
|
|
|
|
// See COPYING file for licensing information.
|
|
|
|
|
|
|
|
#define DEBUG_PREFIX "Scrobbler"
|
|
|
|
|
|
|
|
#include "amarok.h"
|
|
|
|
#include "amarokconfig.h"
|
|
|
|
#include "collectiondb.h"
|
|
|
|
#include "config.h"
|
|
|
|
#include "debug.h"
|
|
|
|
#include "enginecontroller.h"
|
|
|
|
#include "playlist.h"
|
|
|
|
#include "scrobbler.h"
|
|
|
|
#include "statusbar.h"
|
|
|
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
#include <tqdatetime.h>
|
|
|
|
#include <tqdeepcopy.h>
|
|
|
|
|
|
|
|
#include <kapplication.h>
|
|
|
|
#include <kio/job.h>
|
|
|
|
#include <kio/jobclasses.h>
|
|
|
|
#include <klocale.h>
|
|
|
|
#include <kmdcodec.h>
|
|
|
|
#include <kstandarddirs.h>
|
|
|
|
#include <kurl.h>
|
|
|
|
|
|
|
|
//some setups require this
|
|
|
|
#undef PROTOCOL_VERSION
|
|
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// CLASS Scrobbler
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
Scrobbler* Scrobbler::instance()
|
|
|
|
{
|
|
|
|
static Scrobbler scrobbler;
|
|
|
|
return &scrobbler;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Scrobbler::Scrobbler()
|
|
|
|
: EngineObserver( EngineController::instance() )
|
|
|
|
, m_similarArtistsJob( 0 )
|
|
|
|
, m_validForSending( false )
|
|
|
|
, m_startPos( 0 )
|
|
|
|
, m_submitter( new ScrobblerSubmitter() )
|
|
|
|
, m_item( new SubmitItem() )
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
|
|
Scrobbler::~Scrobbler()
|
|
|
|
{
|
|
|
|
delete m_item;
|
|
|
|
delete m_submitter;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Queries similar artists from Audioscrobbler.
|
|
|
|
*/
|
|
|
|
void Scrobbler::similarArtists( const TQString & artist )
|
|
|
|
{
|
|
|
|
TQString safeArtist = TQDeepCopy<TQString>( artist );
|
|
|
|
if ( AmarokConfig::retrieveSimilarArtists() )
|
|
|
|
{
|
|
|
|
// Request looks like this:
|
|
|
|
// http://ws.audioscrobbler.com/1.0/artist/Metallica/similar.xml
|
|
|
|
|
|
|
|
m_similarArtistsBuffer = TQByteArray();
|
|
|
|
m_artist = artist;
|
|
|
|
|
|
|
|
m_similarArtistsJob = KIO::get( "http://ws.audioscrobbler.com/1.0/artist/" + safeArtist + "/similar.xml", false, false );
|
|
|
|
|
|
|
|
connect( m_similarArtistsJob, TQT_SIGNAL( result( KIO::Job* ) ),
|
|
|
|
this, TQT_SLOT( audioScrobblerSimilarArtistsResult( KIO::Job* ) ) );
|
|
|
|
connect( m_similarArtistsJob, TQT_SIGNAL( data( KIO::Job*, const TQByteArray& ) ),
|
|
|
|
this, TQT_SLOT( audioScrobblerSimilarArtistsData( KIO::Job*, const TQByteArray& ) ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when the similar artists TransferJob finishes.
|
|
|
|
*/
|
|
|
|
void Scrobbler::audioScrobblerSimilarArtistsResult( KIO::Job* job ) //SLOT
|
|
|
|
{
|
|
|
|
if ( m_similarArtistsJob != job )
|
|
|
|
return; //not the right job, so let's ignore it
|
|
|
|
|
|
|
|
if ( job->error() )
|
|
|
|
{
|
|
|
|
warning() << "KIO error! errno: " << job->error() << endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Result looks like this:
|
|
|
|
// <?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
// <similarartists artist="Metallica" streamable="1" picture="http://static.last.fm/proposedimages/sidebar/6/1000024/288059.jpg" mbid="">
|
|
|
|
// <artist>
|
|
|
|
// <name>Iron Maiden</name>
|
|
|
|
// <mbid></mbid>
|
|
|
|
// <match>100</match>
|
|
|
|
// <url>http://www.last.fm/music/Iron+Maiden</url>
|
|
|
|
// <image_small>http://static.last.fm/proposedimages/thumbnail/6/1000107/264195.jpg</image_small>
|
|
|
|
// <image>http://static.last.fm/proposedimages/sidebar/6/1000107/264195.jpg</image>
|
|
|
|
// <streamable>1</streamable>
|
|
|
|
// </artist>
|
|
|
|
// </similarartists>
|
|
|
|
|
|
|
|
TQDomDocument document;
|
|
|
|
if ( !document.setContent( m_similarArtistsBuffer ) )
|
|
|
|
{
|
|
|
|
debug() << "Couldn't read similar artists response" << endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQDomNodeList values = document.elementsByTagName( "similarartists" )
|
|
|
|
.item( 0 ).childNodes();
|
|
|
|
|
|
|
|
TQStringList suggestions;
|
|
|
|
for ( uint i = 0; i < values.count() && i < 30; i++ ) // limit to top 30 artists
|
|
|
|
suggestions << values.item( i ).namedItem( "name" ).toElement().text();
|
|
|
|
|
|
|
|
debug() << "Suggestions retrieved (" << suggestions.count() << ")" << endl;
|
|
|
|
if ( !suggestions.isEmpty() )
|
|
|
|
emit similarArtistsFetched( m_artist, suggestions );
|
|
|
|
|
|
|
|
m_similarArtistsJob = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when similar artists data is received for the TransferJob.
|
|
|
|
*/
|
|
|
|
void Scrobbler::audioScrobblerSimilarArtistsData( KIO::Job* job, const TQByteArray& data ) //SLOT
|
|
|
|
{
|
|
|
|
if ( m_similarArtistsJob != job )
|
|
|
|
return; //not the right job, so let's ignore it
|
|
|
|
|
|
|
|
uint oldSize = m_similarArtistsBuffer.size();
|
|
|
|
m_similarArtistsBuffer.resize( oldSize + data.size() );
|
|
|
|
memcpy( m_similarArtistsBuffer.data() + oldSize, data.data(), data.size() );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when the signal is received.
|
|
|
|
*/
|
|
|
|
void Scrobbler::engineNewMetaData( const MetaBundle& bundle, bool trackChanged )
|
|
|
|
{
|
|
|
|
//debug() << "engineNewMetaData: " << bundle.artist() << ":" << bundle.album() << ":" << bundle.title() << ":" << trackChanged << endl;
|
|
|
|
if ( !trackChanged )
|
|
|
|
{
|
|
|
|
debug() << "It's still the same track." << endl;
|
|
|
|
m_item->setArtist( bundle.artist() );
|
|
|
|
m_item->setAlbum( bundle.album() );
|
|
|
|
m_item->setTitle( bundle.title() );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//to work around xine bug, we have to explictly prevent submission the first few seconds of a track
|
|
|
|
//http://sourceforge.net/tracker/index.php?func=detail&aid=1401026&group_id=9655&atid=109655
|
|
|
|
m_timer.stop();
|
|
|
|
m_timer.start( 10000, true );
|
|
|
|
|
|
|
|
m_startPos = 0;
|
|
|
|
|
|
|
|
// Plugins must not submit tracks played from online radio stations, even
|
|
|
|
// if they appear to be providing correct metadata.
|
|
|
|
if ( !bundle.streamUrl().isEmpty() )
|
|
|
|
{
|
|
|
|
debug() << "Won't submit: It's a stream." << endl;
|
|
|
|
m_validForSending = false;
|
|
|
|
}
|
|
|
|
else if( bundle.podcastBundle() != NULL )
|
|
|
|
{
|
|
|
|
debug() << "Won't submit: It's a podcast." << endl;
|
|
|
|
m_validForSending = false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
*m_item = SubmitItem( bundle.artist(), bundle.album(), bundle.title(), bundle.length() );
|
|
|
|
m_validForSending = true; // check length etc later
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when cue file detects track change
|
|
|
|
*/
|
|
|
|
void Scrobbler::subTrack( long currentPos, long startPos, long endPos )
|
|
|
|
{
|
|
|
|
//debug() << "subTrack: " << currentPos << ":" << startPos << ":" << endPos << endl;
|
|
|
|
*m_item = SubmitItem( m_item->artist(), m_item->album(), m_item->title(), endPos - startPos );
|
|
|
|
if ( currentPos <= startPos + 2 ) // only submit if starting from the start of the track (need to allow 2 second difference for rounding/delay)
|
|
|
|
{
|
|
|
|
m_startPos = startPos * 1000;
|
|
|
|
m_validForSending = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
debug() << "Won't submit: Detected cuefile jump to " << currentPos - startPos << " seconds into track." << endl;
|
|
|
|
m_validForSending = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when the signal is received.
|
|
|
|
*/
|
|
|
|
void Scrobbler::engineTrackPositionChanged( long position, bool userSeek )
|
|
|
|
{
|
|
|
|
//debug() << "engineTrackPositionChanged: " << position << ":" << userSeek << endl;
|
|
|
|
if ( !m_validForSending )
|
|
|
|
return;
|
|
|
|
|
|
|
|
if ( userSeek )
|
|
|
|
{
|
|
|
|
m_validForSending = false;
|
|
|
|
debug() << "Won't submit: Seek detected." << endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( m_timer.isActive() )
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Each track must be submitted to the server when it is 50% or 240
|
|
|
|
// seconds complete, whichever comes first.
|
|
|
|
if ( position - m_startPos > 240 * 1000 || position - m_startPos > 0.5 * m_item->length() * 1000 )
|
|
|
|
{
|
|
|
|
if ( m_item->valid() )
|
|
|
|
m_submitter->submitItem( new SubmitItem( *m_item ) );
|
|
|
|
else
|
|
|
|
debug() << "Won't submit: No artist, no title, or less than 30 seconds." << endl;
|
|
|
|
m_validForSending = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Applies settings from the config dialog.
|
|
|
|
*/
|
|
|
|
void Scrobbler::applySettings()
|
|
|
|
{
|
|
|
|
m_submitter->configure( AmarokConfig::scrobblerUsername(), AmarokConfig::scrobblerPassword(), AmarokConfig::submitPlayedSongs() );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// CLASS SubmitItem
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
|
|
SubmitItem::SubmitItem(
|
|
|
|
const TQString& artist,
|
|
|
|
const TQString& album,
|
|
|
|
const TQString& title,
|
|
|
|
int length,
|
|
|
|
bool now)
|
|
|
|
{
|
|
|
|
m_artist = artist;
|
|
|
|
m_album = album;
|
|
|
|
m_title = title;
|
|
|
|
m_length = length;
|
|
|
|
m_playStartTime = now ? TQDateTime::currentDateTime( Qt::UTC ).toTime_t() : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
SubmitItem::SubmitItem( const TQDomElement& element )
|
|
|
|
{
|
|
|
|
m_artist = element.namedItem( "artist" ).toElement().text();
|
|
|
|
m_album = element.namedItem( "album" ).toElement().text();
|
|
|
|
m_title = element.namedItem( "title" ).toElement().text();
|
|
|
|
m_length = element.namedItem( "length" ).toElement().text().toInt();
|
|
|
|
m_playStartTime = element.namedItem( "playtime" ).toElement().text().toUInt();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
SubmitItem::SubmitItem()
|
|
|
|
: m_length( 0 )
|
|
|
|
, m_playStartTime( 0 )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool SubmitItem::operator==( const SubmitItem& item )
|
|
|
|
{
|
|
|
|
bool result = true;
|
|
|
|
|
|
|
|
if ( m_artist != item.artist() || m_album != item.album() || m_title != item.title() ||
|
|
|
|
m_length != item.length() || m_playStartTime != item.playStartTime() )
|
|
|
|
{
|
|
|
|
result = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TQDomElement SubmitItem::toDomElement( TQDomDocument& document ) const
|
|
|
|
{
|
|
|
|
TQDomElement item = document.createElement( "item" );
|
|
|
|
// TODO: In the future, it might be good to store url too
|
|
|
|
//item.setAttribute("url", item->url().url());
|
|
|
|
|
|
|
|
TQDomElement artist = document.createElement( "artist" );
|
|
|
|
TQDomText artistText = document.createTextNode( m_artist );
|
|
|
|
artist.appendChild( artistText );
|
|
|
|
item.appendChild( artist );
|
|
|
|
|
|
|
|
TQDomElement album = document.createElement( "album" );
|
|
|
|
TQDomText albumText = document.createTextNode( m_album );
|
|
|
|
album.appendChild( albumText );
|
|
|
|
item.appendChild( album );
|
|
|
|
|
|
|
|
TQDomElement title = document.createElement( "title" );
|
|
|
|
TQDomText titleText = document.createTextNode( m_title );
|
|
|
|
title.appendChild( titleText );
|
|
|
|
item.appendChild( title );
|
|
|
|
|
|
|
|
TQDomElement length = document.createElement( "length" );
|
|
|
|
TQDomText lengthText = document.createTextNode( TQString::number( m_length ) );
|
|
|
|
length.appendChild( lengthText );
|
|
|
|
item.appendChild( length );
|
|
|
|
|
|
|
|
TQDomElement playtime = document.createElement( "playtime" );
|
|
|
|
TQDomText playtimeText = document.createTextNode( TQString::number( m_playStartTime ) );
|
|
|
|
playtime.appendChild( playtimeText );
|
|
|
|
item.appendChild( playtime );
|
|
|
|
|
|
|
|
return item;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// CLASS SubmitQueue
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
|
|
int SubmitQueue::compareItems( TQPtrCollection::Item item1, TQPtrCollection::Item item2 )
|
|
|
|
{
|
|
|
|
SubmitItem *sItem1 = static_cast<SubmitItem*>( item1 );
|
|
|
|
SubmitItem *sItem2 = static_cast<SubmitItem*>( item2 );
|
|
|
|
int result;
|
|
|
|
|
|
|
|
if ( sItem1 == sItem2 )
|
|
|
|
{
|
|
|
|
result = 0;
|
|
|
|
}
|
|
|
|
else if ( sItem1->playStartTime() > sItem2->playStartTime() )
|
|
|
|
{
|
|
|
|
result = 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
result = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// CLASS ScrobblerSubmitter
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
TQString ScrobblerSubmitter::PROTOCOL_VERSION = "1.1";
|
|
|
|
TQString ScrobblerSubmitter::CLIENT_ID = "ark";
|
|
|
|
TQString ScrobblerSubmitter::CLIENT_VERSION = "1.4";
|
|
|
|
TQString ScrobblerSubmitter::HANDSHAKE_URL = "http://post.audioscrobbler.com/?hs=true";
|
|
|
|
|
|
|
|
|
|
|
|
ScrobblerSubmitter::ScrobblerSubmitter()
|
|
|
|
: m_username( 0 )
|
|
|
|
, m_password( 0 )
|
|
|
|
, m_submitUrl( 0 )
|
|
|
|
, m_challenge( 0 )
|
|
|
|
, m_scrobblerEnabled( false )
|
|
|
|
, m_holdFakeQueue( false )
|
|
|
|
, m_inProgress( false )
|
|
|
|
, m_needHandshake( true )
|
|
|
|
, m_prevSubmitTime( 0 )
|
|
|
|
, m_interval( 0 )
|
|
|
|
, m_backoff( 0 )
|
|
|
|
, m_lastSubmissionFinishTime( 0 )
|
|
|
|
, m_fakeQueueLength( 0 )
|
|
|
|
{
|
|
|
|
connect( &m_timer, TQT_SIGNAL(timeout()), this, TQT_SLOT(scheduledTimeReached()) );
|
|
|
|
readSubmitQueue();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ScrobblerSubmitter::~ScrobblerSubmitter()
|
|
|
|
{
|
|
|
|
// need to rescue current submit. This may meant it gets submitted twice,
|
|
|
|
// but last.fm handles that, and it's better than losing it when you quit
|
|
|
|
// while a submit is happening
|
|
|
|
for ( TQPtrDictIterator<SubmitItem> it( m_ongoingSubmits ); it.current(); ++it )
|
|
|
|
m_submitQueue.inSort( it.current() );
|
|
|
|
m_ongoingSubmits.clear();
|
|
|
|
|
|
|
|
saveSubmitQueue();
|
|
|
|
|
|
|
|
m_submitQueue.setAutoDelete( true );
|
|
|
|
m_submitQueue.clear();
|
|
|
|
m_fakeQueue.setAutoDelete( true );
|
|
|
|
m_fakeQueue.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Performs handshake with Audioscrobbler.
|
|
|
|
*/
|
|
|
|
void ScrobblerSubmitter::performHandshake()
|
|
|
|
{
|
|
|
|
TQString handshakeUrl = TQString();
|
|
|
|
uint currentTime = TQDateTime::currentDateTime( Qt::UTC ).toTime_t();
|
|
|
|
|
|
|
|
if ( PROTOCOL_VERSION == "1.1" )
|
|
|
|
{
|
|
|
|
// Audioscrobbler protocol 1.1 (current)
|
|
|
|
// http://post.audioscrobbler.com/?hs=true
|
|
|
|
// &p=1.1
|
|
|
|
// &c=<clientid>
|
|
|
|
// &v=<clientver>
|
|
|
|
// &u=<user>
|
|
|
|
handshakeUrl =
|
|
|
|
HANDSHAKE_URL +
|
|
|
|
TQString(
|
|
|
|
"&p=%1"
|
|
|
|
"&c=%2"
|
|
|
|
"&v=%3"
|
|
|
|
"&u=%4" )
|
|
|
|
.arg( PROTOCOL_VERSION )
|
|
|
|
.arg( CLIENT_ID )
|
|
|
|
.arg( CLIENT_VERSION )
|
|
|
|
.arg( m_username );
|
|
|
|
}
|
|
|
|
|
|
|
|
else if ( PROTOCOL_VERSION == "1.2" )
|
|
|
|
{
|
|
|
|
// Audioscrobbler protocol 1.2 (RFC)
|
|
|
|
// http://post.audioscrobbler.com/?hs=true
|
|
|
|
// &p=1.2
|
|
|
|
// &c=<clientid>
|
|
|
|
// &v=<clientversion>
|
|
|
|
// &u=<username>
|
|
|
|
// &t=<unix_timestamp>
|
|
|
|
// &a=<passcode>
|
|
|
|
handshakeUrl =
|
|
|
|
HANDSHAKE_URL +
|
|
|
|
TQString(
|
|
|
|
"&p=%1"
|
|
|
|
"&c=%2"
|
|
|
|
"&v=%3"
|
|
|
|
"&u=%4"
|
|
|
|
"&t=%5"
|
|
|
|
"&a=%6" )
|
|
|
|
.arg( PROTOCOL_VERSION )
|
|
|
|
.arg( CLIENT_ID )
|
|
|
|
.arg( CLIENT_VERSION )
|
|
|
|
.arg( m_username )
|
|
|
|
.arg( currentTime )
|
|
|
|
.arg( KMD5( KMD5( m_password.utf8() ).hexDigest() +
|
|
|
|
currentTime ).hexDigest().data() );
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
{
|
|
|
|
debug() << "Handshake not implemented for protocol version: " << PROTOCOL_VERSION << endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
debug() << "Handshake url: " << handshakeUrl << endl;
|
|
|
|
|
|
|
|
m_submitResultBuffer = "";
|
|
|
|
|
|
|
|
m_inProgress = true;
|
|
|
|
KIO::TransferJob* job = KIO::storedGet( handshakeUrl, false, false );
|
|
|
|
connect( job, TQT_SIGNAL( result( KIO::Job* ) ), TQT_SLOT( audioScrobblerHandshakeResult( KIO::Job* ) ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets item for submission to Audioscrobbler. Actual submission
|
|
|
|
* depends on things like (is scrobbling enabled, are Audioscrobbler
|
|
|
|
* profile details filled in etc).
|
|
|
|
*/
|
|
|
|
void ScrobblerSubmitter::submitItem( SubmitItem* item )
|
|
|
|
{
|
|
|
|
if ( m_scrobblerEnabled ) {
|
|
|
|
enqueueItem( item );
|
|
|
|
|
|
|
|
if ( item->playStartTime() == 0 )
|
|
|
|
m_holdFakeQueue = true; // hold on to fake queue until we get it all and can compute when to submit
|
|
|
|
else if ( !schedule( false ) )
|
|
|
|
announceSubmit( item, 1, false ); // couldn't perform submit immediately, let user know
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Flushes the submit queues
|
|
|
|
*/
|
|
|
|
void ScrobblerSubmitter::performSubmit()
|
|
|
|
{
|
|
|
|
TQString data;
|
|
|
|
|
|
|
|
// Audioscrobbler accepts max 10 tracks on one submit.
|
|
|
|
SubmitItem* items[10];
|
|
|
|
for ( int submitCounter = 0; submitCounter < 10; submitCounter++ )
|
|
|
|
items[submitCounter] = 0;
|
|
|
|
|
|
|
|
if ( PROTOCOL_VERSION == "1.1" )
|
|
|
|
{
|
|
|
|
// Audioscrobbler protocol 1.1 (current)
|
|
|
|
// http://post.audioscrobbler.com/v1.1-lite.php
|
|
|
|
// u=<user>
|
|
|
|
// &s=<MD5 response>&
|
|
|
|
// a[0]=<artist 0>&t[0]=<track 0>&b[0]=<album 0>&
|
|
|
|
// m[0]=<mbid 0>&l[0]=<length 0>&i[0]=<time 0>&
|
|
|
|
// a[1]=<artist 1>&t[1]=<track 1>&b[1]=<album 1>&
|
|
|
|
// m[1]=<mbid 1>&l[1]=<length 1>&i[1]=<time 1>&
|
|
|
|
// ...
|
|
|
|
// a[n]=<artist n>&t[n]=<track n>&b[n]=<album n>&
|
|
|
|
// m[n]=<mbid n>&l[n]=<length n>&i[n]=<time n>&
|
|
|
|
|
|
|
|
|
|
|
|
data =
|
|
|
|
"u=" + KURL::encode_string_no_slash( m_username ) +
|
|
|
|
"&s=" +
|
|
|
|
KURL::encode_string_no_slash( KMD5( KMD5( m_password.utf8() ).hexDigest() +
|
|
|
|
m_challenge.utf8() ).hexDigest() );
|
|
|
|
|
|
|
|
m_submitQueue.first();
|
|
|
|
for ( int submitCounter = 0; submitCounter < 10; submitCounter++ )
|
|
|
|
{
|
|
|
|
SubmitItem* itemFromQueue = dequeueItem();
|
|
|
|
if ( itemFromQueue == 0 )
|
|
|
|
{
|
|
|
|
if( submitCounter == 0 )
|
|
|
|
{
|
|
|
|
// this shouldn't happen, since we shouldn't be scheduled until we have something to do!
|
|
|
|
debug() << "Nothing to submit!" << endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
data += '&';
|
|
|
|
|
|
|
|
items[submitCounter] = itemFromQueue;
|
|
|
|
TQDateTime playStartTime = TQDateTime();
|
|
|
|
playStartTime.setTime_t( itemFromQueue->playStartTime() );
|
|
|
|
|
|
|
|
const TQString count = TQString::number( submitCounter );
|
|
|
|
|
|
|
|
data +=
|
|
|
|
"a[" + count + "]=" + KURL::encode_string_no_slash( itemFromQueue->artist(), 106 /*utf-8*/ ) +
|
|
|
|
"&t[" + count + "]=" + KURL::encode_string_no_slash( itemFromQueue->title(), 106 /*utf-8*/ ) +
|
|
|
|
"&b[" + count + "]=" + KURL::encode_string_no_slash( itemFromQueue->album(), 106 /*utf-8*/ ) +
|
|
|
|
"&m[" + count + "]=" +
|
|
|
|
"&l[" + count + "]=" + TQString::number( itemFromQueue->length() ) +
|
|
|
|
"&i[" + count + "]=" + KURL::encode_string_no_slash( playStartTime.toString( "yyyy-MM-dd hh:mm:ss" ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
{
|
|
|
|
debug() << "Submit not implemented for protocol version: " << PROTOCOL_VERSION << endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
debug() << "Submit data: " << data << endl;
|
|
|
|
|
|
|
|
m_submitResultBuffer = "";
|
|
|
|
|
|
|
|
m_inProgress = true;
|
|
|
|
KIO::TransferJob* job = KIO::http_post( m_submitUrl, data.utf8(), false );
|
|
|
|
job->addMetaData( "content-type", "Content-Type: application/x-www-form-urlencoded" );
|
|
|
|
|
|
|
|
// Loop in reverse order, which helps when items are later fetched from
|
|
|
|
// m_ongoingSubmits and possibly put back to queue, in correct order
|
|
|
|
// (i.e. oldest first).
|
|
|
|
for ( int submitCounter = 9; submitCounter >= 0; submitCounter-- )
|
|
|
|
if ( items[submitCounter] != 0 )
|
|
|
|
m_ongoingSubmits.insert( job, items[submitCounter] );
|
|
|
|
|
|
|
|
Amarok::StatusBar::instance()->newProgressOperation( job )
|
|
|
|
.setDescription( i18n( "Submitting to last.fm" ) );
|
|
|
|
|
|
|
|
connect( job, TQT_SIGNAL( result( KIO::Job* ) ),
|
|
|
|
this, TQT_SLOT( audioScrobblerSubmitResult( KIO::Job* ) ) );
|
|
|
|
connect( job, TQT_SIGNAL( data( KIO::Job*, const TQByteArray& ) ),
|
|
|
|
this, TQT_SLOT( audioScrobblerSubmitData( KIO::Job*, const TQByteArray& ) ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Configures the username/password and whether to scrobble
|
|
|
|
*/
|
|
|
|
void ScrobblerSubmitter::configure( const TQString& username, const TQString& password, bool enabled )
|
|
|
|
{
|
|
|
|
if ( username != m_username || password != m_password )
|
|
|
|
m_needHandshake = true;
|
|
|
|
|
|
|
|
m_username = username;
|
|
|
|
m_password = password;
|
|
|
|
m_scrobblerEnabled = enabled;
|
|
|
|
if ( enabled )
|
|
|
|
schedule( false );
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// If submit is disabled, clear submitqueue.
|
|
|
|
m_ongoingSubmits.setAutoDelete( true );
|
|
|
|
m_ongoingSubmits.clear();
|
|
|
|
m_ongoingSubmits.setAutoDelete( false );
|
|
|
|
m_submitQueue.setAutoDelete( true );
|
|
|
|
m_submitQueue.clear();
|
|
|
|
m_submitQueue.setAutoDelete( false );
|
|
|
|
m_fakeQueue.setAutoDelete( true );
|
|
|
|
m_fakeQueue.clear();
|
|
|
|
m_fakeQueue.setAutoDelete( false );
|
|
|
|
m_fakeQueueLength = 0;
|
|
|
|
m_timer.stop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sync from external device complete, can send them off
|
|
|
|
*/
|
|
|
|
void ScrobblerSubmitter::syncComplete()
|
|
|
|
{
|
|
|
|
m_holdFakeQueue = false;
|
|
|
|
saveSubmitQueue();
|
|
|
|
schedule( false );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when timer set up in the schedule function goes off.
|
|
|
|
*/
|
|
|
|
void ScrobblerSubmitter::scheduledTimeReached()
|
|
|
|
{
|
|
|
|
if ( m_needHandshake || m_challenge.isEmpty() )
|
|
|
|
performHandshake();
|
|
|
|
else
|
|
|
|
performSubmit();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when handshake TransferJob has finished and data is received.
|
|
|
|
*/
|
|
|
|
void ScrobblerSubmitter::audioScrobblerHandshakeResult( KIO::Job* job ) //SLOT
|
|
|
|
{
|
|
|
|
m_prevSubmitTime = TQDateTime::currentDateTime( Qt::UTC ).toTime_t();
|
|
|
|
m_inProgress = false;
|
|
|
|
|
|
|
|
if ( job->error() ) {
|
|
|
|
warning() << "KIO error! errno: " << job->error() << endl;
|
|
|
|
schedule( true );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
KIO::StoredTransferJob* const storedJob = static_cast<KIO::StoredTransferJob*>( job );
|
|
|
|
m_submitResultBuffer = TQString::fromUtf8( storedJob->data().data(), storedJob->data().size() );
|
|
|
|
|
|
|
|
// debug()
|
|
|
|
// << "Handshake result received: "
|
|
|
|
// << endl << m_submitResultBuffer << endl;
|
|
|
|
|
|
|
|
// UPTODATE
|
|
|
|
// <md5 challenge>
|
|
|
|
// <url to submit script>
|
|
|
|
// INTERVAL n (protocol 1.1)
|
|
|
|
if (m_submitResultBuffer.startsWith( "UPTODATE" ) )
|
|
|
|
{
|
|
|
|
m_challenge = m_submitResultBuffer.section( "\n", 1, 1 );
|
|
|
|
m_submitUrl = m_submitResultBuffer.section( "\n", 2, 2 );
|
|
|
|
TQString interval = m_submitResultBuffer.section( "\n", 3, 3 );
|
|
|
|
|
|
|
|
if ( interval.startsWith( "INTERVAL" ) )
|
|
|
|
m_interval = interval.mid( 9 ).toUInt();
|
|
|
|
}
|
|
|
|
// UPDATE <updateurl (optional)>
|
|
|
|
// <md5 challenge>
|
|
|
|
// <url to submit script>
|
|
|
|
// INTERVAL n (protocol 1.1)
|
|
|
|
else if ( m_submitResultBuffer.startsWith( "UPDATE" ) )
|
|
|
|
{
|
|
|
|
warning() << "A new version of Amarok is available" << endl;
|
|
|
|
|
|
|
|
m_challenge = m_submitResultBuffer.section( "\n", 1, 1 );
|
|
|
|
m_submitUrl = m_submitResultBuffer.section( "\n", 2, 2 );
|
|
|
|
TQString interval = m_submitResultBuffer.section( "\n", 3, 3 );
|
|
|
|
if ( interval.startsWith( "INTERVAL" ) )
|
|
|
|
m_interval = interval.mid( 9 ).toUInt();
|
|
|
|
}
|
|
|
|
// FAILED <reason (optional)>
|
|
|
|
// INTERVAL n (protocol 1.1)
|
|
|
|
else if ( m_submitResultBuffer.startsWith( "FAILED" ) )
|
|
|
|
{
|
|
|
|
TQString reason = m_submitResultBuffer.mid( 0, m_submitResultBuffer.find( "\n" ) );
|
|
|
|
if ( reason.length() > 6 )
|
|
|
|
reason = reason.mid( 7 ).stripWhiteSpace();
|
|
|
|
|
|
|
|
warning() << "Handshake failed (" << reason << ")" << endl;
|
|
|
|
TQString interval = m_submitResultBuffer.section( "\n", 1, 1 );
|
|
|
|
if ( interval.startsWith( "INTERVAL" ) )
|
|
|
|
m_interval = interval.mid( 9 ).toUInt();
|
|
|
|
}
|
|
|
|
// BADUSER (protocol 1.1) or BADAUTH (protocol 1.2)
|
|
|
|
// INTERVAL n (protocol 1.1)
|
|
|
|
else if ( m_submitResultBuffer.startsWith( "BADUSER" ) ||
|
|
|
|
m_submitResultBuffer.startsWith( "BADAUTH" ) )
|
|
|
|
{
|
|
|
|
warning() << "Handshake failed (Authentication failed)" << endl;
|
|
|
|
TQString interval = m_submitResultBuffer.section( "\n", 1, 1 );
|
|
|
|
if ( interval.startsWith( "INTERVAL" ) )
|
|
|
|
m_interval = interval.mid( 9 ).toUInt();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
warning() << "Unknown handshake response: " << m_submitResultBuffer << endl;
|
|
|
|
|
|
|
|
debug() << "Handshake result parsed: challenge=" << m_challenge << ", submitUrl=" << m_submitUrl << endl;
|
|
|
|
|
|
|
|
schedule( m_challenge.isEmpty() ); // schedule to submit or re-attempt handshake
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when submit TransferJob has finished and data is received.
|
|
|
|
*/
|
|
|
|
void ScrobblerSubmitter::audioScrobblerSubmitResult( KIO::Job* job ) //SLOT
|
|
|
|
{
|
|
|
|
m_prevSubmitTime = TQDateTime::currentDateTime( Qt::UTC ).toTime_t();
|
|
|
|
m_inProgress = false;
|
|
|
|
|
|
|
|
if ( job->error() ) {
|
|
|
|
warning() << "KIO error! errno: " << job->error() << endl;
|
|
|
|
enqueueJob( job );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// debug()
|
|
|
|
// << "Submit result received: "
|
|
|
|
// << endl << m_submitResultBuffer << endl;
|
|
|
|
|
|
|
|
// OK
|
|
|
|
// INTERVAL n (protocol 1.1)
|
|
|
|
if (m_submitResultBuffer.startsWith( "OK" ) )
|
|
|
|
{
|
|
|
|
debug() << "Submit successful" << endl;
|
|
|
|
TQString interval = m_submitResultBuffer.section( "\n", 1, 1 );
|
|
|
|
if ( interval.startsWith( "INTERVAL" ) )
|
|
|
|
m_interval = interval.mid( 9 ).toUInt();
|
|
|
|
|
|
|
|
finishJob( job );
|
|
|
|
}
|
|
|
|
// FAILED <reason (optional)>
|
|
|
|
// INTERVAL n (protocol 1.1)
|
|
|
|
else if ( m_submitResultBuffer.startsWith( "FAILED" ) )
|
|
|
|
{
|
|
|
|
TQString reason = m_submitResultBuffer.mid( 0, m_submitResultBuffer.find( "\n" ) );
|
|
|
|
if ( reason.length() > 6 )
|
|
|
|
reason = reason.mid( 7 ).stripWhiteSpace();
|
|
|
|
|
|
|
|
warning() << "Submit failed (" << reason << ")" << endl;
|
|
|
|
|
|
|
|
TQString interval = m_submitResultBuffer.section( "\n", 1, 1 );
|
|
|
|
if ( interval.startsWith( "INTERVAL" ) )
|
|
|
|
m_interval = interval.mid( 9 ).toUInt();
|
|
|
|
|
|
|
|
enqueueJob( job );
|
|
|
|
}
|
|
|
|
// BADAUTH
|
|
|
|
// INTERVAL n (protocol 1.1)
|
|
|
|
else if ( m_submitResultBuffer.startsWith( "BADAUTH" ) )
|
|
|
|
{
|
|
|
|
warning() << "Submit failed (Authentication failed)" << endl;
|
|
|
|
|
|
|
|
TQString interval = m_submitResultBuffer.section( "\n", 1, 1 );
|
|
|
|
if ( interval.startsWith( "INTERVAL" ) )
|
|
|
|
m_interval = interval.mid( 9 ).toUInt();
|
|
|
|
|
|
|
|
m_challenge = TQString();
|
|
|
|
enqueueJob( job );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
warning() << "Unknown submit response" << endl;
|
|
|
|
enqueueJob( job );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Receives the data from the TransferJob.
|
|
|
|
*/
|
|
|
|
void ScrobblerSubmitter::audioScrobblerSubmitData(
|
|
|
|
KIO::Job*, const TQByteArray& data ) //SLOT
|
|
|
|
{
|
|
|
|
// Append new chunk of string
|
|
|
|
m_submitResultBuffer += TQString::fromUtf8( data, data.size() );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks if it is possible to try to submit the data to Audioscrobbler.
|
|
|
|
*/
|
|
|
|
bool ScrobblerSubmitter::canSubmit() const
|
|
|
|
{
|
|
|
|
if ( !m_scrobblerEnabled || m_username.isEmpty() || m_password.isEmpty() )
|
|
|
|
{
|
|
|
|
debug() << "Unable to submit - no uname/pass or disabled" << endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Enqueues the given item for later submission.
|
|
|
|
*/
|
|
|
|
void ScrobblerSubmitter::enqueueItem( SubmitItem* item )
|
|
|
|
{
|
|
|
|
// Maintain max size of the queue, Audioscrobbler won't accept too old
|
|
|
|
// submissions anyway.
|
|
|
|
m_fakeQueue.first();
|
|
|
|
for ( uint size = m_fakeQueue.count() + m_submitQueue.count(); size >= 500; size-- )
|
|
|
|
{
|
|
|
|
SubmitItem* itemFromQueue = m_fakeQueue.getFirst();
|
|
|
|
m_fakeQueue.removeFirst();
|
|
|
|
|
|
|
|
if ( itemFromQueue )
|
|
|
|
{
|
|
|
|
debug() << "Dropping " << itemFromQueue->artist()
|
|
|
|
<< " - " << itemFromQueue->title() << " from fake queue" << endl;
|
|
|
|
m_fakeQueueLength -= itemFromQueue->length();
|
|
|
|
}
|
|
|
|
|
|
|
|
delete itemFromQueue;
|
|
|
|
}
|
|
|
|
m_submitQueue.first();
|
|
|
|
for ( uint size = m_submitQueue.count(); size >= 500; size-- )
|
|
|
|
{
|
|
|
|
SubmitItem* itemFromQueue = m_submitQueue.getFirst();
|
|
|
|
m_submitQueue.removeFirst();
|
|
|
|
debug() << "Dropping " << itemFromQueue->artist()
|
|
|
|
<< " - " << itemFromQueue->title() << " from submit queue" << endl;
|
|
|
|
|
|
|
|
delete itemFromQueue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( item->playStartTime() == 0 )
|
|
|
|
{
|
|
|
|
m_fakeQueue.inSort( item );
|
|
|
|
m_fakeQueueLength += item->length();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
m_submitQueue.inSort( item );
|
|
|
|
}
|
|
|
|
|
|
|
|
if( !m_holdFakeQueue )
|
|
|
|
{
|
|
|
|
// Save submit queue to disk so it is more uptodate in case of crash.
|
|
|
|
saveSubmitQueue();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Dequeues one item from the queue.
|
|
|
|
*/
|
|
|
|
SubmitItem* ScrobblerSubmitter::dequeueItem()
|
|
|
|
{
|
|
|
|
SubmitItem* item = 0;
|
|
|
|
if( m_lastSubmissionFinishTime > 0 && !m_holdFakeQueue && m_fakeQueue.getFirst() )
|
|
|
|
{
|
|
|
|
uint limit = TQDateTime::currentDateTime( Qt::UTC ).toTime_t();
|
|
|
|
|
|
|
|
if ( m_submitQueue.getFirst() )
|
|
|
|
if ( m_submitQueue.getFirst()->playStartTime() <= limit )
|
|
|
|
limit = m_submitQueue.getFirst()->playStartTime();
|
|
|
|
|
|
|
|
if( m_lastSubmissionFinishTime + m_fakeQueue.getFirst()->length() <= limit )
|
|
|
|
{
|
|
|
|
m_fakeQueue.first();
|
|
|
|
item = m_fakeQueue.take();
|
|
|
|
// don't backdate earlier than we have to
|
|
|
|
if( m_lastSubmissionFinishTime + m_fakeQueueLength < limit )
|
|
|
|
item->m_playStartTime = limit - m_fakeQueueLength;
|
|
|
|
else
|
|
|
|
item->m_playStartTime = m_lastSubmissionFinishTime;
|
|
|
|
m_fakeQueueLength -= item->length();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if( !item )
|
|
|
|
{
|
|
|
|
m_submitQueue.first();
|
|
|
|
item = m_submitQueue.take();
|
|
|
|
}
|
|
|
|
|
|
|
|
if( item )
|
|
|
|
{
|
|
|
|
if( item->playStartTime() < m_lastSubmissionFinishTime )
|
|
|
|
{
|
|
|
|
// debug() << "play times screwed up? - " << item->artist() << " - " << item->title() << ": " << item->playStartTime() << " < " << m_lastSubmissionFinishTime << endl;
|
|
|
|
}
|
|
|
|
int add = 30;
|
|
|
|
if( item->length() / 2 + 1 > add )
|
|
|
|
add = item->length() / 2 + 1;
|
|
|
|
if( item->playStartTime() + add > m_lastSubmissionFinishTime )
|
|
|
|
m_lastSubmissionFinishTime = item->playStartTime() + add;
|
|
|
|
|
|
|
|
// Save submit queue to disk so it is more uptodate in case of crash.
|
|
|
|
saveSubmitQueue();
|
|
|
|
}
|
|
|
|
|
|
|
|
return item;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Enqueues items associated with the job. This is used when the job
|
|
|
|
* has failed (e.g. network problems).
|
|
|
|
*/
|
|
|
|
void ScrobblerSubmitter::enqueueJob( KIO::Job* job )
|
|
|
|
{
|
|
|
|
SubmitItem *lastItem = 0;
|
|
|
|
SubmitItem *item = 0;
|
|
|
|
int counter = 0;
|
|
|
|
while ( ( item = m_ongoingSubmits.take( job ) ) != 0 )
|
|
|
|
{
|
|
|
|
counter++;
|
|
|
|
lastItem = item;
|
|
|
|
enqueueItem( item );
|
|
|
|
}
|
|
|
|
m_submitQueue.first();
|
|
|
|
|
|
|
|
if( lastItem )
|
|
|
|
announceSubmit( lastItem, counter, false );
|
|
|
|
|
|
|
|
schedule( true ); // arrange to flush queue after failure
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Deletes items associated with the job. This is used when the job
|
|
|
|
* has succeeded.
|
|
|
|
*/
|
|
|
|
void ScrobblerSubmitter::finishJob( KIO::Job* job )
|
|
|
|
{
|
|
|
|
SubmitItem *firstItem = 0;
|
|
|
|
SubmitItem *item = 0;
|
|
|
|
int counter = 0;
|
|
|
|
while ( ( item = m_ongoingSubmits.take( job ) ) != 0 )
|
|
|
|
{
|
|
|
|
counter++;
|
|
|
|
if ( firstItem == 0 )
|
|
|
|
firstItem = item;
|
|
|
|
else
|
|
|
|
delete item;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( firstItem )
|
|
|
|
announceSubmit( firstItem, counter, true );
|
|
|
|
delete firstItem;
|
|
|
|
|
|
|
|
schedule( false ); // arrange to flush rest of queue
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Announces on StatusBar if the submit was successful or not.
|
|
|
|
*
|
|
|
|
* @param item One of the items
|
|
|
|
* @param tracks Amount of tracks that were submitted
|
|
|
|
* @param success Indicates if the submission was successful or not
|
|
|
|
*/
|
|
|
|
void ScrobblerSubmitter::announceSubmit( SubmitItem *item, int tracks, bool success ) const
|
|
|
|
{
|
|
|
|
TQString _long, _short;
|
|
|
|
|
|
|
|
if ( success )
|
|
|
|
{
|
|
|
|
if ( tracks == 1 )
|
|
|
|
_short = i18n( "'%1' submitted to last.fm" ).arg( item->title() );
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_short = i18n( "Several tracks submitted to last.fm" );
|
|
|
|
|
|
|
|
_long = "<p>";
|
|
|
|
_long = i18n( "'%1' and one other track submitted",
|
|
|
|
"'%1' and %n other tracks submitted", tracks-1 )
|
|
|
|
.arg( item->title() );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if ( tracks == 1 )
|
|
|
|
_short = i18n( "Failed to submit '%1' to last.fm" ).arg( item->title() );
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_short = i18n( "Failed to submit several tracks to last.fm" );
|
|
|
|
_long = "<p>";
|
|
|
|
_long = i18n( "Failed to submit '%1' and one other track",
|
|
|
|
"Failed to submit '%1' and %n other tracks", tracks-1 )
|
|