const coral = @import("coral"); const ext = @import("./ext.zig"); pub const FileAccessor = struct { context: *anyopaque, actions: *const struct { open_readable: *const fn (context: *anyopaque, file_path: []const u8) OpenError!*ReadableFile, open_writable: *const fn (context: *anyopaque, file_path: []const u8) OpenError!*WritableFile, query: *const fn (context: *anyopaque, file_path: []const u8) QueryError!FileInfo, }, pub fn bind(comptime State: type, state: *State) FileAccessor { const Actions = struct { fn as_concrete(context: *anyopaque) *State { return @ptrCast(*State, @alignCast(@alignOf(State), context)); } fn open_readable(context: *anyopaque, file_path: []const u8) OpenError!*ReadableFile { return as_concrete(context).open_readable(file_path); } fn open_writable(context: *anyopaque, file_path: []const u8) OpenError!*WritableFile { return as_concrete(context).open_writable(file_path); } fn query(context: *anyopaque, file_path: []const u8) QueryError!FileInfo { return as_concrete(context).query(file_path); } }; return .{ .context = @ptrCast(*anyopaque, state), .actions = &.{ .open_readable = Actions.open_readable, .open_writable = Actions.open_writable, .query = Actions.query, }, }; } pub fn open_readable(self: FileAccessor, file_path: []const u8) OpenError!*ReadableFile { return self.actions.open_readable(self.context, file_path); } pub fn open_writable(self: FileAccessor, file_path: []const u8) OpenError!*WritableFile { return self.actions.open_readable(self.context, file_path); } pub fn query(self: FileAccessor, file_path: []const u8) QueryError!FileInfo { return self.actions.query(self.context, file_path); } }; pub const FileInfo = struct { size: u64, }; pub const FileSandbox = struct { prefix: []const u8, flags: packed struct { is_readable: bool = false, is_writable: bool = false, is_queryable: bool = false, }, const native_path_max = 4095; fn native_path_of(file_sandbox: *FileSandbox, file_path: []const u8) [native_path_max + 1]u8 { var native_path = [_]u8{0} ** (native_path_max + 1); if ((file_sandbox.prefix.len + file_path.len) < native_path_max) { coral.io.copy(&native_path, file_sandbox.prefix); coral.io.copy(native_path[file_sandbox.prefix.len ..], file_path); } return native_path; } pub fn open_readable(file_sandbox: *FileSandbox, file_path: []const u8) OpenError!*ReadableFile { if (!file_sandbox.flags.is_readable) return error.AccessDenied; return @ptrCast(*ReadableFile, ext.SDL_RWFromFile(&file_sandbox.native_path_of(file_path), "rb") orelse { return error.FileNotFound; }); } pub fn open_writable(file_sandbox: *FileSandbox, file_path: []const u8) OpenError!*WritableFile { if (!file_sandbox.flags.is_writable) return error.AccessDenied; return @ptrCast(*WritableFile, ext.SDL_RWFromFile(&file_sandbox.native_path_of(file_path), "wb") orelse { return error.FileNotFound; }); } pub fn query(file_sandbox: *FileSandbox, file_path: []const u8) QueryError!FileInfo { if (!file_sandbox.flags.is_queryable) return error.AccessDenied; const rw_ops = ext.SDL_RWFromFile(&file_sandbox.native_path_of(file_path), "rb") orelse { return error.FileNotFound; }; defer _ = ext.SDL_RWclose(rw_ops); const file_size = ext.SDL_RWsize(rw_ops); if (file_size < 0) return error.FileNotFound; return FileInfo{ .size = @intCast(u64, file_size), }; } }; pub const OpenError = QueryError || error {TooManyFiles}; pub const ReadableFile = opaque { pub fn as_reader(self: *ReadableFile) coral.io.Reader { return coral.io.Reader.bind(self, read); } fn as_rw_ops(self: *ReadableFile) *ext.SDL_RWops { return @ptrCast(*ext.SDL_RWops, @alignCast(@alignOf(ext.SDL_RWops), self)); } pub fn close(self: *ReadableFile) bool { return ext.SDL_RWclose(self.as_rw_ops()) != 0; } pub fn read(self: *ReadableFile, buffer: []u8) coral.io.ReadError!usize { ext.SDL_ClearError(); const buffer_read = ext.SDL_RWread(self.as_rw_ops(), buffer.ptr, @sizeOf(u8), buffer.len); if ((buffer_read == 0) and (ext.SDL_GetError().* != 0)) return error.IoUnavailable; return buffer_read; } pub fn rewind(self: *ReadableFile) SeekError!void { return self.seek(0); } pub fn seek(self: *ReadableFile, absolute: u64) SeekError!void { ext.SDL_ClearError(); // TODO: Fix int cast. const sought = ext.SDL_RWseek(self.as_rw_ops(), @intCast(i64, absolute), ext.RW_SEEK_SET); if ((sought == -1) and (ext.SDL_GetError().* != 0)) return error.IoUnavailable; } }; pub const QueryError = error { FileNotFound, AccessDenied, }; pub const SeekError = error { IoUnavailable, }; pub const WritableFile = opaque { pub fn as_writer(self: *WritableFile) coral.io.Writer { return coral.io.Writer.bind(WritableFile, self); } fn as_rw_ops(self: *WritableFile) *ext.SDL_RWops { return @ptrCast(*ext.SDL_RWops, @alignCast(@alignOf(ext.SDL_RWops), self)); } pub fn close(self: *WritableFile) bool { return ext.SDL_RWclose(self.as_rw_ops()) != 0; } pub fn rewind(self: *WritableFile) SeekError!void { return self.seek(0); } pub fn seek(self: *WritableFile, absolute: u64) SeekError!void { ext.SDL_ClearError(); // TODO: Fix int cast. const sought = ext.SDL_RWseek(self.as_rw_ops(), @intCast(i64, absolute), ext.RW_SEEK_SET); if ((sought == -1) and (ext.SDL_GetError().* != 0)) return error.IoUnavailable; } pub fn write(self: *WritableFile, buffer: []const u8) coral.io.WriteError!usize { ext.SDL_ClearError(); const buffer_read = ext.SDL_RWwrite(self.as_rw_ops(), buffer.ptr, @sizeOf(u8), buffer.len); if ((buffer_read == 0) and (ext.SDL_GetError().* != 0)) return error.IoUnavailable; return buffer_read; } };