summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--examples/network/blockingfortuneclient/fortunethread.cpp28
-rw-r--r--examples/network/doc/src/blockingfortuneclient.qdoc33
-rw-r--r--examples/network/doc/src/fortuneclient.qdoc45
-rw-r--r--examples/network/doc/src/fortuneserver.qdoc13
-rw-r--r--examples/network/fortuneclient/client.cpp34
-rw-r--r--examples/network/fortuneclient/client.h3
-rw-r--r--examples/network/fortuneserver/server.cpp7
-rw-r--r--examples/network/threadedfortuneserver/fortunethread.cpp3
-rw-r--r--src/corelib/doc/snippets/code/src_corelib_io_qdatastream.cpp10
-rw-r--r--src/corelib/io/qdatastream.cpp213
-rw-r--r--src/corelib/io/qdatastream.h7
-rw-r--r--src/corelib/io/qdatastream_p.h4
-rw-r--r--tests/auto/corelib/io/qdatastream/tst_qdatastream.cpp164
13 files changed, 435 insertions, 129 deletions
diff --git a/examples/network/blockingfortuneclient/fortunethread.cpp b/examples/network/blockingfortuneclient/fortunethread.cpp
index b948d504ae..a668e45d93 100644
--- a/examples/network/blockingfortuneclient/fortunethread.cpp
+++ b/examples/network/blockingfortuneclient/fortunethread.cpp
@@ -96,37 +96,27 @@ void FortuneThread::run()
emit error(socket.error(), socket.errorString());
return;
}
-//! [8] //! [9]
+//! [8] //! [11]
- while (socket.bytesAvailable() < (int)sizeof(quint16)) {
- if (!socket.waitForReadyRead(Timeout)) {
- emit error(socket.error(), socket.errorString());
- return;
- }
-//! [9] //! [10]
- }
-//! [10] //! [11]
-
- quint16 blockSize;
QDataStream in(&socket);
in.setVersion(QDataStream::Qt_4_0);
- in >> blockSize;
+ QString fortune;
//! [11] //! [12]
- while (socket.bytesAvailable() < blockSize) {
+ do {
if (!socket.waitForReadyRead(Timeout)) {
emit error(socket.error(), socket.errorString());
return;
}
-//! [12] //! [13]
- }
-//! [13] //! [14]
+
+ in.startTransaction();
+ in >> fortune;
+ } while (!in.commitTransaction());
+//! [12] //! [15]
mutex.lock();
- QString fortune;
- in >> fortune;
emit newFortune(fortune);
-//! [7] //! [14] //! [15]
+//! [7]
cond.wait(&mutex);
serverName = hostName;
diff --git a/examples/network/doc/src/blockingfortuneclient.qdoc b/examples/network/doc/src/blockingfortuneclient.qdoc
index fbbfd466a5..12062fbe33 100644
--- a/examples/network/doc/src/blockingfortuneclient.qdoc
+++ b/examples/network/doc/src/blockingfortuneclient.qdoc
@@ -125,39 +125,22 @@
other \c waitFor...() functions, is part of QTcpSocket's \e{blocking
API}.
- After this statement, we have a connected socket to work with. Now it's
- time to see what the fortune server has sent us.
-
- \snippet blockingfortuneclient/fortunethread.cpp 9
- \snippet blockingfortuneclient/fortunethread.cpp 10
-
- This step is to read the size of the packet. Although we are only reading
- two bytes here, and the \c while loop may seem to overdo it, we present this
- code to demonstrate a good pattern for waiting for data using
- QTcpSocket::waitForReadyRead(). It goes like this: For as long as we still
- need more data, we call waitForReadyRead(). If it returns false,
- we abort the operation. After this statement, we know that we have received
- enough data.
+ After this statement, we have a connected socket to work with.
\snippet blockingfortuneclient/fortunethread.cpp 11
Now we can create a QDataStream object, passing the socket to
QDataStream's constructor, and as in the other client examples we set
- the stream protocol version to QDataStream::Qt_4_0, and read the size
- of the packet.
+ the stream protocol version to QDataStream::Qt_4_0.
\snippet blockingfortuneclient/fortunethread.cpp 12
- \snippet blockingfortuneclient/fortunethread.cpp 13
-
- Again, we'll use a loop that waits for more data by calling
- QTcpSocket::waitForReadyRead(). In this loop, we're waiting until
- QTcpSocket::bytesAvailable() returns the full packet size.
-
- \snippet blockingfortuneclient/fortunethread.cpp 14
- Now that we have all the data that we need, we can use QDataStream to
- read the fortune string from the packet. The resulting fortune is
- delivered by emitting newFortune().
+ We proceed by initiating a loop that waits for the fortune string data by
+ calling QTcpSocket::waitForReadyRead(). If it returns false, we abort the
+ operation. After this statement, we start a stream read transaction. We
+ exit the loop when QDataStream::commitTransaction() returns true, which
+ means successful fortune string loading. The resulting fortune is
+ delivered by emitting newFortune():
\snippet blockingfortuneclient/fortunethread.cpp 15
diff --git a/examples/network/doc/src/fortuneclient.qdoc b/examples/network/doc/src/fortuneclient.qdoc
index d9ebb575df..4d726fc5fa 100644
--- a/examples/network/doc/src/fortuneclient.qdoc
+++ b/examples/network/doc/src/fortuneclient.qdoc
@@ -41,8 +41,7 @@
request a line of text from a fortune server (from the
\l{fortuneserver}{Fortune Server} example). The client requests a
fortune by simply connecting to the server. The server then responds with
- a 16-bit (quint16) integer containing the length of the fortune text,
- followed by a QString.
+ a QString which contains the fortune text.
QTcpSocket supports two general approaches to network programming:
@@ -71,8 +70,8 @@
\snippet fortuneclient/client.h 0
Other than the widgets that make up the GUI, the data members include a
- QTcpSocket pointer, a copy of the fortune text currently displayed, and
- the size of the packet we are currently reading (more on this later).
+ QTcpSocket pointer, a QDataStream object that operates on the socket, and
+ a copy of the fortune text currently displayed.
The socket is initialized in the Client constructor. We'll pass the main
widget as parent, so that we won't have to worry about deleting the
@@ -82,6 +81,12 @@
\dots
\snippet fortuneclient/client.cpp 1
+ The protocol is based on QDataStream, so we set the stream device to the
+ newly created socket. We then explicitly set the protocol version of the
+ stream to QDataStream::Qt_4_0 to ensure that we're using the same version
+ as the fortune server, no matter which version of Qt the client and
+ server use.
+
The only QTcpSocket signals we need in this example are
QTcpSocket::readyRead(), signifying that data has been received, and
QTcpSocket::error(), which we will use to catch any connection errors:
@@ -96,8 +101,7 @@
\snippet fortuneclient/client.cpp 6
- In this slot, we initialize \c blockSize to 0, preparing to read a new block
- of data. Because we allow the user to click \uicontrol{Get Fortune} before the
+ Because we allow the user to click \uicontrol{Get Fortune} before the
previous connection finished closing, we start off by aborting the
previous connection by calling QTcpSocket::abort(). (On an unconnected
socket, this function does nothing.) We then proceed to connecting to the
@@ -131,31 +135,26 @@
signal is connected to \c Client::readFortune():
\snippet fortuneclient/client.cpp 8
- \codeline
- \snippet fortuneclient/client.cpp 10
-
- The protocol is based on QDataStream, so we start by creating a stream
- object, passing the socket to QDataStream's constructor. We then
- explicitly set the protocol version of the stream to QDataStream::Qt_4_0
- to ensure that we're using the same version as the fortune server, no
- matter which version of Qt the client and server use.
Now, TCP is based on sending a stream of data, so we cannot expect to get
the entire fortune in one go. Especially on a slow network, the data can
be received in several small fragments. QTcpSocket buffers up all incoming
data and emits \l{QTcpSocket::readyRead()}{readyRead()} for every new
block that arrives, and it is our job to ensure that we have received all
- the data we need before we start parsing. The server's response starts
- with the size of the packet, so first we need to ensure that we can read
- the size, then we will wait until QTcpSocket has received the full packet.
-
- \snippet fortuneclient/client.cpp 11
- \codeline
- \snippet fortuneclient/client.cpp 12
+ the data we need before we start parsing.
+ For this purpose we use a QDataStream read transaction. It keeps reading
+ stream data into an internal buffer and rolls it back in case of an
+ incomplete read. We start by calling startTransaction() which also resets
+ the stream status to indicate that new data was received on the socket.
We proceed by using QDataStream's streaming operator to read the fortune
- from the socket into a QString. Once read, we can call QLabel::setText()
- to display the fortune.
+ from the socket into a QString. Once read, we complete the transaction by
+ calling QDataStream::commitTransaction(). If we did not receive a full
+ packet, this function restores the stream data to the initial position,
+ after which we can wait for a new readyRead() signal.
+
+ After a successful read transaction, we call QLabel::setText() to display
+ the fortune.
\sa {Fortune Server Example}, {Blocking Fortune Client Example}
*/
diff --git a/examples/network/doc/src/fortuneserver.qdoc b/examples/network/doc/src/fortuneserver.qdoc
index 0b110783aa..5419882a62 100644
--- a/examples/network/doc/src/fortuneserver.qdoc
+++ b/examples/network/doc/src/fortuneserver.qdoc
@@ -73,17 +73,8 @@
using QTcpSocket. First we create a QByteArray and a QDataStream object,
passing the bytearray to QDataStream's constructor. We then explicitly set
the protocol version of QDataStream to QDataStream::Qt_4_0 to ensure that
- we can communicate with clients from future versions of Qt. (See
- QDataStream::setVersion().)
-
- \snippet fortuneserver/server.cpp 6
-
- At the start of our QByteArray, we reserve space for a 16 bit integer that
- will contain the total size of the data block we are sending. We continue
- by streaming in a random fortune. Then we seek back to the beginning of
- the QByteArray, and overwrite the reserved 16 bit integer value with the
- total size of the array. By doing this, we provide a way for clients to
- verify how much data they can expect before reading the whole packet.
+ we can communicate with clients from future versions of Qt (see
+ QDataStream::setVersion()). We continue by streaming in a random fortune.
\snippet fortuneserver/server.cpp 7
diff --git a/examples/network/fortuneclient/client.cpp b/examples/network/fortuneclient/client.cpp
index 42fed30445..a71b90dda8 100644
--- a/examples/network/fortuneclient/client.cpp
+++ b/examples/network/fortuneclient/client.cpp
@@ -49,10 +49,7 @@ Client::Client(QWidget *parent)
, hostCombo(new QComboBox)
, portLineEdit(new QLineEdit)
, getFortuneButton(new QPushButton(tr("Get Fortune")))
-//! [1]
, tcpSocket(new QTcpSocket(this))
-//! [1]
- , blockSize(0)
, networkSession(Q_NULLPTR)
{
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
@@ -100,6 +97,11 @@ Client::Client(QWidget *parent)
buttonBox->addButton(getFortuneButton, QDialogButtonBox::ActionRole);
buttonBox->addButton(quitButton, QDialogButtonBox::RejectRole);
+//! [1]
+ in.setDevice(tcpSocket);
+ in.setVersion(QDataStream::Qt_4_0);
+//! [1]
+
connect(hostCombo, &QComboBox::editTextChanged,
this, &Client::enableGetFortuneButton);
connect(portLineEdit, &QLineEdit::textChanged,
@@ -171,7 +173,6 @@ Client::Client(QWidget *parent)
void Client::requestNewFortune()
{
getFortuneButton->setEnabled(false);
- blockSize = 0;
tcpSocket->abort();
//! [7]
tcpSocket->connectToHost(hostCombo->currentText(),
@@ -183,39 +184,24 @@ void Client::requestNewFortune()
//! [8]
void Client::readFortune()
{
-//! [9]
- QDataStream in(tcpSocket);
- in.setVersion(QDataStream::Qt_4_0);
-
- if (blockSize == 0) {
- if (tcpSocket->bytesAvailable() < (int)sizeof(quint16))
- return;
-//! [8]
-
-//! [10]
- in >> blockSize;
- }
-
- if (tcpSocket->bytesAvailable() < blockSize)
- return;
-//! [10] //! [11]
+ in.startTransaction();
QString nextFortune;
in >> nextFortune;
+ if (!in.commitTransaction())
+ return;
+
if (nextFortune == currentFortune) {
QTimer::singleShot(0, this, &Client::requestNewFortune);
return;
}
-//! [11]
-//! [12]
currentFortune = nextFortune;
-//! [9]
statusLabel->setText(currentFortune);
getFortuneButton->setEnabled(true);
}
-//! [12]
+//! [8]
//! [13]
void Client::displayError(QAbstractSocket::SocketError socketError)
diff --git a/examples/network/fortuneclient/client.h b/examples/network/fortuneclient/client.h
index 9b7d6f4dbf..3b07275ded 100644
--- a/examples/network/fortuneclient/client.h
+++ b/examples/network/fortuneclient/client.h
@@ -43,6 +43,7 @@
#include <QDialog>
#include <QTcpSocket>
+#include <QDataStream>
QT_BEGIN_NAMESPACE
class QComboBox;
@@ -75,8 +76,8 @@ private:
QPushButton *getFortuneButton;
QTcpSocket *tcpSocket;
+ QDataStream in;
QString currentFortune;
- quint16 blockSize;
QNetworkSession *networkSession;
};
diff --git a/examples/network/fortuneserver/server.cpp b/examples/network/fortuneserver/server.cpp
index 089f594cab..f318d58a6d 100644
--- a/examples/network/fortuneserver/server.cpp
+++ b/examples/network/fortuneserver/server.cpp
@@ -174,12 +174,9 @@ void Server::sendFortune()
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_0);
-//! [4] //! [6]
- out << (quint16)0;
+
out << fortunes.at(qrand() % fortunes.size());
- out.device()->seek(0);
- out << (quint16)(block.size() - sizeof(quint16));
-//! [6] //! [7]
+//! [4] //! [7]
QTcpSocket *clientConnection = tcpServer->nextPendingConnection();
connect(clientConnection, &QAbstractSocket::disconnected,
diff --git a/examples/network/threadedfortuneserver/fortunethread.cpp b/examples/network/threadedfortuneserver/fortunethread.cpp
index 5e70dd08b9..ec5bb5a168 100644
--- a/examples/network/threadedfortuneserver/fortunethread.cpp
+++ b/examples/network/threadedfortuneserver/fortunethread.cpp
@@ -63,10 +63,7 @@ void FortuneThread::run()
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_0);
- out << (quint16)0;
out << text;
- out.device()->seek(0);
- out << (quint16)(block.size() - sizeof(quint16));
//! [3] //! [4]
tcpSocket.write(block);
diff --git a/src/corelib/doc/snippets/code/src_corelib_io_qdatastream.cpp b/src/corelib/doc/snippets/code/src_corelib_io_qdatastream.cpp
index cc874a1733..b1f4bb6df1 100644
--- a/src/corelib/doc/snippets/code/src_corelib_io_qdatastream.cpp
+++ b/src/corelib/doc/snippets/code/src_corelib_io_qdatastream.cpp
@@ -118,4 +118,14 @@ QDataStream out(file);
out.setVersion(QDataStream::Qt_4_0);
//! [5]
+//! [6]
+in.startTransaction();
+QString str;
+qint32 a;
+in >> str >> a; // try to read packet atomically
+
+if (!in.commitTransaction())
+ return; // wait for more data
+//! [6]
+
}
diff --git a/src/corelib/io/qdatastream.cpp b/src/corelib/io/qdatastream.cpp
index c32df4c8a6..528f99df7a 100644
--- a/src/corelib/io/qdatastream.cpp
+++ b/src/corelib/io/qdatastream.cpp
@@ -180,6 +180,20 @@ QT_BEGIN_NAMESPACE
defined, check the \b {Related Non-Members} section of the
class's documentation page.
+ \section1 Using Read Transactions
+
+ When a data stream operates on an asynchronous device, the chunks of data
+ can arrive at arbitrary points in time. The QDataStream class implements
+ a transaction mechanism that provides the ability to read the data
+ atomically with a series of stream operators. As an example, you can
+ handle incomplete reads from a socket by using a transaction in a slot
+ connected to the readyRead() signal:
+
+ \snippet code/src_corelib_io_qdatastream.cpp 6
+
+ If no full packet is received, this code restores the stream to the
+ initial position, after which you need to wait for more data to arrive.
+
\sa QTextStream, QVariant
*/
@@ -223,6 +237,8 @@ QT_BEGIN_NAMESPACE
QDataStream member functions
*****************************************************************************/
+#define Q_VOID
+
#undef CHECK_STREAM_PRECOND
#ifndef QT_NO_DEBUG
#define CHECK_STREAM_PRECOND(retVal) \
@@ -242,6 +258,12 @@ QT_BEGIN_NAMESPACE
if (q_status != Ok) \
return retVal;
+#define CHECK_STREAM_TRANSACTION_PRECOND(retVal) \
+ if (!d || d->transactionDepth == 0) { \
+ qWarning("QDataStream: No transaction in progress"); \
+ return retVal; \
+ }
+
/*!
Constructs a data stream that has no I/O device.
@@ -567,11 +589,176 @@ void QDataStream::setByteOrder(ByteOrder bo)
\sa version(), Version
*/
+/*!
+ \since 5.7
+
+ Starts a new read transaction on the stream.
+
+ Defines a restorable point within the sequence of read operations. For
+ sequential devices, read data will be duplicated internally to allow
+ recovery in case of incomplete reads. For random-access devices,
+ this function saves the current position of the stream. Call
+ commitTransaction(), rollbackTransaction(), or abortTransaction() to
+ finish the current transaction.
+
+ Once a transaction is started, subsequent calls to this function will make
+ the transaction recursive. Inner transactions act as agents of the
+ outermost transaction (i.e., report the status of read operations to the
+ outermost transaction, which can restore the position of the stream).
+
+ \note Restoring to the point of the nested startTransaction() call is not
+ supported.
+
+ When an error occurs during a transaction (including an inner transaction
+ failing), reading from the data stream is suspended (all subsequent read
+ operations return empty/zero values) and subsequent inner transactions are
+ forced to fail. Starting a new outermost transaction recovers from this
+ state. This behavior makes it unnecessary to error-check every read
+ operation separately.
+
+ \sa commitTransaction(), rollbackTransaction(), abortTransaction()
+*/
+
+void QDataStream::startTransaction()
+{
+ CHECK_STREAM_PRECOND(Q_VOID)
+
+ if (d == 0)
+ d.reset(new QDataStreamPrivate());
+
+ if (++d->transactionDepth == 1) {
+ dev->startTransaction();
+ resetStatus();
+ }
+}
+
+/*!
+ \since 5.7
+
+ Completes a read transaction. Returns \c true if no read errors have
+ occurred during the transaction; otherwise returns \c false.
+
+ If called on an inner transaction, committing will be postponed until
+ the outermost commitTransaction(), rollbackTransaction(), or
+ abortTransaction() call occurs.
+
+ Otherwise, if the stream status indicates reading past the end of the
+ data, this function restores the stream data to the point of the
+ startTransaction() call. When this situation occurs, you need to wait for
+ more data to arrive, after which you start a new transaction. If the data
+ stream has read corrupt data or any of the inner transactions was aborted,
+ this function aborts the transaction.
+
+ \sa startTransaction(), rollbackTransaction(), abortTransaction()
+*/
+
+bool QDataStream::commitTransaction()
+{
+ CHECK_STREAM_TRANSACTION_PRECOND(false)
+ if (--d->transactionDepth == 0) {
+ CHECK_STREAM_PRECOND(false)
+
+ if (q_status == ReadPastEnd) {
+ dev->rollbackTransaction();
+ return false;
+ }
+ dev->commitTransaction();
+ }
+ return q_status == Ok;
+}
+
+/*!
+ \since 5.7
+
+ Reverts a read transaction.
+
+ This function is commonly used to rollback the transaction when an
+ incomplete read was detected prior to committing the transaction.
+
+ If called on an inner transaction, reverting is delegated to the outermost
+ transaction, and subsequently started inner transactions are forced to
+ fail.
+
+ For the outermost transaction, restores the stream data to the point of
+ the startTransaction() call. If the data stream has read corrupt data or
+ any of the inner transactions was aborted, this function aborts the
+ transaction.
+
+ If the preceding stream operations were successful, sets the status of the
+ data stream to \value ReadPastEnd.
+
+ \sa startTransaction(), commitTransaction(), abortTransaction()
+*/
+
+void QDataStream::rollbackTransaction()
+{
+ setStatus(ReadPastEnd);
+
+ CHECK_STREAM_TRANSACTION_PRECOND(Q_VOID)
+ if (--d->transactionDepth != 0)
+ return;
+
+ CHECK_STREAM_PRECOND(Q_VOID)
+ if (q_status == ReadPastEnd)
+ dev->rollbackTransaction();
+ else
+ dev->commitTransaction();
+}
+
+/*!
+ \since 5.7
+
+ Aborts a read transaction.
+
+ This function is commonly used to discard the transaction after
+ higher-level protocol errors or loss of stream synchronization.
+
+ If called on an inner transaction, aborting is delegated to the outermost
+ transaction, and subsequently started inner transactions are forced to
+ fail.
+
+ For the outermost transaction, discards the restoration point and any
+ internally duplicated data of the stream. Will not affect the current
+ read position of the stream.
+
+ Sets the status of the data stream to \value ReadCorruptData.
+
+ \sa startTransaction(), commitTransaction(), rollbackTransaction()
+*/
+
+void QDataStream::abortTransaction()
+{
+ q_status = ReadCorruptData;
+
+ CHECK_STREAM_TRANSACTION_PRECOND(Q_VOID)
+ if (--d->transactionDepth != 0)
+ return;
+
+ CHECK_STREAM_PRECOND(Q_VOID)
+ dev->commitTransaction();
+}
+
/*****************************************************************************
QDataStream read functions
*****************************************************************************/
/*!
+ \internal
+*/
+
+int QDataStream::readBlock(char *data, int len)
+{
+ // Disable reads on failure in transacted stream
+ if (q_status != Ok && dev->isTransactionStarted())
+ return -1;
+
+ const int readResult = dev->read(data, len);
+ if (readResult != len)
+ setStatus(ReadPastEnd);
+ return readResult;
+}
+
+/*!
\fn QDataStream &QDataStream::operator>>(quint8 &i)
\overload
@@ -589,9 +776,7 @@ QDataStream &QDataStream::operator>>(qint8 &i)
i = 0;
CHECK_STREAM_PRECOND(*this)
char c;
- if (!dev->getChar(&c))
- setStatus(ReadPastEnd);
- else
+ if (readBlock(&c, 1) == 1)
i = qint8(c);
return *this;
}
@@ -616,9 +801,8 @@ QDataStream &QDataStream::operator>>(qint16 &i)
{
i = 0;
CHECK_STREAM_PRECOND(*this)
- if (dev->read((char *)&i, 2) != 2) {
+ if (readBlock(reinterpret_cast<char *>(&i), 2) != 2) {
i = 0;
- setStatus(ReadPastEnd);
} else {
if (!noswap) {
i = qbswap(i);
@@ -647,9 +831,8 @@ QDataStream &QDataStream::operator>>(qint32 &i)
{
i = 0;
CHECK_STREAM_PRECOND(*this)
- if (dev->read((char *)&i, 4) != 4) {
+ if (readBlock(reinterpret_cast<char *>(&i), 4) != 4) {
i = 0;
- setStatus(ReadPastEnd);
} else {
if (!noswap) {
i = qbswap(i);
@@ -682,9 +865,8 @@ QDataStream &QDataStream::operator>>(qint64 &i)
*this >> i2 >> i1;
i = ((quint64)i1 << 32) + i2;
} else {
- if (dev->read((char *)&i, 8) != 8) {
+ if (readBlock(reinterpret_cast<char *>(&i), 8) != 8) {
i = qint64(0);
- setStatus(ReadPastEnd);
} else {
if (!noswap) {
i = qbswap(i);
@@ -728,9 +910,8 @@ QDataStream &QDataStream::operator>>(float &f)
f = 0.0f;
CHECK_STREAM_PRECOND(*this)
- if (dev->read((char *)&f, 4) != 4) {
+ if (readBlock(reinterpret_cast<char *>(&f), 4) != 4) {
f = 0.0f;
- setStatus(ReadPastEnd);
} else {
if (!noswap) {
union {
@@ -766,9 +947,8 @@ QDataStream &QDataStream::operator>>(double &f)
f = 0.0;
CHECK_STREAM_PRECOND(*this)
- if (dev->read((char *)&f, 8) != 8) {
+ if (readBlock(reinterpret_cast<char *>(&f), 8) != 8) {
f = 0.0;
- setStatus(ReadPastEnd);
} else {
if (!noswap) {
union {
@@ -845,9 +1025,8 @@ QDataStream &QDataStream::readBytes(char *&s, uint &l)
memcpy(curBuf, prevBuf, allocated);
delete [] prevBuf;
}
- if (dev->read(curBuf + allocated, blockSize) != blockSize) {
+ if (readBlock(curBuf + allocated, blockSize) != blockSize) {
delete [] curBuf;
- setStatus(ReadPastEnd);
return *this;
}
allocated += blockSize;
@@ -871,7 +1050,7 @@ QDataStream &QDataStream::readBytes(char *&s, uint &l)
int QDataStream::readRawData(char *s, int len)
{
CHECK_STREAM_PRECOND(-1)
- return dev->read(s, len);
+ return readBlock(s, len);
}
@@ -1154,7 +1333,7 @@ int QDataStream::skipRawData(int len)
while (len > 0) {
int blockSize = qMin(len, (int)sizeof(buf));
- int n = dev->read(buf, blockSize);
+ int n = readBlock(buf, blockSize);
if (n == -1)
return -1;
if (n == 0)
diff --git a/src/corelib/io/qdatastream.h b/src/corelib/io/qdatastream.h
index 744829c659..a72842671a 100644
--- a/src/corelib/io/qdatastream.h
+++ b/src/corelib/io/qdatastream.h
@@ -168,6 +168,11 @@ public:
int skipRawData(int len);
+ void startTransaction();
+ bool commitTransaction();
+ void rollbackTransaction();
+ void abortTransaction();
+
private:
Q_DISABLE_COPY(QDataStream)
@@ -179,6 +184,8 @@ private:
ByteOrder byteorder;
int ver;
Status q_status;
+
+ int readBlock(char *data, int len);
};
diff --git a/src/corelib/io/qdatastream_p.h b/src/corelib/io/qdatastream_p.h
index 82c8fe251f..677a53f90a 100644
--- a/src/corelib/io/qdatastream_p.h
+++ b/src/corelib/io/qdatastream_p.h
@@ -53,9 +53,11 @@ QT_BEGIN_NAMESPACE
class QDataStreamPrivate
{
public:
- QDataStreamPrivate() : floatingPointPrecision(QDataStream::DoublePrecision) { }
+ QDataStreamPrivate() : floatingPointPrecision(QDataStream::DoublePrecision),
+ transactionDepth(0) { }
QDataStream::FloatingPointPrecision floatingPointPrecision;
+ int transactionDepth;
};
#endif
diff --git a/tests/auto/corelib/io/qdatastream/tst_qdatastream.cpp b/tests/auto/corelib/io/qdatastream/tst_qdatastream.cpp
index 729cf6547e..d63a7a213b 100644
--- a/tests/auto/corelib/io/qdatastream/tst_qdatastream.cpp
+++ b/tests/auto/corelib/io/qdatastream/tst_qdatastream.cpp
@@ -186,6 +186,11 @@ private slots:
void floatingPointNaN();
+ void transaction_data();
+ void transaction();
+ void nestedTransactionsResult_data();
+ void nestedTransactionsResult();
+
private:
void writebool(QDataStream *s);
void writeQBitArray(QDataStream *s);
@@ -3190,6 +3195,165 @@ void tst_QDataStream::floatingPointPrecision()
}
+void tst_QDataStream::transaction_data()
+{
+ QTest::addColumn<qint8>("i8Data");
+ QTest::addColumn<qint16>("i16Data");
+ QTest::addColumn<qint32>("i32Data");
+ QTest::addColumn<qint64>("i64Data");
+ QTest::addColumn<bool>("bData");
+ QTest::addColumn<float>("fData");
+ QTest::addColumn<double>("dData");
+ QTest::addColumn<QByteArray>("strData");
+ QTest::addColumn<QByteArray>("rawData");
+
+ QTest::newRow("1") << qint8(1) << qint16(2) << qint32(3) << qint64(4) << true << 5.0f
+ << double(6.0) << QByteArray("Hello world!") << QByteArray("Qt rocks!");
+ QTest::newRow("2") << qint8(1 << 6) << qint16(1 << 14) << qint32(1 << 30) << qint64Data(3) << false << 123.0f
+ << double(234.0) << stringData(5).toUtf8() << stringData(6).toUtf8();
+ QTest::newRow("3") << qint8(-1) << qint16(-2) << qint32(-3) << qint64(-4) << true << -123.0f
+ << double(-234.0) << stringData(3).toUtf8() << stringData(4).toUtf8();
+}
+
+void tst_QDataStream::transaction()
+{
+ QByteArray testBuffer;
+
+ QFETCH(qint8, i8Data);
+ QFETCH(qint16, i16Data);
+ QFETCH(qint32, i32Data);
+ QFETCH(qint64, i64Data);
+ QFETCH(bool, bData);
+ QFETCH(float, fData);
+ QFETCH(double, dData);
+ QFETCH(QByteArray, strData);
+ QFETCH(QByteArray, rawData);
+
+ {
+ QDataStream stream(&testBuffer, QIODevice::WriteOnly);
+
+ stream << i8Data << i16Data << i32Data << i64Data
+ << bData << fData << dData << strData.constData();
+ stream.writeRawData(rawData.constData(), rawData.size());
+ }
+
+ for (int splitPos = 0; splitPos <= testBuffer.size(); ++splitPos) {
+ QByteArray readBuffer(testBuffer.left(splitPos));
+ SequentialBuffer dev(&readBuffer);
+ dev.open(QIODevice::ReadOnly);
+ QDataStream stream(&dev);
+
+ qint8 i8;
+ qint16 i16;
+ qint32 i32;
+ qint64 i64;
+ bool b;
+ float f;
+ double d;
+ char *str;
+ QByteArray raw(rawData.size(), 0);
+
+ forever {
+ stream.startTransaction();
+ stream >> i8 >> i16 >> i32 >> i64 >> b >> f >> d >> str;
+ stream.readRawData(raw.data(), raw.size());
+
+ if (stream.commitTransaction())
+ break;
+
+ QVERIFY(stream.status() == QDataStream::ReadPastEnd);
+ QVERIFY(splitPos == 0 || !stream.atEnd());
+ QVERIFY(readBuffer.size() < testBuffer.size());
+ delete [] str;
+ raw.fill(0);
+ readBuffer.append(testBuffer.right(testBuffer.size() - splitPos));
+ }
+
+ QVERIFY(stream.atEnd());
+ QCOMPARE(i8, i8Data);
+ QCOMPARE(i16, i16Data);
+ QCOMPARE(i32, i32Data);
+ QCOMPARE(i64, i64Data);
+ QCOMPARE(b, bData);
+ QCOMPARE(f, fData);
+ QCOMPARE(d, dData);
+ QVERIFY(strData == str);
+ delete [] str;
+ QCOMPARE(raw, rawData);
+ }
+}
+
+void tst_QDataStream::nestedTransactionsResult_data()
+{
+ QTest::addColumn<bool>("commitFirst");
+ QTest::addColumn<bool>("rollbackFirst");
+ QTest::addColumn<bool>("commitSecond");
+ QTest::addColumn<bool>("rollbackSecond");
+ QTest::addColumn<bool>("successExpected");
+ QTest::addColumn<bool>("expectedAtEnd");
+ QTest::addColumn<int>("expectedStatus");
+
+ QTest::newRow("1") << false << false << false << false
+ << false << true << int(QDataStream::ReadCorruptData);
+ QTest::newRow("2") << false << false << false << true
+ << false << true << int(QDataStream::ReadCorruptData);
+ QTest::newRow("3") << false << false << true << false
+ << false << true << int(QDataStream::ReadCorruptData);
+
+ QTest::newRow("4") << false << true << false << false
+ << false << true << int(QDataStream::ReadCorruptData);
+ QTest::newRow("5") << false << true << false << true
+ << false << false << int(QDataStream::ReadPastEnd);
+ QTest::newRow("6") << false << true << true << false
+ << false << false << int(QDataStream::ReadPastEnd);
+
+ QTest::newRow("7") << true << false << false << false
+ << false << true << int(QDataStream::ReadCorruptData);
+ QTest::newRow("8") << true << false << false << true
+ << false << false << int(QDataStream::ReadPastEnd);
+ QTest::newRow("9") << true << false << true << false
+ << true << true << int(QDataStream::Ok);
+}
+
+void tst_QDataStream::nestedTransactionsResult()
+{
+ QByteArray testBuffer(1, 0);
+ QDataStream stream(&testBuffer, QIODevice::ReadOnly);
+ uchar c;
+
+ QFETCH(bool, commitFirst);
+ QFETCH(bool, rollbackFirst);
+ QFETCH(bool, commitSecond);
+ QFETCH(bool, rollbackSecond);
+ QFETCH(bool, successExpected);
+ QFETCH(bool, expectedAtEnd);
+ QFETCH(int, expectedStatus);
+
+ stream.startTransaction();
+ stream.startTransaction();
+ stream >> c;
+
+ if (commitFirst)
+ QVERIFY(stream.commitTransaction());
+ else if (rollbackFirst)
+ stream.rollbackTransaction();
+ else
+ stream.abortTransaction();
+
+ stream.startTransaction();
+
+ if (commitSecond)
+ QCOMPARE(stream.commitTransaction(), commitFirst);
+ else if (rollbackSecond)
+ stream.rollbackTransaction();
+ else
+ stream.abortTransaction();
+
+ QCOMPARE(stream.commitTransaction(), successExpected);
+ QCOMPARE(stream.atEnd(), expectedAtEnd);
+ QCOMPARE(int(stream.status()), expectedStatus);
+}
+
QTEST_MAIN(tst_QDataStream)
#include "tst_qdatastream.moc"