QDataStream: handle incomplete reads from QIODevice
This adds a way to resume reading from a stream after a ReadPastEnd error. This is done by introducing a stream read transaction mechanism that keeps read data in an internal buffer and rolls it back on failure. [ChangeLog][QtCore] Added QDataStream startTransaction(), commitTransaction(), rollbackTransaction(), abortTransaction() functions to support read transactions. Task-number: QTBUG-44418 Change-Id: Ibf946e1939a5573c4182fea7e26608947218c2d9 Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@theqtcompany.com>
This commit is contained in:
parent
ca6f11dcf2
commit
184d66caa5
@ -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;
|
||||
|
@ -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
|
||||
|
||||
|
@ -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}
|
||||
*/
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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]
|
||||
|
||||
}
|
||||
|
@ -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,10 +589,175 @@ 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)
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user