/thirdparty/liblastfm2/src/ws/ws.cpp

http://github.com/tomahawk-player/tomahawk · C++ · 237 lines · 161 code · 39 blank · 37 comment · 22 complexity · b4eddc34bf6aeed8f502602978d1bcdf MD5 · raw file

  1. /*
  2. Copyright 2009 Last.fm Ltd.
  3. - Primarily authored by Max Howell, Jono Cole and Doug Mansell
  4. This file is part of liblastfm.
  5. liblastfm is free software: you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation, either version 3 of the License, or
  8. (at your option) any later version.
  9. liblastfm is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with liblastfm. If not, see <http://www.gnu.org/licenses/>.
  15. */
  16. #include "ws.h"
  17. #include "../core/misc.h"
  18. #include "NetworkAccessManager.h"
  19. #include <QCoreApplication>
  20. #include <QDomDocument>
  21. #include <QDomElement>
  22. #include <QLocale>
  23. #include <QStringList>
  24. #include <QUrl>
  25. #include <QThread>
  26. #include <QMutex>
  27. static QMap< QThread*, QNetworkAccessManager* > threadNamHash;
  28. static QSet< QThread* > ourNamSet;
  29. static QMutex namAccessMutex;
  30. QString
  31. lastfm::ws::host()
  32. {
  33. QStringList const args = QCoreApplication::arguments();
  34. if (args.contains( "--debug"))
  35. return "ws.staging.audioscrobbler.com";
  36. int const n = args.indexOf( "--host" );
  37. if (n != -1 && args.count() > n+1)
  38. return args[n+1];
  39. return LASTFM_WS_HOSTNAME;
  40. }
  41. static QUrl baseUrl()
  42. {
  43. QUrl url;
  44. url.setScheme( "http" );
  45. url.setHost( lastfm::ws::host() );
  46. url.setEncodedPath( "/2.0/" );
  47. return url;
  48. }
  49. static QString iso639()
  50. {
  51. return QLocale().name().left( 2 ).toLower();
  52. }
  53. void autograph( QMap<QString, QString>& params )
  54. {
  55. params["api_key"] = lastfm::ws::ApiKey;
  56. params["lang"] = iso639();
  57. }
  58. static void sign( QMap<QString, QString>& params, bool sk = true )
  59. {
  60. autograph( params );
  61. // it's allowed for sk to be null if we this is an auth call for instance
  62. if (sk && lastfm::ws::SessionKey.size())
  63. params["sk"] = lastfm::ws::SessionKey;
  64. QString s;
  65. QMapIterator<QString, QString> i( params );
  66. while (i.hasNext()) {
  67. i.next();
  68. s += i.key() + i.value();
  69. }
  70. s += lastfm::ws::SharedSecret;
  71. params["api_sig"] = lastfm::md5( s.toUtf8() );
  72. }
  73. QUrl
  74. lastfm::ws::url( QMap<QString, QString> params )
  75. {
  76. sign( params );
  77. QUrl url = ::baseUrl();
  78. // Qt setQueryItems doesn't encode a bunch of stuff, so we do it manually
  79. QMapIterator<QString, QString> i( params );
  80. while (i.hasNext()) {
  81. i.next();
  82. QByteArray const key = QUrl::toPercentEncoding( i.key() );
  83. QByteArray const value = QUrl::toPercentEncoding( i.value() );
  84. url.addEncodedQueryItem( key, value );
  85. }
  86. return url;
  87. }
  88. QNetworkReply*
  89. lastfm::ws::get( QMap<QString, QString> params )
  90. {
  91. return nam()->get( QNetworkRequest( url( params ) ) );
  92. }
  93. QNetworkReply*
  94. lastfm::ws::post( QMap<QString, QString> params, bool sk )
  95. {
  96. sign( params, sk );
  97. QByteArray query;
  98. QMapIterator<QString, QString> i( params );
  99. while (i.hasNext()) {
  100. i.next();
  101. query += QUrl::toPercentEncoding( i.key() )
  102. + '='
  103. + QUrl::toPercentEncoding( i.value() )
  104. + '&';
  105. }
  106. QNetworkRequest req( baseUrl() );
  107. req.setHeader( QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded" );
  108. return nam()->post( req, query );
  109. }
  110. QNetworkAccessManager*
  111. lastfm::nam()
  112. {
  113. QMutexLocker l( &namAccessMutex );
  114. QThread* thread = QThread::currentThread();
  115. if ( !threadNamHash.contains( thread ) )
  116. {
  117. NetworkAccessManager* newNam = new NetworkAccessManager();
  118. threadNamHash[thread] = newNam;
  119. ourNamSet.insert( thread );
  120. return newNam;
  121. }
  122. return threadNamHash[thread];
  123. }
  124. void
  125. lastfm::setNetworkAccessManager( QNetworkAccessManager* nam )
  126. {
  127. if ( !nam )
  128. return;
  129. QMutexLocker l( &namAccessMutex );
  130. QThread* thread = QThread::currentThread();
  131. QNetworkAccessManager* oldNam = 0;
  132. if ( threadNamHash.contains( thread ) && ourNamSet.contains( thread ) )
  133. oldNam = threadNamHash[thread];
  134. if ( oldNam == nam )
  135. {
  136. // If we're being passed back our own NAM, assume they want to
  137. // ensure that we don't delete it out from under them
  138. ourNamSet.remove( thread );
  139. return;
  140. }
  141. threadNamHash[thread] = nam;
  142. ourNamSet.remove( thread );
  143. if ( oldNam )
  144. delete oldNam;
  145. }
  146. /** This useful function, fromHttpDate, comes from QNetworkHeadersPrivate
  147. * in qnetworkrequest.cpp. Qt copyright and license apply. */
  148. static QDateTime QByteArrayToHttpDate(const QByteArray &value)
  149. {
  150. // HTTP dates have three possible formats:
  151. // RFC 1123/822 - ddd, dd MMM yyyy hh:mm:ss "GMT"
  152. // RFC 850 - dddd, dd-MMM-yy hh:mm:ss "GMT"
  153. // ANSI C's asctime - ddd MMM d hh:mm:ss yyyy
  154. // We only handle them exactly. If they deviate, we bail out.
  155. int pos = value.indexOf(',');
  156. QDateTime dt;
  157. if (pos == -1) {
  158. // no comma -> asctime(3) format
  159. dt = QDateTime::fromString(QString::fromLatin1(value), Qt::TextDate);
  160. } else {
  161. // eat the weekday, the comma and the space following it
  162. QString sansWeekday = QString::fromLatin1(value.constData() + pos + 2);
  163. QLocale c = QLocale::c();
  164. if (pos == 3)
  165. // must be RFC 1123 date
  166. dt = c.toDateTime(sansWeekday, QLatin1String("dd MMM yyyy hh:mm:ss 'GMT"));
  167. else
  168. // must be RFC 850 date
  169. dt = c.toDateTime(sansWeekday, QLatin1String("dd-MMM-yy hh:mm:ss 'GMT'"));
  170. }
  171. if (dt.isValid())
  172. dt.setTimeSpec(Qt::UTC);
  173. return dt;
  174. }
  175. QDateTime
  176. lastfm::ws::expires( QNetworkReply* reply )
  177. {
  178. return QByteArrayToHttpDate( reply->rawHeader( "Expires" ) );
  179. }
  180. namespace lastfm
  181. {
  182. namespace ws
  183. {
  184. QString SessionKey;
  185. QString Username;
  186. /** we leave these unset as you can't use the webservices without them
  187. * so lets make the programmer aware of it during testing by crashing */
  188. const char* SharedSecret;
  189. const char* ApiKey;
  190. /** if this is found set to "" we conjure ourselves a suitable one */
  191. const char* UserAgent = 0;
  192. }
  193. }
  194. QDebug operator<<( QDebug, lastfm::ws::Error );