1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-08-23 06:02:53 +02:00

* Updated liblastfm2 to latest snapshot.

This commit is contained in:
Christian Muehlhaeuser
2011-09-10 04:34:34 +02:00
parent 76d4821295
commit 030b91ba9f
41 changed files with 1192 additions and 749 deletions

View File

@@ -818,19 +818,21 @@ LastFmPlugin::createScrobbler()
QList<lastfm::Track>
LastFmPlugin::parseTrackList( QNetworkReply * reply )
LastFmPlugin::parseTrackList( QNetworkReply* reply )
{
QList<lastfm::Track> tracks;
try {
lastfm::XmlQuery lfm = lastfm::ws::parse(reply);
foreach (lastfm::XmlQuery xq, lfm.children( "track" )) {
lastfm::XmlQuery lfm = reply->readAll();
foreach ( lastfm::XmlQuery xq, lfm.children( "track" ) )
{
tracks.append( lastfm::Track( xq ) );
}
}
catch (lastfm::ws::ParseError& e)
catch( lastfm::ws::ParseError& e )
{
qWarning() << e.what();
}
return tracks;
}

View File

@@ -1,13 +1,7 @@
cwd = File.dirname( __FILE__ )
require "#{cwd}/platform.rb"
def h(s, n)
case Platform::IMPL
when :mswin
puts '==> '+s
else
puts "\033[0;#{n}m==>\033[0;0;1m #{s} \033[0;0m"
end
end
def h1 s
@@ -37,4 +31,4 @@ def pkgconfig pkg, prettyname
system "pkg-config --exists '#{pkg}'"
raise PkgConfigNotFound if $? == 127
raise PkgNotFound.new(prettyname) if $? != 0
end
end

View File

@@ -1,32 +1,8 @@
#!/usr/bin/ruby
if ARGV.include? '--help'
puts "usage: ./configure [--prefix <dir>] [--release] [--no-strip] [--skip-checks]"
exit
end
cwd = File.dirname( __FILE__ )
require "#{cwd}/admin/platform.rb"
require "#{cwd}/admin/which_qmake.rb"
require "#{cwd}/admin/utils.rb"
begin
IO.read("#{cwd}/src/global.h") =~ /LASTFM_VERSION_STRING\s+"((\d\.)*\d)"/
abort "Couldn't determine our version!" if $1.nil?
LFM_VERSION=$1
ENV['LFM_VERSION']=LFM_VERSION
h1 "Configuring liblastfm-#{LFM_VERSION}..."
unless ARGV.include? '--skip-checks'
$qmake=which_qmake
pkgconfig 'samplerate', 'libsamplerate'
pkgconfig 'fftw3f', 'fftw'
puts 'Using '+`which #{$qmake}` unless Platform::IMPL == :mswin
else
$qmake='qmake'
end
h2 'Determining installation prefix' do
h2 'Determining installation prefix' do
if ARGV.include? '--prefix'
n=ARGV.index '--prefix'
ENV['LFM_PREFIX'] = ARGV[n+1]
@@ -38,34 +14,10 @@ begin
puts "Will install to: "+ENV['LFM_PREFIX']
end
h1 'Generating Build System'
h2 'Generating .qmake.env' do
f = File.new("#{cwd}/.qmake.env", 'w')
f.write qmake_env('CC', 'QMAKE_CC')
f.write qmake_env('CXX', 'QMAKE_CXX')
f.write qmake_env('LDFLAGS', 'QMAKE_LFLAGS_RELEASE')
f.write qmake_env(['CFLAGS', 'CPPFLAGS'], 'QMAKE_CFLAGS_RELEASE')
f.write qmake_env(['CXXFLAGS', 'CPPFLAGS'], 'QMAKE_CXXFLAGS_RELEASE')
f.close
end unless Platform::IMPL == :mswin
h2 "Running qpp..." do
['src/lastfm.pro','src/fingerprint/fingerprint.pro'].each do |p|
d="#{cwd}/#{File.dirname p}"
f=File.new "#{d}/_files.qmake", 'w'
f.write `ruby admin/qpp #{p}`
# on Windows VERSION produces lastfm0.dll, the 0 breaks the build
f.puts "VERSION = #{LFM_VERSION}" unless Platform::OS == :win32
end
end
h2 "Configuring qmake..." do
args=Array.new
args << '-spec macx-g++' if Platform::IMPL == :macosx
if ARGV.include? '--release'
args << '-config release'
args << '"CONFIG += app_bundle"' if Platform::IMPL == :macosx and ARGV.include? '--bundle'
else
args << '-config debug'
end
@@ -82,46 +34,13 @@ begin
hs << 'fingerprint/Fingerprint.h' << 'fingerprint/FingerprintableSource.h'
hs << 'radio/RadioStation.h' << 'radio/RadioTuner.h'
hs << 'scrobble/Audioscrobbler.h' << 'scrobble/ScrobblePoint.h' << 'scrobble/ScrobbleCache.h'
hs << 'types/AbstractType.h' << 'types/Track.h' << 'types/Mbid.h' << 'types/Artist.h' << 'types/Album.h' << 'types/FingerprintId.h' << 'types/Playlist.h' << 'types/Tag.h' << 'types/User.h' << 'types/Xspf.h'
hs << 'types/Tasteometer.h' << 'types/AbstractType.h' << 'types/Track.h' << 'types/Mbid.h' << 'types/Artist.h' << 'types/Album.h' << 'types/FingerprintId.h' << 'types/Playlist.h' << 'types/Tag.h' << 'types/User.h' << 'types/Xspf.h'
hs << 'ws/ws.h' << 'ws/InternetConnectionMonitor.h' << 'ws/NetworkAccessManager.h'
File.new("#{cwd}/Makefile", 'w').write `ruby admin/Makefile.rb #{hs.join(' ')}`
end
case Platform::IMPL
when :mswin then make='nmake all'
else make='make' # NOTE only tested with GNU make, sorry :(
uname = `uname`
ENV['LFM_VERSION']="0.4.0"
File.new("#{cwd}/Makefile", 'w').write `ruby admin/Makefile.rb #{hs.join(' ')}`
end
puts
puts "Good, your configure is finished! Now type: #{make}"
puts "Good, your configure is finished! Now type: make"
rescue QMakeTooOld
puts <<-sput
Your version of Qt seems to be too old, we require Qt 4.4 or above.
It is possible you have Qt3 and Qt4 both installed. Locate your Qt4
installation and ensure it is placed first in the path, eg:
PATH=/opt/qt4/bin:\$PATH ./configure
sput
exit 1
rescue QMakeNotFound
puts "Sorry, qmake was not found, is Qt4 installed?"
exit 2
rescue PkgNotFound => e
puts <<-sput
Sorry, we couldn't find #{e}.
You can try to compile anyway by forcing configure to finish:
./configure --skip-checks
sput
exit 3
rescue PkgConfigNotFound
puts "Sorry, pkg-config could not be found. You should install it!"
exit 4
end

View File

@@ -36,7 +36,7 @@ lastfm::UrlBuilder::url() const
QByteArray //static
lastfm::UrlBuilder::encode( QString s )
{
foreach (QChar c, QList<QChar>() << '&' << '/' << ';' << '+' << '#' << '%')
foreach (QChar c, QList<QChar>() << '%' << '&' << '/' << ';' << '+' << '#' << '"')
if (s.contains( c ))
// the middle step may seem odd but this is what the site does
// eg. search for the exact string "Radiohead 2 + 2 = 5"

View File

@@ -18,14 +18,55 @@
along with liblastfm. If not, see <http://www.gnu.org/licenses/>.
*/
#include "XmlQuery.h"
#include <QCoreApplication>
#include <QStringList>
using lastfm::XmlQuery;
XmlQuery::XmlQuery( const QByteArray& bytes )
{
domdoc.setContent(bytes);
e = domdoc.documentElement();
XmlQuery::XmlQuery( const QByteArray& bytes ) throw( lastfm::ws::ParseError )
{
try
{
if ( !bytes.size() )
throw lastfm::ws::ParseError( lastfm::ws::MalformedResponse, "No data" );
if( !domdoc.setContent( bytes ) )
throw lastfm::ws::ParseError( lastfm::ws::MalformedResponse, "Invalid XML" );
e = domdoc.documentElement();
if (e.isNull())
throw lastfm::ws::ParseError( lastfm::ws::MalformedResponse, "Lfm is null" );
QString const status = e.attribute( "status" );
QDomElement error = e.firstChildElement( "error" );
uint const n = e.childNodes().count();
// no elements beyond the lfm is perfectably acceptable <-- wtf?
// if (n == 0) // nothing useful in the response
if (status == "failed" || (n == 1 && !error.isNull()) )
throw error.isNull()
? lastfm::ws::ParseError( lastfm::ws::MalformedResponse, "" )
: lastfm::ws::ParseError( lastfm::ws::Error( error.attribute( "code" ).toUInt() ), error.text() );
}
catch ( lastfm::ws::ParseError e )
{
switch ( e.enumValue() )
{
case lastfm::ws::OperationFailed:
case lastfm::ws::InvalidApiKey:
case lastfm::ws::InvalidSessionKey:
// NOTE will never be received during the LoginDialog stage
// since that happens before this slot is registered with
// QMetaObject in App::App(). Neat :)
QMetaObject::invokeMethod( qApp, "onWsError", Q_ARG( lastfm::ws::Error, e.enumValue() ) );
default:
throw e;
}
}
}

View File

@@ -21,6 +21,7 @@
#define LASTFM_XMLQUERY_H
#include <lastfm/global.h>
#include <lastfm/ws.h>
#include <QDomDocument>
#include <QDomElement>
@@ -44,7 +45,7 @@ namespace lastfm
* Notice the lfm node is not referenced, that is because it is the
* document-element of the XML document.
*/
XmlQuery( const QByteArray& );
XmlQuery( const QByteArray& ) throw( lastfm::ws::ParseError );
XmlQuery( const QDomElement& e, const char* name = "" ) : e( e )
{

View File

@@ -60,7 +60,38 @@ static QDir dataDotDot()
return QDir::home();
#elif defined(Q_WS_MAC)
return QDir::home().filePath( "Library/Application Support" );
#define EIT( x ) { OSErr err = x; if (err != noErr) throw 1; }
try
{
short vRefNum = 0;
long dirId;
EIT( ::FindFolder( kOnAppropriateDisk,
kApplicationSupportFolderType,
kDontCreateFolder,
&vRefNum,
&dirId ) );
// Now we have a vRefNum and a dirID - but *not* an Unix-Path as string.
// Lets make one based from this:
FSSpec fsspec;
EIT( ::FSMakeFSSpec( vRefNum, dirId, NULL, &fsspec ) );
// ...and build an FSRef based on thes FSSpec.
FSRef fsref;
EIT( ::FSpMakeFSRef( &fsspec, &fsref ) );
// ...then extract the Unix Path as a C-String from the FSRef
unsigned char path[512];
EIT( ::FSRefMakePath( &fsref, path, 512 ) );
return QDir::homePath() + QString::fromUtf8( (char*)path );
}
catch (int)
{
return QDir::home().filePath( "Library/Application Support" );
}
#elif defined(Q_WS_X11)
return QDir::home().filePath( ".local/share" );

View File

@@ -1,6 +1,5 @@
TEMPLATE = lib
QT = core network xml
include( _files.qmake )
INSTALLS = target
target.path = /lib
@@ -18,3 +17,71 @@ mac{
linux*{
QT += dbus
}
SOURCES += \
ws/ws.cpp \
ws/NetworkConnectionMonitor.cpp \
ws/NetworkAccessManager.cpp \
ws/InternetConnectionMonitor.cpp \
types/Xspf.cpp \
types/User.cpp \
types/Track.cpp \
types/Tasteometer.cpp \
types/Tag.cpp \
types/Playlist.cpp \
types/Mbid.cpp \
types/FingerprintId.cpp \
types/Artist.cpp \
types/Album.cpp \
scrobble/ScrobbleCache.cpp \
scrobble/Audioscrobbler.cpp \
radio/RadioTuner.cpp \
radio/RadioStation.cpp \
core/XmlQuery.cpp \
core/UrlBuilder.cpp \
core/misc.cpp
HEADERS += \
ws/ws.h \
ws/NetworkConnectionMonitor.h \
ws/NetworkAccessManager.h \
ws/InternetConnectionMonitor.h \
types/Xspf.h \
types/User.h \
types/Track.h \
types/Tasteometer.h \
types/Tag.h \
types/Playlist.h \
types/Mbid.h \
types/FingerprintId.h \
types/Artist.h \
types/Album.h \
types/AbstractType.h \
scrobble/ScrobblePoint.h \
scrobble/ScrobbleCache.h \
scrobble/Audioscrobbler.h \
radio/RadioTuner.h \
radio/RadioStation.h \
global.h \
core/XmlQuery.h \
core/UrlBuilder.h \
core/misc.h
win32:SOURCES += ws/win/WNetworkConnectionMonitor_win.cpp \
ws/win/WmiSink.cpp \
ws/win/Pac.cpp \
ws/win/NdisEvents.cpp
win32:HEADERS += ws/win/WNetworkConnectionMonitor.h \
ws/win/WmiSink.h \
ws/win/Pac.h \
ws/win/NdisEvents.h \
ws/win/IeSettings.h \
ws/win/ComSetup.h
mac:SOURCES += ws/mac/MNetworkConnectionMonitor_mac.cpp
mac:HEADERS += ws/mac/ProxyDict.h \
ws/mac/MNetworkConnectionMonitor.h
!win32:VERSION = 0.4.0

View File

@@ -23,22 +23,190 @@
#include "RadioStation.h"
#include "../core/XmlQuery.h"
QRegExp rxDisco("opt:discovery\\|(\\S+)", Qt::CaseSensitive, QRegExp::RegExp2);
QRegExp rxRep("opt:rep\\|([\\d\\.]+)", Qt::CaseSensitive, QRegExp::RegExp2);
QRegExp rxMainstr("opt:mainstr\\|([\\d\\.]+)", Qt::CaseSensitive, QRegExp::RegExp2);
const float k_defaultRep(0.5);
const float k_defaultMainstr(0.5);
const bool k_defaultDisco(false);
lastfm::RadioStation
lastfm::RadioStation::library( const lastfm::User& user )
{
QList<lastfm::User> users;
users << user;
return library( users );
}
lastfm::RadioStation
lastfm::RadioStation::library( QList<lastfm::User>& users )
{
RadioStation s( libraryStr( users ) );
if( users.count() == 1 )
s.setTitle( QObject::tr( "%1%2s Library Radio").arg( lastfm::ws::Username, QChar(0x2019) ));
else {
QString title;
for( QList<lastfm::User>::const_iterator i = users.begin(); i != users.end(); i++ ) {
if( i == users.end() - 1 )
title += " and " + *i;
else
title += ", " + *i;
}
s.setTitle( title );
}
return s;
}
lastfm::RadioStation
lastfm::RadioStation::recommendations( const lastfm::User& user )
{
RadioStation s( recommendationsStr( user ) );
s.setTitle( QObject::tr( "%1%2s Recommended Radio").arg( lastfm::ws::Username, QChar(0x2019) ));
return s;
}
lastfm::RadioStation
lastfm::RadioStation::friends( const lastfm::User& user )
{
RadioStation s( friendsStr( user ) );
s.setTitle( QObject::tr( "%1%2s Friends Radio").arg( lastfm::ws::Username, QChar(0x2019) ));
return s;
}
lastfm::RadioStation
lastfm::RadioStation::neighbourhood( const lastfm::User& user )
{
RadioStation s( neighbourhoodStr( user ) );
s.setTitle( QObject::tr( "%1%2s Neighbours%2 Radio").arg( lastfm::ws::Username, QChar(0x2019) ));
return s;
}
lastfm::RadioStation
lastfm::RadioStation::tag( const lastfm::Tag& tag )
{
QList<lastfm::Tag> tags;
tags << tag;
return lastfm::RadioStation::tag( tags );
}
lastfm::RadioStation
lastfm::RadioStation::tag( QList<lastfm::Tag>& tag )
{
return RadioStation( tagStr( tag ) );
}
lastfm::RadioStation
lastfm::RadioStation::similar( const lastfm::Artist& artist )
{
QList<lastfm::Artist> artists;
artists << artist;
return similar( artists );
}
lastfm::RadioStation
lastfm::RadioStation::similar( QList<lastfm::Artist>& artists )
{
return RadioStation( similarStr( artists ) );
}
lastfm::RadioStation
lastfm::RadioStation::mix( const lastfm::User& user )
{
RadioStation s( mixStr( user ) );
s.setTitle( QObject::tr( "%1%2s Mix Radio").arg( lastfm::ws::Username, QChar(0x2019) ) );
return s;
}
QString
lastfm::RadioStation::url() const
{
return m_url.toString() + (m_tagFilter.isEmpty() ? "" : "/tag/" + m_tagFilter);
}
void
lastfm::RadioStation::setTitle( const QString& s )
{
// Stop the radio station getting renamed when the web services don't know what it's called
if ( !m_title.isEmpty() && s.compare( "a radio station", Qt::CaseInsensitive ) == 0 )
return;
QString title = s.trimmed();
if ( title.compare( QObject::tr("%1%2s Library Radio").arg( lastfm::ws::Username, QChar(0x2019) ), Qt::CaseInsensitive ) == 0 )
title = QObject::tr("My Library Radio");
else if ( title.compare( QObject::tr("%1%2s Mix Radio").arg( lastfm::ws::Username, QChar(0x2019) ), Qt::CaseInsensitive ) == 0 )
title = QObject::tr("My Mix Radio");
else if ( title.compare( QObject::tr("%1%2s Recommended Radio").arg( lastfm::ws::Username, QChar(0x2019) ), Qt::CaseInsensitive ) == 0 )
title = QObject::tr("My Recommended Radio");
else if ( title.compare( QObject::tr("%1%2s Friends%2 Radio").arg( lastfm::ws::Username, QChar(0x2019) ), Qt::CaseInsensitive ) == 0 )
title = QObject::tr("My Friends%1 Radio").arg( QChar( 0x2019 ) );
else if ( title.compare( QObject::tr("%1%2s Friends Radio").arg( lastfm::ws::Username, QChar(0x2019) ), Qt::CaseInsensitive ) == 0 )
title = QObject::tr("My Friends%1 Radio").arg( QChar( 0x2019 ) );
else if ( title.compare( QObject::tr("%1%2s Neighbours%2 Radio").arg( lastfm::ws::Username, QChar(0x2019) ), Qt::CaseInsensitive ) == 0 )
title = QObject::tr("My Neighbours%1 Radio").arg( QChar( 0x2019 ) );
else if ( title.compare( QObject::tr("%1%2s Neighbours Radio").arg( lastfm::ws::Username ), Qt::CaseInsensitive ) == 0 )
title = QObject::tr("My Neighbours%1 Radio").arg( QChar( 0x2019 ) );
m_title = title;
}
QString
lastfm::RadioStation::title() const
{
return m_title; // + (m_tagFilter.isEmpty() ? "" : ": " + m_tagFilter);
}
void
lastfm::RadioStation::setTagFilter( const QString& tag )
{
m_tagFilter = tag;
}
QNetworkReply*
lastfm::RadioStation::getSampleArtists( int limit ) const
{
QMap<QString, QString> map;
map["method"] = "radio.getSampleArtists";
map["station"] = m_url.toString();
map["limit"] = QString::number( limit );
return ws::get( map );
}
QNetworkReply*
lastfm::RadioStation::getTagSuggestions( int limit ) const
{
QMap<QString, QString> map;
map["method"] = "radio.getTagSuggestions";
map["station"] = m_url.toString();
map["limit"] = QString::number( limit );
return ws::get( map );
}
//static
QList<lastfm::RadioStation>
lastfm::RadioStation::list( QNetworkReply* r )
{
QList<lastfm::RadioStation> result;
try {
foreach (XmlQuery xq, XmlQuery(ws::parse(r)).children("station")) {
foreach (XmlQuery xq, XmlQuery( r->readAll() ).children("station")) {
lastfm::RadioStation rs( QUrl::fromPercentEncoding( xq["url"].text().toUtf8() ) );
rs.setTitle(xq["name"].text());
result.append(rs);
@@ -51,151 +219,119 @@ lastfm::RadioStation::list( QNetworkReply* r )
return result;
}
bool
lastfm::RadioStation::operator==( const RadioStation& that ) const
{
return this->m_url == that.m_url && this->m_tagFilter == that.m_tagFilter;
}
void
lastfm::RadioStation::setString( const QString& string )
{
QString replaceString( string );
QString decodedString = QUrl::fromPercentEncoding( replaceString.replace( QChar('+'), QChar(' ') ).toUtf8() );
// If it's a tag filtered station then extract that part
QString tempString = string;
QRegExp rxRql( "lastfm:\\/\\/rql\\/(.+)$" );
QRegExp rxPersonal( "lastfm:\\/\\/user\\/(.+)\\/personal" );
QRegExp rxRecommended( "lastfm://user/(.+)\\/recommended" );
QRegExp rxNeighbours( "lastfm:\\/\\/user\\/(.+)\\/neighbours" );
QRegExp rxLoved( "lastfm:\\/\\/user\\/(.+)\\/loved" );
QRegExp rxGlobalTags( "lastfm:\\/\\/globaltags\\/(.+)" );
QRegExp rxSimilarArtists( "lastfm:\\/\\/artist\\/(.+)\\/similarartists" );
QRegExp rxUserTags( "lastfm:\\/\\/usertags\\/(.+)\\/(.+)" );
QRegExp rxPlaylist( "lastfm:\\/\\/playlist/(.+)\\/shuffle" );
if (rxRql.indexIn(decodedString) == 0)
setRql( QByteArray::fromBase64( rxRql.capturedTexts()[1].toAscii() ) );
else if (rxPersonal.indexIn(decodedString) == 0)
setRql( libraryStr( rxPersonal.capturedTexts()[1] ) );
else if ( rxRecommended.indexIn(decodedString) == 0)
setRql( recommendationsStr( rxRecommended.capturedTexts()[1] ) );
else if ( rxNeighbours.indexIn(decodedString) == 0)
setRql( neighbourhoodStr( rxNeighbours.capturedTexts()[1] ) );
else if ( rxLoved.indexIn(decodedString) == 0)
setRql( lovedTracksStr( rxLoved.capturedTexts()[1] ) );
else if ( rxGlobalTags.indexIn(decodedString) == 0)
setRql( globalTagStr( rxGlobalTags.capturedTexts()[1] ) );
else if ( rxSimilarArtists.indexIn(decodedString) == 0)
setRql( similarStr( rxSimilarArtists.capturedTexts()[1] ) );
else if ( rxUserTags.indexIn(decodedString) == 0)
setRql( userTagStr( rxUserTags.capturedTexts()[1], rxUserTags.capturedTexts()[2] ) );
else if ( rxPlaylist.indexIn(decodedString) == 0)
setRql( playlistStr( rxPlaylist.capturedTexts()[1].toInt() ) );
else
if ( !tempString.startsWith("lastfm://tag/") )
{
m_url = string;
int index = tempString.indexOf("/tag/");
if ( index != -1 )
{
m_tagFilter = tempString.mid( index + 5, tempString.count() - (index + 5) );
tempString = tempString.mid( 0, index );
}
}
m_url = tempString;
}
bool
void
lastfm::RadioStation::setRep(float rep)
{
if ( m_rql.isEmpty() )
return false;
int indexIn = rxRep.indexIn(m_rql);
if ( indexIn != -1 )
{
if (rep != k_defaultRep)
m_rql.replace( indexIn, rxRep.capturedTexts()[0].length(), QString("opt:rep|%1").arg(rep) );
else
m_rql.replace( indexIn, rxRep.capturedTexts()[0].length(), "" );
}
else
{
// the rql doesn't have rep in it
// so append it to the end
if (rep != k_defaultRep)
m_rql.append( QString(" opt:rep|%1").arg(rep) );
}
setRql(m_rql);
return true;
m_rep = rep;
}
bool
void
lastfm::RadioStation::setMainstr(float mainstr)
{
if ( m_rql.isEmpty() )
return false;
int indexIn = rxMainstr.indexIn(m_rql);
if ( indexIn != -1 )
{
if (mainstr != k_defaultMainstr)
m_rql.replace( indexIn, rxMainstr.capturedTexts()[0].length(), QString("opt:mainstr|%1").arg(mainstr) );
else
m_rql.replace( indexIn, rxMainstr.capturedTexts()[0].length(), "" );
}
else
{
// the rql doesn't have rep in it
// so append it to the end
if ( mainstr != k_defaultMainstr )
m_rql.append( QString(" opt:mainstr|%1").arg(mainstr) );
}
setRql(m_rql);
return true;
m_mainstr = mainstr;
}
bool
void
lastfm::RadioStation::setDisco(bool disco)
{
if ( m_rql.isEmpty() )
return false;
int indexIn = rxDisco.indexIn(m_rql);
if ( indexIn != -1 )
{
if (disco)
m_rql.replace( indexIn, rxDisco.capturedTexts()[0].length(), "opt:discovery|true" );
else
m_rql.replace( indexIn, rxDisco.capturedTexts()[0].length(), "" );
}
else
{
// the rql doesn't have disco in it
// so append it to the end if it is set
if (disco)
m_rql.append( " opt:discovery|true" );
}
setRql(m_rql);
return true;
m_disco = disco;
}
float lastfm::RadioStation::rep() const
{
if ( rxRep.indexIn(m_rql) != -1 )
return rxRep.capturedTexts()[1].toFloat();
return k_defaultRep;
return m_rep;
}
float lastfm::RadioStation::mainstr() const
{
if ( rxMainstr.indexIn(m_rql) != -1 )
return rxMainstr.capturedTexts()[1].toFloat();
return k_defaultMainstr;
return m_mainstr;
}
bool lastfm::RadioStation::disco() const
{
if ( rxDisco.indexIn(m_rql) != -1 )
return rxDisco.capturedTexts()[1] == "true";
return k_defaultDisco;
return m_disco;
}
QString lastfm::RadioStation::libraryStr( QList<lastfm::User>& users )
{
qSort(users.begin(), users.end());
QString url = (users.count() > 1) ? "lastfm://users/" : "lastfm://user/";
url.append( users[0].name() );
for ( int i = 1 ; i < users.count() ; ++i )
url.append( "," + users[i].name() );
url.append("/personal");
return url;
}
QString lastfm::RadioStation::tagStr( QList<lastfm::Tag>& tags )
{
qSort(tags.begin(), tags.end());
QString url = (tags.count() > 1) ? "lastfm://tag/" : "lastfm://globaltags/";
url.append( tags[0].name() );
for ( int i = 1 ; i < tags.count() ; ++i )
url.append( "*" + tags[i].name() );
return url;
}
QString lastfm::RadioStation::similarStr( QList<lastfm::Artist>& artists )
{
qSort(artists.begin(), artists.end());
QString url = (artists.count() > 1) ? "lastfm://artistnames/" : "lastfm://artist/";
url.append( artists[0].name() );
for ( int i = 1 ; i < artists.count() ; ++i )
url.append( "," + artists[i].name() );
if (artists.count() == 1)
url.append( "/similarartists" );
return url;
}

View File

@@ -38,38 +38,40 @@ namespace lastfm
setString( s );
}
static RadioStation library( const lastfm::User& user ) { return rql( libraryStr( user ) ); }
static RadioStation recommendations( const lastfm::User& user ) { return rql( recommendationsStr( user ) ); }
static RadioStation neighbourhood( const lastfm::User& user ) { return rql( neighbourhoodStr( user ) ); }
static RadioStation lovedTracks( const lastfm::User& user ) { return rql( lovedTracksStr( user ) ); }
static RadioStation globalTag( const lastfm::Tag& tag ) { return rql( globalTagStr( tag ) ); }
static RadioStation similar( const lastfm::Artist& artist ) { return rql( similarStr( artist ) ); }
static RadioStation userTag( const lastfm::User& user, const lastfm::Tag& tag) { return rql( userTagStr( user, tag ) ); }
static RadioStation playlist( int playlistId ) { return rql( playlistStr( playlistId ) ); }
static RadioStation adventure( const lastfm::User& user ) { return rql( adventureStr( user ) ); }
static RadioStation library( const lastfm::User& user );
static RadioStation library( QList<lastfm::User>& users );
static RadioStation rql( const QString& rql )
{
RadioStation station;
station.setRql( rql );
return station;
}
static RadioStation similar( const lastfm::Artist& artist );
static RadioStation similar( QList<lastfm::Artist>& artist );
static RadioStation tag( const lastfm::Tag& tag );
static RadioStation tag( QList<lastfm::Tag>& tag );
static RadioStation recommendations( const lastfm::User& user );
static RadioStation friends( const lastfm::User& user );
static RadioStation neighbourhood( const lastfm::User& user );
static RadioStation mix( const lastfm::User& user );
QNetworkReply* getSampleArtists( int limit = 50 ) const;
QNetworkReply* getTagSuggestions( int limit = 50 ) const;
/** eg. "mxcl's Loved Tracks"
* It is worth noting that the Radio doesn't set the title of RadioStation
* object until we have tuned to it, and then we only set the one we give
* you back.
*/
QString title() const { return m_title; }
QString title() const;
/** the Last.fm url, eg. lastfm://user/mxcl/loved */
QString url() const { return m_url; }
QString rql() const { return m_rql; }
QString url() const;
void setTitle( const QString& s ) { m_title = s; }
void setTitle( const QString& title );
bool setRep(float rep);
bool setMainstr(float mainstr);
bool setDisco(bool disco);
void setTagFilter( const QString& tag );
void setRep(float rep);
void setMainstr(float mainstr);
void setDisco(bool disco);
float rep() const;
float mainstr() const;
@@ -77,37 +79,35 @@ namespace lastfm
bool isLegacyPlaylist() const
{
return m_url.startsWith( "lastfm://play/" ) ||
m_url.startsWith( "lastfm://preview/" ) ||
m_url.startsWith( "lastfm://track/" ) ||
m_url.startsWith( "lastfm://playlist/" );
return m_url.toString().startsWith( "lastfm://play/" ) ||
m_url.toString().startsWith( "lastfm://preview/" ) ||
m_url.toString().startsWith( "lastfm://track/" ) ||
m_url.toString().startsWith( "lastfm://playlist/" );
}
// good for getRecentStations:
static QList<RadioStation> list( QNetworkReply* );
private:
void setRql( const QString& rql )
{
m_rql = rql;
m_url = "lastfm://rql/" + QString(rql.toUtf8().toBase64());
}
bool operator==( const RadioStation& that ) const;
private:
void setString( const QString& s );
static QString libraryStr( const lastfm::User& user ) { return "library:" + user ; }
static QString recommendationsStr( const lastfm::User& user ) { return "rec:" + user ; }
static QString neighbourhoodStr( const lastfm::User& user ) { return "neigh:" + user ; }
static QString lovedTracksStr( const lastfm::User& user ) { return "loved:" + user ; }
static QString globalTagStr( const lastfm::Tag& tag ) { return "tag:\"" + tag + "\"" ; }
static QString similarStr( const lastfm::Artist& artist ) { return "simart:\"" + artist + "\""; }
static QString userTagStr( const lastfm::User& user, const lastfm::Tag& tag) { return "ptag:\"" + tag + "\"|" + user ; }
static QString playlistStr( int playlistId ) { return "playlist:" + QString::number(playlistId) ; }
static QString adventureStr( const lastfm::User& user ) { return "adv:" + user ; }
static QString libraryStr( QList<lastfm::User>& user );
static QString recommendationsStr( const lastfm::User& user ) { return "lastfm://user/" + user + "/recommended"; }
static QString friendsStr( const lastfm::User& user ) { return "lastfm://user/" + user + "/friends"; }
static QString neighbourhoodStr( const lastfm::User& user ) { return "lastfm://user/" + user + "/neighbours"; }
static QString tagStr( QList<lastfm::Tag>& tag );
static QString similarStr( QList<lastfm::Artist>& artist );
static QString mixStr( const lastfm::User& user ) { return "lastfm://user/" + user + "/mix"; }
private:
QString m_rql;
QString m_url;
QUrl m_url;
QString m_title;
QString m_tagFilter;
float m_rep;
float m_mainstr;
bool m_disco;
};
}

View File

@@ -17,10 +17,14 @@
You should have received a copy of the GNU General Public License
along with liblastfm. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QTimer>
#include "RadioTuner.h"
#include "../core/XmlQuery.h"
#include "../types/Xspf.h"
#include "../ws/ws.h"
using namespace lastfm;
//TODO skips left
@@ -32,34 +36,37 @@ using namespace lastfm;
RadioTuner::RadioTuner( const RadioStation& station )
: m_retry_counter( 0 )
: m_retry_counter( 0 ), m_fetchingPlaylist( false ), m_requestedPlaylist(false)
{
m_twoSecondTimer = new QTimer( this );
m_twoSecondTimer->setSingleShot( true );
connect( m_twoSecondTimer, SIGNAL(timeout()), SLOT(onTwoSecondTimeout()));
qDebug() << station.url();
//Empty RadioStation implies that the radio
//should tune to the previous station.
if( station.url().isEmpty() ) {
if( station.url().isEmpty() )
{
fetchFiveMoreTracks();
return;
}
QMap<QString, QString> map;
map["method"] = "radio.tune";
map["station"] = station.url();
map["additional_info"] = "1";
QNetworkReply* reply = ws::post(map);
connect( reply, SIGNAL(finished()), SLOT(onTuneReturn()) );
else
{
QMap<QString, QString> map;
map["method"] = "radio.tune";
map["station"] = station.url();
map["additional_info"] = "1";
connect( ws::post(map), SIGNAL(finished()), SLOT(onTuneReturn()) );
}
}
void
RadioTuner::retune( const RadioStation& station)
RadioTuner::retune( const RadioStation& station )
{
m_queue.clear();
m_playlistQueue.clear();
m_retuneStation = station;
QMap<QString, QString> map;
map["method"] = "radio.tune";
map["station"] = station.url();
map["additional_info"] = "1";
QNetworkReply* reply = ws::post(map);
connect( reply, SIGNAL(finished()), SLOT(onTuneReturn()) );
qDebug() << station.url();
}
@@ -67,10 +74,8 @@ void
RadioTuner::onTuneReturn()
{
try {
XmlQuery lfm = ws::parse( (QNetworkReply*)sender() );
// TODO: uncomment this is we are to get a radio station
// name when we tune to an rql radio station
//emit title( lfm["station"]["name"].text() );
XmlQuery lfm = qobject_cast<QNetworkReply*>(sender())->readAll();
emit title( lfm["station"]["name"].text() );
qDebug() << lfm;
@@ -79,22 +84,43 @@ RadioTuner::onTuneReturn()
}
catch (ws::ParseError& e)
{
emit error( e.enumValue() );
emit error( e.enumValue(), e.message() );
}
}
bool
void
RadioTuner::fetchFiveMoreTracks()
{
//TODO check documentation, I figure this needs a session key
QMap<QString, QString> map;
map["method"] = "radio.getPlaylist";
map["additional_info"] = "1";
map["rtp"] = "1"; // see above
QNetworkReply* reply = ws::post( map );
connect( reply, SIGNAL(finished()), SLOT(onGetPlaylistReturn()) );
return true;
if ( !m_retuneStation.url().isEmpty() )
{
// We have been asked to retune so do it now
QMap<QString, QString> map;
map["method"] = "radio.tune";
map["station"] = m_retuneStation.url();
map["additional_info"] = "1";
QNetworkReply* reply = ws::post(map);
connect( reply, SIGNAL(finished()), SLOT(onTuneReturn()) );
m_retuneStation = RadioStation();
m_twoSecondTimer->stop();
}
else
{
if ( !m_twoSecondTimer->isActive() )
{
//TODO check documentation, I figure this needs a session key
QMap<QString, QString> map;
map["method"] = "radio.getPlaylist";
map["additional_info"] = "1";
map["rtp"] = "1"; // see above
connect( ws::post( map ), SIGNAL(finished()), SLOT(onGetPlaylistReturn()) );
m_fetchingPlaylist = true;
}
else
m_requestedPlaylist = true;
}
}
@@ -113,39 +139,80 @@ RadioTuner::tryAgain()
void
RadioTuner::onGetPlaylistReturn()
{
// We shouldn't request another playlist for 2 seconds because we'll get the same one
// in a different order. This QTimer will block until it has finished. If one or more
// playlists have been requested in the meantime, it will fetch one on timeout
m_twoSecondTimer->start( 2000 );
// This will block us fetching two playlists at once
m_fetchingPlaylist = false;
try {
XmlQuery lfm = ws::parse( (QNetworkReply*)sender() );
Xspf xspf( lfm["playlist"] );
QList<Track> tracks( xspf.tracks() );
if (tracks.isEmpty()) {
XmlQuery lfm = qobject_cast<QNetworkReply*>(sender())->readAll();
emit title( lfm["playlist"]["title"].text() );
qDebug() << lfm;
Xspf* xspf = new Xspf( lfm["playlist"], this );
connect( xspf, SIGNAL(expired()), SLOT(onXspfExpired()) );
if ( xspf->isEmpty() )
{
// give up after too many empty playlists :(
if (!tryAgain())
emit error( ws::NotEnoughContent );
} else {
emit error( ws::NotEnoughContent, tr("Not enough content") );
}
else
{
m_retry_counter = 0;
foreach (Track t, tracks)
MutableTrack( t ).setSource( Track::LastFmRadio );
m_queue += tracks;
m_playlistQueue << xspf;
emit trackAvailable();
}
}
catch (ws::ParseError& e)
{
qWarning() << e.what();
emit error( e.enumValue() );
emit error( e.enumValue(), e.message() );
}
}
void
RadioTuner::onTwoSecondTimeout()
{
if (m_requestedPlaylist)
{
m_requestedPlaylist = false;
fetchFiveMoreTracks();
}
}
void
RadioTuner::onXspfExpired()
{
int index = m_playlistQueue.indexOf( static_cast<Xspf*>(sender()) );
if ( index != -1 )
m_playlistQueue.takeAt( index )->deleteLater();
}
Track
RadioTuner::takeNextTrack()
{
//TODO presumably, we should check if fetchMoreTracks is working?
if (m_queue.isEmpty())
if ( m_playlistQueue.isEmpty() )
{
// If there are no tracks here and we're not fetching tracks
// it's probably because the playlist expired so fetch more now
if ( !m_fetchingPlaylist )
fetchFiveMoreTracks();
return Track();
Track result = m_queue.takeFirst();
if (m_queue.isEmpty())
}
Track result = m_playlistQueue[0]->takeFirst();
if ( m_playlistQueue[0]->isEmpty() )
m_playlistQueue.removeFirst();
if ( m_playlistQueue.isEmpty() )
fetchFiveMoreTracks();
return result;

View File

@@ -22,6 +22,7 @@
#include <lastfm/RadioStation>
#include <lastfm/Track>
#include <lastfm/Xspf>
#include <lastfm/ws.h>
#include <QList>
@@ -47,11 +48,14 @@ namespace lastfm
void title( const QString& );
void supportsDisco( bool supportsDisco );
void trackAvailable();
void error( lastfm::ws::Error );
void error( lastfm::ws::Error, const QString& message );
private slots:
void onTuneReturn();
void onGetPlaylistReturn();
void onXspfExpired();
void onTwoSecondTimeout();
private:
/** Tries again up to 5 times
@@ -66,10 +70,15 @@ namespace lastfm
* is also not allowed according to our terms and conditions, which you
* already agreed to in order to get your API key. Sorry about that dude.
*/
bool fetchFiveMoreTracks();
void fetchFiveMoreTracks();
QList<Track> m_queue;
private:
QList<Xspf*> m_playlistQueue;
uint m_retry_counter;
bool m_fetchingPlaylist;
bool m_requestedPlaylist;
class QTimer* m_twoSecondTimer;
RadioStation m_retuneStation;
};
}

View File

@@ -88,7 +88,7 @@ lastfm::Audioscrobbler::cache( const Track& track )
void
lastfm::Audioscrobbler::cacheBatch( const QList<Track>& tracks )
lastfm::Audioscrobbler::cacheBatch( const QList<lastfm::Track>& tracks )
{
d->m_cache.add( tracks );
@@ -151,13 +151,28 @@ lastfm::Audioscrobbler::parseTrack( const XmlQuery& trackXml, const Track& track
void
lastfm::Audioscrobbler::onNowPlayingReturn()
{
lastfm::XmlQuery lfm = static_cast<QNetworkReply*>(sender())->readAll();
qDebug() << lfm;
try
{
lastfm::XmlQuery lfm = static_cast<QNetworkReply*>(sender())->readAll();
if ( lfm.attribute("status") == "ok" )
parseTrack( lfm["nowplaying"], d->m_nowPlayingTrack );
else
emit nowPlayingError( lfm["error"].attribute("code").toInt(), lfm["error"].text() );
qDebug() << lfm;
if ( lfm.attribute("status") == "ok" )
parseTrack( lfm["nowplaying"], d->m_nowPlayingTrack );
else
emit nowPlayingError( lfm["error"].attribute("code").toInt(), lfm["error"].text() );
d->m_nowPlayingTrack = Track();
d->m_nowPlayingReply = 0;
}
catch ( lastfm::ws::ParseError p )
{
qDebug() << p.message() << p.enumValue();
}
catch ( lastfm::ws::Error p )
{
qDebug() << p;
}
d->m_nowPlayingTrack = Track();
d->m_nowPlayingReply = 0;
@@ -167,37 +182,65 @@ lastfm::Audioscrobbler::onNowPlayingReturn()
void
lastfm::Audioscrobbler::onTrackScrobbleReturn()
{
lastfm::XmlQuery lfm = d->m_scrobbleReply->readAll();
qDebug() << lfm;
if (lfm.attribute("status") == "ok")
try
{
int index = 0;
lastfm::XmlQuery lfm = d->m_scrobbleReply->readAll();
foreach ( const XmlQuery& scrobble, lfm["scrobbles"].children("scrobble") )
parseTrack( scrobble, d->m_batch.at( index++ ) );
qDebug() << lfm;
d->m_cache.remove( d->m_batch );
d->m_batch.clear();
}
else
{
// The scrobble submission failed
if ( !(lfm["error"].attribute("code") == "9" // Bad session
|| lfm["error"].attribute("code") == "11" // Service offline
|| lfm["error"].attribute("code") == "16") ) // Service temporarily unavailable
if (lfm.attribute("status") == "ok")
{
// clear the cache if it was not one of these error codes
int index = 0;
foreach ( const XmlQuery& scrobble, lfm["scrobbles"].children("scrobble") )
parseTrack( scrobble, d->m_batch.at( index++ ) );
emit scrobblesSubmitted( d->m_batch );
d->m_cache.remove( d->m_batch );
d->m_batch.clear();
}
else
else if ( d->m_scrobbleReply->error() == QNetworkReply::NoError )
{
qWarning() << "Got error in scrobble submission:" << lfm[ "error" ] << "and silently ignoring. Submission is cached.";
//Q_ASSERT(false);
}
}
// The scrobble submission failed, but the http request was sucessful
d->m_scrobbleReply = 0;
if ( !(lfm["error"].attribute("code") == "9" // Bad session
|| lfm["error"].attribute("code") == "11" // Service offline
|| lfm["error"].attribute("code") == "16") ) // Service temporarily unavailable
{
foreach ( const Track& track, d->m_batch )
{
MutableTrack mTrack = MutableTrack( track );
mTrack.setScrobbleError( static_cast<Track::ScrobbleError>(lfm["error"].attribute("code").toInt()) );
mTrack.setScrobbleErrorText( lfm["error"].text() );
mTrack.setScrobbleStatus( Track::Error );
}
emit scrobblesSubmitted( d->m_batch );
// clear the cache if it was not one of these error codes
d->m_cache.remove( d->m_batch );
d->m_batch.clear();
}
else
{
Q_ASSERT(false);
}
}
d->m_scrobbleReply = 0;
// check is there are anymore scrobbles to submit
submit();
}
catch ( lastfm::ws::ParseError p )
{
qDebug() << p.message() << p.enumValue();
d->m_scrobbleReply = 0;
}
catch ( lastfm::ws::Error p )
{
qDebug() << p;
d->m_scrobbleReply = 0;
}
}

View File

@@ -47,6 +47,9 @@ namespace lastfm
signals:
void scrobblesCached( const QList<lastfm::Track>& tracks );
/* Note that this is emitted after we tried to submit the scrobbles
It could just be that they have an error code */
void scrobblesSubmitted( const QList<lastfm::Track>& tracks );
void nowPlayingError( int code, QString message );
public slots:
@@ -55,7 +58,7 @@ namespace lastfm
void nowPlaying( const Track& );
/** will cache the track and call submit() */
void cache( const Track& );
void cacheBatch( const QList<Track>& );
void cacheBatch( const QList<lastfm::Track>& );
/** will submit the submission cache for this user */
void submit();

View File

@@ -28,14 +28,14 @@
#include <QTimer>
QNetworkReply*
lastfm::Album::getInfo(const QString& user, const QString& sk) const
lastfm::Album::getInfo() const
{
QMap<QString, QString> map;
map["method"] = "album.getInfo";
map["artist"] = m_artist;
map["album"] = m_title;
if (!user.isEmpty()) map["username"] = user;
if (!sk.isEmpty()) map["sk"] = sk;
if (!lastfm::ws::Username.isEmpty()) map["username"] = lastfm::ws::Username;
if (!lastfm::ws::SessionKey.isEmpty()) map["sk"] = lastfm::ws::SessionKey;
return lastfm::ws::get(map);
}

View File

@@ -55,7 +55,7 @@ namespace lastfm
bool isNull() const { return m_title.isEmpty() && m_mbid.isNull(); }
/** Album.getInfo WebService */
QNetworkReply* getInfo(const QString& user = "", const QString& sk = "") const;
QNetworkReply* getInfo() const;
QNetworkReply* share( const QStringList& recipients, const QString& message = "", bool isPublic = true ) const;
/** use Tag::list to get the tag list out of the finished reply */

View File

@@ -94,11 +94,11 @@ Artist::getEvents(int limit) const
}
QNetworkReply*
Artist::getInfo(const QString& user, const QString& sk) const
Artist::getInfo() const
{
QMap<QString, QString> map = params("getInfo");
if (!user.isEmpty()) map["username"] = user;
if (!sk.isEmpty()) map["sk"] = sk;
if (!lastfm::ws::Username.isEmpty()) map["username"] = lastfm::ws::Username;
if (!lastfm::ws::SessionKey.isEmpty()) map["sk"] = lastfm::ws::SessionKey;
return ws::get( map );
}
@@ -124,9 +124,11 @@ Artist::getTopTracks() const
QNetworkReply*
Artist::getSimilar() const
Artist::getSimilar( int limit ) const
{
return ws::get( params("getSimilar") );
QMap<QString, QString> map = params("getSimilar");
if ( limit != -1 ) map["limit"] = QString::number( limit );
return ws::get( map );
}
@@ -145,7 +147,7 @@ Artist::getSimilar( QNetworkReply* r )
QMap<int, QString> artists;
try
{
XmlQuery lfm = ws::parse(r);
XmlQuery lfm = r->readAll();
foreach (XmlQuery e, lfm.children( "artist" ))
{
// convert floating percentage to int in range 0 to 10,000
@@ -167,7 +169,7 @@ Artist::getTopTracks( QNetworkReply* r )
QStringList tracks;
try
{
XmlQuery lfm = ws::parse(r);
XmlQuery lfm = r->readAll();
foreach (XmlQuery e, lfm.children( "track" ))
{
tracks << e["name"].text();
@@ -186,7 +188,7 @@ Artist::list( QNetworkReply* r )
{
QList<Artist> artists;
try {
XmlQuery lfm = ws::parse(r);
XmlQuery lfm = r->readAll();
foreach (XmlQuery xq, lfm.children( "artist" )) {
Artist artist( xq );
artists += artist;
@@ -204,7 +206,7 @@ Artist
Artist::getInfo( QNetworkReply* r )
{
try {
XmlQuery lfm = ws::parse(r);
XmlQuery lfm = r->readAll();
Artist artist = lfm["artist"]["name"].text();
artist.m_images = images( lfm["artist"] );
return artist;

View File

@@ -49,14 +49,16 @@ namespace lastfm
QUrl imageUrl( ImageSize size = Large, bool square = false ) const;
bool isNull() const { return m_name.isEmpty(); }
/** the url for this artist's page at www.last.fm */
QUrl www() const;
Artist& operator=( const Artist& that ) { m_name = that.name(); m_images = that.m_images; return *this; }
bool operator==( const Artist& that ) const { return m_name == that.m_name; }
bool operator!=( const Artist& that ) const { return m_name != that.m_name; }
operator QString() const
bool operator<( const Artist& that ) const { return m_name < that.m_name; }
operator QString() const
{
/** if no artist name is set, return the musicbrainz unknown identifier
* in case some part of the GUI tries to display it anyway. Note isNull
@@ -65,37 +67,37 @@ namespace lastfm
}
QString toString() const { return name(); }
QString name() const { return QString(*this); }
QString name() const { return QString(*this); }
QDomElement toDomElement( QDomDocument& ) const { return QDomElement(); }
QNetworkReply* share( const QStringList& recipients, const QString& message = "", bool isPublic = true ) const;
QNetworkReply* getEvents(int limit = 0) const;
QNetworkReply* getInfo(const QString& user = "", const QString& sk = "") const;
QNetworkReply* getInfo() const;
static Artist getInfo( QNetworkReply* );
QNetworkReply* getSimilar() const;
/** The match percentage is returned from last.fm as a 4 significant
* figure floating point value. So we multply it by 100 to make an
* integer in the range of 0 to 10,000. This is possible confusing
* for you, but I felt it best not to lose any precision, and floats
QNetworkReply* getSimilar( int limit = -1 ) const;
/** The match percentage is returned from last.fm as a 4 significant
* figure floating point value. So we multply it by 100 to make an
* integer in the range of 0 to 10,000. This is possible confusing
* for you, but I felt it best not to lose any precision, and floats
* aren't much fun. */
static QMap<int, QString> getSimilar( QNetworkReply* );
/** use Tag::list to get the tag list out of the finished reply */
QNetworkReply* getTags() const;
QNetworkReply* getTopTags() const;
QNetworkReply* getTopTracks() const;
static QStringList getTopTracks( QNetworkReply* );
/** Last.fm dictates that you may submit at most 10 of these */
QNetworkReply* addTags( const QStringList& ) const;
QNetworkReply* search( int limit = -1 ) const;
static QList<Artist> list( QNetworkReply* );
QMap<QString, QString> params( const QString& method ) const;
};
}

View File

@@ -45,8 +45,6 @@ namespace lastfm
static QNetworkReply* create( const QString& title, const QString& description = "" );
static QNetworkReply* fetch( const QUrl& url );
static Xspf fetch( QNetworkReply* );
};
}

View File

@@ -63,7 +63,7 @@ Tag::list( QNetworkReply* r )
{
QMap<int, QString> tags;
try {
foreach (XmlQuery xq, XmlQuery(ws::parse(r)).children("tag"))
foreach (XmlQuery xq, XmlQuery( r->readAll() ).children("tag"))
// we toLower always as otherwise it is ugly mixed case, as first
// ever tag decides case, and Last.fm is case insensitive about it
// anyway

View File

@@ -37,6 +37,9 @@ namespace lastfm
operator QString() const { return m_name; }
QString name() const { return m_name; }
lastfm::Tag operator=( const Tag& that ) const { return Tag( that.name() ); }
bool operator<( const Tag& that ) const { return this->m_name < that.m_name; }
/** the global tag page at www.last.fm */
QUrl www() const;

View File

@@ -0,0 +1,20 @@
#include "Tasteometer.h"
#include "lastfm.h"
lastfm::Tasteometer::Tasteometer()
{
}
QNetworkReply*
lastfm::Tasteometer::compare( const User& left, const User& right )
{
QMap<QString, QString> map;
map["method"] = "Tasteometer.compare";
map["type1"] = "user";
map["value1"] = left.name();
map["type2"] = "user";
map["value2"] = right.name();
return lastfm::ws::get( map );
}

View File

@@ -0,0 +1,18 @@
#ifndef TASTEOMETER_H
#define TASTEOMETER_H
#include <lastfm/global.h>
namespace lastfm
{
class LASTFM_DLLEXPORT Tasteometer
{
public:
Tasteometer();
public:
static QNetworkReply* compare( const User& left, const User& right );
};
}
#endif // TASTEOMETER_H

View File

@@ -17,13 +17,58 @@
You should have received a copy of the GNU General Public License
along with liblastfm. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QFileInfo>
#include <QStringList>
#include <QAbstractNetworkCache>
#include "Track.h"
#include "User.h"
#include "../core/UrlBuilder.h"
#include "../core/XmlQuery.h"
#include "../ws/ws.h"
#include <QFileInfo>
#include <QStringList>
lastfm::TrackContext::TrackContext()
:m_type( Unknown )
{
}
lastfm::TrackContext::TrackContext( const QString& type, const QList<QString>& values )
:m_type( getType( type ) ), m_values( values )
{
}
lastfm::TrackContext::Type
lastfm::TrackContext::getType( const QString& typeString )
{
Type type = Unknown;
if ( typeString == "artist" )
type = Artist;
else if ( typeString == "user" )
type = User;
else if ( typeString == "neighbour" )
type = Neighbour;
else if ( typeString == "friend" )
type = Friend;
return type;
}
lastfm::TrackContext::Type
lastfm::TrackContext::type() const
{
return m_type;
}
QList<QString>
lastfm::TrackContext::values() const
{
return m_values;
}
lastfm::TrackData::TrackData()
@@ -33,9 +78,9 @@ lastfm::TrackData::TrackData()
rating( 0 ),
fpid( -1 ),
loved( false ),
null( false ),
scrobbleStatus( Track::Null ),
scrobbleError( Track::None ),
null( false )
scrobbleError( Track::None )
{}
lastfm::Track::Track()
@@ -51,32 +96,11 @@ lastfm::Track::Track( const QDomElement& e )
d = new TrackData;
if (e.isNull()) { d->null = true; return; }
//TODO: not sure of lastfm's xml changed, but <track> nodes have
// <artist><name>Artist Name</name><mbid>..<url></artist>
// as children isntead of <artist>Artist Name<artist>
// we detect both here.
QDomNode artistName = e.namedItem( "artist" ).namedItem( "name" );
if( artistName.isNull() ) {
d->artist = e.namedItem( "artist" ).toElement().text();
} else {
d->artist = artistName.toElement().text();
}
d->artist = e.namedItem( "artist" ).toElement().text();
d->albumArtist = e.namedItem( "albumArtist" ).toElement().text();
d->album = e.namedItem( "album" ).toElement().text();
//TODO: not sure if lastfm xml's changed, or if chart.getTopTracks uses
//a different format, but the title is stored at
//<track><name>Title</name>...
//we detect both here.
QDomNode trackTitle = e.namedItem( "name" );
if( trackTitle.isNull() )
d->title = e.namedItem( "track" ).toElement().text();
else
d->title = trackTitle.toElement().text();
d->title = e.namedItem( "track" ).toElement().text();
d->correctedArtist = e.namedItem( "correctedArtist" ).toElement().text();
d->correctedAlbumArtist = e.namedItem( "correctedAlbumArtist" ).toElement().text();
d->correctedAlbum = e.namedItem( "correctedAlbum" ).toElement().text();
@@ -108,9 +132,16 @@ lastfm::Track::Track( const QDomElement& e )
void
lastfm::TrackData::onLoveFinished()
{
XmlQuery lfm = static_cast<QNetworkReply*>(sender())->readAll();
if ( lfm.attribute( "status" ) == "ok")
loved = true;
try
{
XmlQuery lfm = static_cast<QNetworkReply*>(sender())->readAll();
if ( lfm.attribute( "status" ) == "ok")
loved = true;
}
catch (...)
{
}
emit loveToggled( loved );
}
@@ -118,35 +149,51 @@ lastfm::TrackData::onLoveFinished()
void
lastfm::TrackData::onUnloveFinished()
{
XmlQuery lfm = static_cast<QNetworkReply*>(sender())->readAll();
if ( lfm.attribute( "status" ) == "ok")
loved = false;
try
{
XmlQuery lfm = static_cast<QNetworkReply*>(sender())->readAll();
if ( lfm.attribute( "status" ) == "ok")
loved = false;
}
catch (...)
{
}
emit loveToggled( loved );
}
void
lastfm::TrackData::onGotInfo()
{
lastfm::XmlQuery lfm( static_cast<QNetworkReply*>(sender())->readAll() );
const QByteArray data = static_cast<QNetworkReply*>(sender())->readAll();
QString imageUrl = lfm["track"]["image size=small"].text();
if ( !imageUrl.isEmpty() ) m_images[lastfm::Small] = imageUrl;
imageUrl = lfm["track"]["image size=medium"].text();
if ( !imageUrl.isEmpty() ) m_images[lastfm::Medium] = imageUrl;
imageUrl = lfm["track"]["image size=large"].text();
if ( !imageUrl.isEmpty() ) m_images[lastfm::Large] = imageUrl;
imageUrl = lfm["track"]["image size=extralarge"].text();
if ( !imageUrl.isEmpty() ) m_images[lastfm::ExtraLarge] = imageUrl;
imageUrl = lfm["track"]["image size=mega"].text();
if ( !imageUrl.isEmpty() ) m_images[lastfm::Mega] = imageUrl;
try
{
lastfm::XmlQuery lfm( data );
loved = lfm["track"]["userloved"].text().toInt();
QString imageUrl = lfm["track"]["image size=small"].text();
if ( !imageUrl.isEmpty() ) m_images[lastfm::Small] = imageUrl;
imageUrl = lfm["track"]["image size=medium"].text();
if ( !imageUrl.isEmpty() ) m_images[lastfm::Medium] = imageUrl;
imageUrl = lfm["track"]["image size=large"].text();
if ( !imageUrl.isEmpty() ) m_images[lastfm::Large] = imageUrl;
imageUrl = lfm["track"]["image size=extralarge"].text();
if ( !imageUrl.isEmpty() ) m_images[lastfm::ExtraLarge] = imageUrl;
imageUrl = lfm["track"]["image size=mega"].text();
if ( !imageUrl.isEmpty() ) m_images[lastfm::Mega] = imageUrl;
emit gotInfo( lfm );
emit loveToggled( loved );
loved = lfm["track"]["userloved"].text().toInt();
emit gotInfo( data );
emit loveToggled( loved );
}
catch (...)
{
emit gotInfo( data );
}
// you should connect everytime you call getInfo
disconnect( this, SIGNAL(gotInfo(const XmlQuery&)), 0, 0);
disconnect( this, SIGNAL(gotInfo(const QByteArray&)), 0, 0);
}
@@ -310,6 +357,21 @@ lastfm::Track::share( const QStringList& recipients, const QString& message, boo
}
void
lastfm::Track::invalidateGetInfo()
{
// invalidate the track.getInfo cache
QAbstractNetworkCache* cache = lastfm::nam()->cache();
if ( cache )
{
QMap<QString, QString> map = params("getInfo", true);
if (!lastfm::ws::Username.isEmpty()) map["username"] = lastfm::ws::Username;
if (!lastfm::ws::SessionKey.isEmpty()) map["sk"] = lastfm::ws::SessionKey;
cache->remove( lastfm::ws::url( map ) );
}
}
void
lastfm::MutableTrack::setFromLfm( const XmlQuery& lfm )
{
@@ -329,12 +391,20 @@ lastfm::MutableTrack::setFromLfm( const XmlQuery& lfm )
d->forceLoveToggled( d->loved );
}
void
lastfm::MutableTrack::setImageUrl( lastfm::ImageSize size, const QString& url )
{
d->m_images[size] = url;
}
void
lastfm::MutableTrack::love()
{
QNetworkReply* reply = ws::post(params("love"));
QObject::connect( reply, SIGNAL(finished()), signalProxy(), SLOT(onLoveFinished()));
invalidateGetInfo();
}
@@ -343,8 +413,9 @@ lastfm::MutableTrack::unlove()
{
QNetworkReply* reply = ws::post(params("unlove"));
QObject::connect( reply, SIGNAL(finished()), signalProxy(), SLOT(onUnloveFinished()));
}
invalidateGetInfo();
}
QNetworkReply*
lastfm::MutableTrack::ban()
@@ -390,15 +461,24 @@ lastfm::Track::getTags() const
}
void
lastfm::Track::getInfo(const QString& user, const QString& sk) const
lastfm::Track::getInfo() const
{
QMap<QString, QString> map = params("getInfo", true);
if (!user.isEmpty()) map["username"] = user;
if (!sk.isEmpty()) map["sk"] = sk;
if (!lastfm::ws::Username.isEmpty()) map["username"] = lastfm::ws::Username;
if (!lastfm::ws::SessionKey.isEmpty()) map["sk"] = lastfm::ws::SessionKey;
QObject::connect( ws::get( map ), SIGNAL(finished()), d.data(), SLOT(onGotInfo()));
}
QNetworkReply*
lastfm::Track::getBuyLinks( const QString& country ) const
{
QMap<QString, QString> map = params( "getBuyLinks", true );
map["country"] = country;
return ws::get( map );
}
QNetworkReply*
lastfm::Track::addTags( const QStringList& tags ) const
{
@@ -423,9 +503,15 @@ lastfm::Track::removeTag( const QString& tag ) const
QNetworkReply*
lastfm::Track::updateNowPlaying() const
{
return updateNowPlaying(duration());
}
QNetworkReply*
lastfm::Track::updateNowPlaying( int duration ) const
{
QMap<QString, QString> map = params("updateNowPlaying");
map["duration"] = QString::number( duration() );
map["duration"] = QString::number( duration );
if ( !album().isNull() ) map["album"] = album();
map["context"] = extra("playerId");
@@ -434,6 +520,17 @@ lastfm::Track::updateNowPlaying() const
return ws::post(map);
}
QNetworkReply*
lastfm::Track::removeNowPlaying() const
{
QMap<QString, QString> map;
map["method"] = "track.removeNowPlaying";
qDebug() << map;
return ws::post(map);
}
QNetworkReply*
lastfm::Track::scrobble() const
@@ -477,7 +574,7 @@ lastfm::Track::scrobble(const QList<lastfm::Track>& tracks)
QUrl
lastfm::Track::www() const
{
return UrlBuilder( "music" ).slash( d->artist ).slash( album().isNull() ? QString("_") : album()).slash( d->title ).url();
return UrlBuilder( "music" ).slash( artist( Corrected ) ).slash( album( Corrected ).isNull() ? QString("_") : album( Corrected )).slash( title( Corrected ) ).url();
}

View File

@@ -33,6 +33,31 @@
namespace lastfm {
class LASTFM_DLLEXPORT TrackContext
{
public:
enum Type
{
Unknown,
User,
Friend,
Neighbour,
Artist
};
TrackContext();
TrackContext( const QString& type, const QList<QString>& values );
Type type() const;
QList<QString> values() const;
private:
static Type getType( const QString& type );
private:
Type m_type;
QList<QString> m_values;
};
class TrackData : public QObject, public QSharedData
{
Q_OBJECT
@@ -51,6 +76,7 @@ public:
QString correctedAlbumArtist;
QString correctedAlbum;
QString correctedTitle;
TrackContext context;
uint trackNumber;
uint duration;
short source;
@@ -63,6 +89,7 @@ public:
QMap<lastfm::ImageSize, QUrl> m_images;
short scrobbleStatus;
short scrobbleError;
QString scrobbleErrorText;
//FIXME I hate this, but is used for radio trackauth etc.
QMap<QString,QString> extras;
@@ -83,13 +110,12 @@ signals:
void loveToggled( bool love );
void loveFinished();
void unlovedFinished();
void gotInfo( const XmlQuery& );
void gotInfo( const QByteArray& );
void scrobbleStatusChanged();
void corrected( QString correction );
};
/** Our track type. It's quite good, you may want to use it as your track type
* in general. It is explicitly shared. Which means when you make a copy, they
* both point to the same data still. This is like Qt's implicitly shared
@@ -191,6 +217,7 @@ public:
ScrobbleStatus scrobbleStatus() const { return static_cast<ScrobbleStatus>(d->scrobbleStatus); }
ScrobbleError scrobbleError() const { return static_cast<ScrobbleError>(d->scrobbleError); }
QString scrobbleErrorText() const { return d->scrobbleErrorText; }
/** default separator is an en-dash */
QString toString() const { return toString( Corrected ); }
@@ -198,6 +225,8 @@ public:
QString toString( const QChar& separator, Corrections corrections = Original ) const;
/** the standard representation of this object as an XML node */
QDomElement toDomElement( class QDomDocument& ) const;
TrackContext context() const { return d->context; }
QString extra( const QString& key ) const{ return d->extras[ key ]; }
@@ -219,7 +248,8 @@ public:
QNetworkReply* getTags() const; // for the logged in user
QNetworkReply* getTopTags() const;
QNetworkReply* getTopFans() const;
void getInfo(const QString& user = "", const QString& sk = "") const;
void getInfo() const;
QNetworkReply* getBuyLinks( const QString& country ) const;
/** you can only add 10 tags, we submit everything you give us, but the
* docs state 10 only. Will return 0 if the list is empty. */
@@ -229,6 +259,8 @@ public:
/** scrobble the track */
QNetworkReply* updateNowPlaying() const;
QNetworkReply* updateNowPlaying( int duration ) const;
QNetworkReply* removeNowPlaying() const;
QNetworkReply* scrobble() const;
static QNetworkReply* scrobble(const QList<lastfm::Track>& tracks);
@@ -238,7 +270,7 @@ public:
protected:
QExplicitlySharedDataPointer<TrackData> d;
QMap<QString, QString> params( const QString& method, bool use_mbid = false ) const;
void invalidateGetInfo();
private:
Track( TrackData* that_d ) : d( that_d )
{}
@@ -272,6 +304,7 @@ public:
}
void setFromLfm( const XmlQuery& lfm );
void setImageUrl( lastfm::ImageSize size, const QString& url );
void setArtist( QString artist ) { d->artist = artist.trimmed(); }
void setAlbumArtist( QString albumArtist ) { d->albumArtist = albumArtist.trimmed(); }
@@ -293,6 +326,7 @@ public:
d->forceScrobbleStatusChanged();
}
void setScrobbleError( ScrobbleError scrobbleError ) { d->scrobbleError = scrobbleError; }
void setScrobbleErrorText( const QString& scrobbleErrorText ) { d->scrobbleErrorText = scrobbleErrorText; }
/** you also must scrobble this track for the love to become permenant */
void love();
@@ -304,6 +338,8 @@ public:
void setExtra( const QString& key, const QString& value ) { d->extras[key] = value; }
void removeExtra( QString key ) { d->extras.remove( key ); }
void setTimeStamp( const QDateTime& dt ) { d->time = dt; }
void setContext( TrackContext context ) { d->context = context;}
};

View File

@@ -23,6 +23,7 @@
#include "../core/XmlQuery.h"
#include <QStringList>
#include <lastfm/UserList>
#include <QAbstractNetworkCache>
using lastfm::User;
using lastfm::UserList;
@@ -63,11 +64,22 @@ User::params(const QString& method) const
QNetworkReply*
User::getFriends( int perPage, int page ) const
User::getFriends( bool recentTracks, int limit, int page ) const
{
QMap<QString, QString> map = params( "getFriends" );
map["limit"] = QString::number(perPage);
map["page"] = QString::number(page);
map["limit"] = QString::number( limit );
map["page"] = QString::number( page );
if ( recentTracks ) map["recenttracks"] = "1";
return ws::get( map );
}
QNetworkReply*
User::getFriendsListeningNow( int limit, int page ) const
{
QMap<QString, QString> map = params( "getFriendsListeningNow" );
map["limit"] = QString::number( limit );
map["page"] = QString::number( page );
return ws::get( map );
}
@@ -80,9 +92,13 @@ User::getTopTags() const
QNetworkReply*
User::getTopArtists() const
User::getTopArtists( QString period, int limit, int page ) const
{
return ws::get( params( "getTopArtists" ) );
QMap<QString, QString> map = params( "getTopArtists" );
map["period"] = period;
map["limit"] = QString::number( limit );
map["page"] = QString::number( page );
return ws::get( map );
}
@@ -94,21 +110,46 @@ User::getRecentArtists() const
QNetworkReply*
User::getRecentTracks() const
User::getRecentTracks( int limit , int page ) const
{
return ws::get( params( "getRecentTracks" ) );
}
QMap<QString, QString> map = params( "getRecentTracks" );
map["limit"] = QString::number( limit );
map["page"] = QString::number( page );
QNetworkReply*
User::getRecentStations() const
{
return ws::post( params( "getRecentStations" ) );
QAbstractNetworkCache* cache = lastfm::nam()->cache();
if ( cache )
cache->remove( lastfm::ws::url( map ) );
return ws::get( map );
}
QNetworkReply*
User::getNeighbours() const
User::getRecentStations( int limit, int page ) const
{
return ws::get( params( "getNeighbours" ) );
QMap<QString, QString> map = params( "getRecentStations" );
map["limit"] = QString::number( limit );
map["page"] = QString::number( page );
return ws::get( map );
}
QNetworkReply*
User::getRecommendedArtists( int limit, int page ) const
{
QMap<QString, QString> map = params( "getRecommendedArtists" );
map["limit"] = QString::number( limit );
map["page"] = QString::number( page );
return ws::get( map );
}
QNetworkReply*
User::getNeighbours( int limit, int page ) const
{
QMap<QString, QString> map = params( "getNeighbours" );
map["limit"] = QString::number( limit );
map["page"] = QString::number( page );
return ws::get( map );
}
@@ -124,7 +165,7 @@ User::list( QNetworkReply* r )
{
UserList users;
try {
XmlQuery lfm = ws::parse(r);
XmlQuery lfm = r->readAll();
foreach (XmlQuery e, lfm.children( "user" ))
{
User u( e );
@@ -185,7 +226,7 @@ UserDetails::UserDetails( QNetworkReply* reply )
{
try
{
XmlQuery user = XmlQuery(ws::parse(reply))["user"];
XmlQuery user = XmlQuery( reply->readAll() )["user"];
m_age = user["age"].text().toUInt();
m_scrobbles = user["playcount"].text().toUInt();
m_registered = QDateTime::fromTime_t(user["registered"].attribute("unixtime").toUInt());
@@ -209,25 +250,15 @@ UserDetails::UserDetails( QNetworkReply* reply )
QString
UserDetails::getInfoString() const
{
#define tr QObject::tr
;
QString text;
if (m_gender.known() && m_age > 0 && m_scrobbles > 0)
{
text = tr("A %1, %2 years of age with %L3 scrobbles")
.arg( m_gender.toString() )
.arg( m_age )
.arg( m_scrobbles );
}
else if (m_scrobbles > 0)
{
text = tr("%L1 scrobbles").arg( m_scrobbles );
}
text = QObject::tr("%1").arg( m_realName.isEmpty() ? m_name : m_realName );
if ( m_age ) text.append( QObject::tr(", %1").arg( m_age ) );
if ( m_gender.known() ) text.append( QObject::tr(", %1").arg( m_gender.toString() ) );
if ( !m_country.isEmpty() ) text.append( QObject::tr(", %1").arg( m_country ) );
if ( m_scrobbles ) text.append( QObject::tr(", %L1 scrobbles").arg( m_scrobbles ) );
return text;
#undef tr
}
void

View File

@@ -42,7 +42,9 @@ namespace lastfm
User( const class XmlQuery& xml );
lastfm::User& operator=( const lastfm::User& that ) { m_name = that.name(); m_images = that.m_images; m_realName = that.m_realName; m_match = that.m_match; return *this; }
bool operator==(const lastfm::User& that) const { return m_name == that.m_name; }
bool operator<(const lastfm::User& that) const { return m_name < that.m_name; }
operator QString() const { return m_name; }
QString name() const { return m_name; }
@@ -52,14 +54,16 @@ namespace lastfm
QNetworkReply* getTopTags() const;
/** use User::list() on the response to get a QList<User> */
QNetworkReply* getFriends(int perPage = 50, int page = 1) const;
QNetworkReply* getNeighbours() const;
QNetworkReply* getFriends( bool recentTracks = false, int limit = 50, int page = 1 ) const;
QNetworkReply* getFriendsListeningNow( int limit = 50, int page = 1 ) const;
QNetworkReply* getNeighbours( int limit = 50, int page = 1 ) const;
QNetworkReply* getPlaylists() const;
QNetworkReply* getTopArtists() const;
QNetworkReply* getRecentTracks() const;
QNetworkReply* getTopArtists( QString period = "overall", int limit = 50, int page = 1 ) const;
QNetworkReply* getRecentTracks( int limit = 50, int page = 1 ) const;
QNetworkReply* getRecentArtists() const;
QNetworkReply* getRecentStations() const;
QNetworkReply* getRecentStations( int limit = 10, int page = 1 ) const;
QNetworkReply* getRecommendedArtists( int limit = 50, int page = 1 ) const;
static UserList list( QNetworkReply* );
@@ -90,6 +94,35 @@ namespace lastfm
QMap<QString, QString> params( const QString& method ) const;
};
class LASTFM_DLLEXPORT Gender
{
QString s;
public:
Gender() :s(/*confused!*/){}
Gender( const QString& ss ) :s( ss.toLower() )
{}
bool known() const { return male() || female(); }
bool male() const { return s == "m"; }
bool female() const { return s == "f"; }
QString toString() const
{
QString result;
if (male())
result = QObject::tr( "m" );
else if (female())
result = QObject::tr( "f" );
else
result = QObject::tr( "n" ); // as in neuter
return result;
}
};
/** The Extended User contains extra information about a user's account */
class LASTFM_DLLEXPORT UserDetails : public User
@@ -109,6 +142,8 @@ namespace lastfm
bool canBootstrap() const{ return m_canBootstrap; }
quint32 scrobbleCount() const{ return m_scrobbles; }
QDateTime dateRegistered() const { return m_registered; }
Gender gender() const { return m_gender; }
QString country() const { return m_country; }
void setScrobbleCount( quint32 scrobblesCount );
void setDateRegistered( const QDateTime& date );
@@ -127,39 +162,7 @@ namespace lastfm
// static QNetworkReply* getRecommendedArtists();
protected:
class Gender
{
QString s;
public:
Gender() :s(/*confused!*/){}
Gender( const QString& ss ) :s( ss.toLower() )
{}
bool known() const { return male() || female(); }
bool male() const { return s == "m"; }
bool female() const { return s == "f"; }
QString toString() const
{
#define tr QObject::tr
QStringList list;
if (male())
list << tr("boy") << tr("lad") << tr("chap") << tr("guy");
else if (female())
// I'm not sexist, it's just I'm gutless and couldn't think
// of any other non offensive terms for women!
list << tr("girl") << tr("lady") << tr("lass");
else
return tr("person");
return list.value( QDateTime::currentDateTime().toTime_t() % list.count() );
#undef tr
}
} m_gender;
Gender m_gender;
unsigned short m_age;
unsigned int m_scrobbles;
QDateTime m_registered;

View File

@@ -17,13 +17,22 @@
You should have received a copy of the GNU General Public License
along with liblastfm. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Xspf.h"
#include "../core/XmlQuery.h"
#include <QTimer>
#include <QUrl>
lastfm::Xspf::Xspf( const QDomElement& playlist_node )
#include "../core/XmlQuery.h"
#include "Xspf.h"
lastfm::Xspf::Xspf( const QDomElement& playlist_node, QObject* parent )
:QObject( parent )
{
XmlQuery e( playlist_node );
int expirySeconds = e["link rel=http://www.last.fm/expiry"].text().toInt();
QTimer::singleShot( expirySeconds * 1000, this, SLOT(onExpired()));
m_title = e["title"].text();
@@ -44,6 +53,23 @@ lastfm::Xspf::Xspf( const QDomElement& playlist_node )
t.setAlbum( e["album"].text() );
t.setDuration( e["duration"].text().toInt() / 1000 );
t.setLoved( e["extension"]["loved"].text() == "1" );
m_tracks += t; // outside try block since location is enough basically
t.setSource( Track::LastFmRadio );
QList<QString> contexts;
QDomNodeList contextsNodeList = QDomElement(e["extension"]["context"]).childNodes();
for ( int i = 0 ; i < contextsNodeList.count() ; ++i )
contexts.append( contextsNodeList.item(i).toElement().text() );
if ( contexts.count() > 0 )
t.setContext( TrackContext( contextsNodeList.item(0).toElement().tagName(), contexts ) );
m_tracks << t; // outside try block since location is enough basically
}
}
void
lastfm::Xspf::onExpired()
{
emit expired();
}

View File

@@ -25,15 +25,24 @@
namespace lastfm
{
class LASTFM_DLLEXPORT Xspf
class LASTFM_DLLEXPORT Xspf : public QObject
{
Q_OBJECT
public:
/** pass in the playlist node! */
Xspf( const class QDomElement& playlist_node );
Xspf( const class QDomElement& playlist_node, QObject* parent );
QList<Track> tracks() const { return m_tracks; }
QString title() const{ return m_title; }
bool isEmpty() const { return m_tracks.isEmpty(); }
Track takeFirst() { return m_tracks.takeFirst(); }
signals:
void expired();
private slots:
void onExpired();
private:
QList<Track> m_tracks;
QString m_title;

View File

@@ -43,6 +43,8 @@ lastfm::InternetConnectionMonitor::InternetConnectionMonitor( QObject *parent )
void
lastfm::InternetConnectionMonitor::onFinished( QNetworkReply* reply )
{
if( reply->attribute( QNetworkRequest::SourceIsFromCacheAttribute).toBool() ) return;
switch( reply->error() )
{
case QNetworkReply::NoError:
@@ -51,6 +53,7 @@ lastfm::InternetConnectionMonitor::onFinished( QNetworkReply* reply )
m_up = true;
emit up();
emit connectivityChanged( m_up );
qDebug() << "Internet connection is reachable :)";
}
break;
case QNetworkReply::HostNotFoundError:
@@ -78,9 +81,10 @@ lastfm::InternetConnectionMonitor::onNetworkUp()
#ifdef Q_OS_MAC
// We don't need to check on mac as the
// check is done as part of the reach api
m_up = true;
m_up = true;
emit up();
emit connectivityChanged( m_up );
qDebug() << "Internet connection is reachable :)";
#else
qDebug() << "Network seems to be up again. Let's try if there's internet connection!";
lastfm::nam()->head( QNetworkRequest( QUrl( tr( "http://www.last.fm/" ) ) ) );
@@ -90,7 +94,7 @@ lastfm::InternetConnectionMonitor::onNetworkUp()
void
lastfm::InternetConnectionMonitor::onNetworkDown()
{
qDebug() << "Internet is down :( boo!!";
qDebug() << "Internet is unreachable :(";
m_up = false;
emit down();
emit connectivityChanged( m_up );

View File

@@ -1,5 +1,5 @@
/*
Copyright 2009 Last.fm Ltd.
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
This file is part of liblastfm.
@@ -34,11 +34,11 @@
static struct NetworkAccessManagerInit
{
// We do this upfront because then our Firehose QTcpSocket will have a proxy
// We do this upfront because then our Firehose QTcpSocket will have a proxy
// set by default. As well as any plain QNetworkAcessManager stuff, and the
// scrobbler
// In theory we should do this every request in case the configuration
// changes but that is fairly unlikely use case, init? Maybe we should
// In theory we should do this every request in case the configuration
// changes but that is fairly unlikely use case, init? Maybe we should
// anyway..
NetworkAccessManagerInit()
@@ -50,7 +50,7 @@ static struct NetworkAccessManagerInit
// at two seconds, so that hangs startup
if (!s.fAutoDetect && s.lpszProxy)
{
QUrl url( QString::fromUtf16((const unsigned short*)s.lpszProxy) );
QUrl url( QString::fromUtf16(s.lpszProxy) );
QNetworkProxy proxy( QNetworkProxy::HttpProxy );
proxy.setHostName( url.host() );
proxy.setPort( url.port() );
@@ -69,10 +69,10 @@ static struct NetworkAccessManagerInit
}
#endif
}
} init;
} init;
namespace lastfm
namespace lastfm
{
LASTFM_DLLEXPORT QByteArray UserAgent;
}
@@ -106,12 +106,12 @@ lastfm::NetworkAccessManager::~NetworkAccessManager()
QNetworkProxy
lastfm::NetworkAccessManager::proxy( const QNetworkRequest& request )
{
{
Q_UNUSED( request );
#ifdef WIN32
IeSettings s;
if (s.fAutoDetect)
if (s.fAutoDetect)
{
if (!m_pac) {
m_pac = new Pac;
@@ -122,9 +122,9 @@ lastfm::NetworkAccessManager::proxy( const QNetworkRequest& request )
}
}
return m_pac->resolve( request, s.lpszAutoConfigUrl );
}
}
#endif
return QNetworkProxy::applicationProxy();
}
@@ -136,7 +136,7 @@ lastfm::NetworkAccessManager::createRequest( Operation op, const QNetworkRequest
request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
request.setRawHeader( "User-Agent", lastfm::UserAgent );
#ifdef WIN32
// PAC proxies can vary by domain, so we have to check everytime :(
QNetworkProxy proxy = this->proxy( request );
@@ -152,7 +152,7 @@ void
lastfm::NetworkAccessManager::onConnectivityChanged( bool up )
{
Q_UNUSED( up );
#ifdef WIN32
if (up && m_pac) m_pac->resetFailedState();
#endif

View File

@@ -19,7 +19,7 @@
*/
#include "MNetworkConnectionMonitor.h"
#include "../ws.h"
#include "ws/ws.h"
#include <QPointer>
#include <SystemConfiguration/SCNetworkReachability.h>
@@ -58,8 +58,6 @@ MNetworkConnectionMonitor::callback( SCNetworkReachabilityRef target,
else
b = flags & (kSCNetworkFlagsReachable | kSCNetworkFlagsTransientConnection | kSCNetworkFlagsConnectionAutomatic);
qDebug() << "Can reach " LASTFM_WS_HOSTNAME ":" << b << ", flags:" << flags;
// basically, avoids telling everyone that we're up already on startup
if (up == b)
return;

View File

@@ -1,5 +1,5 @@
/*
Copyright 2009 Last.fm Ltd.
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
This file is part of liblastfm.
@@ -23,13 +23,12 @@
#endif
#include <objbase.h>
//#include <boost/range/atl.hpp>
//#include <atlbase.h>
//#include <atlcom.h>
#include <winable.h>
#include <atlbase.h>
#include <atlcom.h>
/** @brief WsConnectionMonitor needs Com to work as early as possible so we do this
* @author <doug@last.fm>
* @author <doug@last.fm>
*/
class ComSetup
{
@@ -38,27 +37,27 @@ public:
{
HRESULT hr = CoInitialize(0);
m_bComInitialised = SUCCEEDED(hr);
//_ASSERT(m_bComInitialised);
_ASSERT(m_bComInitialised);
if (m_bComInitialised) {
setupSecurity();
}
}
void setupSecurity()
{
//CSecurityDescriptor sd;
//sd.InitializeFromThreadToken();
//HRESULT hr = CoInitializeSecurity(sd, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_PKT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);
//_ASSERT(SUCCEEDED(hr));
CSecurityDescriptor sd;
sd.InitializeFromThreadToken();
HRESULT hr = CoInitializeSecurity(sd, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_PKT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);
_ASSERT(SUCCEEDED(hr));
}
~ComSetup()
{
if (m_bComInitialised) {
CoUninitialize();
}
}
private:
bool m_bComInitialised;
};

View File

@@ -1,5 +1,5 @@
/*
Copyright 2009 Last.fm Ltd.
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
This file is part of liblastfm.
@@ -26,7 +26,6 @@
*/
struct IeSettings : WINHTTP_CURRENT_USER_IE_PROXY_CONFIG
{
#ifndef WIN32
IeSettings()
{
if (!WinHttpGetIEProxyConfigForCurrentUser(this)) {
@@ -34,14 +33,11 @@ struct IeSettings : WINHTTP_CURRENT_USER_IE_PROXY_CONFIG
lpszAutoConfigUrl = lpszProxy = lpszProxyBypass = 0;
}
}
#endif
#ifndef WIN32
~IeSettings()
{
if (lpszAutoConfigUrl) GlobalFree(lpszAutoConfigUrl);
if (lpszProxy) GlobalFree(lpszProxy);
if (lpszProxyBypass) GlobalFree(lpszProxyBypass);
}
#endif
};

View File

@@ -1,5 +1,5 @@
/*
Copyright 2009 Last.fm Ltd.
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
This file is part of liblastfm.
@@ -32,37 +32,35 @@ NdisEvents::NdisEvents()
NdisEvents::~NdisEvents()
{
#ifndef WIN32
if (m_pSink)
m_pSink->disconnect();
#endif
//if (m_pServices && m_pSink)
//m_pServices->CancelAsyncCall(m_pSink);
if (m_pServices && m_pSink)
m_pServices->CancelAsyncCall(m_pSink);
// and reference counting will take care of the WmiSink object
}
HRESULT
NdisEvents::registerForNdisEvents()
{
HRESULT hr = 0; //m_pLocator.CoCreateInstance(CLSID_WbemLocator);
HRESULT hr = m_pLocator.CoCreateInstance(CLSID_WbemLocator);
if (FAILED(hr))
return hr;
// Connect to the root\wmi namespace with the current user.
hr = 0; /*m_pLocator->ConnectServer(CComBSTR("ROOT\\WMI"), // strNetworkResource
hr = m_pLocator->ConnectServer(CComBSTR("ROOT\\WMI"), // strNetworkResource
NULL, // strUser
NULL, // strPassword
NULL, // strLocale
NULL, // strLocale
0, // lSecurityFlags
CComBSTR(""), // strAuthority
CComBSTR(""), // strAuthority
NULL, // pCtx
&m_pServices
);*/
);
if (FAILED(hr))
return hr;
#ifndef WIN32
m_pSink = new WmiSink(this);
#endif
//////////////////////////
// other notifications we're not interested in right now include...
@@ -77,12 +75,12 @@ NdisEvents::registerForNdisEvents()
// MSNdis_StatusProtocolUnbind
// MSNdis_StatusMediaSpecificIndication
/*CComBSTR wql("WQL");
CComBSTR wql("WQL");
CComBSTR query("SELECT * FROM MSNdis_StatusMediaDisconnect");
hr = m_pServices->ExecNotificationQueryAsync(wql, query, 0, 0, m_pSink);
query = "SELECT * FROM MSNdis_StatusMediaConnect";
hr = m_pServices->ExecNotificationQueryAsync(wql, query, 0, 0, m_pSink);*/
hr = m_pServices->ExecNotificationQueryAsync(wql, query, 0, 0, m_pSink);
return S_OK;
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2009 Last.fm Ltd.
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
This file is part of liblastfm.
@@ -21,8 +21,8 @@
#define NDIS_EVENTS_H
#include <windows.h>
//#include <atlbase.h>
//#include <WbemCli.h>
#include <atlbase.h>
#include <WbemCli.h>
class NdisEvents
{
@@ -35,8 +35,8 @@ public:
virtual void onConnectionDown(BSTR name) = 0;
private:
//CComPtr<IWbemLocator> m_pLocator;
//CComPtr<IWbemServices> m_pServices;
CComPtr<IWbemLocator> m_pLocator;
CComPtr<IWbemServices> m_pServices;
class WmiSink *m_pSink;
};

View File

@@ -1,5 +1,5 @@
/*
Copyright 2009 Last.fm Ltd.
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
This file is part of liblastfm.
@@ -21,9 +21,8 @@
#include <QNetworkRequest>
#include <QStringList>
#include <QUrl>
//#include <atlbase.h>
//#include <atlconv.h>
#include <winhttp.h>
#include <atlbase.h>
#include <atlconv.h>
static bool
@@ -46,7 +45,7 @@ parsePacServer(const QString &s, QNetworkProxy &p)
static QList<QNetworkProxy>
parsePacResult(const QString &pacResult)
{
// msdn says: "The proxy server list contains one or more of the
// msdn says: "The proxy server list contains one or more of the
// following strings separated by semicolons or whitespace."
// ([<scheme>=][<scheme>"://"]<server>[":"<port>])
@@ -74,10 +73,8 @@ lastfm::Pac::Pac()
lastfm::Pac::~Pac()
{
#ifndef WIN32
if (m_hSession)
WinHttpCloseHandle(m_hSession);
#endif
}
QNetworkProxy
@@ -89,45 +86,42 @@ lastfm::Pac::resolve(const QNetworkRequest &request, const wchar_t* pacUrl)
if (!m_hSession)
{
QByteArray user_agent = request.rawHeader("user-agent");
//m_hSession = WinHttpOpen(CA2W(user_agent), WINHTTP_ACCESS_TYPE_NO_PROXY, 0, 0, WINHTTP_FLAG_ASYNC);
m_hSession = WinHttpOpen(CA2W(user_agent), WINHTTP_ACCESS_TYPE_NO_PROXY, 0, 0, WINHTTP_FLAG_ASYNC);
}
if (m_hSession)
{
WINHTTP_PROXY_INFO info;
WINHTTP_AUTOPROXY_OPTIONS opts;
memset(&opts, 0, sizeof(opts));
if (pacUrl)
if (pacUrl)
{
//opts.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;
//opts.lpszAutoConfigUrl = pacUrl;
}
opts.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;
opts.lpszAutoConfigUrl = pacUrl;
}
else
{
//opts.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT;
//opts.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A;
opts.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT;
opts.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A;
}
opts.fAutoLogonIfChallenged = TRUE;
#ifndef WIN32
if (WinHttpGetProxyForUrl(m_hSession, (const WCHAR*)(request.url().toString().utf16()), &opts, &info)) {
if (info.lpszProxy)
if (WinHttpGetProxyForUrl(m_hSession, request.url().toString().utf16(), &opts, &info)) {
if (info.lpszProxy)
{
QList<QNetworkProxy> proxies = parsePacResult(QString::fromUtf16((const ushort*)info.lpszProxy));
QList<QNetworkProxy> proxies = parsePacResult(QString::fromUtf16(info.lpszProxy));
if (!proxies.empty())
{
out = proxies.at(0);
}
// pay attention! casting away constness
GlobalFree((void*)info.lpszProxy);
GlobalFree(info.lpszProxy);
}
if (info.lpszProxyBypass)
{
// pay attention! casting away constness
GlobalFree((void*)info.lpszProxyBypass);
GlobalFree(info.lpszProxyBypass);
}
} else {
m_bFailed = true;
}
#endif
}
return out;

View File

@@ -1,5 +1,5 @@
/*
Copyright 2009 Last.fm Ltd.
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
This file is part of liblastfm.
@@ -24,14 +24,14 @@
#include <windows.h>
#include <winhttp.h>
class QNetworkRequest;
namespace lastfm
{
/** @brief simple wrapper to do per url automatic proxy detection
/** @brief simple wrapper to do per url automatic proxy detection
* @author <doug@last.fm>
*/
class Pac
{
{
HINTERNET m_hSession;
bool m_bFailed;
@@ -49,4 +49,4 @@ namespace lastfm
};
}
#endif
#endif

View File

@@ -1,5 +1,5 @@
/*
Copyright 2009 Last.fm Ltd.
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
This file is part of liblastfm.
@@ -20,7 +20,7 @@
#ifndef WMISINK_WIN_H
#define WMISINK_WIN_H
#include "wbemcli.h"
#include "WbemCli.h"
// Sink object for WMI NDIS notifications
class WmiSink : public IWbemObjectSink

View File

@@ -1,5 +1,5 @@
/*
Copyright 2009 Last.fm Ltd.
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
This file is part of liblastfm.
@@ -25,15 +25,11 @@
#include <QDomElement>
#include <QLocale>
#include <QStringList>
#include <QThread>
#include <QMutex>
#include <QUrl>
static QNetworkAccessManager* nam = 0;
static QMap< QThread*, QNetworkAccessManager* > threadNamHash;
static QSet< QThread* > ourNamSet;
static QMutex namAccessMutex;
QString
QString
lastfm::ws::host()
{
QStringList const args = QCoreApplication::arguments();
@@ -47,18 +43,18 @@ lastfm::ws::host()
return LASTFM_WS_HOSTNAME;
}
static QUrl url()
static QUrl baseUrl()
{
QUrl url;
url.setScheme( "http" );
url.setHost( lastfm::ws::host() );
url.setPath( "/2.0/" );
url.setEncodedPath( "/2.0/" );
return url;
}
static QString iso639()
{
return QLocale().name().left( 2 ).toLower();
{
return QLocale().name().left( 2 ).toLower();
}
void autograph( QMap<QString, QString>& params )
@@ -86,11 +82,11 @@ static void sign( QMap<QString, QString>& params, bool sk = true )
}
QNetworkReply*
lastfm::ws::get( QMap<QString, QString> params )
QUrl
lastfm::ws::url( QMap<QString, QString> params )
{
sign( params );
QUrl url = ::url();
QUrl url = ::baseUrl();
// Qt setQueryItems doesn't encode a bunch of stuff, so we do it manually
QMapIterator<QString, QString> i( params );
while (i.hasNext()) {
@@ -100,16 +96,21 @@ lastfm::ws::get( QMap<QString, QString> params )
url.addEncodedQueryItem( key, value );
}
qDebug() << url;
return url;
}
return nam()->get( QNetworkRequest(url) );
QNetworkReply*
lastfm::ws::get( QMap<QString, QString> params )
{
return nam()->get( QNetworkRequest( url( params ) ) );
}
QNetworkReply*
lastfm::ws::post( QMap<QString, QString> params, bool sk )
{
sign( params, sk );
{
sign( params, sk );
QByteArray query;
QMapIterator<QString, QString> i( params );
while (i.hasNext()) {
@@ -120,117 +121,24 @@ lastfm::ws::post( QMap<QString, QString> params, bool sk )
+ '&';
}
QNetworkRequest req(url());
req.setHeader( QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded" );
return nam()->post( req, query );
}
QByteArray
lastfm::ws::parse( QNetworkReply* reply ) throw( ParseError )
{
try
{
QByteArray data = reply->readAll();
if (!data.size())
throw MalformedResponse;
QDomDocument xml;
xml.setContent( data );
QDomElement lfm = xml.documentElement();
if (lfm.isNull())
throw MalformedResponse;
QString const status = lfm.attribute( "status" );
QDomElement error = lfm.firstChildElement( "error" );
uint const n = lfm.childNodes().count();
// no elements beyond the lfm is perfectably acceptable <-- wtf?
// if (n == 0) // nothing useful in the response
if (status == "failed" || (n == 1 && !error.isNull()) )
throw error.isNull()
? MalformedResponse
: Error( error.attribute( "code" ).toUInt() );
switch (reply->error())
{
case QNetworkReply::RemoteHostClosedError:
case QNetworkReply::ConnectionRefusedError:
case QNetworkReply::TimeoutError:
case QNetworkReply::ContentAccessDenied:
case QNetworkReply::ContentOperationNotPermittedError:
case QNetworkReply::UnknownContentError:
case QNetworkReply::ProtocolInvalidOperationError:
case QNetworkReply::ProtocolFailure:
throw TryAgainLater;
case QNetworkReply::NoError:
default:
break;
}
//FIXME pretty wasteful to parse XML document twice..
return data;
}
catch (Error e)
{
switch (e)
{
case OperationFailed:
case InvalidApiKey:
case InvalidSessionKey:
// NOTE will never be received during the LoginDialog stage
// since that happens before this slot is registered with
// QMetaObject in App::App(). Neat :)
QMetaObject::invokeMethod( qApp, "onWsError", Q_ARG( lastfm::ws::Error, e ) );
default:
throw ParseError(e);
}
}
// bit dodgy, but prolly for the best
reply->deleteLater();
return nam()->post( QNetworkRequest(baseUrl()), query );
}
QNetworkAccessManager*
lastfm::nam()
{
QMutexLocker l( &namAccessMutex );
QThread* thread = QThread::currentThread();
if ( !threadNamHash.contains( thread ) )
{
NetworkAccessManager* newNam = new NetworkAccessManager();
threadNamHash[thread] = newNam;
ourNamSet.insert( thread );
return newNam;
}
return threadNamHash[thread];
{
if (!::nam) ::nam = new NetworkAccessManager( qApp );
return ::nam;
}
void
lastfm::setNetworkAccessManager( QNetworkAccessManager* nam )
{
if ( !nam )
return;
QMutexLocker l( &namAccessMutex );
QThread* thread = QThread::currentThread();
QNetworkAccessManager* oldNam = 0;
if ( threadNamHash.contains( thread ) && ourNamSet.contains( thread ) )
oldNam = threadNamHash[thread];
if ( oldNam == nam )
return;
threadNamHash[thread] = nam;
ourNamSet.remove( thread );
if ( oldNam )
delete oldNam;
delete ::nam;
::nam = nam;
nam->setParent( qApp ); // ensure it isn't deleted out from under us
}
@@ -288,7 +196,7 @@ namespace lastfm
const char* ApiKey;
/** if this is found set to "" we conjure ourselves a suitable one */
const char* UserAgent = 0;
const char* UserAgent = 0;
}
}

View File

@@ -112,6 +112,7 @@ namespace lastfm
LASTFM_DLLEXPORT QString host();
/** the map needs a method entry, as per http://last.fm/api */
LASTFM_DLLEXPORT QUrl url( QMap<QString, QString> );
LASTFM_DLLEXPORT QNetworkReply* get( QMap<QString, QString> );
/** generates api sig, includes api key, and posts, don't add the api
* key yourself as well--it'll break */
@@ -121,29 +122,16 @@ namespace lastfm
class ParseError : public std::runtime_error
{
Error e;
QString m_message;
public:
explicit ParseError(Error e) : std::runtime_error("lastfm::ws::Error"), e(e)
explicit ParseError( Error e, QString message )
:std::runtime_error("lastfm::ws::Error"), e(e), m_message(message)
{}
Error enumValue() const { return e; }
};
QString message() const { return m_message; }
/** Generally you don't use this, eg. if you called Artist::getInfo(),
* use the Artist::getInfo( QNetworkReply* ) function to get the
* results, you have to pass a QDomDocument because QDomElements stop
* existing when the parent DomDocument is deleted.
*
* The QByteArray is basically reply->readAll(), so all this function
* does is sanity check the response and throw if it is bad.
*
* Thus if you don't care about errors just do: reply->readAll()
*
* Not caring about errors is often fine with Qt as you just get null
* strings and that instead, and you can handle those as you go.
*
* The QByteArray is an XML document. You can parse it with QDom or
* use our much more convenient lastfm::XmlQuery.
*/
LASTFM_DLLEXPORT QByteArray parse( QNetworkReply* reply ) throw( ParseError );
~ParseError() throw() {;}
};
/** returns the expiry date of this HTTP response */
LASTFM_DLLEXPORT QDateTime expires( QNetworkReply* );