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> *
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. *
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. *
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
***************************************************************************/
23
#include "CachedHttp.h"
25
#include "WebService.h"
29
#include <QCoreApplication>
40
QString CachedHttp::s_customUserAgent; // whole name
41
QString CachedHttp::s_customCachePath; // whole path
44
CachedHttp::CachedHttp( QObject* parent ) :
45
RedirectHttp( parent ),
48
m_proxyOverride( NONE ),
56
CachedHttp::CachedHttp( const QString& hostName, int port, QObject* parent, ProxyOverrideState proxyOverride ) :
57
RedirectHttp( parent ),
59
m_hostname( hostName ),
61
m_proxyOverride( proxyOverride ),
66
setHost( hostName, port );
70
CachedHttp::~CachedHttp()
72
// EJ: Added this warning just to prevent stupidity like forgetting to
73
// create the Http object on the stack instead of the heap.
76
qDebug() << "CachedHttp object destroyed while in progress:\n" <<
77
m_hostname + currentRequest().path();
85
QDir( cachePath() ).mkdir( cachePath() ); //rofl@Qt.com
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 )) );
96
CachedHttp::applyProxy()
98
//TODO really this should be determined by an settingsservice key like autoDetectProxy()
100
SharedSettings* settings = SharedSettings::instance();
102
if ( settings->isUseProxy() ) {
103
setProxy( settings->getProxyHost(),
104
settings->getProxyPort(),
105
settings->getProxyUser(),
106
settings->getProxyPassword() );
108
else if ( ( The::webService()->isAutoDetectedProxy() && m_proxyOverride != PROXYOFF ) ||
109
m_proxyOverride == PROXYON ) {
110
setProxy( The::webService()->proxyHost(),
111
The::webService()->proxyPort(),
123
CachedHttp::applyUserAgent( QHttpRequestHeader& header )
125
// NEVER CHANGE THIS STRING!
126
// martin says we can append stuff if we like, just never change the first bit
128
QString s = userAgent(); //"Last.fm Client " + The::settings().version();
131
#elif defined (Q_WS_MAC)
133
#elif defined (Q_WS_X11)
137
header.setValue( "User-Agent", s );
142
CachedHttp::get( const QString& path, bool useCache )
146
QString url = m_hostname + path;
148
if ( useCache && haveCachedCopy( url ) )
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() ) );
157
QHttpRequestHeader header( "GET", path );
158
header.setValue( "Host", m_hostname );
159
applyUserAgent( header );
161
m_dataID = request( header );
163
m_requestStack.insert( m_dataID, CachedRequestData( ++m_nextId, url ) );
172
CachedHttp::get( const QString& path, QIODevice* to )
176
return RedirectHttp::get( path, to );
181
CachedHttp::post( const QString& path, QIODevice* data )
186
m_dataID = RedirectHttp::post( path, data );
195
CachedHttp::post( const QString& path, const QByteArray& data )
200
m_dataID = RedirectHttp::post( path, data );
209
CachedHttp::request( const QHttpRequestHeader& header, QIODevice* data, QIODevice* to )
211
QHttpRequestHeader h( header );
216
m_dataID = RedirectHttp::request( h, data, to );
225
CachedHttp::request( const QHttpRequestHeader& header, const QByteArray& data, QIODevice* to, bool useCache )
227
QHttpRequestHeader h( header );
233
if ( useCache && haveCachedCopy( key ) )
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() ) );
242
m_dataID = RedirectHttp::request( h, data, to );
247
m_requestStack.insert( m_dataID, CachedRequestData( ++m_nextId, key ) );
254
CachedHttp::headerReceived( const QHttpResponseHeader& resp )
256
m_statuscode = resp.statusCode();
259
if ( !resp.value( "expires" ).isEmpty() )
261
QString expire = resp.value( "expires" );
262
QStringList datelist = expire.split( " " ); //Split the datestring
264
if ( datelist.count() == 6 ) // 6 items in a regular expire-date
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( " " );
270
m_expireDate = QDateTime::fromString( expdate, "dd MMM yyyy hh:mm:ss" ).toTime_t();
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)
279
if ( (unsigned int)m_expireDate < QDateTime::currentDateTime().toTime_t() ) //If the expiredate is not set, or if the expiredate is in the past.
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();
288
CachedHttp::dataFinished( int id, bool error )
291
emit errorOccured( RedirectHttp::error(), RedirectHttp::errorString() );
293
if ( id == m_dataID )
297
CachedRequestData req = m_requestStack.take( id );
298
if ( !req.m_cacheKey.isEmpty() && m_statuscode == 200)
300
putCachedCopy( req.m_cacheKey, m_buffer );
303
emit dataAvailable( m_buffer );
309
CachedHttp::userAgent()
311
if ( s_customUserAgent.isEmpty() )
313
return QCoreApplication::organizationName() + " " +
314
QCoreApplication::applicationName();
318
return s_customUserAgent;
324
CachedHttp::cachePath()
326
if ( s_customCachePath.isEmpty() )
328
return UnicornUtils::appDataPath() + "/" + QCoreApplication::organizationName() + "/" +
329
QCoreApplication::applicationName() + "/cache";
333
return s_customCachePath;
339
CachedHttp::pathToCachedCopy( QString cacheKey )
341
QString keyMd5 = UnicornUtils::md5Digest( qPrintable( cacheKey ) );
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;
350
CachedHttp::haveCachedCopy( QString url )
352
if (!QFile::exists( pathToCachedCopy( url ) )) return false;
353
if (!QFileInfo( pathToCachedCopy( url ) ).isReadable()) return false;
355
QFile f( pathToCachedCopy( url ) );
356
if ( !f.open( QIODevice::ReadOnly ) )
361
QByteArray expdate = f.read(10);
363
if (expdate.toUInt() < QDateTime::currentDateTime().toTime_t())
375
CachedHttp::putCachedCopy( QString url, const QByteArray& data )
378
if ( url.size() > MAX_PATH )
380
url.chop( url.size() - MAX_PATH );
384
QFile f( pathToCachedCopy( url ) );
386
QByteArray date = QByteArray::number ( m_expireDate );
387
date = date.rightJustified(10,'0');
389
if ( !f.open( QIODevice::WriteOnly ) )
391
//LOGL( 1, "Failed to open file " << url << " for writing to cache" );
400
void CachedHttp::getFromCache()
402
Q_ASSERT( !m_cacheStack.isEmpty() );
404
// Pick next request off cache stack
405
CachedRequestData req = m_cacheStack.pop();
407
QFile f( pathToCachedCopy( req.m_cacheKey ) );
408
if ( !f.open( QIODevice::ReadOnly ) )
410
//LOGL( 1, "Failed to open cached file, returning error" );
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 );
425
void CachedHttp::abort()
427
m_inProgress = false;
428
RedirectHttp::abort();
432
void CachedHttp::requestDone( bool /*error*/ )
434
m_inProgress = false;