class

BakedFileSystem::BakedFile

Inherits IO / Reference / Object

BakedFile represents a virtual file in a BakedFileSystem.

BakedFile is a read-only IO wrapper around files embedded at compile-time. Write operations will raise ReadOnlyError since embedded files cannot be modified at runtime.

Architecture

Files are stored in the binary's read-only data section as byte slices. On first access, BakedFile creates:

  1. Memory IO: Wraps the embedded byte slice
  2. Gzip Reader: Wraps the memory IO when storage compression is enabled
  3. Wrapped IO: Either the memory IO or gzip reader

This lazy-loading approach minimizes memory usage:

  • Embedded data stays in read-only binary section
  • Decompression happens on-demand during read
  • No heap allocation unless content is explicitly stored

Streaming Behavior

BakedFile is a forward-only stream by default:

  • Read operations consume the stream
  • Call rewind to return to the beginning
  • Each rewind recreates the decompression reader (see #rewind for details)

Thread Safety

Each call to get() or get?() returns a new BakedFile instance with independent state, making concurrent access safe.

Usage

file = MyFileSystem.get("hello-world.txt")
file.path        # => "hello-world.txt"
file.size        # => 12
file.gets_to_end # => "Hello World\n"
file.compressed? # => false

Constructors

new(path : String, size : Int32, compressed : Bool, slice : Bytes, stored_compressed : Bool | Nil = nil, modification_time : Time | Nil = nil, digest : String | Nil = nil)
Source

Instance methods

close

Closes the file and releases resources. Can be called multiple times safely. After closing, read operations will raise IO::Error.

Source
closed?

Returns true if the file has been closed.

Source
compressed?

Returns whether this file is already compressed content, such as a .gz file.

Source
compressed_size

Returns the stored size of this virtual file.

See #size for the real size of the original file.

Source
digest

Returns the SHA-256 digest of the source file if this file was baked from disk.

Source
finalize

Ensures resources are freed when the object is garbage collected.

Source
modification_time

Returns the source file modification time if this file was baked from disk.

Source
path

Returns the path in the virtual file system.

Source
raw

Returns the raw bytes stored in the binary.

Source
raw_io

Returns a fresh IO over the raw bytes stored in the binary.

Source
read(slice : Bytes)

Reads at most slice.size bytes from this IO into slice. Returns the number of bytes read, which is 0 if and only if there is no more data to read (so checking for 0 is the way to detect end of file).

io = IO::Memory.new "hello"
slice = Bytes.new(4)
io.read(slice) # => 4
slice          # => Bytes[104, 101, 108, 108]
io.read(slice) # => 1
slice          # => Bytes[111, 101, 108, 108]
io.read(slice) # => 0
Source
read

Return the data for this file as a String.

DEPRECATED: BakedFile can be used as an IO directly. Use gets_to_end instead

Source
rewind

Rewinds the file to the beginning for re-reading.

Implementation Note

This method recreates the gzip decompression reader instead of rewinding it because Compress::Gzip::Reader is a forward-only stream that doesn't support seeking backward. This is intentional and necessary for correct behavior.

Why Recreation is Required

  • Compress::Gzip::Reader maintains internal state during decompression
  • This state cannot be reset to return to the beginning
  • Creating a new reader over the rewound underlying stream is the only way to re-read

Performance Implications

  • Creating a new reader is fast (no decompression happens yet)
  • Decompression happens on-demand during read operations
  • Memory usage is minimal (same underlying byte slice is reused)
  • This is the standard approach for streaming decompression

Alternatives Considered

Cache decompressed content:

  • Pro: True rewind without recreation
  • Con: Significant memory overhead (defeats purpose of streaming)
  • Con: Not suitable for large files
  • Decision: Rejected

Add seeking to Gzip::Reader:

  • Pro: More intuitive API
  • Con: Requires changes to Crystal standard library
  • Con: Decompression algorithms are inherently forward-only
  • Decision: Not feasible
Source
size

Returns the size of this virtual file.

Source
stored_compressed?

Returns whether the embedded bytes are gzip-compressed for storage.

Source
to_encoded(compressed = true)

Return the data for this file as a URL-safe Base64-encoded String.

DEPRECATED: BakedFile can be used as an IO directly.

Source
to_slice(compressed)

Return the file's data as a Slice(UInt8)

DEPRECATED: BakedFile can be used as an IO directly.

Source
to_slice

Returns a Bytes holding the embedded content of this virtual file. This data needs to be extracted using a Compress::Gzip::Reader if #stored_compressed? is true.

Source
write(slice : Bytes) : Nil

Writes the contents of slice into this IO.

io = IO::Memory.new
slice = Bytes.new(4) { |i| ('a'.ord + i).to_u8 }
io.write(slice)
io.to_s # => "abcd"
Source
write_to_io(io, compressed = true)

Write the file's data to the given IO, minimizing any memory copies or unnecessary conversions.

DEPRECATED: BakedFile can be used as an IO directly.

Source