BakedFileSystem::BakedFile
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:
- Memory IO: Wraps the embedded byte slice
- Gzip Reader: Wraps the memory IO when storage compression is enabled
- 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
rewindto return to the beginning - Each
rewindrecreates the decompression reader (see#rewindfor 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
Instance methods
Closes the file and releases resources. Can be called multiple times safely. After closing, read operations will raise IO::Error.
Returns the stored size of this virtual file.
See #size for the real size of the original file.
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
Return the data for this file as a String.
DEPRECATED: BakedFile can be used as an IO directly. Use gets_to_end instead
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::Readermaintains 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
Return the data for this file as a URL-safe Base64-encoded String.
DEPRECATED: BakedFile can be used as an IO directly.
Return the file's data as a Slice(UInt8)
DEPRECATED: BakedFile can be used as an IO directly.
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.
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"
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.