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
26Q_GLOBAL_STATIC(RequestQueueThread, _requestQueueThread)
27
28RequestQueue::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
39RequestQueue::~RequestQueue()
40{
41}
42
43QNetworkAccessManager *RequestQueue::manager() const
44{
45 return m_manager;
46}
47
48void 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
56void 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
70void 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
77void 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*/
89void RequestQueue::setCacheExpiration(int seconds)
90{
91 m_cacheExpirationSeconds = seconds;
92}
93
94void 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
130void RequestQueue::setCookies(const QList<QNetworkCookie> &cookies)
131{
132 m_cookies = cookies;
133}
134
135void RequestQueue::setUserAgent(const QByteArray &userAgent)
136{
137 setRawHeader("User-Agent", userAgent);
138}
139
140void RequestQueue::setRawHeader(const QByteArray &headerName, const QByteArray &headerValue)
141{
142 m_headerHash.insert(headerName, headerValue);
143}
144
145void RequestQueue::abort(Receiver *receiver)
146{
147 if (QNetworkReply *reply = m_receivers.key(receiver, 0))
148 reply->abort();
149}
150
151void 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
165void 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
173QMutex RequestQueueThread::m_mutex;
174QWaitCondition RequestQueueThread::m_condition;
175bool RequestQueueThread::m_connected = false;
176bool RequestQueueThread::m_threaded = true;
177RequestQueue *RequestQueueThread::m_queue = 0;
178
179RequestQueueThread::RequestQueueThread(QObject *parent)
180 : QThread(parent)
181{
182}
183
184RequestQueueThread::~RequestQueueThread()
185{
186 quit();
187 wait();
188}
189
190RequestQueueThread *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
209QNetworkAccessManager *RequestQueueThread::manager()
210{
211 m_threaded = false;
212 return instance()->m_queue->manager();
213}
214
215void RequestQueueThread::setManager(QNetworkAccessManager *manager)
216{
217 m_threaded = false;
218 instance()->m_queue->setManager(manager);
219}
220
221void RequestQueueThread::enableCaching(bool enable)
222{
223 emit enableCachingSignal(enable);
224}
225
226void RequestQueueThread::setCacheSize(int size)
227{
228 emit setCacheSizeSignal(size);
229}
230
231void RequestQueueThread::setCacheMechanism(QNetworkRequest::CacheLoadControl mechanism)
232{
233 emit setCacheMechanismSignal(mechanism);
234}
235
236void RequestQueueThread::setCacheExpiration(int seconds)
237{
238 emit setCacheExpirationSignal(seconds);
239}
240
241void RequestQueueThread::sendRequest(const QUrl &url, Receiver *receiver, QNetworkAccessManager::Operation operation, const QByteArray &data)
242{
243 emit sendRequestSignal(url, receiver, operation, data);
244}
245
246void RequestQueueThread::setCookies(const QList<QNetworkCookie> &cookies)
247{
248 emit setCookiesSignal(cookies);
249}
250
251void RequestQueueThread::setUserAgent(const QByteArray &userAgent)
252{
253 emit setUserAgentSignal(userAgent);
254}
255
256void RequestQueueThread::setRawHeader(const QByteArray &headerName, const QByteArray &headerValue)
257{
258 emit setRawHeaderSignal(headerName, headerValue);
259}
260
261void RequestQueueThread::abort(Receiver *receiver)
262{
263 emit abortSignal(receiver);
264}
265
266void RequestQueueThread::error(Receiver *receiver, const QString &errorString)
267{
268 if (receiver)
269 receiver->error(errorString);
270}
271
272void RequestQueueThread::replyFinished(Receiver *receiver, const QByteArray &reply, const QString &errorString)
273{
274 if (receiver)
275 receiver->replyFinished(reply, errorString);
276}
277
278void 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
293void 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
314Receiver::Receiver(QObject *parent)
315 : QObject(parent)
316{
317}
318
319Receiver::~Receiver()
320{
321}
322
323void Receiver::request(const QUrl &url)
324{
325 RequestQueueThread::instance()->sendRequest(url, this);
326}
327
328void Receiver::abortRequest()
329{
330 RequestQueueThread::instance()->abort(this);
331}
332
333void Receiver::replyFinished(const QByteArray &reply, const QString &errorString)
334{
335 Q_UNUSED(reply);
336 Q_UNUSED(errorString);
337 emit finished();
338}
339
340NodeReceiver::NodeReceiver(QObject *parent)
341 : Receiver(parent), m_root(0)
342{
343}
344
345NodeReceiver::~NodeReceiver()
346{
347 clear();
348}
349
350void NodeReceiver::clear()
351{
352 delete m_root;
353 m_root = 0;
354}
355
356Node *NodeReceiver::rootNode() const
357{
358 return m_root;
359}
360
361void NodeReceiver::setRootNode(Node *node)
362{
363 m_root = node;
364}
365
366XmlReceiver::XmlReceiver(QObject *parent)
367 : NodeReceiver(parent)
368{
369}
370
371XmlReceiver::~XmlReceiver()
372{
373}
374
375void 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
423JsonReceiver::JsonReceiver(QObject *parent)
424 : NodeReceiver(parent)
425{
426}
427
428JsonReceiver::~JsonReceiver()
429{
430}
431
432void 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
507PixmapReceiver::PixmapReceiver(QObject *parent)
508 : Receiver(parent)
509{
510}
511
512QPixmap PixmapReceiver::pixmap() const
513{
514 return m_pixmap;
515}
516#if 0
517void 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
527void 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