1 | /* |
2 | * Copyright (C) 2012 Cutehacks AS. All rights reserved. |
3 | * info@cutehacks.com |
4 | * http://cutehacks.com |
5 | * |
6 | * This file is part of Fly. |
7 | * |
8 | * Fly is free software: you can redistribute it and/or modify |
9 | * it under the terms of the GNU General Public License as published by |
10 | * the Free Software Foundation, either version 3 of the License, or |
11 | * (at your option) any later version. |
12 | * |
13 | * Fly is distributed in the hope that it will be useful, |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | * GNU General Public License for more details. |
17 | * |
18 | * You should have received a copy of the GNU General Public License |
19 | * along with Fly. If not, see <http://www.gnu.org/licenses/>. |
20 | */ |
21 | |
22 | #include "receiver.h" |
23 | #include <QNetworkRequest> |
24 | #include <QMutexLocker> |
25 | |
26 | Q_GLOBAL_STATIC(RequestQueueThread, _requestQueueThread) |
27 | |
28 | RequestQueue::RequestQueue(QObject *parent) |
29 | : QObject(parent), |
30 | m_manager(0), |
31 | m_cacheMaxSize(512*1024), |
32 | m_cacheMechanism(QNetworkRequest::PreferNetwork), |
33 | m_cacheExpirationSeconds(0) |
34 | { |
35 | qRegisterMetaType<QNetworkAccessManager::Operation>("Operation" ); |
36 | setManager(new QNetworkAccessManager(this)); |
37 | } |
38 | |
39 | RequestQueue::~RequestQueue() |
40 | { |
41 | } |
42 | |
43 | QNetworkAccessManager *RequestQueue::manager() const |
44 | { |
45 | return m_manager; |
46 | } |
47 | |
48 | void RequestQueue::setManager(QNetworkAccessManager *manager) |
49 | { |
50 | delete m_manager; |
51 | m_manager = manager; |
52 | connect(m_manager, SIGNAL(finished(QNetworkReply*)), |
53 | this, SLOT(replyFinished(QNetworkReply*))); |
54 | } |
55 | |
56 | void RequestQueue::enableCaching(bool enable) |
57 | { |
58 | if (enable) { |
59 | if (m_manager->cache() == 0) { |
60 | QNetworkDiskCache *cache = new QNetworkDiskCache(this); |
61 | cache->setCacheDirectory("." ); |
62 | cache->setMaximumCacheSize(m_cacheMaxSize); |
63 | m_manager->setCache(cache); |
64 | } |
65 | } else if (m_manager->cache() != 0) { |
66 | m_manager->setCache(0); |
67 | } |
68 | } |
69 | |
70 | void RequestQueue::setCacheSize(int size) |
71 | { |
72 | m_cacheMaxSize = size; |
73 | if (QNetworkDiskCache *cache = qobject_cast<QNetworkDiskCache *>(m_manager->cache())) |
74 | cache->setMaximumCacheSize(m_cacheMaxSize); |
75 | } |
76 | |
77 | void RequestQueue::setCacheMechanism(QNetworkRequest::CacheLoadControl mechanism) |
78 | { |
79 | m_cacheMechanism = mechanism; |
80 | } |
81 | |
82 | /*! |
83 | Overrides expiration of cached network requests to QDateTime::currentDateTime() + seconds. |
84 | Existing cache is not affected, only new requests will be overriden. |
85 | A value lower or equeal to 0 leaves network traffic untouched and keeps original expiration date as specified from the server. |
86 | |
87 | Default value is 0 |
88 | */ |
89 | void RequestQueue::setCacheExpiration(int seconds) |
90 | { |
91 | m_cacheExpirationSeconds = seconds; |
92 | } |
93 | |
94 | void RequestQueue::sendRequest(const QUrl &url, Receiver *receiver, QNetworkAccessManager::Operation operation, const QByteArray &data) |
95 | { |
96 | QNetworkRequest request(url); |
97 | if (!m_cookies.isEmpty()) |
98 | request.setHeader(QNetworkRequest::CookieHeader, qVariantFromValue(m_cookies)); |
99 | QHashIterator<QByteArray, QByteArray> it(m_headerHash); |
100 | while (it.hasNext()) { |
101 | it.next(); |
102 | request.setRawHeader(it.key(), it.value()); |
103 | } |
104 | request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, m_cacheMechanism); |
105 | QNetworkReply *reply = 0; |
106 | switch (operation) { |
107 | case QNetworkAccessManager::HeadOperation: |
108 | reply = m_manager->head(request); |
109 | break; |
110 | case QNetworkAccessManager::GetOperation: |
111 | reply = m_manager->get(request); |
112 | break; |
113 | case QNetworkAccessManager::PutOperation: |
114 | reply = m_manager->put(request, data); |
115 | break; |
116 | case QNetworkAccessManager::DeleteOperation: |
117 | reply = m_manager->deleteResource(request); |
118 | break; |
119 | default: |
120 | break; |
121 | } |
122 | if (reply && reply->error() != QNetworkReply::NoError) { |
123 | emit error(receiver, reply->errorString()); |
124 | reply->deleteLater(); |
125 | } else if (reply) { |
126 | m_receivers.insert(reply, receiver); |
127 | } |
128 | } |
129 | |
130 | void RequestQueue::setCookies(const QList<QNetworkCookie> &cookies) |
131 | { |
132 | m_cookies = cookies; |
133 | } |
134 | |
135 | void RequestQueue::setUserAgent(const QByteArray &userAgent) |
136 | { |
137 | setRawHeader("User-Agent" , userAgent); |
138 | } |
139 | |
140 | void RequestQueue::(const QByteArray &, const QByteArray &) |
141 | { |
142 | m_headerHash.insert(headerName, headerValue); |
143 | } |
144 | |
145 | void RequestQueue::abort(Receiver *receiver) |
146 | { |
147 | if (QNetworkReply *reply = m_receivers.key(receiver, 0)) |
148 | reply->abort(); |
149 | } |
150 | |
151 | void RequestQueue::replyFinished(QNetworkReply *reply) |
152 | { |
153 | if (Receiver *receiver = m_receivers.value(reply, 0)) |
154 | emit replyFinished(receiver, reply->readAll(), reply->errorString()); |
155 | |
156 | if (m_cacheExpirationSeconds > 0 && m_manager->cache() && !reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool()) { |
157 | QNetworkCacheMetaData metaData = m_manager->cache()->metaData(reply->url()); |
158 | QDateTime oldExpire = metaData.expirationDate(); |
159 | metaData.setExpirationDate(QDateTime::currentDateTime().addSecs(m_cacheExpirationSeconds)); |
160 | m_manager->cache()->updateMetaData(metaData); |
161 | } |
162 | reply->deleteLater(); |
163 | } |
164 | |
165 | void RequestQueue::authenticationRequired(QNetworkReply *reply, QAuthenticator *authenticator) |
166 | { |
167 | if (Receiver *receiver = m_receivers.value(reply, 0)) |
168 | emit authenticationRequired(receiver, reply->readAll(), authenticator); |
169 | } |
170 | |
171 | // RequestQueueThread |
172 | |
173 | QMutex RequestQueueThread::m_mutex; |
174 | QWaitCondition RequestQueueThread::m_condition; |
175 | bool RequestQueueThread::m_connected = false; |
176 | bool RequestQueueThread::m_threaded = true; |
177 | RequestQueue *RequestQueueThread::m_queue = 0; |
178 | |
179 | RequestQueueThread::RequestQueueThread(QObject *parent) |
180 | : QThread(parent) |
181 | { |
182 | } |
183 | |
184 | RequestQueueThread::~RequestQueueThread() |
185 | { |
186 | quit(); |
187 | wait(); |
188 | } |
189 | |
190 | RequestQueueThread *RequestQueueThread::instance() |
191 | { |
192 | RequestQueueThread *queueThread = _requestQueueThread(); |
193 | if (m_threaded) { |
194 | if (!queueThread->isRunning()) |
195 | queueThread->start(); |
196 | QMutexLocker locker(&m_mutex); |
197 | while (!m_connected) |
198 | m_condition.wait(&m_mutex); |
199 | } else { // unthreaded |
200 | if (!m_queue) { |
201 | m_queue = new RequestQueue(queueThread); |
202 | queueThread->connectToQueue(m_queue); |
203 | m_connected = true; |
204 | } |
205 | } |
206 | return queueThread; |
207 | } |
208 | |
209 | QNetworkAccessManager *RequestQueueThread::manager() |
210 | { |
211 | m_threaded = false; |
212 | return instance()->m_queue->manager(); |
213 | } |
214 | |
215 | void RequestQueueThread::setManager(QNetworkAccessManager *manager) |
216 | { |
217 | m_threaded = false; |
218 | instance()->m_queue->setManager(manager); |
219 | } |
220 | |
221 | void RequestQueueThread::enableCaching(bool enable) |
222 | { |
223 | emit enableCachingSignal(enable); |
224 | } |
225 | |
226 | void RequestQueueThread::setCacheSize(int size) |
227 | { |
228 | emit setCacheSizeSignal(size); |
229 | } |
230 | |
231 | void RequestQueueThread::setCacheMechanism(QNetworkRequest::CacheLoadControl mechanism) |
232 | { |
233 | emit setCacheMechanismSignal(mechanism); |
234 | } |
235 | |
236 | void RequestQueueThread::setCacheExpiration(int seconds) |
237 | { |
238 | emit setCacheExpirationSignal(seconds); |
239 | } |
240 | |
241 | void RequestQueueThread::sendRequest(const QUrl &url, Receiver *receiver, QNetworkAccessManager::Operation operation, const QByteArray &data) |
242 | { |
243 | emit sendRequestSignal(url, receiver, operation, data); |
244 | } |
245 | |
246 | void RequestQueueThread::setCookies(const QList<QNetworkCookie> &cookies) |
247 | { |
248 | emit setCookiesSignal(cookies); |
249 | } |
250 | |
251 | void RequestQueueThread::setUserAgent(const QByteArray &userAgent) |
252 | { |
253 | emit setUserAgentSignal(userAgent); |
254 | } |
255 | |
256 | void RequestQueueThread::(const QByteArray &, const QByteArray &) |
257 | { |
258 | emit setRawHeaderSignal(headerName, headerValue); |
259 | } |
260 | |
261 | void RequestQueueThread::abort(Receiver *receiver) |
262 | { |
263 | emit abortSignal(receiver); |
264 | } |
265 | |
266 | void RequestQueueThread::error(Receiver *receiver, const QString &errorString) |
267 | { |
268 | if (receiver) |
269 | receiver->error(errorString); |
270 | } |
271 | |
272 | void RequestQueueThread::replyFinished(Receiver *receiver, const QByteArray &reply, const QString &errorString) |
273 | { |
274 | if (receiver) |
275 | receiver->replyFinished(reply, errorString); |
276 | } |
277 | |
278 | void RequestQueueThread::run() |
279 | { |
280 | RequestQueue queue; |
281 | connectToQueue(&queue); |
282 | |
283 | // start eventloop for this thread |
284 | |
285 | m_mutex.lock(); |
286 | m_connected = true; |
287 | m_condition.wakeAll(); |
288 | m_mutex.unlock(); |
289 | |
290 | exec(); |
291 | } |
292 | |
293 | void RequestQueueThread::connectToQueue(RequestQueue *queue) |
294 | { |
295 | // connections from Thread to Queue |
296 | connect(this, SIGNAL(enableCachingSignal(bool)), queue, SLOT(enableCaching(bool))); |
297 | connect(this, SIGNAL(setCacheSizeSignal(int)), queue, SLOT(setCacheSize(int))); |
298 | qRegisterMetaType<QNetworkRequest::CacheLoadControl>("QNetworkRequest::CacheLoadControl" ); |
299 | connect(this, SIGNAL(setCacheMechanismSignal(QNetworkRequest::CacheLoadControl)), queue, SLOT(setCacheMechanism(QNetworkRequest::CacheLoadControl))); |
300 | connect(this, SIGNAL(setCacheExpirationSignal(int)), queue, SLOT(setCacheExpiration(int))); |
301 | connect(this, SIGNAL(sendRequestSignal(QUrl,Receiver*,QNetworkAccessManager::Operation,QByteArray)), |
302 | queue, SLOT(sendRequest(QUrl,Receiver*,QNetworkAccessManager::Operation,QByteArray))); |
303 | connect(this, SIGNAL(setCookiesSignal(QList<QNetworkCookie>)), queue, SLOT(setCookies(QList<QNetworkCookie>))); |
304 | connect(this, SIGNAL(setUserAgentSignal(QByteArray)), queue, SLOT(setUserAgent(QByteArray))); |
305 | connect(this, SIGNAL(setRawHeaderSignal(QByteArray,QByteArray)), queue, SLOT(setRawHeader(QByteArray,QByteArray))); |
306 | |
307 | // connections from Queue to Thread |
308 | connect(queue, SIGNAL(error(Receiver*,QString)), this, SLOT(error(Receiver*,QString))); |
309 | connect(queue, SIGNAL(replyFinished(Receiver*,QByteArray,QString)), this, SLOT(replyFinished(Receiver*,QByteArray,QString))); |
310 | } |
311 | |
312 | // Receiver |
313 | |
314 | Receiver::Receiver(QObject *parent) |
315 | : QObject(parent) |
316 | { |
317 | } |
318 | |
319 | Receiver::~Receiver() |
320 | { |
321 | } |
322 | |
323 | void Receiver::request(const QUrl &url) |
324 | { |
325 | RequestQueueThread::instance()->sendRequest(url, this); |
326 | } |
327 | |
328 | void Receiver::abortRequest() |
329 | { |
330 | RequestQueueThread::instance()->abort(this); |
331 | } |
332 | |
333 | void Receiver::replyFinished(const QByteArray &reply, const QString &errorString) |
334 | { |
335 | Q_UNUSED(reply); |
336 | Q_UNUSED(errorString); |
337 | emit finished(); |
338 | } |
339 | |
340 | NodeReceiver::NodeReceiver(QObject *parent) |
341 | : Receiver(parent), m_root(0) |
342 | { |
343 | } |
344 | |
345 | NodeReceiver::~NodeReceiver() |
346 | { |
347 | clear(); |
348 | } |
349 | |
350 | void NodeReceiver::clear() |
351 | { |
352 | delete m_root; |
353 | m_root = 0; |
354 | } |
355 | |
356 | Node *NodeReceiver::rootNode() const |
357 | { |
358 | return m_root; |
359 | } |
360 | |
361 | void NodeReceiver::setRootNode(Node *node) |
362 | { |
363 | m_root = node; |
364 | } |
365 | |
366 | XmlReceiver::XmlReceiver(QObject *parent) |
367 | : NodeReceiver(parent) |
368 | { |
369 | } |
370 | |
371 | XmlReceiver::~XmlReceiver() |
372 | { |
373 | } |
374 | |
375 | void XmlReceiver::replyFinished(const QByteArray &reply, const QString &errorString) |
376 | { |
377 | clear(); |
378 | setRootNode(new Node("root" )); |
379 | Node *current = rootNode(); |
380 | if (reply.isEmpty()) |
381 | emit error(errorString); |
382 | QXmlStreamReader xml(reply); |
383 | while (!xml.atEnd()) { |
384 | switch(xml.readNext()) { |
385 | case QXmlStreamReader::NoToken: |
386 | break; |
387 | case QXmlStreamReader::Invalid: |
388 | break; |
389 | case QXmlStreamReader::StartDocument: |
390 | break; |
391 | case QXmlStreamReader::EndDocument: |
392 | break; |
393 | case QXmlStreamReader::StartElement: { |
394 | const QString name = xml.name().toString(); |
395 | current = new Node(name, current); |
396 | foreach (QXmlStreamAttribute attribute, xml.attributes()) |
397 | current->setAttribute(attribute.name().toString(), attribute.value().toString()); |
398 | break; } |
399 | case QXmlStreamReader::EndElement: |
400 | current = current->parent(); |
401 | break; |
402 | case QXmlStreamReader::Characters: |
403 | if (current && !xml.isWhitespace()) |
404 | current->setText(xml.text().toString()); |
405 | break; |
406 | case QXmlStreamReader::Comment: |
407 | break; |
408 | case QXmlStreamReader::DTD: |
409 | break; |
410 | case QXmlStreamReader::EntityReference: |
411 | break; |
412 | case QXmlStreamReader::ProcessingInstruction: |
413 | break; |
414 | default: |
415 | break; |
416 | } |
417 | } |
418 | if (xml.hasError()) |
419 | qDebug() << xml.errorString(); |
420 | emit finished(); |
421 | } |
422 | |
423 | JsonReceiver::JsonReceiver(QObject *parent) |
424 | : NodeReceiver(parent) |
425 | { |
426 | } |
427 | |
428 | JsonReceiver::~JsonReceiver() |
429 | { |
430 | } |
431 | |
432 | void JsonReceiver::replyFinished(const QByteArray &reply, const QString &errorString) |
433 | { |
434 | clear(); |
435 | setRootNode(new Node("root" )); |
436 | Node *current = rootNode(); |
437 | if (reply.isEmpty()) |
438 | emit error(errorString); |
439 | // FIXME: a very simple, naive json parser - will break |
440 | QByteArray element; |
441 | QByteArray property; |
442 | for (int i = 0; i < reply.size(); ++i) { |
443 | switch (reply.at(i)) { |
444 | case '{': // start object |
445 | if (!property.isEmpty()) { // object is property value |
446 | current = new Node("element" , current); |
447 | current->setText(QString::fromUtf8(property.data(), property.size())); |
448 | } |
449 | current = new Node("object" , current); |
450 | break; |
451 | case '}': // end object |
452 | do { |
453 | current = current ? current->parent() : 0; |
454 | } while (current && current->type() != "object" ); |
455 | if (!element.isEmpty()) { |
456 | Node *node = new Node("element" , current); |
457 | if (!property.isEmpty()) |
458 | node->setAttribute(property, element); |
459 | else |
460 | node->setText(QString::fromUtf8(element.data(), element.size())); |
461 | property.clear(); |
462 | element.clear(); |
463 | } |
464 | break; |
465 | case ':': // text was propery name |
466 | property = element; |
467 | element.clear(); |
468 | break; |
469 | case '[': // start list |
470 | if (!property.isEmpty()) { // list is property value |
471 | current = new Node("element" , current); |
472 | current->setText(QString::fromUtf8(property.data(), property.size())); |
473 | } |
474 | current = new Node("list" , current); |
475 | break; |
476 | case ']': // end list |
477 | if (!element.isEmpty()) { // add last element |
478 | (new Node("element" , current))->setText(QString::fromUtf8(element.data(), element.size())); |
479 | element.clear(); |
480 | } |
481 | current = current ? current->parent() : 0; |
482 | break; |
483 | case ',': { // end element |
484 | if (!element.isEmpty()) { |
485 | Node *node = new Node("element" , current); |
486 | if (!property.isEmpty()) |
487 | node->setAttribute(property, element); |
488 | else |
489 | node->setText(QString::fromUtf8(element.data(), element.size())); |
490 | property.clear(); |
491 | element.clear(); |
492 | } |
493 | break; } |
494 | case '"': |
495 | while (reply.at(++i) != '"') |
496 | element.append(reply.at(i)); |
497 | break; |
498 | break; // ignore |
499 | default: |
500 | element.append(reply.at(i)); |
501 | break; |
502 | } |
503 | } |
504 | emit finished(); |
505 | } |
506 | |
507 | PixmapReceiver::PixmapReceiver(QObject *parent) |
508 | : Receiver(parent) |
509 | { |
510 | } |
511 | |
512 | QPixmap PixmapReceiver::pixmap() const |
513 | { |
514 | return m_pixmap; |
515 | } |
516 | #if 0 |
517 | void PixmapReceiver::request(const QUrl &url) |
518 | { |
519 | const QString path = url.path(); |
520 | m_path = QDir::currentPath() + path.mid(path.lastIndexOf('/')); |
521 | if (!m_path.isEmpty() && QFile::exists(m_path)) |
522 | m_pixmap.load(m_path); |
523 | else |
524 | Receiver::request(url) |
525 | } |
526 | #endif |
527 | void PixmapReceiver::replyFinished(const QByteArray &reply, const QString &errorString) |
528 | { |
529 | if (reply.isEmpty()) |
530 | emit error(errorString); |
531 | m_pixmap.loadFromData(reply); |
532 | emit finished(m_pixmap); |
533 | #if 0 |
534 | // cache file |
535 | if (!m_path.isEmpty() && !QFile::exists(m_path)) |
536 | m_pixmap.save(m_path, "PNG" ); |
537 | #endif |
538 | } |
539 | |