class Generic
Definitions
def read(size = nil)
Reads size
bytes from the stream. If size is not specified, read until end of file.
Implementation
def read(size = nil)
return String.new(encoding: Encoding::BINARY) if size == 0
if size
until @eof or @read_buffer.bytesize >= size
# Compute the amount of data we need to read from the underlying stream:
read_size = size - @read_buffer.bytesize
# Don't read less than @block_size to avoid lots of small reads:
fill_read_buffer(read_size > @block_size ? read_size : @block_size)
end
else
until @eof
fill_read_buffer
end
end
return consume_read_buffer(size)
end
def read_partial(size = nil)
Read at most size
bytes from the stream. Will avoid reading from the underlying stream if possible.
Implementation
def read_partial(size = nil)
return String.new(encoding: Encoding::BINARY) if size == 0
if !@eof and @read_buffer.empty?
fill_read_buffer
end
return consume_read_buffer(size)
end
def readpartial(size = nil)
This is a compatibility shim for existing code that uses readpartial
.
Implementation
def readpartial(size = nil)
read_partial(size) or raise EOFError, "Encountered eof while reading data!"
end
def read_until(pattern, offset = 0, limit: nil, chomp: true)
Efficiently read data from the stream until encountering pattern.
Signature
-
parameter
pattern
String
The pattern to match.
-
parameter
offset
Integer
The offset to start searching from.
-
parameter
limit
Integer
The maximum number of bytes to read, including the pattern (even if chomped).
-
returns
String | Nil
The contents of the stream up until the pattern, which is consumed but not returned.
Implementation
def read_until(pattern, offset = 0, limit: nil, chomp: true)
if index = index_of(pattern, offset, limit)
return nil if limit and index >= limit
@read_buffer.freeze
matched = @read_buffer.byteslice(0, index+(chomp ? 0 : pattern.bytesize))
@read_buffer = @read_buffer.byteslice(index+pattern.bytesize, @read_buffer.bytesize)
return matched
end
end
def flush
Flushes buffered data to the stream.
Implementation
def flush
return if @write_buffer.empty?
@writing.synchronize do
self.drain(@write_buffer)
end
end
def write(string, flush: false)
Writes string
to the buffer. When the buffer is full or #sync is true the
buffer is flushed to the underlying io
.
Signature
-
parameter
string
String
the string to write to the buffer.
-
returns
Integer
the number of bytes appended to the buffer.
Implementation
def write(string, flush: false)
@writing.synchronize do
@write_buffer << string
flush |= (@write_buffer.bytesize >= @block_size)
if flush
self.drain(@write_buffer)
end
end
return string.bytesize
end
def <<(string)
Writes string
to the stream and returns self.
Implementation
def <<(string)
write(string)
return self
end
def close
Best effort to flush any unwritten data, and then close the underling IO.
Implementation
def close
return if closed?
begin
flush
rescue
# We really can't do anything here unless we want #close to raise exceptions.
ensure
self.sysclose
end
end
def eof?
Determins if the stream has consumed all available data. May block if the stream is not readable.
See IO::Stream::Generic#readable?
for a non-blocking alternative.
Signature
-
returns
Boolean
If the stream is at file which means there is no more data to be read.
Implementation
def eof?
if !@read_buffer.empty?
return false
elsif @eof
return true
else
return !self.fill_read_buffer
end
end
def readable?
Whether there is a chance that a read operation will succeed or not.
Signature
-
returns
Boolean
If the stream is readable, i.e. a
read
operation has a chance of success.
Implementation
def readable?
# If we are at the end of the file, we can't read any more data:
if @eof
return false
end
# If the read buffer is not empty, we can read more data:
if !@read_buffer.empty?
return true
end
# If the underlying stream is readable, we can read more data:
return !closed?
end
def sysread(size, buffer)
Reads data from the underlying stream as efficiently as possible.
Implementation
def sysread(size, buffer)
raise NotImplementedError
end
def fill_read_buffer(size = @block_size)
Fills the buffer from the underlying stream.
Implementation
def fill_read_buffer(size = @block_size)
# We impose a limit because the underlying `read` system call can fail if we request too much data in one go.
if size > @maximum_read_size
size = @maximum_read_size
end
# This effectively ties the input and output stream together.
flush
if @read_buffer.empty?
if sysread(size, @read_buffer)
# Console.info(self, name: "read") {@read_buffer.inspect}
return true
end
else
if chunk = sysread(size, @input_buffer)
@read_buffer << chunk
# Console.info(self, name: "read") {@read_buffer.inspect}
return true
end
end
# else for both cases above:
@eof = true
return false
end
def consume_read_buffer(size = nil)
Consumes at most size
bytes from the buffer.
Signature
-
parameter
size
Integer|nil
The amount of data to consume. If nil, consume entire buffer.
Implementation
def consume_read_buffer(size = nil)
# If we are at eof, and the read buffer is empty, we can't consume anything.
return nil if @eof && @read_buffer.empty?
result = nil
if size.nil? or size >= @read_buffer.bytesize
# Consume the entire read buffer:
result = @read_buffer
@read_buffer = StringBuffer.new
else
# This approach uses more memory.
# result = @read_buffer.slice!(0, size)
# We know that we are not going to reuse the original buffer.
# But byteslice will generate a hidden copy. So let's freeze it first:
@read_buffer.freeze
result = @read_buffer.byteslice(0, size)
@read_buffer = @read_buffer.byteslice(size, @read_buffer.bytesize)
end
return result
end