mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 09:27:35 +00:00
AK: Move Stream
and SeekableStream
from LibCore
`Stream` will be qualified as `AK::Stream` until we remove the `Core::Stream` namespace. `IODevice` now reuses the `SeekMode` that is defined by `SeekableStream`, since defining its own would require us to qualify it with `AK::SeekMode` everywhere.
This commit is contained in:
parent
5f2ea31816
commit
8464da1439
96 changed files with 620 additions and 586 deletions
|
@ -19,6 +19,7 @@ set(AK_SOURCES
|
|||
NumberFormat.cpp
|
||||
Random.cpp
|
||||
StackInfo.cpp
|
||||
Stream.cpp
|
||||
String.cpp
|
||||
StringBuilder.cpp
|
||||
StringFloatingPointConversions.cpp
|
||||
|
|
|
@ -35,6 +35,8 @@ class StackInfo;
|
|||
class DeprecatedFlyString;
|
||||
class DeprecatedString;
|
||||
class DeprecatedStringCodePointIterator;
|
||||
class SeekableStream;
|
||||
class Stream;
|
||||
class StringBuilder;
|
||||
class StringImpl;
|
||||
class StringView;
|
||||
|
@ -183,9 +185,11 @@ using AK::Optional;
|
|||
using AK::OwnPtr;
|
||||
using AK::ReadonlyBytes;
|
||||
using AK::RefPtr;
|
||||
using AK::SeekableStream;
|
||||
using AK::SinglyLinkedList;
|
||||
using AK::Span;
|
||||
using AK::StackInfo;
|
||||
using AK::Stream;
|
||||
using AK::String;
|
||||
using AK::StringBuilder;
|
||||
using AK::StringImpl;
|
||||
|
|
132
AK/Stream.cpp
Normal file
132
AK/Stream.cpp
Normal file
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021, sin-ack <sin-ack@protonmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/Format.h>
|
||||
#include <AK/Stream.h>
|
||||
|
||||
namespace AK {
|
||||
|
||||
ErrorOr<void> Stream::read_entire_buffer(Bytes buffer)
|
||||
{
|
||||
size_t nread = 0;
|
||||
while (nread < buffer.size()) {
|
||||
if (is_eof())
|
||||
return Error::from_string_literal("Reached end-of-file before filling the entire buffer");
|
||||
|
||||
auto result = read(buffer.slice(nread));
|
||||
if (result.is_error()) {
|
||||
if (result.error().is_errno() && result.error().code() == EINTR) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return result.release_error();
|
||||
}
|
||||
|
||||
nread += result.value().size();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<ByteBuffer> Stream::read_until_eof(size_t block_size)
|
||||
{
|
||||
return read_until_eof_impl(block_size);
|
||||
}
|
||||
|
||||
ErrorOr<ByteBuffer> Stream::read_until_eof_impl(size_t block_size, size_t expected_file_size)
|
||||
{
|
||||
ByteBuffer data;
|
||||
data.ensure_capacity(expected_file_size);
|
||||
|
||||
size_t total_read = 0;
|
||||
Bytes buffer;
|
||||
while (!is_eof()) {
|
||||
if (buffer.is_empty()) {
|
||||
buffer = TRY(data.get_bytes_for_writing(block_size));
|
||||
}
|
||||
|
||||
auto nread = TRY(read(buffer)).size();
|
||||
total_read += nread;
|
||||
buffer = buffer.slice(nread);
|
||||
}
|
||||
|
||||
data.resize(total_read);
|
||||
return data;
|
||||
}
|
||||
|
||||
ErrorOr<void> Stream::discard(size_t discarded_bytes)
|
||||
{
|
||||
// Note: This was chosen arbitrarily.
|
||||
// Note: This can't be PAGE_SIZE because it is defined to sysconf() on Lagom.
|
||||
constexpr size_t continuous_read_size = 4096;
|
||||
|
||||
Array<u8, continuous_read_size> buffer;
|
||||
|
||||
while (discarded_bytes > 0) {
|
||||
if (is_eof())
|
||||
return Error::from_string_literal("Reached end-of-file before reading all discarded bytes");
|
||||
|
||||
auto slice = TRY(read(buffer.span().slice(0, min(discarded_bytes, continuous_read_size))));
|
||||
discarded_bytes -= slice.size();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> Stream::write_entire_buffer(ReadonlyBytes buffer)
|
||||
{
|
||||
size_t nwritten = 0;
|
||||
while (nwritten < buffer.size()) {
|
||||
auto result = write(buffer.slice(nwritten));
|
||||
if (result.is_error()) {
|
||||
if (result.error().is_errno() && result.error().code() == EINTR) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return result.release_error();
|
||||
}
|
||||
|
||||
nwritten += result.value();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<size_t> SeekableStream::tell() const
|
||||
{
|
||||
// Seek with 0 and SEEK_CUR does not modify anything despite the const_cast,
|
||||
// so it's safe to do this.
|
||||
return const_cast<SeekableStream*>(this)->seek(0, SeekMode::FromCurrentPosition);
|
||||
}
|
||||
|
||||
ErrorOr<size_t> SeekableStream::size()
|
||||
{
|
||||
auto original_position = TRY(tell());
|
||||
|
||||
auto seek_result = seek(0, SeekMode::FromEndPosition);
|
||||
if (seek_result.is_error()) {
|
||||
// Let's try to restore the original position, just in case.
|
||||
auto restore_result = seek(original_position, SeekMode::SetPosition);
|
||||
if (restore_result.is_error()) {
|
||||
dbgln("SeekableStream::size: Couldn't restore initial position, stream might have incorrect position now!");
|
||||
}
|
||||
|
||||
return seek_result.release_error();
|
||||
}
|
||||
|
||||
TRY(seek(original_position, SeekMode::SetPosition));
|
||||
return seek_result.value();
|
||||
}
|
||||
|
||||
ErrorOr<void> SeekableStream::discard(size_t discarded_bytes)
|
||||
{
|
||||
TRY(seek(discarded_bytes, SeekMode::FromCurrentPosition));
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
133
AK/Stream.h
Normal file
133
AK/Stream.h
Normal file
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* Copyright (c) 2021, sin-ack <sin-ack@protonmail.com>
|
||||
* Copyright (c) 2022, the SerenityOS developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Error.h>
|
||||
#include <AK/Forward.h>
|
||||
#include <AK/Traits.h>
|
||||
|
||||
namespace AK {
|
||||
|
||||
/// The base, abstract class for stream operations. This class defines the
|
||||
/// operations one can perform on every stream.
|
||||
/// Operations without a sensible default that are unsupported by an implementation
|
||||
/// of a Stream should return EBADF as an error.
|
||||
class Stream {
|
||||
public:
|
||||
/// Reads into a buffer, with the maximum size being the size of the buffer.
|
||||
/// The amount of bytes read can be smaller than the size of the buffer.
|
||||
/// Returns either the bytes that were read, or an errno in the case of
|
||||
/// failure.
|
||||
virtual ErrorOr<Bytes> read(Bytes) = 0;
|
||||
/// Tries to fill the entire buffer through reading. Returns whether the
|
||||
/// buffer was filled without an error.
|
||||
virtual ErrorOr<void> read_entire_buffer(Bytes);
|
||||
/// Reads the stream until EOF, storing the contents into a ByteBuffer which
|
||||
/// is returned once EOF is encountered. The block size determines the size
|
||||
/// of newly allocated chunks while reading.
|
||||
virtual ErrorOr<ByteBuffer> read_until_eof(size_t block_size = 4096);
|
||||
/// Discards the given number of bytes from the stream. As this is usually used
|
||||
/// as an efficient version of `read_entire_buffer`, it returns an error
|
||||
/// if reading failed or if not all bytes could be discarded.
|
||||
/// Unless specifically overwritten, this just uses read() to read into an
|
||||
/// internal stack-based buffer.
|
||||
virtual ErrorOr<void> discard(size_t discarded_bytes);
|
||||
|
||||
/// Tries to write the entire contents of the buffer. It is possible for
|
||||
/// less than the full buffer to be written. Returns either the amount of
|
||||
/// bytes written into the stream, or an errno in the case of failure.
|
||||
virtual ErrorOr<size_t> write(ReadonlyBytes) = 0;
|
||||
/// Same as write, but does not return until either the entire buffer
|
||||
/// contents are written or an error occurs.
|
||||
virtual ErrorOr<void> write_entire_buffer(ReadonlyBytes);
|
||||
|
||||
template<typename T>
|
||||
requires(requires(Stream& stream) { { T::read_from_stream(stream) } -> SameAs<ErrorOr<T>>; })
|
||||
ErrorOr<T> read_value()
|
||||
{
|
||||
return T::read_from_stream(*this);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
requires(Traits<T>::is_trivially_serializable())
|
||||
ErrorOr<T> read_value()
|
||||
{
|
||||
alignas(T) u8 buffer[sizeof(T)] = {};
|
||||
TRY(read_entire_buffer({ &buffer, sizeof(buffer) }));
|
||||
return bit_cast<T>(buffer);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
requires(requires(T t, Stream& stream) { { t.write_to_stream(stream) } -> SameAs<ErrorOr<void>>; })
|
||||
ErrorOr<void> write_value(T const& value)
|
||||
{
|
||||
return value.write_to_stream(*this);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
requires(Traits<T>::is_trivially_serializable())
|
||||
ErrorOr<void> write_value(T const& value)
|
||||
{
|
||||
return write_entire_buffer({ &value, sizeof(value) });
|
||||
}
|
||||
|
||||
/// Returns whether the stream has reached the end of file. For sockets,
|
||||
/// this most likely means that the protocol has disconnected (in the case
|
||||
/// of TCP). For seekable streams, this means the end of the file. Note that
|
||||
/// is_eof will only return true _after_ a read with 0 length, so this
|
||||
/// method should be called after a read.
|
||||
virtual bool is_eof() const = 0;
|
||||
|
||||
virtual bool is_open() const = 0;
|
||||
virtual void close() = 0;
|
||||
|
||||
virtual ~Stream()
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
/// Provides a default implementation of read_until_eof that works for streams
|
||||
/// that behave like POSIX file descriptors. expected_file_size can be
|
||||
/// passed as a heuristic for what the Stream subclass expects the file
|
||||
/// content size to be in order to reduce allocations (does not affect
|
||||
/// actual reading).
|
||||
ErrorOr<ByteBuffer> read_until_eof_impl(size_t block_size, size_t expected_file_size = 0);
|
||||
};
|
||||
|
||||
enum class SeekMode {
|
||||
SetPosition,
|
||||
FromCurrentPosition,
|
||||
FromEndPosition,
|
||||
};
|
||||
|
||||
/// Adds seekability to a Stream. Classes inheriting from SeekableStream
|
||||
/// will be seekable to any point in the stream.
|
||||
class SeekableStream : public Stream {
|
||||
public:
|
||||
/// Seeks to the given position in the given mode. Returns either the
|
||||
/// current position of the file, or an errno in the case of an error.
|
||||
virtual ErrorOr<size_t> seek(i64 offset, SeekMode) = 0;
|
||||
/// Returns the current position of the file, or an errno in the case of
|
||||
/// an error.
|
||||
virtual ErrorOr<size_t> tell() const;
|
||||
/// Returns the total size of the stream, or an errno in the case of an
|
||||
/// error. May not preserve the original position on the stream on failure.
|
||||
virtual ErrorOr<size_t> size();
|
||||
/// Shrinks or extends the stream to the given size. Returns an errno in
|
||||
/// the case of an error.
|
||||
virtual ErrorOr<void> truncate(off_t length) = 0;
|
||||
/// Seeks until after the given amount of bytes to be discarded instead of
|
||||
/// reading and discarding everything manually;
|
||||
virtual ErrorOr<void> discard(size_t discarded_bytes) override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#if USING_AK_GLOBALLY
|
||||
using AK::SeekMode;
|
||||
#endif
|
Loading…
Add table
Add a link
Reference in a new issue