QIODevice: Add overloads of QIODevice::readLineInto() that takes QSpan
The existing QIODevice::readLineInto takes a QByteArray *, so it's dependent on a QByteArray and therefore needs at least one (initial) allocation. Add QIODevice::readLineInto() that takes a QSpan, allowing the user to provide a buffer to write into and returns a QByteArrayView. QBAV and QSpan are not null terminated. Make null termination optional when reading using QIODevicePrivate::readLine(). Extend the function to accept a ReadLineOption enum parameter with a default value of "NullTerminated" to preserve old calls. Do the same to QRingBufferRef::readLine(). Add QRingBuffer::readLineWithoutTerminatingNull(). [ChangeLog][QtCore][QIODevice] Added overloads of QIODevice::readLineInto() taking QSpan and returnig a QByteArrayView. Task-number: QTBUG-126574 Change-Id: I278a42cef4c9aa64ed884027f79105b5b7d44813 Reviewed-by: Marc Mutz <marc.mutz@qt.io> Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
parent
e23dc7c420
commit
c39c3fe0cb
@ -86,6 +86,14 @@ static void checkWarnMessage(const QIODevice *device, const char *function, cons
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define CHECK_LINEMAXLEN_1(function, returnType) \
|
||||
do { \
|
||||
if (maxSize < 1) { \
|
||||
checkWarnMessage(this, #function, "Called with maxSize < 1"); \
|
||||
return returnType; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define CHECK_MAXBYTEARRAYSIZE(function) \
|
||||
do { \
|
||||
if (maxSize >= QByteArray::maxSize()) { \
|
||||
@ -1335,13 +1343,17 @@ qint64 QIODevice::readLine(char *data, qint64 maxSize)
|
||||
/*!
|
||||
\internal
|
||||
*/
|
||||
qint64 QIODevicePrivate::readLine(char *data, qint64 maxSize)
|
||||
qint64 QIODevicePrivate::readLine(char *data, qint64 maxSize, ReadLineOption option)
|
||||
{
|
||||
Q_Q(QIODevice);
|
||||
Q_ASSERT(maxSize >= 2);
|
||||
const auto appendNullByte = option & ReadLineOption::NullTerminated;
|
||||
|
||||
// Leave room for a '\0'
|
||||
--maxSize;
|
||||
if (appendNullByte) {
|
||||
Q_ASSERT(maxSize >= 2);
|
||||
--maxSize; // Leave room for a '\0'
|
||||
} else {
|
||||
Q_ASSERT(maxSize >= 1);
|
||||
}
|
||||
|
||||
const bool sequential = isSequential();
|
||||
const bool keepDataInBuffer = sequential && transactionStarted;
|
||||
@ -1358,8 +1370,8 @@ qint64 QIODevicePrivate::readLine(char *data, qint64 maxSize)
|
||||
q->readData(data, 0);
|
||||
}
|
||||
} else if (!buffer.isEmpty()) {
|
||||
// QRingBuffer::readLine() terminates the line with '\0'
|
||||
readSoFar = buffer.readLine(data, maxSize + 1);
|
||||
// QRingBuffer::readLine() terminates the line with '\0' if requested
|
||||
readSoFar = buffer.readLine(data, maxSize + (appendNullByte ? 1 : 0), option);
|
||||
if (buffer.isEmpty())
|
||||
q->readData(data, 0);
|
||||
if (!sequential)
|
||||
@ -1380,7 +1392,9 @@ qint64 QIODevicePrivate::readLine(char *data, qint64 maxSize)
|
||||
data[readSoFar - 1] = '\n';
|
||||
}
|
||||
}
|
||||
data[readSoFar] = '\0';
|
||||
if (appendNullByte)
|
||||
data[readSoFar] = '\0';
|
||||
|
||||
return readSoFar;
|
||||
}
|
||||
}
|
||||
@ -1401,7 +1415,8 @@ qint64 QIODevicePrivate::readLine(char *data, qint64 maxSize)
|
||||
}
|
||||
#endif
|
||||
if (readBytes < 0) {
|
||||
data[readSoFar] = '\0';
|
||||
if (appendNullByte)
|
||||
data[readSoFar] = '\0';
|
||||
return readSoFar ? readSoFar : -1;
|
||||
}
|
||||
readSoFar += readBytes;
|
||||
@ -1411,12 +1426,14 @@ qint64 QIODevicePrivate::readLine(char *data, qint64 maxSize)
|
||||
// assume the device position is invalid and force a seek.
|
||||
devicePos = qint64(-1);
|
||||
}
|
||||
data[readSoFar] = '\0';
|
||||
if (appendNullByte)
|
||||
data[readSoFar] = '\0';
|
||||
|
||||
if (openMode & QIODevice::Text) {
|
||||
if (readSoFar > 1 && data[readSoFar - 1] == '\n' && data[readSoFar - 2] == '\r') {
|
||||
data[readSoFar - 2] = '\n';
|
||||
data[readSoFar - 1] = '\0';
|
||||
if (appendNullByte)
|
||||
data[readSoFar - 1] = '\0';
|
||||
--readSoFar;
|
||||
}
|
||||
}
|
||||
@ -1527,6 +1544,64 @@ bool QIODevice::readLineInto(QByteArray *line, qint64 maxSize)
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
\since 6.9
|
||||
|
||||
\fn QIODevice::readLineInto(QSpan<char> buffer);
|
||||
\fn QIODevice::readLineInto(QSpan<uchar> buffer);
|
||||
\fn QIODevice::readLineInto(QSpan<std::byte> buffer);
|
||||
|
||||
Reads a line from this device into \a buffer, and returns the subset of
|
||||
\a buffer that contains the data read.
|
||||
|
||||
If \a buffer's size is smaller than the length of the line, only the
|
||||
characters that fit within \a buffer are read and returned. In this case,
|
||||
calling readLineInto() again will retrieve the remainder of the line.
|
||||
To determine whether the entire line was read, first check if the device is
|
||||
atEnd(), in case the last line didn't end with a newline. If not atEnd(),
|
||||
verify whether the returned view ends with '\n'. Otherwise, another call to
|
||||
readLineInto() is required.
|
||||
|
||||
The resulting line can have trailing end-of-line characters ("\n" or "\r\n"),
|
||||
so calling QByteArrayView::trimmed() may be necessary.
|
||||
|
||||
In case an error occurred, this function returns a null QByteArrayView.
|
||||
Otherwise it is a sub-span of \a buffer. If no data was currently
|
||||
available for reading or the device is atEnd(), this function returns an
|
||||
empty QByteArrayView.
|
||||
|
||||
Note that the return value is not null terminated. If you want
|
||||
null-termination, you can pass \c{buffer.chopped(1)} and then insert '\\0'
|
||||
at \c{buffer[result.size()]}.
|
||||
|
||||
\sa readLine()
|
||||
*/
|
||||
QByteArrayView QIODevice::readLineInto(QSpan<std::byte> buffer)
|
||||
{
|
||||
Q_D(QIODevice);
|
||||
qint64 maxSize = buffer.size();
|
||||
#if defined QIODEVICE_DEBUG
|
||||
printf("%p QIODevice::readLineInto(%lld), d->pos = %lld, d->buffer.size() = %lld\n",
|
||||
this, qlonglong(maxSize), qlonglong(d->pos), qlonglong(d->buffer.size()));
|
||||
#endif
|
||||
|
||||
CHECK_READABLE(readLineInto, {});
|
||||
|
||||
if (atEnd())
|
||||
return buffer.first(0);
|
||||
|
||||
CHECK_LINEMAXLEN_1(readLineInto, {});
|
||||
CHECK_MAXBYTEARRAYSIZE(readLineInto);
|
||||
|
||||
const qint64 readBytes = d->readLine(reinterpret_cast<char*>(buffer.data()), buffer.size(),
|
||||
QIODevicePrivate::ReadLineOption::NotNullTerminated);
|
||||
|
||||
if (readBytes < 0)
|
||||
return {};
|
||||
|
||||
return buffer.first(readBytes);
|
||||
}
|
||||
|
||||
/*!
|
||||
Reads up to \a maxSize characters into \a data and returns the
|
||||
number of characters read.
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include <QtCore/qobjectdefs.h>
|
||||
#include <QtCore/qscopedpointer.h>
|
||||
#endif
|
||||
#include <QtCore/qspan.h>
|
||||
#include <QtCore/qstring.h>
|
||||
|
||||
#ifdef open
|
||||
@ -79,6 +80,13 @@ public:
|
||||
qint64 readLine(char *data, qint64 maxlen);
|
||||
QByteArray readLine(qint64 maxlen = 0);
|
||||
bool readLineInto(QByteArray *result, qint64 maxlen = 0);
|
||||
|
||||
QByteArrayView readLineInto(QSpan<char> buffer)
|
||||
{ return readLineInto(as_writable_bytes(buffer)); }
|
||||
QByteArrayView readLineInto(QSpan<uchar> buffer)
|
||||
{ return readLineInto(as_writable_bytes(buffer)); }
|
||||
QByteArrayView readLineInto(QSpan<std::byte> buffer);
|
||||
|
||||
virtual bool canReadLine() const;
|
||||
|
||||
void startTransaction();
|
||||
|
@ -45,6 +45,12 @@ public:
|
||||
QIODevicePrivate();
|
||||
virtual ~QIODevicePrivate();
|
||||
|
||||
enum class ReadLineOption {
|
||||
NotNullTerminated,
|
||||
NullTerminated,
|
||||
};
|
||||
Q_DECLARE_FLAGS(ReadLineOptions, ReadLineOption)
|
||||
|
||||
// The size of this class is a subject of the library hook data.
|
||||
// When adding a new member, do not make gaps and be aware
|
||||
// about the padding. Accordingly, adjust offsets in
|
||||
@ -85,7 +91,14 @@ public:
|
||||
inline void append(const char *data, qint64 size) { Q_ASSERT(m_buf); m_buf->append(data, size); }
|
||||
inline void append(const QByteArray &qba) { Q_ASSERT(m_buf); m_buf->append(qba); }
|
||||
inline qint64 skip(qint64 length) { return (m_buf ? m_buf->skip(length) : Q_INT64_C(0)); }
|
||||
inline qint64 readLine(char *data, qint64 maxLength) { return (m_buf ? m_buf->readLine(data, maxLength) : Q_INT64_C(-1)); }
|
||||
qint64 readLine(char *data, qint64 maxLength,
|
||||
ReadLineOptions option = ReadLineOption::NullTerminated)
|
||||
{
|
||||
const auto appendNullByte = option & ReadLineOption::NullTerminated;
|
||||
return !m_buf ? Q_INT64_C(-1) :
|
||||
appendNullByte ? m_buf->readLine(data, maxLength) :
|
||||
m_buf->readLineWithoutTerminatingNull(data, maxLength);
|
||||
}
|
||||
inline bool canReadLine() const { return m_buf && m_buf->canReadLine(); }
|
||||
};
|
||||
|
||||
@ -145,7 +158,9 @@ public:
|
||||
void setWriteChannelCount(int count);
|
||||
|
||||
qint64 read(char *data, qint64 maxSize, bool peeking = false);
|
||||
qint64 readLine(char *data, qint64 maxSize);
|
||||
qint64 readLine(char *data, qint64 maxSize,
|
||||
ReadLineOption option = ReadLineOption::NullTerminated);
|
||||
|
||||
virtual qint64 peek(char *data, qint64 maxSize);
|
||||
virtual QByteArray peek(qint64 maxSize);
|
||||
qint64 skipByReading(qint64 maxSize);
|
||||
@ -164,6 +179,8 @@ public:
|
||||
#endif
|
||||
};
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(QIODevicePrivate::ReadLineOptions)
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // QIODEVICE_P_H
|
||||
|
@ -340,14 +340,22 @@ void QRingBuffer::append(QByteArray &&qba)
|
||||
bufferSize += qbaSize;
|
||||
}
|
||||
|
||||
qint64 QRingBuffer::readLine(char *data, qint64 maxLength)
|
||||
qint64 QRingBuffer::readLineWithoutTerminatingNull(char *data, qint64 maxLength)
|
||||
{
|
||||
Q_ASSERT(data != nullptr && maxLength > 1);
|
||||
Q_ASSERT(data != nullptr && maxLength > 0);
|
||||
|
||||
--maxLength;
|
||||
qint64 i = indexOf('\n', maxLength);
|
||||
i = read(data, i >= 0 ? (i + 1) : maxLength);
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
qint64 QRingBuffer::readLine(char *data, qint64 maxLength)
|
||||
{
|
||||
Q_ASSERT(maxLength > 1);
|
||||
|
||||
qint64 i = readLineWithoutTerminatingNull(data, maxLength - 1);
|
||||
|
||||
// Terminate it.
|
||||
data[i] = '\0';
|
||||
return i;
|
||||
|
@ -214,6 +214,7 @@ public:
|
||||
return bytesToSkip;
|
||||
}
|
||||
|
||||
Q_CORE_EXPORT qint64 readLineWithoutTerminatingNull(char *data, qint64 maxLength);
|
||||
Q_CORE_EXPORT qint64 readLine(char *data, qint64 maxLength);
|
||||
|
||||
inline bool canReadLine() const {
|
||||
|
@ -32,6 +32,7 @@ private slots:
|
||||
void readLineInto_Checks();
|
||||
|
||||
void readLineInto();
|
||||
void readLineInto_qspan();
|
||||
|
||||
void readAllKeepPosition();
|
||||
void writeInTextMode();
|
||||
@ -628,6 +629,75 @@ void tst_QIODevice::readLineInto()
|
||||
QCOMPARE(buffer.pos(), pos_before);
|
||||
}
|
||||
|
||||
void tst_QIODevice::readLineInto_qspan()
|
||||
{
|
||||
QByteArray data ("1st Line\r\nL2\r\nRead the rest");
|
||||
QBuffer buffer(&data);
|
||||
|
||||
{
|
||||
QVERIFY(buffer.open(QIODevice::ReadOnly));
|
||||
QVERIFY(buffer.canReadLine());
|
||||
buffer.seek(0);
|
||||
|
||||
QSpan<char> span; // zero-sized span
|
||||
QTest::ignoreMessage(QtWarningMsg,
|
||||
"QIODevice::readLineInto (QBuffer): Called with maxSize < 1");
|
||||
QCOMPARE(buffer.readLineInto(span), "");
|
||||
|
||||
char buffer1[1024];
|
||||
QCOMPARE(buffer.readLineInto(buffer1), "1st Line\r\n");
|
||||
|
||||
uchar buffer2[4]; // length of the buffer is equal to the size of the line
|
||||
QCOMPARE(buffer.readLineInto(buffer2), "L2\r\n");
|
||||
|
||||
std::byte buffer3[5]; // length of the buffer is less than the size of the line
|
||||
QCOMPARE(buffer.readLineInto(buffer3), "Read ");
|
||||
QCOMPARE(buffer.readLineInto(buffer1), "the rest"); // read the rest
|
||||
|
||||
QCOMPARE(buffer.readLineInto(span), ""); // No warning even thought maxSize < 1 because we
|
||||
// are at the end
|
||||
}
|
||||
|
||||
{
|
||||
QVERIFY(buffer.open(QIODevice::ReadOnly | QIODevice::Text)); // "\r\n" is translated to '\n'
|
||||
QVERIFY(buffer.canReadLine());
|
||||
buffer.seek(0);
|
||||
|
||||
QSpan<char> span; // zero-sized span
|
||||
QTest::ignoreMessage(QtWarningMsg,
|
||||
"QIODevice::readLineInto (QBuffer): Called with maxSize < 1");
|
||||
QCOMPARE(buffer.readLineInto(span), "");
|
||||
|
||||
char buffer1[1024];
|
||||
QCOMPARE(buffer.readLineInto(buffer1), "1st Line\n");
|
||||
|
||||
uchar buffer2[3]; // length of the buffer is equal to the size of the line
|
||||
QCOMPARE(buffer.readLineInto(buffer2), "L2\n");
|
||||
|
||||
std::byte buffer3[5]; // length of the buffer is less than the size of the line
|
||||
QCOMPARE(buffer.readLineInto(buffer3), "Read ");
|
||||
QCOMPARE(buffer.readLineInto(buffer1), "the rest"); // read the rest
|
||||
|
||||
QCOMPARE(buffer.readLineInto(span), ""); // No warning even thought maxSize < 1 because we
|
||||
// are at the end
|
||||
}
|
||||
|
||||
{
|
||||
// This test checks the behavior when !keepDataInBuffer and !buffer.isEmpty().
|
||||
// 'buffer' was always empty in the previous tests and calling ungetChar() changes that.
|
||||
QByteArray data2 ("Q");
|
||||
QBuffer buffer2(&data2);
|
||||
QVERIFY(buffer2.open(QIODevice::ReadOnly));
|
||||
buffer2.seek(0);
|
||||
buffer2.read(1);
|
||||
buffer2.ungetChar('t'); // Make the buffer size equal to 1
|
||||
|
||||
char buf[1];
|
||||
QCOMPARE(buffer2.readLineInto(buf), "t");
|
||||
QCOMPARE(buffer2.readLineInto(buf), ""); // no more data to read
|
||||
}
|
||||
}
|
||||
|
||||
class SequentialReadBuffer : public QIODevice
|
||||
{
|
||||
public:
|
||||
|
Loading…
x
Reference in New Issue
Block a user