source: trunk/src/network/access/qnetworkreplyimpl.cpp@ 428

Last change on this file since 428 was 2, checked in by Dmitry A. Kuminov, 16 years ago

Initially imported qt-all-opensource-src-4.5.1 from Trolltech.

File size: 17.9 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** Contact: Qt Software Information ([email protected])
5**
6** This file is part of the QtNetwork module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial Usage
10** Licensees holding valid Qt Commercial licenses may use this file in
11** accordance with the Qt Commercial License Agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and Nokia.
14**
15** GNU Lesser General Public License Usage
16** Alternatively, this file may be used under the terms of the GNU Lesser
17** General Public License version 2.1 as published by the Free Software
18** Foundation and appearing in the file LICENSE.LGPL included in the
19** packaging of this file. Please review the following information to
20** ensure the GNU Lesser General Public License version 2.1 requirements
21** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
22**
23** In addition, as a special exception, Nokia gives you certain
24** additional rights. These rights are described in the Nokia Qt LGPL
25** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
26** package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you are unsure which license is appropriate for your use, please
37** contact the sales department at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qnetworkreplyimpl_p.h"
43#include "qnetworkaccessbackend_p.h"
44#include "qnetworkcookie.h"
45#include "qabstractnetworkcache.h"
46#include "QtCore/qcoreapplication.h"
47#include "QtCore/qdatetime.h"
48#include "QtNetwork/qsslconfiguration.h"
49
50#include <QtCore/QCoreApplication>
51
52QT_BEGIN_NAMESPACE
53
54inline QNetworkReplyImplPrivate::QNetworkReplyImplPrivate()
55 : copyDevice(0), networkCache(0),
56 cacheEnabled(false), cacheSaveDevice(0),
57 bytesDownloaded(0), lastBytesDownloaded(-1), bytesUploaded(-1),
58 state(Idle)
59{
60}
61
62void QNetworkReplyImplPrivate::_q_startOperation()
63{
64 // This function is called exactly once
65 state = Working;
66 if (!backend) {
67 error(QNetworkReplyImpl::ProtocolUnknownError,
68 QCoreApplication::translate("QNetworkReply", "Protocol \"%1\" is unknown").arg(url.scheme())); // not really true!;
69 finished();
70 return;
71 }
72
73 backend->open();
74 if (state != Finished) {
75 if (operation == QNetworkAccessManager::GetOperation)
76 pendingNotifications.append(NotifyDownstreamReadyWrite);
77 if (outgoingData) {
78 _q_sourceReadyRead();
79#if 0 // ### FIXME
80 if (outgoingData->atEndOfStream() && writeBuffer.isEmpty())
81 // empty upload
82 emit q->uploadProgress(0, 0);
83#endif
84 }
85
86 handleNotifications();
87 }
88}
89
90void QNetworkReplyImplPrivate::_q_sourceReadyRead()
91{
92 // read data from the outgoingData QIODevice into our internal buffer
93 enum { DesiredBufferSize = 32 * 1024 };
94
95 if (writeBuffer.size() >= DesiredBufferSize)
96 return; // don't grow the buffer too much
97
98 // read as many bytes are available or up until we fill up the buffer
99 // but always read at least one byte
100 qint64 bytesToRead = qBound<qint64>(1, outgoingData->bytesAvailable(),
101 DesiredBufferSize - writeBuffer.size());
102 char *ptr = writeBuffer.reserve(bytesToRead);
103 qint64 bytesActuallyRead = outgoingData->read(ptr, bytesToRead);
104 if (bytesActuallyRead == -1) {
105 // EOF
106 writeBuffer.chop(bytesToRead);
107 backendNotify(NotifyCloseUpstreamChannel);
108 return;
109 }
110
111 if (bytesActuallyRead < bytesToRead)
112 writeBuffer.chop(bytesToRead - bytesActuallyRead);
113
114 // if we did read anything, let the backend know and handle it
115 if (bytesActuallyRead)
116 backendNotify(NotifyUpstreamReadyRead);
117
118 // check for EOF again
119 if (!outgoingData->isSequential() && outgoingData->atEnd())
120 backendNotify(NotifyCloseUpstreamChannel);
121}
122
123void QNetworkReplyImplPrivate::_q_sourceReadChannelFinished()
124{
125 _q_sourceReadyRead();
126}
127
128void QNetworkReplyImplPrivate::_q_copyReadyRead()
129{
130 Q_Q(QNetworkReplyImpl);
131 if (!copyDevice && !q->isOpen())
132 return;
133
134 qint64 bytesToRead = nextDownstreamBlockSize();
135 if (bytesToRead == 0)
136 // we'll be called again, eventually
137 return;
138
139 bytesToRead = qBound<qint64>(1, bytesToRead, copyDevice->bytesAvailable());
140 char *ptr = readBuffer.reserve(bytesToRead);
141 qint64 bytesActuallyRead = copyDevice->read(ptr, bytesToRead);
142 if (bytesActuallyRead == -1) {
143 readBuffer.chop(bytesToRead);
144 backendNotify(NotifyCopyFinished);
145 return;
146 }
147
148 if (bytesActuallyRead != bytesToRead)
149 readBuffer.chop(bytesToRead - bytesActuallyRead);
150
151 if (!copyDevice->isSequential() && copyDevice->atEnd())
152 backendNotify(NotifyCopyFinished);
153
154 bytesDownloaded += bytesActuallyRead;
155 lastBytesDownloaded = bytesDownloaded;
156 QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader);
157 emit q->downloadProgress(bytesDownloaded,
158 totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong());
159 emit q->readyRead();
160}
161
162void QNetworkReplyImplPrivate::_q_copyReadChannelFinished()
163{
164 _q_copyReadyRead();
165}
166
167void QNetworkReplyImplPrivate::setup(QNetworkAccessManager::Operation op, const QNetworkRequest &req,
168 QIODevice *data)
169{
170 Q_Q(QNetworkReplyImpl);
171
172 outgoingData = data;
173 request = req;
174 url = request.url();
175 operation = op;
176
177 if (outgoingData) {
178 q->connect(outgoingData, SIGNAL(readyRead()), SLOT(_q_sourceReadyRead()));
179 q->connect(outgoingData, SIGNAL(readChannelFinished()), SLOT(_q_sourceReadChannelFinished()));
180 }
181
182 q->QIODevice::open(QIODevice::ReadOnly);
183 QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
184}
185
186void QNetworkReplyImplPrivate::setNetworkCache(QAbstractNetworkCache *nc)
187{
188 networkCache = nc;
189}
190
191void QNetworkReplyImplPrivate::backendNotify(InternalNotifications notification)
192{
193 Q_Q(QNetworkReplyImpl);
194 if (!pendingNotifications.contains(notification))
195 pendingNotifications.enqueue(notification);
196
197 if (pendingNotifications.size() == 1)
198 QCoreApplication::postEvent(q, new QEvent(QEvent::NetworkReplyUpdated));
199}
200
201void QNetworkReplyImplPrivate::handleNotifications()
202{
203 NotificationQueue current = pendingNotifications;
204 pendingNotifications.clear();
205
206 if (state != Working)
207 return;
208
209 while (!current.isEmpty()) {
210 InternalNotifications notification = current.dequeue();
211 switch (notification) {
212 case NotifyDownstreamReadyWrite:
213 if (copyDevice)
214 _q_copyReadyRead();
215 else
216 backend->downstreamReadyWrite();
217 break;
218
219 case NotifyUpstreamReadyRead:
220 backend->upstreamReadyRead();
221 break;
222
223 case NotifyCloseDownstreamChannel:
224 backend->closeDownstreamChannel();
225 break;
226
227 case NotifyCloseUpstreamChannel:
228 backend->closeUpstreamChannel();
229 break;
230
231 case NotifyCopyFinished: {
232 QIODevice *dev = copyDevice;
233 copyDevice = 0;
234 backend->copyFinished(dev);
235 break;
236 }
237 }
238 }
239}
240
241void QNetworkReplyImplPrivate::createCache()
242{
243 // check if we can save and if we're allowed to
244 if (!networkCache || !request.attribute(QNetworkRequest::CacheSaveControlAttribute, true).toBool())
245 return;
246 cacheEnabled = true;
247}
248
249bool QNetworkReplyImplPrivate::isCachingEnabled() const
250{
251 return (cacheEnabled && networkCache != 0);
252}
253
254void QNetworkReplyImplPrivate::setCachingEnabled(bool enable)
255{
256 if (!enable && !cacheEnabled)
257 return; // nothing to do
258 if (enable && cacheEnabled)
259 return; // nothing to do either!
260
261 if (enable) {
262 if (bytesDownloaded) {
263 // refuse to enable in this case
264 qCritical("QNetworkReplyImpl: backend error: caching was enabled after some bytes had been written");
265 return;
266 }
267
268 createCache();
269 } else {
270 // someone told us to turn on, then back off?
271 // ok... but you should make up your mind
272 qDebug("QNetworkReplyImpl: setCachingEnabled(true) called after setCachingEnabled(false) -- "
273 "backend %s probably needs to be fixed",
274 backend->metaObject()->className());
275 networkCache->remove(url);
276 cacheSaveDevice = 0;
277 cacheEnabled = false;
278 }
279}
280
281void QNetworkReplyImplPrivate::completeCacheSave()
282{
283 if (cacheEnabled && errorCode != QNetworkReplyImpl::NoError) {
284 networkCache->remove(url);
285 } else if (cacheEnabled && cacheSaveDevice) {
286 networkCache->insert(cacheSaveDevice);
287 }
288 cacheSaveDevice = 0;
289 cacheEnabled = false;
290}
291
292void QNetworkReplyImplPrivate::consume(qint64 count)
293{
294 Q_Q(QNetworkReplyImpl);
295 if (count <= 0) {
296 qWarning("QNetworkConnection: backend signalled that it consumed %ld bytes", long(count));
297 return;
298 }
299
300 if (outgoingData)
301 // schedule another read from the source
302 QMetaObject::invokeMethod(q_func(), "_q_sourceReadyRead", Qt::QueuedConnection);
303
304 writeBuffer.skip(count);
305 if (bytesUploaded == -1)
306 bytesUploaded = count;
307 else
308 bytesUploaded += count;
309
310 QVariant totalSize = request.header(QNetworkRequest::ContentLengthHeader);
311 emit q->uploadProgress(bytesUploaded,
312 totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong());
313}
314
315qint64 QNetworkReplyImplPrivate::nextDownstreamBlockSize() const
316{
317 enum { DesiredBufferSize = 32 * 1024 };
318 if (readBufferMaxSize == 0)
319 return DesiredBufferSize;
320
321 return qMax<qint64>(0, readBufferMaxSize - readBuffer.size());
322}
323
324void QNetworkReplyImplPrivate::feed(const QByteArray &data)
325{
326 Q_Q(QNetworkReplyImpl);
327 if (!q->isOpen())
328 return;
329
330 char *ptr = readBuffer.reserve(data.size());
331 memcpy(ptr, data.constData(), data.size());
332
333 if (cacheEnabled && !cacheSaveDevice) {
334 // save the meta data
335 QNetworkCacheMetaData metaData;
336 metaData.setUrl(url);
337 metaData = backend->fetchCacheMetaData(metaData);
338 cacheSaveDevice = networkCache->prepare(metaData);
339 if (!cacheSaveDevice || (cacheSaveDevice && !cacheSaveDevice->isOpen())) {
340 if (cacheSaveDevice && !cacheSaveDevice->isOpen())
341 qCritical("QNetworkReplyImpl: network cache returned a device that is not open -- "
342 "class %s probably needs to be fixed",
343 networkCache->metaObject()->className());
344
345 networkCache->remove(url);
346 cacheSaveDevice = 0;
347 cacheEnabled = false;
348 }
349 }
350
351 if (cacheSaveDevice)
352 cacheSaveDevice->write(data);
353
354 bytesDownloaded += data.size();
355 lastBytesDownloaded = bytesDownloaded;
356
357 QPointer<QNetworkReplyImpl> qq = q;
358
359 QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader);
360 emit q->downloadProgress(bytesDownloaded,
361 totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong());
362 emit q->readyRead();
363
364 // hopefully we haven't been deleted here
365 if (!qq.isNull()) {
366 // do we still have room in the buffer?
367 if (nextDownstreamBlockSize() > 0)
368 backendNotify(QNetworkReplyImplPrivate::NotifyDownstreamReadyWrite);
369 }
370}
371
372void QNetworkReplyImplPrivate::feed(QIODevice *data)
373{
374 Q_Q(QNetworkReplyImpl);
375 Q_ASSERT(q->isOpen());
376
377 // read until EOF from data
378 if (copyDevice) {
379 qCritical("QNetworkReplyImpl: copy from QIODevice already in progress -- "
380 "backend probly needs to be fixed");
381 return;
382 }
383
384 copyDevice = data;
385 q->connect(copyDevice, SIGNAL(readyRead()), SLOT(_q_copyReadyRead()));
386 q->connect(copyDevice, SIGNAL(readChannelFinished()), SLOT(_q_copyReadChannelFinished()));
387
388 // start the copy:
389 _q_copyReadyRead();
390}
391
392void QNetworkReplyImplPrivate::finished()
393{
394 Q_Q(QNetworkReplyImpl);
395 Q_ASSERT_X(state != Finished, "QNetworkReplyImpl",
396 "Backend called finished/finishedWithError more than once");
397
398 state = Finished;
399 pendingNotifications.clear();
400
401 QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader);
402 if (bytesDownloaded != lastBytesDownloaded || totalSize.isNull())
403 emit q->downloadProgress(bytesDownloaded, bytesDownloaded);
404 if (bytesUploaded == -1 && outgoingData)
405 emit q->uploadProgress(0, 0);
406
407 completeCacheSave();
408
409 // note: might not be a good idea, since users could decide to delete us
410 // which would delete the backend too...
411 // maybe we should protect the backend
412 emit q->readChannelFinished();
413 emit q->finished();
414}
415
416void QNetworkReplyImplPrivate::error(QNetworkReplyImpl::NetworkError code, const QString &errorMessage)
417{
418 Q_Q(QNetworkReplyImpl);
419
420 errorCode = code;
421 q->setErrorString(errorMessage);
422
423 // note: might not be a good idea, since users could decide to delete us
424 // which would delete the backend too...
425 // maybe we should protect the backend
426 emit q->error(code);
427}
428
429void QNetworkReplyImplPrivate::metaDataChanged()
430{
431 Q_Q(QNetworkReplyImpl);
432 // do we have cookies?
433 if (cookedHeaders.contains(QNetworkRequest::SetCookieHeader) && !manager.isNull()) {
434 QList<QNetworkCookie> cookies =
435 qvariant_cast<QList<QNetworkCookie> >(cookedHeaders.value(QNetworkRequest::SetCookieHeader));
436 QNetworkCookieJar *jar = manager->cookieJar();
437 if (jar)
438 jar->setCookiesFromUrl(cookies, url);
439 }
440 emit q->metaDataChanged();
441}
442
443void QNetworkReplyImplPrivate::redirectionRequested(const QUrl &target)
444{
445 attributes.insert(QNetworkRequest::RedirectionTargetAttribute, target);
446}
447
448void QNetworkReplyImplPrivate::sslErrors(const QList<QSslError> &errors)
449{
450#ifndef QT_NO_OPENSSL
451 Q_Q(QNetworkReplyImpl);
452 emit q->sslErrors(errors);
453#else
454 Q_UNUSED(errors);
455#endif
456}
457
458QNetworkReplyImpl::QNetworkReplyImpl(QObject *parent)
459 : QNetworkReply(*new QNetworkReplyImplPrivate, parent)
460{
461}
462
463QNetworkReplyImpl::~QNetworkReplyImpl()
464{
465 Q_D(QNetworkReplyImpl);
466 if (d->isCachingEnabled())
467 d->networkCache->remove(url());
468}
469
470void QNetworkReplyImpl::abort()
471{
472 Q_D(QNetworkReplyImpl);
473 if (d->state == QNetworkReplyImplPrivate::Aborted)
474 return;
475
476 // stop both upload and download
477 if (d->backend) {
478 d->backend->deleteLater();
479 d->backend = 0;
480 }
481 if (d->outgoingData)
482 disconnect(d->outgoingData, 0, this, 0);
483 if (d->copyDevice)
484 disconnect(d->copyDevice, 0, this, 0);
485
486 QNetworkReply::close();
487
488 if (d->state != QNetworkReplyImplPrivate::Finished) {
489 // emit signals
490 d->error(OperationCanceledError, tr("Operation canceled"));
491 d->finished();
492 }
493 d->state = QNetworkReplyImplPrivate::Aborted;
494}
495
496void QNetworkReplyImpl::close()
497{
498 Q_D(QNetworkReplyImpl);
499 if (d->state == QNetworkReplyImplPrivate::Aborted ||
500 d->state == QNetworkReplyImplPrivate::Finished)
501 return;
502
503 // stop the download
504 if (d->backend)
505 d->backend->closeDownstreamChannel();
506 if (d->copyDevice)
507 disconnect(d->copyDevice, 0, this, 0);
508
509 QNetworkReply::close();
510
511 // emit signals
512 d->error(OperationCanceledError, tr("Operation canceled"));
513 d->finished();
514}
515
516/*!
517 Returns the number of bytes available for reading with
518 QIODevice::read(). The number of bytes available may grow until
519 the finished() signal is emitted.
520*/
521qint64 QNetworkReplyImpl::bytesAvailable() const
522{
523 return QNetworkReply::bytesAvailable() + d_func()->readBuffer.size();
524}
525
526void QNetworkReplyImpl::setReadBufferSize(qint64 size)
527{
528 Q_D(QNetworkReplyImpl);
529 if (size > d->readBufferMaxSize &&
530 size == d->readBuffer.size())
531 d->backendNotify(QNetworkReplyImplPrivate::NotifyDownstreamReadyWrite);
532
533 QNetworkReply::setReadBufferSize(size);
534}
535
536#ifndef QT_NO_OPENSSL
537QSslConfiguration QNetworkReplyImpl::sslConfigurationImplementation() const
538{
539 Q_D(const QNetworkReplyImpl);
540 QSslConfiguration config;
541 if (d->backend)
542 d->backend->fetchSslConfiguration(config);
543 return config;
544}
545
546void QNetworkReplyImpl::setSslConfigurationImplementation(const QSslConfiguration &config)
547{
548 Q_D(QNetworkReplyImpl);
549 if (d->backend && !config.isNull())
550 d->backend->setSslConfiguration(config);
551}
552
553void QNetworkReplyImpl::ignoreSslErrors()
554{
555 Q_D(QNetworkReplyImpl);
556 if (d->backend)
557 d->backend->ignoreSslErrors();
558}
559
560#endif // QT_NO_OPENSSL
561
562/*!
563 \internal
564*/
565qint64 QNetworkReplyImpl::readData(char *data, qint64 maxlen)
566{
567 Q_D(QNetworkReplyImpl);
568 if (d->readBuffer.isEmpty())
569 return d->state == QNetworkReplyImplPrivate::Finished ? -1 : 0;
570
571 d->backendNotify(QNetworkReplyImplPrivate::NotifyDownstreamReadyWrite);
572 if (maxlen == 1) {
573 // optimization for getChar()
574 *data = d->readBuffer.getChar();
575 return 1;
576 }
577
578 maxlen = qMin<qint64>(maxlen, d->readBuffer.size());
579 return d->readBuffer.read(data, maxlen);
580}
581
582/*!
583 \internal Reimplemented for internal purposes
584*/
585bool QNetworkReplyImpl::event(QEvent *e)
586{
587 if (e->type() == QEvent::NetworkReplyUpdated) {
588 d_func()->handleNotifications();
589 return true;
590 }
591
592 return QObject::event(e);
593}
594
595QT_END_NAMESPACE
596
597#include "moc_qnetworkreplyimpl_p.cpp"
598
Note: See TracBrowser for help on using the repository browser.