ona/source/ona/file.zig

199 lines
4.4 KiB
Zig

const coral = @import("coral");
const ext = @import("./ext.zig");
pub const Access = union (enum) {
null,
sandboxed_path: *const Path,
pub fn open_readable(self: Access, readable_path: Path) ?*Readable {
switch (self) {
.null => return null,
.sandboxed_path => |sandboxed_path| {
const readable_path_string = sandboxed_path.joined(readable_path).to_string() orelse return null;
return @ptrCast(ext.SDL_RWFromFile(readable_path_string.ptr, "rb"));
},
}
}
pub fn query(self: Access, path: Path) ?Info {
switch (self) {
.null => return null,
.sandboxed_path => |sandboxed_path| {
const path_string = sandboxed_path.joined(path).to_string() orelse return null;
const rw_ops = ext.SDL_RWFromFile(path_string, "rb") orelse return null;
const file_size = ext.SDL_RWseek(rw_ops, 0, ext.RW_SEEK_END);
if (ext.SDL_RWclose(rw_ops) != 0 or file_size < 0) {
return null;
}
return Info{
.size = @intCast(file_size),
};
},
}
}
};
pub const Info = struct {
size: u64,
};
pub const Path = extern struct {
data: [4096]coral.io.Byte = [_]coral.io.Byte{0} ** 4096,
pub const cwd = Path.from(&.{"./"});
pub fn from(components: []const []const u8) Path {
// TODO: Implement proper parsing / removal of duplicate path delimiters.
var path = Path{};
{
var writable_slice = coral.io.FixedBuffer{.bytes = &path.data};
for (components) |component| {
if (writable_slice.write(component) != component.len) {
break;
}
}
}
return path;
}
pub fn joined(self: Path, other: Path) Path {
var path = Path{};
{
var writable = coral.io.FixedBuffer{.bytes = &path.data};
var written = @as(usize, 0);
for (&self.data) |byte| {
if ((byte == 0) or !(writable.put(byte))) {
break;
}
written += 1;
}
if ((written > 0) and (path.data[written - 1] != '/') and writable.put('/')) {
written += 1;
}
for (&other.data) |byte| {
if ((byte == 0) or !(writable.put(byte))) {
break;
}
written += 1;
}
}
return path;
}
pub fn to_string(self: Path) ?[:0]const coral.io.Byte {
const last_index = self.data.len - 1;
if (self.data[last_index] != 0) {
return null;
}
return coral.io.slice_sentineled(@as(coral.io.Byte, 0), @as([*:0]const coral.io.Byte, @ptrCast(&self.data)));
}
};
pub const Readable = opaque {
pub fn as_reader(self: *Readable) coral.io.Reader {
return coral.io.Reader.bind(Readable, self, read_into);
}
pub fn close(self: *Readable) void {
if (ext.SDL_RWclose(rw_ops_cast(self)) != 0) {
@panic("Failed to close file");
}
}
pub fn read_into(self: *Readable, buffer: []coral.io.Byte) ?usize {
ext.SDL_ClearError();
const bytes_read = ext.SDL_RWread(rw_ops_cast(self), buffer.ptr, @sizeOf(coral.io.Byte), buffer.len);
const error_message = ext.SDL_GetError();
if (bytes_read == 0 and error_message != null and error_message.* != 0) {
return null;
}
return bytes_read;
}
pub fn seek_head(self: *Readable, cursor: u64) ?u64 {
// TODO: Fix safety of int cast.
const byte_offset = ext.SDL_RWseek(rw_ops_cast(self), @intCast(cursor), ext.RW_SEEK_SET);
if (byte_offset < 0) {
return null;
}
return @intCast(byte_offset);
}
pub fn seek_tail(self: *Readable) ?usize {
const byte_offset = ext.SDL_RWseek(rw_ops_cast(self), 0, ext.RW_SEEK_END);
if (byte_offset < 0) {
return error.FileUnavailable;
}
return @intCast(byte_offset);
}
pub fn skip(self: *Readable, offset: i64) ?u64 {
const byte_offset = ext.SDL_RWseek(rw_ops_cast(self), offset, ext.RW_SEEK_CUR);
if (byte_offset < 0) {
return error.FileUnavailable;
}
return @intCast(byte_offset);
}
};
pub fn allocate_and_load(allocator: coral.io.Allocator, access: Access, path: Path) coral.io.AllocationError!?[]coral.io.Byte {
const allocation = try allocator.reallocate(null, query_file_size: {
const info = access.query(path) orelse return null;
break: query_file_size info.size;
});
const readable = access.open_readable(path) orelse {
allocator.deallocate(allocation);
return null;
};
defer _ = readable.close();
const bytes_read = readable.read_into(allocation) orelse {
allocator.deallocate(allocation);
return null;
};
if (bytes_read != allocation.len) {
allocator.deallocate(allocation);
return null;
}
return allocation;
}
fn rw_ops_cast(ptr: *anyopaque) *ext.SDL_RWops {
return @ptrCast(@alignCast(ptr));
}