diff -r b72c6db6890b -r 5dc02b23752f src/network/access/qnetworkreplyimpl.cpp --- a/src/network/access/qnetworkreplyimpl.cpp Wed Jun 23 19:07:03 2010 +0300 +++ b/src/network/access/qnetworkreplyimpl.cpp Tue Jul 06 15:10:48 2010 +0300 @@ -46,7 +46,9 @@ #include "QtCore/qcoreapplication.h" #include "QtCore/qdatetime.h" #include "QtNetwork/qsslconfiguration.h" +#include "QtNetwork/qnetworksession.h" #include "qnetworkaccesshttpbackend_p.h" +#include "qnetworkaccessmanager_p.h" #include @@ -57,7 +59,7 @@ copyDevice(0), cacheEnabled(false), cacheSaveDevice(0), notificationHandlingPaused(false), - bytesDownloaded(0), lastBytesDownloaded(-1), bytesUploaded(-1), + bytesDownloaded(0), lastBytesDownloaded(-1), bytesUploaded(-1), preMigrationDownloaded(-1), httpStatusCode(0), state(Idle) { @@ -82,7 +84,31 @@ return; } - backend->open(); +#ifndef QT_NO_BEARERMANAGEMENT + if (!backend->start()) { + // backend failed to start because the session state is not Connected. + // QNetworkAccessManager will call reply->backend->start() again for us when the session + // state changes. + state = WaitingForSession; + + QNetworkSession *session = manager->d_func()->networkSession; + + if (session) { + Q_Q(QNetworkReplyImpl); + + QObject::connect(session, SIGNAL(error(QNetworkSession::SessionError)), + q, SLOT(_q_networkSessionFailed())); + + if (!session->isOpen()) + session->open(); + } else { + qWarning("Backend is waiting for QNetworkSession to connect, but there is none!"); + } + + return; + } +#endif + if (state != Finished) { if (operation == QNetworkAccessManager::GetOperation) pendingNotifications.append(NotifyDownstreamReadyWrite); @@ -134,6 +160,8 @@ lastBytesDownloaded = bytesDownloaded; QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader); + if (preMigrationDownloaded != Q_INT64_C(-1)) + totalSize = totalSize.toLongLong() + preMigrationDownloaded; pauseNotificationHandling(); emit q->downloadProgress(bytesDownloaded, totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong()); @@ -206,6 +234,49 @@ } } +#ifndef QT_NO_BEARERMANAGEMENT +void QNetworkReplyImplPrivate::_q_networkSessionConnected() +{ + Q_Q(QNetworkReplyImpl); + + if (manager.isNull()) + return; + + QNetworkSession *session = manager->d_func()->networkSession; + if (!session) + return; + + if (session->state() != QNetworkSession::Connected) + return; + + switch (state) { + case QNetworkReplyImplPrivate::Buffering: + case QNetworkReplyImplPrivate::Working: + case QNetworkReplyImplPrivate::Reconnecting: + // Migrate existing downloads to new network connection. + migrateBackend(); + break; + case QNetworkReplyImplPrivate::WaitingForSession: + // Start waiting requests. + QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection); + break; + default: + ; + } +} + +void QNetworkReplyImplPrivate::_q_networkSessionFailed() +{ + // Abort waiting replies. + if (state == WaitingForSession) { + state = Working; + error(QNetworkReplyImpl::UnknownNetworkError, + QCoreApplication::translate("QNetworkReply", "Network session error.")); + finished(); + } +} +#endif + void QNetworkReplyImplPrivate::setup(QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *data) { @@ -251,11 +322,15 @@ // for HTTP, we want to send out the request as fast as possible to the network, without // invoking methods in a QueuedConnection +#ifndef QT_NO_HTTP if (qobject_cast(backend)) { _q_startOperation(); } else { QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection); } +#else + QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection); +#endif // QT_NO_HTTP } q->QIODevice::open(QIODevice::ReadOnly); @@ -471,6 +546,8 @@ QPointer qq = q; QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader); + if (preMigrationDownloaded != Q_INT64_C(-1)) + totalSize = totalSize.toLongLong() + preMigrationDownloaded; pauseNotificationHandling(); emit q->downloadProgress(bytesDownloaded, totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong()); @@ -521,14 +598,44 @@ void QNetworkReplyImplPrivate::finished() { Q_Q(QNetworkReplyImpl); - if (state == Finished || state == Aborted) + + if (state == Finished || state == Aborted || state == WaitingForSession) return; + pauseNotificationHandling(); + QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader); + if (preMigrationDownloaded != Q_INT64_C(-1)) + totalSize = totalSize.toLongLong() + preMigrationDownloaded; + + if (!manager.isNull()) { +#ifndef QT_NO_BEARERMANAGEMENT + QNetworkSession *session = manager->d_func()->networkSession; + if (session && session->state() == QNetworkSession::Roaming && + state == Working && errorCode != QNetworkReply::OperationCanceledError) { + // only content with a known size will fail with a temporary network failure error + if (!totalSize.isNull()) { + if (bytesDownloaded != totalSize) { + if (migrateBackend()) { + // either we are migrating or the request is finished/aborted + if (state == Reconnecting || state == WaitingForSession) { + resumeNotificationHandling(); + return; // exit early if we are migrating. + } + } else { + error(QNetworkReply::TemporaryNetworkFailureError, + QNetworkReply::tr("Temporary network failure.")); + } + } + } + } +#endif + } + resumeNotificationHandling(); + state = Finished; pendingNotifications.clear(); pauseNotificationHandling(); - QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader); if (totalSize.isNull() || totalSize == -1) { emit q->downloadProgress(bytesDownloaded, bytesDownloaded); } @@ -537,7 +644,9 @@ emit q->uploadProgress(0, 0); resumeNotificationHandling(); - completeCacheSave(); + // if we don't know the total size of or we received everything save the cache + if (totalSize.isNull() || totalSize == -1 || bytesDownloaded == totalSize) + completeCacheSave(); // note: might not be a good idea, since users could decide to delete us // which would delete the backend too... @@ -564,8 +673,12 @@ void QNetworkReplyImplPrivate::metaDataChanged() { Q_Q(QNetworkReplyImpl); - // do we have cookies? - if (cookedHeaders.contains(QNetworkRequest::SetCookieHeader) && !manager.isNull()) { + // 1. do we have cookies? + // 2. are we allowed to set them? + if (cookedHeaders.contains(QNetworkRequest::SetCookieHeader) && !manager.isNull() + && (static_cast + (request.attribute(QNetworkRequest::CookieSaveControlAttribute, + QNetworkRequest::Automatic).toInt()) == QNetworkRequest::Automatic)) { QList cookies = qvariant_cast >(cookedHeaders.value(QNetworkRequest::SetCookieHeader)); QNetworkCookieJar *jar = manager->cookieJar(); @@ -662,6 +775,13 @@ d->finished(); } +bool QNetworkReplyImpl::canReadLine () const +{ + Q_D(const QNetworkReplyImpl); + return QNetworkReply::canReadLine() || d->readBuffer.canReadLine(); +} + + /*! Returns the number of bytes available for reading with QIODevice::read(). The number of bytes available may grow until @@ -750,6 +870,89 @@ return QObject::event(e); } +/* + Migrates the backend of the QNetworkReply to a new network connection if required. Returns + true if the reply is migrated or it is not required; otherwise returns false. +*/ +bool QNetworkReplyImplPrivate::migrateBackend() +{ + Q_Q(QNetworkReplyImpl); + + // Network reply is already finished or aborted, don't need to migrate. + if (state == Finished || state == Aborted) + return true; + + // Backend does not support resuming download. + if (!backend->canResume()) + return false; + + // Request has outgoing data, not migrating. + if (outgoingData) + return false; + + // Request is serviced from the cache, don't need to migrate. + if (copyDevice) + return true; + + state = QNetworkReplyImplPrivate::Reconnecting; + + if (backend) { + delete backend; + backend = 0; + } + + cookedHeaders.clear(); + rawHeaders.clear(); + + preMigrationDownloaded = bytesDownloaded; + + backend = manager->d_func()->findBackend(operation, request); + + if (backend) { + backend->setParent(q); + backend->reply = this; + backend->setResumeOffset(bytesDownloaded); + } + +#ifndef QT_NO_HTTP + if (qobject_cast(backend)) { + _q_startOperation(); + } else { + QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection); + } +#else + QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection); +#endif // QT_NO_HTTP + + return true; +} + +#ifndef QT_NO_BEARERMANAGEMENT +QDisabledNetworkReply::QDisabledNetworkReply(QObject *parent, + const QNetworkRequest &req, + QNetworkAccessManager::Operation op) +: QNetworkReply(parent) +{ + setRequest(req); + setUrl(req.url()); + setOperation(op); + + qRegisterMetaType("QNetworkReply::NetworkError"); + + QString msg = QCoreApplication::translate("QNetworkAccessManager", + "Network access is disabled."); + setError(UnknownNetworkError, msg); + + QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, + Q_ARG(QNetworkReply::NetworkError, UnknownNetworkError)); + QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection); +} + +QDisabledNetworkReply::~QDisabledNetworkReply() +{ +} +#endif + QT_END_NAMESPACE #include "moc_qnetworkreplyimpl_p.cpp"