mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-21 23:20:20 +00:00
AK: Add methods to BufferedStream to resize the user supplied buffer
These changes allow lines of arbitrary length to be read with BufferedStream. When the user supplied buffer is smaller than the line, it will be resized to fit the line. When the internal buffer in BufferedStream is smaller than the line, it will be read into the user supplied buffer chunk by chunk with the buffer growing accordingly. Other behaviors match the behavior of the existing read_line method.
This commit is contained in:
parent
4db1712f90
commit
08aaf4fb07
Notes:
sideshowbarker
2024-07-16 22:22:13 +09:00
Author: https://github.com/d-gaston 🔰 Commit: https://github.com/SerenityOS/serenity/commit/08aaf4fb07 Pull-request: https://github.com/SerenityOS/serenity/pull/23982 Reviewed-by: https://github.com/timschumi ✅
2 changed files with 171 additions and 0 deletions
|
@ -115,6 +115,52 @@ public:
|
|||
return m_buffer.read(buffer);
|
||||
}
|
||||
|
||||
ErrorOr<StringView> read_line_with_resize(ByteBuffer& buffer)
|
||||
{
|
||||
return StringView { TRY(read_until_with_resize(buffer, "\n"sv)) };
|
||||
}
|
||||
|
||||
ErrorOr<Bytes> read_until_with_resize(ByteBuffer& buffer, StringView candidate)
|
||||
{
|
||||
return read_until_any_of_with_resize(buffer, Array { candidate });
|
||||
}
|
||||
|
||||
template<size_t N>
|
||||
ErrorOr<Bytes> read_until_any_of_with_resize(ByteBuffer& buffer, Array<StringView, N> candidates)
|
||||
{
|
||||
if (!stream().is_open())
|
||||
return Error::from_errno(ENOTCONN);
|
||||
|
||||
auto candidate = TRY(find_and_populate_until_any_of(candidates));
|
||||
|
||||
size_t bytes_read_to_user_buffer = 0;
|
||||
while (!candidate.has_value()) {
|
||||
if (m_buffer.used_space() == 0 && stream().is_eof()) {
|
||||
// If we read to the very end of the buffered and unbuffered data,
|
||||
// then treat the remainder as a full line (the last one), even if it
|
||||
// doesn't end in the delimiter.
|
||||
return buffer.span().trim(bytes_read_to_user_buffer);
|
||||
}
|
||||
|
||||
if (buffer.size() - bytes_read_to_user_buffer < m_buffer.used_space()) {
|
||||
// Resize the user supplied buffer because it cannot fit
|
||||
// the contents of m_buffer.
|
||||
TRY(buffer.try_resize(buffer.size() + m_buffer.used_space()));
|
||||
}
|
||||
|
||||
// Read bytes into the buffer starting from the offset of how many bytes have previously been read.
|
||||
bytes_read_to_user_buffer += m_buffer.read(buffer.span().slice(bytes_read_to_user_buffer)).size();
|
||||
candidate = TRY(find_and_populate_until_any_of(candidates));
|
||||
}
|
||||
|
||||
// Once the candidate has been found, read the contents of m_buffer into the buffer,
|
||||
// offset by how many bytes have already been read in.
|
||||
TRY(buffer.try_resize(bytes_read_to_user_buffer + candidate->offset));
|
||||
m_buffer.read(buffer.span().slice(bytes_read_to_user_buffer));
|
||||
TRY(m_buffer.discard(candidate->size));
|
||||
return buffer.span();
|
||||
}
|
||||
|
||||
struct Match {
|
||||
size_t offset {};
|
||||
size_t size {};
|
||||
|
@ -308,6 +354,12 @@ public:
|
|||
ErrorOr<Bytes> read_until_any_of(Bytes buffer, Array<StringView, N> candidates) { return m_helper.read_until_any_of(move(buffer), move(candidates)); }
|
||||
ErrorOr<bool> can_read_up_to_delimiter(ReadonlyBytes delimiter) { return m_helper.can_read_up_to_delimiter(delimiter); }
|
||||
|
||||
// Methods for reading stream into an auto-adjusting buffer
|
||||
ErrorOr<StringView> read_line_with_resize(ByteBuffer& buffer) { return m_helper.read_line_with_resize(buffer); }
|
||||
ErrorOr<Bytes> read_until_with_resize(ByteBuffer& buffer, StringView candidate) { return m_helper.read_until_with_resize(move(buffer), move(candidate)); }
|
||||
template<size_t N>
|
||||
ErrorOr<Bytes> read_until_any_of_with_resize(ByteBuffer& buffer, Array<StringView, N> candidates) { return m_helper.read_until_any_of_with_resize(move(buffer), move(candidates)); }
|
||||
|
||||
size_t buffer_size() const { return m_helper.buffer_size(); }
|
||||
|
||||
virtual ~InputBufferedSeekable() override = default;
|
||||
|
|
|
@ -298,3 +298,122 @@ TEST_CASE(buffered_memory_stream_read_line)
|
|||
EXPECT(read_or_error.is_error());
|
||||
EXPECT_EQ(read_or_error.error().code(), EMSGSIZE);
|
||||
}
|
||||
|
||||
TEST_CASE(buffered_memory_stream_read_line_with_resizing_where_stream_buffer_is_sufficient)
|
||||
{
|
||||
auto array = Array<u8, 24> {};
|
||||
|
||||
// The first line is 8 A's, the second line is 14 A's, two bytes are newline characters.
|
||||
array.fill('A');
|
||||
array[8] = '\n';
|
||||
array[23] = '\n';
|
||||
|
||||
auto memory_stream = make<FixedMemoryStream>(array.span(), FixedMemoryStream::Mode::ReadOnly);
|
||||
|
||||
auto buffered_stream = TRY_OR_FAIL(InputBufferedSeekable<FixedMemoryStream>::create(move(memory_stream), 64));
|
||||
|
||||
size_t initial_buffer_size = 4;
|
||||
auto buffer = TRY_OR_FAIL(ByteBuffer::create_zeroed(initial_buffer_size));
|
||||
|
||||
auto read_bytes = TRY_OR_FAIL(buffered_stream->read_line_with_resize(buffer));
|
||||
|
||||
// The first line, which is 8 A's, should be read in.
|
||||
EXPECT_EQ(read_bytes, "AAAAAAAA"sv);
|
||||
|
||||
read_bytes = TRY_OR_FAIL(buffered_stream->read_line_with_resize(buffer));
|
||||
|
||||
// The second line, which is 14 A's, should be read in.
|
||||
EXPECT_EQ(read_bytes, "AAAAAAAAAAAAAA"sv);
|
||||
|
||||
// A resize should have happened because the user supplied buffer was too small.
|
||||
EXPECT(buffer.size() > initial_buffer_size);
|
||||
|
||||
// Reading from the stream again should return an empty StringView.
|
||||
read_bytes = TRY_OR_FAIL(buffered_stream->read_line_with_resize(buffer));
|
||||
EXPECT(read_bytes.is_empty());
|
||||
}
|
||||
|
||||
TEST_CASE(buffered_memory_stream_read_line_with_resizing_where_stream_buffer_is_not_sufficient)
|
||||
{
|
||||
// This is the same as "buffered_memory_stream_read_line_with_resizing_where_stream_buffer_is_sufficient"
|
||||
// but with a smaller stream buffer, meaning that the line must be read into the user supplied
|
||||
// buffer in chunks. All assertions and invariants should remain unchanged.
|
||||
auto array = Array<u8, 24> {};
|
||||
|
||||
// The first line is 8 A's, the second line is 14 A's, two bytes are newline characters.
|
||||
array.fill('A');
|
||||
array[8] = '\n';
|
||||
array[23] = '\n';
|
||||
|
||||
auto memory_stream = make<FixedMemoryStream>(array.span(), FixedMemoryStream::Mode::ReadOnly);
|
||||
|
||||
auto buffered_stream = TRY_OR_FAIL(InputBufferedSeekable<FixedMemoryStream>::create(move(memory_stream), 6));
|
||||
|
||||
size_t initial_buffer_size = 4;
|
||||
auto buffer = TRY_OR_FAIL(ByteBuffer::create_zeroed(initial_buffer_size));
|
||||
|
||||
auto read_bytes = TRY_OR_FAIL(buffered_stream->read_line_with_resize(buffer));
|
||||
|
||||
// The first line, which is 8 A's, should be read in.
|
||||
EXPECT_EQ(read_bytes, "AAAAAAAA"sv);
|
||||
|
||||
read_bytes = TRY_OR_FAIL(buffered_stream->read_line_with_resize(buffer));
|
||||
|
||||
// The second line, which is 14 A's, should be read in.
|
||||
EXPECT_EQ(read_bytes, "AAAAAAAAAAAAAA"sv);
|
||||
|
||||
// A resize should have happened because the user supplied buffer was too small.
|
||||
EXPECT(buffer.size() > initial_buffer_size);
|
||||
|
||||
// Reading from the stream again should return an empty StringView.
|
||||
read_bytes = TRY_OR_FAIL(buffered_stream->read_line_with_resize(buffer));
|
||||
EXPECT(read_bytes.is_empty());
|
||||
}
|
||||
|
||||
TEST_CASE(buffered_memory_stream_read_line_with_resizing_with_no_newline_where_stream_buffer_is_sufficient)
|
||||
{
|
||||
auto array = Array<u8, 24> {};
|
||||
|
||||
array.fill('A');
|
||||
|
||||
auto memory_stream = make<FixedMemoryStream>(array.span(), FixedMemoryStream::Mode::ReadOnly);
|
||||
|
||||
auto buffered_stream = TRY_OR_FAIL(InputBufferedSeekable<FixedMemoryStream>::create(move(memory_stream), 64));
|
||||
|
||||
size_t initial_buffer_size = 4;
|
||||
auto buffer = TRY_OR_FAIL(ByteBuffer::create_zeroed(initial_buffer_size));
|
||||
|
||||
auto read_bytes = TRY_OR_FAIL(buffered_stream->read_line_with_resize(buffer));
|
||||
|
||||
// All the contents of the buffer should have been read in.
|
||||
EXPECT_EQ(read_bytes.length(), array.size());
|
||||
|
||||
// Reading from the stream again should return an empty StringView.
|
||||
read_bytes = TRY_OR_FAIL(buffered_stream->read_line_with_resize(buffer));
|
||||
EXPECT(read_bytes.is_empty());
|
||||
}
|
||||
|
||||
TEST_CASE(buffered_memory_stream_read_line_with_resizing_with_no_newline_where_stream_buffer_is_not_sufficient)
|
||||
{
|
||||
// This should behave as buffered_memory_stream_read_line_with_resizing_with_no_newline_where_stream_buffer_is_sufficient
|
||||
// but the internal buffer of the stream must be copied over in chunks.
|
||||
auto array = Array<u8, 24> {};
|
||||
|
||||
array.fill('A');
|
||||
|
||||
auto memory_stream = make<FixedMemoryStream>(array.span(), FixedMemoryStream::Mode::ReadOnly);
|
||||
|
||||
auto buffered_stream = TRY_OR_FAIL(InputBufferedSeekable<FixedMemoryStream>::create(move(memory_stream), 6));
|
||||
|
||||
size_t initial_buffer_size = 4;
|
||||
auto buffer = TRY_OR_FAIL(ByteBuffer::create_zeroed(initial_buffer_size));
|
||||
|
||||
auto read_bytes = TRY_OR_FAIL(buffered_stream->read_line_with_resize(buffer));
|
||||
|
||||
// All the contents of the buffer should have been read in.
|
||||
EXPECT_EQ(read_bytes.length(), array.size());
|
||||
|
||||
// Reading from the stream again should return an empty StringView.
|
||||
read_bytes = TRY_OR_FAIL(buffered_stream->read_line_with_resize(buffer));
|
||||
EXPECT(read_bytes.is_empty());
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue