PageRenderTime 84ms CodeModel.GetById 30ms app.highlight 33ms RepoModel.GetById 18ms app.codeStats 0ms

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