diff --git a/src/libtomahawk/infosystem/infoplugins/generic/lastfmplugin.cpp b/src/libtomahawk/infosystem/infoplugins/generic/lastfmplugin.cpp index b187111af..55d08df0a 100644 --- a/src/libtomahawk/infosystem/infoplugins/generic/lastfmplugin.cpp +++ b/src/libtomahawk/infosystem/infoplugins/generic/lastfmplugin.cpp @@ -818,19 +818,21 @@ LastFmPlugin::createScrobbler() QList -LastFmPlugin::parseTrackList( QNetworkReply * reply ) +LastFmPlugin::parseTrackList( QNetworkReply* reply ) { QList 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; } diff --git a/thirdparty/liblastfm2/admin/utils.rb b/thirdparty/liblastfm2/admin/utils.rb index ddcb01917..5a6772c4c 100644 --- a/thirdparty/liblastfm2/admin/utils.rb +++ b/thirdparty/liblastfm2/admin/utils.rb @@ -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 \ No newline at end of file +end diff --git a/thirdparty/liblastfm2/configure b/thirdparty/liblastfm2/configure index 757edcbc1..f6c02fa88 100755 --- a/thirdparty/liblastfm2/configure +++ b/thirdparty/liblastfm2/configure @@ -1,32 +1,8 @@ #!/usr/bin/ruby -if ARGV.include? '--help' - puts "usage: ./configure [--prefix ] [--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 diff --git a/thirdparty/liblastfm2/src/core/UrlBuilder.cpp b/thirdparty/liblastfm2/src/core/UrlBuilder.cpp index 0f451c7e5..1e0171aa1 100644 --- a/thirdparty/liblastfm2/src/core/UrlBuilder.cpp +++ b/thirdparty/liblastfm2/src/core/UrlBuilder.cpp @@ -36,7 +36,7 @@ lastfm::UrlBuilder::url() const QByteArray //static lastfm::UrlBuilder::encode( QString s ) { - foreach (QChar c, QList() << '&' << '/' << ';' << '+' << '#' << '%') + foreach (QChar c, QList() << '%' << '&' << '/' << ';' << '+' << '#' << '"') 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" diff --git a/thirdparty/liblastfm2/src/core/XmlQuery.cpp b/thirdparty/liblastfm2/src/core/XmlQuery.cpp index 5c68aaa81..b443e94b7 100644 --- a/thirdparty/liblastfm2/src/core/XmlQuery.cpp +++ b/thirdparty/liblastfm2/src/core/XmlQuery.cpp @@ -18,14 +18,55 @@ along with liblastfm. If not, see . */ #include "XmlQuery.h" + +#include #include + 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; + } + } } diff --git a/thirdparty/liblastfm2/src/core/XmlQuery.h b/thirdparty/liblastfm2/src/core/XmlQuery.h index a3c22d5c2..564e0fb38 100644 --- a/thirdparty/liblastfm2/src/core/XmlQuery.h +++ b/thirdparty/liblastfm2/src/core/XmlQuery.h @@ -21,6 +21,7 @@ #define LASTFM_XMLQUERY_H #include +#include #include #include @@ -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 ) { diff --git a/thirdparty/liblastfm2/src/core/misc.cpp b/thirdparty/liblastfm2/src/core/misc.cpp index 006089925..13b5d16af 100644 --- a/thirdparty/liblastfm2/src/core/misc.cpp +++ b/thirdparty/liblastfm2/src/core/misc.cpp @@ -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" ); diff --git a/thirdparty/liblastfm2/src/lastfm.pro b/thirdparty/liblastfm2/src/lastfm.pro index c0b306f84..e3ae70af6 100644 --- a/thirdparty/liblastfm2/src/lastfm.pro +++ b/thirdparty/liblastfm2/src/lastfm.pro @@ -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 diff --git a/thirdparty/liblastfm2/src/radio/RadioStation.cpp b/thirdparty/liblastfm2/src/radio/RadioStation.cpp index 38e9c7d3c..c0889d409 100755 --- a/thirdparty/liblastfm2/src/radio/RadioStation.cpp +++ b/thirdparty/liblastfm2/src/radio/RadioStation.cpp @@ -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 users; + users << user; + return library( users ); +} + +lastfm::RadioStation +lastfm::RadioStation::library( QList& 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::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 tags; + tags << tag; + return lastfm::RadioStation::tag( tags ); +} + + +lastfm::RadioStation +lastfm::RadioStation::tag( QList& tag ) +{ + return RadioStation( tagStr( tag ) ); +} + + +lastfm::RadioStation +lastfm::RadioStation::similar( const lastfm::Artist& artist ) +{ + QList artists; + artists << artist; + return similar( artists ); +} + + +lastfm::RadioStation +lastfm::RadioStation::similar( QList& 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 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 map; + map["method"] = "radio.getTagSuggestions"; + map["station"] = m_url.toString(); + map["limit"] = QString::number( limit ); + return ws::get( map ); +} + + //static QList lastfm::RadioStation::list( QNetworkReply* r ) { QList 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& 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& 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& 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; } diff --git a/thirdparty/liblastfm2/src/radio/RadioStation.h b/thirdparty/liblastfm2/src/radio/RadioStation.h index 09ee27743..816c49657 100644 --- a/thirdparty/liblastfm2/src/radio/RadioStation.h +++ b/thirdparty/liblastfm2/src/radio/RadioStation.h @@ -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& 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& artist ); + + static RadioStation tag( const lastfm::Tag& tag ); + static RadioStation tag( QList& 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 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& 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& tag ); + static QString similarStr( QList& 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; }; } diff --git a/thirdparty/liblastfm2/src/radio/RadioTuner.cpp b/thirdparty/liblastfm2/src/radio/RadioTuner.cpp index f09b560e1..9f875ee22 100644 --- a/thirdparty/liblastfm2/src/radio/RadioTuner.cpp +++ b/thirdparty/liblastfm2/src/radio/RadioTuner.cpp @@ -17,10 +17,14 @@ You should have received a copy of the GNU General Public License along with liblastfm. If not, see . */ + +#include + #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 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 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 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(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 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 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 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 tracks( xspf.tracks() ); - if (tracks.isEmpty()) { + XmlQuery lfm = qobject_cast(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(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; diff --git a/thirdparty/liblastfm2/src/radio/RadioTuner.h b/thirdparty/liblastfm2/src/radio/RadioTuner.h index 7032dfd08..7193d53ab 100644 --- a/thirdparty/liblastfm2/src/radio/RadioTuner.h +++ b/thirdparty/liblastfm2/src/radio/RadioTuner.h @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -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 m_queue; + private: + QList m_playlistQueue; uint m_retry_counter; + bool m_fetchingPlaylist; + bool m_requestedPlaylist; + class QTimer* m_twoSecondTimer; + RadioStation m_retuneStation; }; } diff --git a/thirdparty/liblastfm2/src/scrobble/Audioscrobbler.cpp b/thirdparty/liblastfm2/src/scrobble/Audioscrobbler.cpp index 0a98b7994..a6680fbe2 100644 --- a/thirdparty/liblastfm2/src/scrobble/Audioscrobbler.cpp +++ b/thirdparty/liblastfm2/src/scrobble/Audioscrobbler.cpp @@ -88,7 +88,7 @@ lastfm::Audioscrobbler::cache( const Track& track ) void -lastfm::Audioscrobbler::cacheBatch( const QList& tracks ) +lastfm::Audioscrobbler::cacheBatch( const QList& 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(sender())->readAll(); - qDebug() << lfm; + try + { + lastfm::XmlQuery lfm = static_cast(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(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; + } } diff --git a/thirdparty/liblastfm2/src/scrobble/Audioscrobbler.h b/thirdparty/liblastfm2/src/scrobble/Audioscrobbler.h index 8ad93c026..02e381415 100644 --- a/thirdparty/liblastfm2/src/scrobble/Audioscrobbler.h +++ b/thirdparty/liblastfm2/src/scrobble/Audioscrobbler.h @@ -47,6 +47,9 @@ namespace lastfm signals: void scrobblesCached( const QList& 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& 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& ); + void cacheBatch( const QList& ); /** will submit the submission cache for this user */ void submit(); diff --git a/thirdparty/liblastfm2/src/types/Album.cpp b/thirdparty/liblastfm2/src/types/Album.cpp index fce542911..8a545897d 100644 --- a/thirdparty/liblastfm2/src/types/Album.cpp +++ b/thirdparty/liblastfm2/src/types/Album.cpp @@ -28,14 +28,14 @@ #include QNetworkReply* -lastfm::Album::getInfo(const QString& user, const QString& sk) const +lastfm::Album::getInfo() const { QMap 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); } diff --git a/thirdparty/liblastfm2/src/types/Album.h b/thirdparty/liblastfm2/src/types/Album.h index afbd52e05..f64b59986 100644 --- a/thirdparty/liblastfm2/src/types/Album.h +++ b/thirdparty/liblastfm2/src/types/Album.h @@ -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 */ diff --git a/thirdparty/liblastfm2/src/types/Artist.cpp b/thirdparty/liblastfm2/src/types/Artist.cpp index 9af4303e1..1d9e14258 100644 --- a/thirdparty/liblastfm2/src/types/Artist.cpp +++ b/thirdparty/liblastfm2/src/types/Artist.cpp @@ -94,11 +94,11 @@ Artist::getEvents(int limit) const } QNetworkReply* -Artist::getInfo(const QString& user, const QString& sk) const +Artist::getInfo() const { QMap 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 map = params("getSimilar"); + if ( limit != -1 ) map["limit"] = QString::number( limit ); + return ws::get( map ); } @@ -145,7 +147,7 @@ Artist::getSimilar( QNetworkReply* r ) QMap 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 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; diff --git a/thirdparty/liblastfm2/src/types/Artist.h b/thirdparty/liblastfm2/src/types/Artist.h index 9eae719f5..f84fcce39 100644 --- a/thirdparty/liblastfm2/src/types/Artist.h +++ b/thirdparty/liblastfm2/src/types/Artist.h @@ -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 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 list( QNetworkReply* ); - + QMap params( const QString& method ) const; }; } diff --git a/thirdparty/liblastfm2/src/types/Playlist.h b/thirdparty/liblastfm2/src/types/Playlist.h index a271e7d5b..89ee42ce6 100644 --- a/thirdparty/liblastfm2/src/types/Playlist.h +++ b/thirdparty/liblastfm2/src/types/Playlist.h @@ -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* ); }; } diff --git a/thirdparty/liblastfm2/src/types/Tag.cpp b/thirdparty/liblastfm2/src/types/Tag.cpp index 40ddfb43b..675a147c7 100644 --- a/thirdparty/liblastfm2/src/types/Tag.cpp +++ b/thirdparty/liblastfm2/src/types/Tag.cpp @@ -63,7 +63,7 @@ Tag::list( QNetworkReply* r ) { QMap 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 diff --git a/thirdparty/liblastfm2/src/types/Tag.h b/thirdparty/liblastfm2/src/types/Tag.h index bf4d5cfca..917faf1df 100644 --- a/thirdparty/liblastfm2/src/types/Tag.h +++ b/thirdparty/liblastfm2/src/types/Tag.h @@ -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; diff --git a/thirdparty/liblastfm2/src/types/Tasteometer.cpp b/thirdparty/liblastfm2/src/types/Tasteometer.cpp new file mode 100644 index 000000000..5ec0e58a2 --- /dev/null +++ b/thirdparty/liblastfm2/src/types/Tasteometer.cpp @@ -0,0 +1,20 @@ +#include "Tasteometer.h" + +#include "lastfm.h" + +lastfm::Tasteometer::Tasteometer() +{ +} + + +QNetworkReply* +lastfm::Tasteometer::compare( const User& left, const User& right ) +{ + QMap map; + map["method"] = "Tasteometer.compare"; + map["type1"] = "user"; + map["value1"] = left.name(); + map["type2"] = "user"; + map["value2"] = right.name(); + return lastfm::ws::get( map ); +} diff --git a/thirdparty/liblastfm2/src/types/Tasteometer.h b/thirdparty/liblastfm2/src/types/Tasteometer.h new file mode 100644 index 000000000..ef691b8f6 --- /dev/null +++ b/thirdparty/liblastfm2/src/types/Tasteometer.h @@ -0,0 +1,18 @@ +#ifndef TASTEOMETER_H +#define TASTEOMETER_H + +#include + +namespace lastfm +{ + class LASTFM_DLLEXPORT Tasteometer + { + public: + Tasteometer(); + + public: + static QNetworkReply* compare( const User& left, const User& right ); + }; +} + +#endif // TASTEOMETER_H diff --git a/thirdparty/liblastfm2/src/types/Track.cpp b/thirdparty/liblastfm2/src/types/Track.cpp index 35e972b81..069283a97 100644 --- a/thirdparty/liblastfm2/src/types/Track.cpp +++ b/thirdparty/liblastfm2/src/types/Track.cpp @@ -17,13 +17,58 @@ You should have received a copy of the GNU General Public License along with liblastfm. If not, see . */ + +#include +#include +#include + #include "Track.h" #include "User.h" #include "../core/UrlBuilder.h" #include "../core/XmlQuery.h" #include "../ws/ws.h" -#include -#include + + +lastfm::TrackContext::TrackContext() + :m_type( Unknown ) +{ + +} + +lastfm::TrackContext::TrackContext( const QString& type, const QList& 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 +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 nodes have - // Artist Name.. - // as children isntead of Artist Name - // 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 - //Title... - //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(sender())->readAll(); - if ( lfm.attribute( "status" ) == "ok") - loved = true; + try + { + XmlQuery lfm = static_cast(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(sender())->readAll(); - if ( lfm.attribute( "status" ) == "ok") - loved = false; + try + { + XmlQuery lfm = static_cast(sender())->readAll(); + if ( lfm.attribute( "status" ) == "ok") + loved = false; + } + catch (...) + { + } + emit loveToggled( loved ); } void lastfm::TrackData::onGotInfo() { - lastfm::XmlQuery lfm( static_cast(sender())->readAll() ); + const QByteArray data = static_cast(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 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 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 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 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 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& 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(); } diff --git a/thirdparty/liblastfm2/src/types/Track.h b/thirdparty/liblastfm2/src/types/Track.h index eb759d0a2..56e97b272 100644 --- a/thirdparty/liblastfm2/src/types/Track.h +++ b/thirdparty/liblastfm2/src/types/Track.h @@ -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& values ); + + Type type() const; + QList values() const; +private: + static Type getType( const QString& type ); + +private: + Type m_type; + QList 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 m_images; short scrobbleStatus; short scrobbleError; + QString scrobbleErrorText; //FIXME I hate this, but is used for radio trackauth etc. QMap 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(d->scrobbleStatus); } ScrobbleError scrobbleError() const { return static_cast(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& tracks); @@ -238,7 +270,7 @@ public: protected: QExplicitlySharedDataPointer d; QMap 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;} }; diff --git a/thirdparty/liblastfm2/src/types/User.cpp b/thirdparty/liblastfm2/src/types/User.cpp index 03e1a1eda..c19bf46ce 100644 --- a/thirdparty/liblastfm2/src/types/User.cpp +++ b/thirdparty/liblastfm2/src/types/User.cpp @@ -23,6 +23,7 @@ #include "../core/XmlQuery.h" #include #include +#include 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 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 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 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 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 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 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 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 diff --git a/thirdparty/liblastfm2/src/types/User.h b/thirdparty/liblastfm2/src/types/User.h index 2d3eb0d84..4c8adf08d 100644 --- a/thirdparty/liblastfm2/src/types/User.h +++ b/thirdparty/liblastfm2/src/types/User.h @@ -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 */ - 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 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; diff --git a/thirdparty/liblastfm2/src/types/Xspf.cpp b/thirdparty/liblastfm2/src/types/Xspf.cpp index 2100ec265..e9a3175b3 100644 --- a/thirdparty/liblastfm2/src/types/Xspf.cpp +++ b/thirdparty/liblastfm2/src/types/Xspf.cpp @@ -17,13 +17,22 @@ You should have received a copy of the GNU General Public License along with liblastfm. If not, see . */ -#include "Xspf.h" -#include "../core/XmlQuery.h" + +#include #include -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 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(); +} diff --git a/thirdparty/liblastfm2/src/types/Xspf.h b/thirdparty/liblastfm2/src/types/Xspf.h index e8bb55a15..66046b931 100644 --- a/thirdparty/liblastfm2/src/types/Xspf.h +++ b/thirdparty/liblastfm2/src/types/Xspf.h @@ -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 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 m_tracks; QString m_title; diff --git a/thirdparty/liblastfm2/src/ws/InternetConnectionMonitor.cpp b/thirdparty/liblastfm2/src/ws/InternetConnectionMonitor.cpp index 44eb51b4d..0fe9b9394 100644 --- a/thirdparty/liblastfm2/src/ws/InternetConnectionMonitor.cpp +++ b/thirdparty/liblastfm2/src/ws/InternetConnectionMonitor.cpp @@ -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 ); diff --git a/thirdparty/liblastfm2/src/ws/NetworkAccessManager.cpp b/thirdparty/liblastfm2/src/ws/NetworkAccessManager.cpp index 95845c0ff..2d628b1ad 100644 --- a/thirdparty/liblastfm2/src/ws/NetworkAccessManager.cpp +++ b/thirdparty/liblastfm2/src/ws/NetworkAccessManager.cpp @@ -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 diff --git a/thirdparty/liblastfm2/src/ws/mac/MNetworkConnectionMonitor_mac.cpp b/thirdparty/liblastfm2/src/ws/mac/MNetworkConnectionMonitor_mac.cpp index 9f99e89fd..9ab425592 100644 --- a/thirdparty/liblastfm2/src/ws/mac/MNetworkConnectionMonitor_mac.cpp +++ b/thirdparty/liblastfm2/src/ws/mac/MNetworkConnectionMonitor_mac.cpp @@ -19,7 +19,7 @@ */ #include "MNetworkConnectionMonitor.h" -#include "../ws.h" +#include "ws/ws.h" #include #include @@ -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; diff --git a/thirdparty/liblastfm2/src/ws/win/ComSetup.h b/thirdparty/liblastfm2/src/ws/win/ComSetup.h index 7389f0976..fc3816bd7 100644 --- a/thirdparty/liblastfm2/src/ws/win/ComSetup.h +++ b/thirdparty/liblastfm2/src/ws/win/ComSetup.h @@ -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 -//#include -//#include -//#include -#include +#include +#include + /** @brief WsConnectionMonitor needs Com to work as early as possible so we do this - * @author + * @author */ 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; }; diff --git a/thirdparty/liblastfm2/src/ws/win/IeSettings.h b/thirdparty/liblastfm2/src/ws/win/IeSettings.h index 6205284e5..5d756a5ea 100644 --- a/thirdparty/liblastfm2/src/ws/win/IeSettings.h +++ b/thirdparty/liblastfm2/src/ws/win/IeSettings.h @@ -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 }; diff --git a/thirdparty/liblastfm2/src/ws/win/NdisEvents.cpp b/thirdparty/liblastfm2/src/ws/win/NdisEvents.cpp index 7b5822ac6..7263af24b 100644 --- a/thirdparty/liblastfm2/src/ws/win/NdisEvents.cpp +++ b/thirdparty/liblastfm2/src/ws/win/NdisEvents.cpp @@ -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; } diff --git a/thirdparty/liblastfm2/src/ws/win/NdisEvents.h b/thirdparty/liblastfm2/src/ws/win/NdisEvents.h index 8b4efe0e1..1e1cfcf9c 100644 --- a/thirdparty/liblastfm2/src/ws/win/NdisEvents.h +++ b/thirdparty/liblastfm2/src/ws/win/NdisEvents.h @@ -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 -//#include -//#include +#include +#include class NdisEvents { @@ -35,8 +35,8 @@ public: virtual void onConnectionDown(BSTR name) = 0; private: - //CComPtr m_pLocator; - //CComPtr m_pServices; + CComPtr m_pLocator; + CComPtr m_pServices; class WmiSink *m_pSink; }; diff --git a/thirdparty/liblastfm2/src/ws/win/Pac.cpp b/thirdparty/liblastfm2/src/ws/win/Pac.cpp index b0a836c7f..3e3d72b12 100644 --- a/thirdparty/liblastfm2/src/ws/win/Pac.cpp +++ b/thirdparty/liblastfm2/src/ws/win/Pac.cpp @@ -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 #include #include -//#include -//#include -#include +#include +#include static bool @@ -46,7 +45,7 @@ parsePacServer(const QString &s, QNetworkProxy &p) static QList 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." // ([=]["://"][":"]) @@ -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 proxies = parsePacResult(QString::fromUtf16((const ushort*)info.lpszProxy)); + QList 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; diff --git a/thirdparty/liblastfm2/src/ws/win/Pac.h b/thirdparty/liblastfm2/src/ws/win/Pac.h index 98d3e6b0e..075d128f6 100644 --- a/thirdparty/liblastfm2/src/ws/win/Pac.h +++ b/thirdparty/liblastfm2/src/ws/win/Pac.h @@ -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 #include 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 */ class Pac - { + { HINTERNET m_hSession; bool m_bFailed; @@ -49,4 +49,4 @@ namespace lastfm }; } -#endif \ No newline at end of file +#endif \ No newline at end of file diff --git a/thirdparty/liblastfm2/src/ws/win/WmiSink.h b/thirdparty/liblastfm2/src/ws/win/WmiSink.h index 8f58a1f4a..1bce28fdd 100644 --- a/thirdparty/liblastfm2/src/ws/win/WmiSink.h +++ b/thirdparty/liblastfm2/src/ws/win/WmiSink.h @@ -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 diff --git a/thirdparty/liblastfm2/src/ws/ws.cpp b/thirdparty/liblastfm2/src/ws/ws.cpp index 93d06b929..49fd7b5ee 100644 --- a/thirdparty/liblastfm2/src/ws/ws.cpp +++ b/thirdparty/liblastfm2/src/ws/ws.cpp @@ -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 #include #include -#include -#include #include +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& params ) @@ -86,11 +82,11 @@ static void sign( QMap& params, bool sk = true ) } -QNetworkReply* -lastfm::ws::get( QMap params ) +QUrl +lastfm::ws::url( QMap params ) { sign( params ); - QUrl url = ::url(); + QUrl url = ::baseUrl(); // Qt setQueryItems doesn't encode a bunch of stuff, so we do it manually QMapIterator i( params ); while (i.hasNext()) { @@ -100,16 +96,21 @@ lastfm::ws::get( QMap params ) url.addEncodedQueryItem( key, value ); } - qDebug() << url; + return url; +} - return nam()->get( QNetworkRequest(url) ); + +QNetworkReply* +lastfm::ws::get( QMap params ) +{ + return nam()->get( QNetworkRequest( url( params ) ) ); } QNetworkReply* lastfm::ws::post( QMap params, bool sk ) -{ - sign( params, sk ); +{ + sign( params, sk ); QByteArray query; QMapIterator i( params ); while (i.hasNext()) { @@ -120,117 +121,24 @@ lastfm::ws::post( QMap 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; } } diff --git a/thirdparty/liblastfm2/src/ws/ws.h b/thirdparty/liblastfm2/src/ws/ws.h index b6f7cd922..b9bb90e0c 100644 --- a/thirdparty/liblastfm2/src/ws/ws.h +++ b/thirdparty/liblastfm2/src/ws/ws.h @@ -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 ); LASTFM_DLLEXPORT QNetworkReply* get( QMap ); /** 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* );