Skip to content

Instantly share code, notes, and snippets.

@y8
Last active April 18, 2021 16:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save y8/b5c2c4e1fccacffc3bbaa0a93a1c3964 to your computer and use it in GitHub Desktop.
Save y8/b5c2c4e1fccacffc3bbaa0a93a1c3964 to your computer and use it in GitHub Desktop.
# Usage:
#
# file = File.tmpfile('test')
# chunk_size_bytes = BufferedReader.new(sizeof(UInt32), file)
# file.write_byte 0xBE
# file.write_byte 0xEF
# file.write_byte 0xFE
# file.pos -= 3
# chunk_size_bytes.consume? => false
# file.write_byte 0xFE
# # file.pos -= 1
# chunk_size_bytes.consume? => true
# Int32.from_io(chunk_size_bytes.to_memory, IO::ByteFormat::LittleEndian)
struct BufferedReader
property start_pos : UInt64,
end_pos : UInt64,
current_pos : UInt64
# FIXME: Workaround for type mismatch between arches
# https://github.com/crystal-lang/crystal/issues/10645
def initialize(@requested_size : Int32, @io : File)
size = requested_size.to_u64
@start_pos = position
@end_pos = @start_pos + size
@current_pos = @start_pos
@memory = IO::Memory.new(size)
end
def initialize(@requested_size : UInt64, @io : File)
@start_pos = position
@end_pos = @start_pos + @requested_size
@current_pos = @start_pos
@memory = IO::Memory.new(@requested_size)
end
# Read from IO until @requested_size is fully read. Returns true when buffer
# fully read from IO and false otherwise.
def consume?
return true if ready?
# Make a snapshot of current position
available = available_bytes
waiting = waiting_bytes
read_bytes(waiting)
# Return true, if there was more bytes that we needed to complete the buffer
return available >= waiting
end
def ready?
@current_pos >= @end_pos
end
def waiting?
available_bytes <= 0
end
def to_slice
@memory.to_slice if ready?
end
def to_memory
return unless ready?
@memory.rewind
@memory
end
# 100 bytes availabe, but we are waiting for 10. We'll allocate slice of
# 10 bytes, read 10 bytes form file to slice and then append slice to memory
# buffer. Then buffer is complete and can be returned to
private def read_bytes(waiting)
slice = Bytes.new(waiting)
@io.read(slice)
@current_pos = position
@memory.write(slice)
end
# Ex: file size = 100 bytes, size = 200, current_pos = 90
# start_pos = 50, end_pos = 50 + 200 = 250, hence 250 - 90 = 160 bytes
# we are waiting for
private def waiting_bytes
@end_pos - @current_pos
end
# Ex: file size is 100 bytes, we are on 20th byte, hence 100 - 20 = 80 bytes
# available in file
# FIXME: Force UInt64 value (#10645)
private def available_bytes
(@io.size - @io.pos).to_u64
end
# FIXME: Force UInt64 value (#10645)
private def position
@io.pos.to_u64
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment