diff options
author | Dominik Riebeling <Dominik.Riebeling@gmail.com> | 2008-10-12 19:21:58 +0000 |
---|---|---|
committer | Dominik Riebeling <Dominik.Riebeling@gmail.com> | 2008-10-12 19:21:58 +0000 |
commit | f958717d43420655519ae079ef0d35aa912411b2 (patch) | |
tree | df0dbb774dc3619e2b53c944c6928724092dc171 /rbutil/rbutilqt/base/httpget.cpp | |
parent | 3d30029883e7e2a862bceede967d95d0bfbd93bb (diff) | |
download | rockbox-f958717d43420655519ae079ef0d35aa912411b2.tar.gz rockbox-f958717d43420655519ae079ef0d35aa912411b2.zip |
Separate basic functionality from GUI parts by moving it into a separate folder. Some files still need to get cleaned up prior moving them too.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@18788 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'rbutil/rbutilqt/base/httpget.cpp')
-rw-r--r-- | rbutil/rbutilqt/base/httpget.cpp | 413 |
1 files changed, 413 insertions, 0 deletions
diff --git a/rbutil/rbutilqt/base/httpget.cpp b/rbutil/rbutilqt/base/httpget.cpp new file mode 100644 index 0000000000..129545d158 --- /dev/null +++ b/rbutil/rbutilqt/base/httpget.cpp | |||
@@ -0,0 +1,413 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Riebeling | ||
10 | * $Id$ | ||
11 | * | ||
12 | * All files in this archive are subject to the GNU General Public License. | ||
13 | * See the file COPYING in the source tree root for full license agreement. | ||
14 | * | ||
15 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
16 | * KIND, either express or implied. | ||
17 | * | ||
18 | ****************************************************************************/ | ||
19 | |||
20 | #include <QtCore> | ||
21 | #include <QtNetwork> | ||
22 | #include <QtDebug> | ||
23 | |||
24 | #include "httpget.h" | ||
25 | |||
26 | QDir HttpGet::m_globalCache; //< global cach path value for new objects | ||
27 | QUrl HttpGet::m_globalProxy; //< global proxy value for new objects | ||
28 | bool HttpGet::m_globalDumbCache = false; //< globally set cache "dumb" mode | ||
29 | QString HttpGet::m_globalUserAgent; //< globally set user agent for requests | ||
30 | |||
31 | HttpGet::HttpGet(QObject *parent) | ||
32 | : QObject(parent) | ||
33 | { | ||
34 | outputToBuffer = true; | ||
35 | m_cached = false; | ||
36 | m_dumbCache = m_globalDumbCache; | ||
37 | getRequest = -1; | ||
38 | headRequest = -1; | ||
39 | // if a request is cancelled before a reponse is available return some | ||
40 | // hint about this in the http response instead of nonsense. | ||
41 | m_response = -1; | ||
42 | |||
43 | // default to global proxy / cache if not empty. | ||
44 | // proxy is automatically enabled, disable it by setting an empty proxy | ||
45 | // cache is enabled to be in line, can get disabled with setCache(bool) | ||
46 | if(!m_globalProxy.isEmpty()) | ||
47 | setProxy(m_globalProxy); | ||
48 | m_usecache = false; | ||
49 | m_cachedir = m_globalCache; | ||
50 | |||
51 | m_serverTimestamp = QDateTime(); | ||
52 | |||
53 | connect(&http, SIGNAL(done(bool)), this, SLOT(httpDone(bool))); | ||
54 | connect(&http, SIGNAL(dataReadProgress(int, int)), this, SIGNAL(dataReadProgress(int, int))); | ||
55 | connect(&http, SIGNAL(requestFinished(int, bool)), this, SLOT(httpFinished(int, bool))); | ||
56 | connect(&http, SIGNAL(responseHeaderReceived(const QHttpResponseHeader&)), this, SLOT(httpResponseHeader(const QHttpResponseHeader&))); | ||
57 | connect(&http, SIGNAL(stateChanged(int)), this, SLOT(httpState(int))); | ||
58 | connect(&http, SIGNAL(requestStarted(int)), this, SLOT(httpStarted(int))); | ||
59 | |||
60 | connect(&http, SIGNAL(readyRead(const QHttpResponseHeader&)), this, SLOT(httpResponseHeader(const QHttpResponseHeader&))); | ||
61 | |||
62 | } | ||
63 | |||
64 | |||
65 | //! @brief set cache path | ||
66 | // @param d new directory to use as cache path | ||
67 | void HttpGet::setCache(QDir d) | ||
68 | { | ||
69 | m_cachedir = d; | ||
70 | bool result; | ||
71 | result = initializeCache(d); | ||
72 | qDebug() << "[HTTP]"<< __func__ << "(QDir)" << d.absolutePath() << result; | ||
73 | m_usecache = result; | ||
74 | } | ||
75 | |||
76 | |||
77 | /** @brief enable / disable cache useage | ||
78 | * @param c set cache usage | ||
79 | */ | ||
80 | void HttpGet::setCache(bool c) | ||
81 | { | ||
82 | qDebug() << "[HTTP]" << __func__ << "(bool) =" << c; | ||
83 | m_usecache = c; | ||
84 | // make sure cache is initialized | ||
85 | if(c) | ||
86 | m_usecache = initializeCache(m_cachedir); | ||
87 | } | ||
88 | |||
89 | |||
90 | bool HttpGet::initializeCache(const QDir& d) | ||
91 | { | ||
92 | bool result; | ||
93 | QString p = d.absolutePath() + "/rbutil-cache"; | ||
94 | if(QFileInfo(d.absolutePath()).isDir()) | ||
95 | { | ||
96 | if(!QFileInfo(p).isDir()) | ||
97 | result = d.mkdir("rbutil-cache"); | ||
98 | else | ||
99 | result = true; | ||
100 | } | ||
101 | else | ||
102 | result = false; | ||
103 | |||
104 | return result; | ||
105 | |||
106 | } | ||
107 | |||
108 | |||
109 | /** @brief read all downloaded data into a buffer | ||
110 | * @return data | ||
111 | */ | ||
112 | QByteArray HttpGet::readAll() | ||
113 | { | ||
114 | return dataBuffer; | ||
115 | } | ||
116 | |||
117 | |||
118 | /** @brief get http error | ||
119 | * @return http error | ||
120 | */ | ||
121 | QHttp::Error HttpGet::error() | ||
122 | { | ||
123 | return http.error(); | ||
124 | } | ||
125 | |||
126 | |||
127 | void HttpGet::setProxy(const QUrl &proxy) | ||
128 | { | ||
129 | qDebug() << "[HTTP]" << __func__ << "(QUrl)" << proxy.toString(); | ||
130 | m_proxy = proxy; | ||
131 | http.setProxy(m_proxy.host(), m_proxy.port(), m_proxy.userName(), m_proxy.password()); | ||
132 | } | ||
133 | |||
134 | |||
135 | void HttpGet::setProxy(bool enable) | ||
136 | { | ||
137 | qDebug() << "[HTTP]" << __func__ << "(bool)" << enable; | ||
138 | if(enable) | ||
139 | http.setProxy(m_proxy.host(), m_proxy.port(), m_proxy.userName(), m_proxy.password()); | ||
140 | else | ||
141 | http.setProxy("", 0); | ||
142 | } | ||
143 | |||
144 | |||
145 | void HttpGet::setFile(QFile *file) | ||
146 | { | ||
147 | outputFile = file; | ||
148 | outputToBuffer = false; | ||
149 | qDebug() << "[HTTP]" << __func__ << "(QFile*)" << outputFile->fileName(); | ||
150 | } | ||
151 | |||
152 | |||
153 | void HttpGet::abort() | ||
154 | { | ||
155 | http.abort(); | ||
156 | if(!outputToBuffer) | ||
157 | outputFile->close(); | ||
158 | } | ||
159 | |||
160 | |||
161 | bool HttpGet::getFile(const QUrl &url) | ||
162 | { | ||
163 | if (!url.isValid()) { | ||
164 | qDebug() << "[HTTP] Error: Invalid URL" << endl; | ||
165 | return false; | ||
166 | } | ||
167 | |||
168 | if (url.scheme() != "http") { | ||
169 | qDebug() << "[HTTP] Error: URL must start with 'http:'" << endl; | ||
170 | return false; | ||
171 | } | ||
172 | |||
173 | if (url.path().isEmpty()) { | ||
174 | qDebug() << "[HTTP] Error: URL has no path" << endl; | ||
175 | return false; | ||
176 | } | ||
177 | m_serverTimestamp = QDateTime(); | ||
178 | // if no output file was set write to buffer | ||
179 | if(!outputToBuffer) { | ||
180 | if (!outputFile->open(QIODevice::ReadWrite)) { | ||
181 | qDebug() << "[HTTP] Error: Cannot open " << qPrintable(outputFile->fileName()) | ||
182 | << " for writing: " << qPrintable(outputFile->errorString()) | ||
183 | << endl; | ||
184 | return false; | ||
185 | } | ||
186 | } | ||
187 | qDebug() << "[HTTP] downloading" << url.toEncoded(); | ||
188 | // create request | ||
189 | http.setHost(url.host(), url.port(80)); | ||
190 | // construct query (if any) | ||
191 | QList<QPair<QString, QString> > qitems = url.queryItems(); | ||
192 | if(url.hasQuery()) { | ||
193 | m_query = "?"; | ||
194 | for(int i = 0; i < qitems.size(); i++) | ||
195 | m_query += QUrl::toPercentEncoding(qitems.at(i).first, "/") + "=" | ||
196 | + QUrl::toPercentEncoding(qitems.at(i).second, "/") + "&"; | ||
197 | } | ||
198 | |||
199 | // create hash used for caching | ||
200 | m_hash = QCryptographicHash::hash(url.toEncoded(), QCryptographicHash::Md5).toHex(); | ||
201 | m_path = QString(QUrl::toPercentEncoding(url.path(), "/")); | ||
202 | |||
203 | // construct request header | ||
204 | m_header.setValue("Host", url.host()); | ||
205 | m_header.setValue("User-Agent", m_globalUserAgent); | ||
206 | m_header.setValue("Connection", "Keep-Alive"); | ||
207 | |||
208 | if(m_dumbCache || !m_usecache) { | ||
209 | getFileFinish(); | ||
210 | } | ||
211 | else { | ||
212 | // schedule HTTP header request | ||
213 | connect(this, SIGNAL(headerFinished()), this, SLOT(getFileFinish())); | ||
214 | m_header.setRequest("HEAD", m_path + m_query); | ||
215 | headRequest = http.request(m_header); | ||
216 | } | ||
217 | |||
218 | return true; | ||
219 | } | ||
220 | |||
221 | |||
222 | void HttpGet::getFileFinish() | ||
223 | { | ||
224 | m_cachefile = m_cachedir.absolutePath() + "/rbutil-cache/" + m_hash; | ||
225 | if(m_usecache) { | ||
226 | // check if the file is present in cache | ||
227 | qDebug() << "[HTTP] cache ENABLED"; | ||
228 | QFileInfo cachefile = QFileInfo(m_cachefile); | ||
229 | if(cachefile.isReadable() | ||
230 | && cachefile.size() > 0 | ||
231 | && cachefile.lastModified() > m_serverTimestamp) { | ||
232 | |||
233 | qDebug() << "[HTTP] cached file found:" << m_cachefile; | ||
234 | |||
235 | getRequest = -1; | ||
236 | QFile c(m_cachefile); | ||
237 | if(!outputToBuffer) { | ||
238 | qDebug() << "[HTTP] copying cache file to output" << outputFile->fileName(); | ||
239 | c.open(QIODevice::ReadOnly); | ||
240 | outputFile->open(QIODevice::ReadWrite); | ||
241 | outputFile->write(c.readAll()); | ||
242 | outputFile->close(); | ||
243 | c.close(); | ||
244 | } | ||
245 | else { | ||
246 | qDebug() << "[HTTP] reading cache file into buffer"; | ||
247 | c.open(QIODevice::ReadOnly); | ||
248 | dataBuffer = c.readAll(); | ||
249 | c.close(); | ||
250 | } | ||
251 | m_response = 200; // fake "200 OK" HTTP response | ||
252 | m_cached = true; | ||
253 | httpDone(false); // we're done now. Fake http "done" signal. | ||
254 | return; | ||
255 | } | ||
256 | else { | ||
257 | if(cachefile.isReadable()) | ||
258 | qDebug() << "[HTTP] file in cache timestamp:" << cachefile.lastModified(); | ||
259 | else | ||
260 | qDebug() << "[HTTP] file not in cache."; | ||
261 | qDebug() << "[HTTP] server file timestamp:" << m_serverTimestamp; | ||
262 | qDebug() << "[HTTP] downloading file to" << m_cachefile; | ||
263 | // unlink old cache file | ||
264 | if(cachefile.isReadable()) | ||
265 | QFile(m_cachefile).remove(); | ||
266 | } | ||
267 | |||
268 | } | ||
269 | else { | ||
270 | qDebug() << "[HTTP] cache DISABLED"; | ||
271 | } | ||
272 | // schedule GET request | ||
273 | m_header.setRequest("GET", m_path + m_query); | ||
274 | if(outputToBuffer) { | ||
275 | qDebug() << "[HTTP] downloading to buffer."; | ||
276 | getRequest = http.request(m_header); | ||
277 | } | ||
278 | else { | ||
279 | qDebug() << "[HTTP] downloading to file:" | ||
280 | << qPrintable(outputFile->fileName()); | ||
281 | getRequest = http.request(m_header, 0, outputFile); | ||
282 | } | ||
283 | qDebug() << "[HTTP] GET request scheduled, id:" << getRequest; | ||
284 | |||
285 | return; | ||
286 | } | ||
287 | |||
288 | |||
289 | void HttpGet::httpDone(bool error) | ||
290 | { | ||
291 | if (error) { | ||
292 | qDebug() << "[HTTP] Error: " << qPrintable(http.errorString()) << httpResponse(); | ||
293 | } | ||
294 | if(!outputToBuffer) | ||
295 | outputFile->close(); | ||
296 | |||
297 | if(m_usecache && !m_cached && !error) { | ||
298 | qDebug() << "[HTTP] creating cache file" << m_cachefile; | ||
299 | QFile c(m_cachefile); | ||
300 | c.open(QIODevice::ReadWrite); | ||
301 | if(!outputToBuffer) { | ||
302 | outputFile->open(QIODevice::ReadOnly | QIODevice::Truncate); | ||
303 | c.write(outputFile->readAll()); | ||
304 | outputFile->close(); | ||
305 | } | ||
306 | else | ||
307 | c.write(dataBuffer); | ||
308 | |||
309 | c.close(); | ||
310 | } | ||
311 | // if cached file found and cache enabled ignore http errors | ||
312 | if(m_usecache && m_cached && !http.hasPendingRequests()) { | ||
313 | error = false; | ||
314 | } | ||
315 | // take care of concurring requests. If there is still one running, | ||
316 | // don't emit done(). That request will call this slot again. | ||
317 | if(http.currentId() == 0 && !http.hasPendingRequests()) | ||
318 | emit done(error); | ||
319 | } | ||
320 | |||
321 | |||
322 | void HttpGet::httpFinished(int id, bool error) | ||
323 | { | ||
324 | qDebug() << "[HTTP]" << __func__ << "(int, bool) =" << id << error; | ||
325 | if(id == getRequest) { | ||
326 | dataBuffer = http.readAll(); | ||
327 | |||
328 | emit requestFinished(id, error); | ||
329 | } | ||
330 | qDebug() << "[HTTP] hasPendingRequests =" << http.hasPendingRequests(); | ||
331 | |||
332 | |||
333 | if(id == headRequest) { | ||
334 | QHttpResponseHeader h = http.lastResponse(); | ||
335 | |||
336 | QString date = h.value("Last-Modified").simplified(); | ||
337 | if(date.isEmpty()) { | ||
338 | m_serverTimestamp = QDateTime(); // no value = invalid | ||
339 | emit headerFinished(); | ||
340 | return; | ||
341 | } | ||
342 | // to successfully parse the date strip weekday and timezone | ||
343 | date.remove(0, date.indexOf(" ") + 1); | ||
344 | if(date.endsWith("GMT")) | ||
345 | date.truncate(date.indexOf(" GMT")); | ||
346 | // distinguish input formats (see RFC1945) | ||
347 | // RFC 850 | ||
348 | if(date.contains("-")) | ||
349 | m_serverTimestamp = QDateTime::fromString(date, "dd-MMM-yy hh:mm:ss"); | ||
350 | // asctime format | ||
351 | else if(date.at(0).isLetter()) | ||
352 | m_serverTimestamp = QDateTime::fromString(date, "MMM d hh:mm:ss yyyy"); | ||
353 | // RFC 822 | ||
354 | else | ||
355 | m_serverTimestamp = QDateTime::fromString(date, "dd MMM yyyy hh:mm:ss"); | ||
356 | qDebug() << "[HTTP] Header Request Date:" << date << ", parsed:" << m_serverTimestamp; | ||
357 | emit headerFinished(); | ||
358 | return; | ||
359 | } | ||
360 | if(id == getRequest) | ||
361 | emit requestFinished(id, error); | ||
362 | } | ||
363 | |||
364 | void HttpGet::httpStarted(int id) | ||
365 | { | ||
366 | qDebug() << "[HTTP]" << __func__ << "(int) =" << id; | ||
367 | qDebug() << "headRequest" << headRequest << "getRequest" << getRequest; | ||
368 | } | ||
369 | |||
370 | |||
371 | QString HttpGet::errorString() | ||
372 | { | ||
373 | return http.errorString(); | ||
374 | } | ||
375 | |||
376 | |||
377 | void HttpGet::httpResponseHeader(const QHttpResponseHeader &resp) | ||
378 | { | ||
379 | // if there is a network error abort all scheduled requests for | ||
380 | // this download | ||
381 | m_response = resp.statusCode(); | ||
382 | if(m_response != 200) { | ||
383 | qDebug() << "[HTTP] response error =" << m_response << resp.reasonPhrase(); | ||
384 | http.abort(); | ||
385 | } | ||
386 | // 301 -- moved permanently | ||
387 | // 302 -- found | ||
388 | // 303 -- see other | ||
389 | // 307 -- moved temporarily | ||
390 | // in all cases, header: location has the correct address so we can follow. | ||
391 | if(m_response == 301 || m_response == 302 || m_response == 303 || m_response == 307) { | ||
392 | // start new request with new url | ||
393 | qDebug() << "[HTTP] response =" << m_response << "- following"; | ||
394 | getFile(resp.value("location") + m_query); | ||
395 | } | ||
396 | } | ||
397 | |||
398 | |||
399 | int HttpGet::httpResponse() | ||
400 | { | ||
401 | return m_response; | ||
402 | } | ||
403 | |||
404 | |||
405 | void HttpGet::httpState(int state) | ||
406 | { | ||
407 | QString s[] = {"Unconnected", "HostLookup", "Connecting", "Sending", | ||
408 | "Reading", "Connected", "Closing"}; | ||
409 | if(state <= 6) | ||
410 | qDebug() << "[HTTP]" << __func__ << "() = " << s[state]; | ||
411 | else qDebug() << "[HTTP]" << __func__ << "() = " << state; | ||
412 | } | ||
413 | |||