src/network/access/qnetworkreplyimpl.cpp
changeset 30 5dc02b23752f
parent 23 89e065397ea6
--- 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 <QtCore/QCoreApplication>
 
@@ -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<QNetworkAccessHttpBackend *>(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<QNetworkReplyImpl> 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<QNetworkRequest::LoadControl>
+            (request.attribute(QNetworkRequest::CookieSaveControlAttribute,
+                               QNetworkRequest::Automatic).toInt()) == QNetworkRequest::Automatic)) {
         QList<QNetworkCookie> cookies =
             qvariant_cast<QList<QNetworkCookie> >(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<QNetworkAccessHttpBackend *>(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>("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"