~ubuntu-branches/ubuntu/lucid/lastfm/lucid

« back to all changes in this revision

Viewing changes to src/libUnicorn/CachedHttp.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Pedro Fragoso
  • Date: 2007-12-31 09:49:54 UTC
  • mfrom: (1.1.5 upstream)
  • Revision ID: james.westby@ubuntu.com-20071231094954-ix1amvcsj9pk61ya
Tags: 1:1.4.1.57486.dfsg-1ubuntu1
* Merge from Debian unstable (LP: #180254), remaining changes:
  - debian/rules;
    - Added dh_icons
  - Modify Maintainer value to match Debian-Maintainer-Field Spec

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/***************************************************************************
 
2
 *   Copyright (C) 2005 - 2007 by                                          *
 
3
 *      Christian Muehlhaeuser, Last.fm Ltd <chris@last.fm>                *
 
4
 *      Erik Jaelevik, Last.fm Ltd <erik@last.fm>                          *
 
5
 *      Jono Cole, Last.fm Ltd <jono@last.fm>                              *
 
6
 *                                                                         *
 
7
 *   This program 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 2 of the License, or     *
 
10
 *   (at your option) any later version.                                   *
 
11
 *                                                                         *
 
12
 *   This program 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 this program; if not, write to the                         *
 
19
 *   Free Software Foundation, Inc.,                                       *
 
20
 *   51 Franklin Steet, Fifth Floor, Boston, MA  02110-1301, USA.          *
 
21
 ***************************************************************************/
 
22
 
 
23
#include "CachedHttp.h"
 
24
#include "Settings.h"
 
25
#include "WebService.h"
 
26
 
 
27
#include <QDir>
 
28
#include <QTimer>
 
29
#include <QCoreApplication>
 
30
 
 
31
#ifdef WIN32
 
32
    #include <windows.h>
 
33
#endif
 
34
 
 
35
#undef LOG
 
36
#undef LOGL
 
37
#define LOG(x, y)
 
38
#define LOGL(x, y)
 
39
 
 
40
QString CachedHttp::s_customUserAgent; // whole name
 
41
QString CachedHttp::s_customCachePath; // whole path
 
42
 
 
43
 
 
44
CachedHttp::CachedHttp( QObject* parent ) :
 
45
    RedirectHttp( parent ),
 
46
    m_dataID( -1 ),
 
47
    m_statuscode( 0 ),
 
48
    m_proxyOverride( NONE ),
 
49
    m_nextId( 0 ),
 
50
    m_inProgress( false )
 
51
{
 
52
    init();
 
53
}
 
54
 
 
55
 
 
56
CachedHttp::CachedHttp( const QString& hostName, int port, QObject* parent, ProxyOverrideState proxyOverride ) :
 
57
    RedirectHttp( parent ),
 
58
    m_dataID( -1 ),
 
59
    m_hostname( hostName ),
 
60
    m_statuscode( 0 ),
 
61
    m_proxyOverride( proxyOverride ),
 
62
    m_nextId( 0 ),
 
63
    m_inProgress( false )
 
64
{
 
65
    init();
 
66
    setHost( hostName, port );
 
67
}
 
68
 
 
69
 
 
70
CachedHttp::~CachedHttp()
 
71
{
 
72
    // EJ: Added this warning just to prevent stupidity like forgetting to
 
73
    // create the Http object on the stack instead of the heap.
 
74
    if ( m_inProgress )
 
75
    {
 
76
        qDebug() << "CachedHttp object destroyed while in progress:\n" <<
 
77
            m_hostname + currentRequest().path();
 
78
    }
 
79
}
 
80
 
 
81
 
 
82
void
 
83
CachedHttp::init()
 
84
{
 
85
    QDir( cachePath() ).mkdir( cachePath() ); //rofl@Qt.com
 
86
 
 
87
    applyProxy();
 
88
 
 
89
    connect( this, SIGNAL(requestFinished( int, bool )), this, SLOT(dataFinished( int, bool )) );
 
90
    connect( this, SIGNAL(responseHeaderReceived (const QHttpResponseHeader&)), this, SLOT(headerReceived (const QHttpResponseHeader&)) );
 
91
    connect( this, SIGNAL(done( bool )), this, SLOT(requestDone( bool )) );
 
92
}
 
93
 
 
94
 
 
95
void
 
96
CachedHttp::applyProxy()
 
97
{
 
98
    //TODO really this should be determined by an settingsservice key like autoDetectProxy()
 
99
 
 
100
    SharedSettings* settings = SharedSettings::instance();
 
101
 
 
102
    if ( settings->isUseProxy() ) {
 
103
        setProxy( settings->getProxyHost(),
 
104
                  settings->getProxyPort(),
 
105
                  settings->getProxyUser(),
 
106
                  settings->getProxyPassword() );
 
107
    }
 
108
    else if ( ( The::webService()->isAutoDetectedProxy() && m_proxyOverride != PROXYOFF ) ||
 
109
                m_proxyOverride == PROXYON ) {
 
110
        setProxy( The::webService()->proxyHost(),
 
111
                  The::webService()->proxyPort(),
 
112
                  QString(),
 
113
                  QString() );
 
114
    }
 
115
    else
 
116
    {
 
117
        setProxy( "", 0 );
 
118
    }
 
119
}
 
120
 
 
121
 
 
122
void
 
123
CachedHttp::applyUserAgent( QHttpRequestHeader& header )
 
124
{
 
125
    // NEVER CHANGE THIS STRING!
 
126
    // martin says we can append stuff if we like, just never change the first bit
 
127
 
 
128
    QString s = userAgent(); //"Last.fm Client " + The::settings().version();
 
129
  #ifdef WIN32
 
130
    s += " (Windows)";
 
131
  #elif defined (Q_WS_MAC)
 
132
    s += " (OS X)";
 
133
  #elif defined (Q_WS_X11)
 
134
    s += " (X11)";
 
135
  #endif
 
136
 
 
137
    header.setValue( "User-Agent", s );
 
138
}
 
139
 
 
140
 
 
141
int
 
142
CachedHttp::get( const QString& path, bool useCache )
 
143
{
 
144
    applyProxy();
 
145
    m_buffer.clear();
 
146
    QString url = m_hostname + path;
 
147
 
 
148
    if ( useCache && haveCachedCopy( url ) )
 
149
    {
 
150
        // Using a singleshot so that we can return an ID and have
 
151
        // the operation proceed asynchronously.
 
152
        m_cacheStack.push( CachedRequestData( ++m_nextId, url ) );
 
153
        QTimer::singleShot( 0, this, SLOT( getFromCache() ) );
 
154
        return m_nextId;
 
155
    }
 
156
 
 
157
    QHttpRequestHeader header( "GET", path );
 
158
    header.setValue( "Host", m_hostname );
 
159
    applyUserAgent( header );
 
160
 
 
161
    m_dataID = request( header );
 
162
    if ( useCache )
 
163
        m_requestStack.insert( m_dataID, CachedRequestData( ++m_nextId, url ) );
 
164
 
 
165
    m_inProgress = true;
 
166
 
 
167
    return m_dataID;
 
168
}
 
169
 
 
170
 
 
171
int
 
172
CachedHttp::get( const QString& path, QIODevice* to )
 
173
{
 
174
    applyProxy();
 
175
    m_inProgress = true;
 
176
    return RedirectHttp::get( path, to );
 
177
}
 
178
 
 
179
 
 
180
int
 
181
CachedHttp::post( const QString& path, QIODevice* data )
 
182
{
 
183
    applyProxy();
 
184
    m_buffer.clear();
 
185
 
 
186
    m_dataID = RedirectHttp::post( path, data );
 
187
 
 
188
    m_inProgress = true;
 
189
 
 
190
    return m_dataID;
 
191
}
 
192
 
 
193
 
 
194
int
 
195
CachedHttp::post( const QString& path, const QByteArray& data )
 
196
{
 
197
    applyProxy();
 
198
    m_buffer.clear();
 
199
 
 
200
    m_dataID = RedirectHttp::post( path, data );
 
201
 
 
202
    m_inProgress = true;
 
203
 
 
204
    return m_dataID;
 
205
}
 
206
 
 
207
 
 
208
int
 
209
CachedHttp::request( const QHttpRequestHeader& header, QIODevice* data, QIODevice* to )
 
210
{
 
211
    QHttpRequestHeader h( header );
 
212
    applyProxy();
 
213
    applyUserAgent( h );
 
214
 
 
215
    m_buffer.clear();
 
216
    m_dataID = RedirectHttp::request( h, data, to );
 
217
 
 
218
    m_inProgress = true;
 
219
 
 
220
    return m_dataID;
 
221
}
 
222
 
 
223
 
 
224
int
 
225
CachedHttp::request( const QHttpRequestHeader& header, const QByteArray& data, QIODevice* to, bool useCache )
 
226
{
 
227
    QHttpRequestHeader h( header );
 
228
    applyProxy();
 
229
    applyUserAgent( h );
 
230
 
 
231
    m_buffer.clear();
 
232
    QString key( data );
 
233
    if ( useCache && haveCachedCopy( key ) )
 
234
    {
 
235
        // Using a singleshot so that we can return an ID and have
 
236
        // the operation proceed asynchronously.
 
237
        m_cacheStack.push( CachedRequestData( ++m_nextId, key ) );
 
238
        QTimer::singleShot( 0, this, SLOT( getFromCache() ) );
 
239
        return m_nextId;
 
240
    }
 
241
 
 
242
    m_dataID = RedirectHttp::request( h, data, to );
 
243
 
 
244
    m_inProgress = true;
 
245
 
 
246
    if ( useCache )
 
247
        m_requestStack.insert( m_dataID, CachedRequestData( ++m_nextId, key ) );
 
248
 
 
249
    return m_dataID;
 
250
}
 
251
 
 
252
 
 
253
void
 
254
CachedHttp::headerReceived( const QHttpResponseHeader& resp )
 
255
{
 
256
    m_statuscode = resp.statusCode();
 
257
 
 
258
    m_expireDate = 0;
 
259
    if ( !resp.value( "expires" ).isEmpty() )
 
260
    {
 
261
        QString expire = resp.value( "expires" );
 
262
        QStringList datelist = expire.split( " " ); //Split the datestring
 
263
 
 
264
        if ( datelist.count() == 6 ) // 6 items in a regular expire-date
 
265
        {
 
266
            datelist.removeLast(); //Pop the timezone, always GMT
 
267
            datelist.removeFirst(); //Pop the weekday (Mon, Tue...), we rely on the date
 
268
            QString expdate = datelist.join( " " );
 
269
 
 
270
            m_expireDate = QDateTime::fromString( expdate, "dd MMM yyyy hh:mm:ss" ).toTime_t();
 
271
        }
 
272
 
 
273
        if ( m_expireDate == -1 ) //If it's -1 we need to change it to 0 so we can compare it to a time_t (uint)
 
274
        {
 
275
            m_expireDate = 0;
 
276
        }
 
277
    }
 
278
 
 
279
    if ( (unsigned int)m_expireDate < QDateTime::currentDateTime().toTime_t() ) //If the expiredate is not set, or if the expiredate is in the past.
 
280
    {
 
281
        LOG( Severity::Warning, "The webservice " + objectName() + " does not set expiredate or the expiredate is in the past.\n" );
 
282
        m_expireDate = QDateTime::currentDateTime().addDays( 7 ).toTime_t();
 
283
    }
 
284
}
 
285
 
 
286
 
 
287
void
 
288
CachedHttp::dataFinished( int id, bool error )
 
289
{
 
290
    if ( error )
 
291
        emit errorOccured( RedirectHttp::error(), RedirectHttp::errorString() );
 
292
    else
 
293
        if ( id == m_dataID )
 
294
        {
 
295
            checkBuffer();
 
296
 
 
297
            CachedRequestData req = m_requestStack.take( id );
 
298
            if ( !req.m_cacheKey.isEmpty() && m_statuscode == 200)
 
299
            {
 
300
                putCachedCopy( req.m_cacheKey, m_buffer );
 
301
            }
 
302
 
 
303
            emit dataAvailable( m_buffer );
 
304
        }
 
305
}
 
306
 
 
307
 
 
308
QString
 
309
CachedHttp::userAgent()
 
310
{
 
311
    if ( s_customUserAgent.isEmpty() )
 
312
    {
 
313
        return QCoreApplication::organizationName() + " " +
 
314
               QCoreApplication::applicationName();
 
315
    }
 
316
    else
 
317
    {
 
318
        return s_customUserAgent;
 
319
    }
 
320
}
 
321
 
 
322
 
 
323
QString
 
324
CachedHttp::cachePath()
 
325
{
 
326
    if ( s_customCachePath.isEmpty() )
 
327
    {
 
328
        return UnicornUtils::appDataPath() + "/" + QCoreApplication::organizationName() + "/" +
 
329
               QCoreApplication::applicationName() + "/cache";
 
330
    }
 
331
    else
 
332
    {
 
333
        return s_customCachePath;
 
334
    }
 
335
}
 
336
 
 
337
 
 
338
QString
 
339
CachedHttp::pathToCachedCopy( QString cacheKey )
 
340
{
 
341
    QString keyMd5 = UnicornUtils::md5Digest( qPrintable( cacheKey ) );
 
342
 
 
343
    // The c in the front is because we use a different cacheformat now.
 
344
    // (The 10 first bytes are the expire timestamp);
 
345
    return cachePath() + "c" + keyMd5; 
 
346
}
 
347
 
 
348
 
 
349
bool
 
350
CachedHttp::haveCachedCopy( QString url )
 
351
{
 
352
    if (!QFile::exists( pathToCachedCopy( url ) )) return false;
 
353
    if (!QFileInfo( pathToCachedCopy( url ) ).isReadable()) return false;
 
354
 
 
355
    QFile f( pathToCachedCopy( url ) );
 
356
    if ( !f.open( QIODevice::ReadOnly ) )
 
357
    {
 
358
        return false;
 
359
    }
 
360
 
 
361
    QByteArray expdate = f.read(10);
 
362
    f.close();
 
363
    if (expdate.toUInt() < QDateTime::currentDateTime().toTime_t())
 
364
    {
 
365
        return false;
 
366
    }
 
367
    else
 
368
    {
 
369
        return true;
 
370
    }
 
371
}
 
372
 
 
373
 
 
374
void
 
375
CachedHttp::putCachedCopy( QString url, const QByteArray& data )
 
376
{
 
377
    #ifdef WIN32
 
378
        if ( url.size() > MAX_PATH )
 
379
        {
 
380
            url.chop( url.size() - MAX_PATH );
 
381
        }
 
382
    #endif
 
383
 
 
384
    QFile f( pathToCachedCopy( url ) );
 
385
 
 
386
    QByteArray date = QByteArray::number ( m_expireDate );
 
387
    date = date.rightJustified(10,'0');
 
388
 
 
389
    if ( !f.open( QIODevice::WriteOnly ) )
 
390
    {
 
391
        //LOGL( 1, "Failed to open file " << url << " for writing to cache" );
 
392
        return;
 
393
    }
 
394
 
 
395
    f.write(date);
 
396
    f.write(data);
 
397
}
 
398
 
 
399
 
 
400
void CachedHttp::getFromCache()
 
401
{
 
402
    Q_ASSERT( !m_cacheStack.isEmpty() );
 
403
 
 
404
    // Pick next request off cache stack
 
405
    CachedRequestData req = m_cacheStack.pop();
 
406
 
 
407
    QFile f( pathToCachedCopy( req.m_cacheKey ) );
 
408
    if ( !f.open( QIODevice::ReadOnly ) )
 
409
    {
 
410
        //LOGL( 1, "Failed to open cached file, returning error" );
 
411
 
 
412
        // TODO: emit error
 
413
        emit done( true );
 
414
        return;
 
415
    }
 
416
 
 
417
    // Keeping it as UTF-8, conversion will the done in parse function
 
418
    QByteArray date = f.read(10);
 
419
    QByteArray result = f.readAll();
 
420
    emit dataAvailable( result );
 
421
    emit done( false );
 
422
}
 
423
 
 
424
 
 
425
void CachedHttp::abort()
 
426
{
 
427
    m_inProgress = false;
 
428
    RedirectHttp::abort();
 
429
}
 
430
 
 
431
 
 
432
void CachedHttp::requestDone( bool /*error*/ )
 
433
{
 
434
    m_inProgress = false;
 
435
}