use std::cmp;
use std::io::{BufRead, ErrorKind, Read, Seek, SeekFrom, Write};
use crate::builtin::{GString, PackedByteArray, PackedStringArray, Variant, real};
use crate::classes::FileAccess;
use crate::classes::file_access::{CompressionMode, ModeFlags};
use crate::global::Error;
use crate::meta::error::IoError;
use crate::meta::{AsArg, arg_into_ref};
use crate::obj::Gd;
pub struct GFile {
fa: Gd<FileAccess>,
buffer: Vec<u8>,
last_buffer_size: usize,
write_buffer: PackedByteArray,
file_length: Option<u64>,
}
impl GFile {
const BUFFER_SIZE: usize = 4096;
pub fn open(path: impl AsArg<GString>, flags: ModeFlags) -> std::io::Result<Self> {
arg_into_ref!(path);
let fa = FileAccess::open(path, flags).ok_or_else(|| {
std::io::Error::other(format!(
"can't open file {path} in mode {flags:?}; GodotError: {:?}",
FileAccess::get_open_error()
))
})?;
Ok(Self::from_inner(fa))
}
pub fn open_compressed(
path: impl AsArg<GString>,
flags: ModeFlags,
compression_mode: CompressionMode,
) -> std::io::Result<Self> {
arg_into_ref!(path);
let fa = FileAccess::open_compressed_ex(path, flags)
.compression_mode(compression_mode)
.done()
.ok_or_else(|| {
std::io::Error::other(format!(
"can't open file {path} in mode {flags:?}; GodotError: {:?}",
FileAccess::get_open_error()
))
})?;
Ok(Self::from_inner(fa))
}
pub fn open_encrypted(
path: impl AsArg<GString>,
flags: ModeFlags,
key: &PackedByteArray,
) -> std::io::Result<Self> {
arg_into_ref!(path);
let fa = FileAccess::open_encrypted(path, flags, key).ok_or_else(|| {
std::io::Error::other(format!(
"can't open file {path} in mode {flags:?}; GodotError: {:?}",
FileAccess::get_open_error()
))
})?;
Ok(Self::from_inner(fa))
}
pub fn open_encrypted_with_pass(
path: impl AsArg<GString>,
flags: ModeFlags,
password: impl AsArg<GString>,
) -> std::io::Result<Self> {
arg_into_ref!(path);
arg_into_ref!(password);
let fa = FileAccess::open_encrypted_with_pass(path, flags, password).ok_or_else(|| {
std::io::Error::other(format!(
"can't open file {path} in mode {flags:?}; GodotError: {:?}",
FileAccess::get_open_error()
))
})?;
Ok(Self::from_inner(fa))
}
pub fn try_from_unique(file_access: Gd<FileAccess>) -> Result<Self, IoError> {
let file_access = IoError::check_unique_open_file_access(file_access)?;
Ok(Self::from_inner(file_access))
}
pub fn into_inner(self) -> Gd<FileAccess> {
self.fa
}
#[doc(alias = "get_modified_time")]
pub fn modified_time(path: impl AsArg<GString>) -> std::io::Result<u64> {
arg_into_ref!(path);
let modified_time = FileAccess::get_modified_time(path);
if modified_time == 0 {
Err(std::io::Error::other(format!(
"can't retrieve last modified time: {path}"
)))
} else {
Ok(modified_time)
}
}
#[doc(alias = "get_md5")]
pub fn md5(path: impl AsArg<GString>) -> std::io::Result<GString> {
arg_into_ref!(path);
let md5 = FileAccess::get_md5(path);
if md5.is_empty() {
Err(std::io::Error::other(format!(
"failed to compute file's MD5 checksum: {path}"
)))
} else {
Ok(md5)
}
}
#[doc(alias = "get_sha256")]
pub fn sha256(path: impl AsArg<GString>) -> std::io::Result<GString> {
arg_into_ref!(path);
let sha256 = FileAccess::get_sha256(path);
if sha256.is_empty() {
Err(std::io::Error::other(format!(
"failed to compute file's SHA-256 checksum: {path}"
)))
} else {
Ok(sha256)
}
}
#[doc(alias = "get_8")]
pub fn read_u8(&mut self) -> std::io::Result<u8> {
let val = self.fa.get_8();
self.check_error()?;
Ok(val)
}
#[doc(alias = "get_16")]
pub fn read_u16(&mut self) -> std::io::Result<u16> {
let val = self.fa.get_16();
self.check_error()?;
Ok(val)
}
#[doc(alias = "get_32")]
pub fn read_u32(&mut self) -> std::io::Result<u32> {
let val = self.fa.get_32();
self.check_error()?;
Ok(val)
}
#[doc(alias = "get_64")]
pub fn read_u64(&mut self) -> std::io::Result<u64> {
let val = self.fa.get_64();
self.check_error()?;
Ok(val)
}
#[doc(alias = "get_pascal_string")]
pub fn read_pascal_string(&mut self) -> std::io::Result<GString> {
let val = self.fa.get_pascal_string();
self.check_error()?;
Ok(val)
}
#[doc(alias = "get_line")]
pub fn read_gstring_line(&mut self) -> std::io::Result<GString> {
let val = self.fa.get_line();
self.check_error()?;
Ok(val)
}
#[doc(alias = "get_as_text")]
pub fn read_as_gstring_entire(&mut self) -> std::io::Result<GString> {
let val = self.fa.get_as_text();
self.check_error()?;
Ok(val)
}
#[doc(alias = "get_csv_line")]
pub fn read_csv_line(
&mut self,
delim: impl AsArg<GString>,
) -> std::io::Result<PackedStringArray> {
arg_into_ref!(delim);
let val = self.fa.get_csv_line_ex().delim(delim).done();
self.check_error()?;
Ok(val)
}
#[doc(alias = "get_float")]
pub fn read_f32(&mut self) -> std::io::Result<f32> {
let val = self.fa.get_float();
self.check_error()?;
Ok(val)
}
#[doc(alias = "get_double")]
pub fn read_f64(&mut self) -> std::io::Result<f64> {
let val = self.fa.get_double();
self.check_error()?;
Ok(val)
}
#[doc(alias = "get_real")]
pub fn read_real(&mut self) -> std::io::Result<real> {
#[cfg(feature = "double-precision")] #[cfg_attr(published_docs, doc(cfg(feature = "double-precision")))]
let val = self.fa.get_double();
#[cfg(not(feature = "double-precision"))] #[cfg_attr(published_docs, doc(cfg(not(feature = "double-precision"))))]
let val = self.fa.get_float();
self.check_error()?;
Ok(val)
}
#[doc(alias = "get_var")]
pub fn read_variant(&mut self, allow_objects: bool) -> std::io::Result<Variant> {
let val = self.fa.get_var_ex().allow_objects(allow_objects).done();
self.check_error()?;
Ok(val)
}
#[doc(alias = "store_8")]
pub fn write_u8(&mut self, value: u8) -> std::io::Result<()> {
self.fa.store_8(value);
self.clear_file_length();
self.check_error()?;
Ok(())
}
#[doc(alias = "store_16")]
pub fn write_u16(&mut self, value: u16) -> std::io::Result<()> {
self.fa.store_16(value);
self.clear_file_length();
self.check_error()?;
Ok(())
}
#[doc(alias = "store_32")]
pub fn write_u32(&mut self, value: u32) -> std::io::Result<()> {
self.fa.store_32(value);
self.clear_file_length();
self.check_error()?;
Ok(())
}
#[doc(alias = "store_64")]
pub fn write_u64(&mut self, value: u64) -> std::io::Result<()> {
self.fa.store_64(value);
self.clear_file_length();
self.check_error()?;
Ok(())
}
#[doc(alias = "store_float")]
pub fn write_f32(&mut self, value: f32) -> std::io::Result<()> {
self.fa.store_float(value);
self.clear_file_length();
self.check_error()?;
Ok(())
}
#[doc(alias = "store_double")]
pub fn write_f64(&mut self, value: f64) -> std::io::Result<()> {
self.fa.store_double(value);
self.clear_file_length();
self.check_error()?;
Ok(())
}
#[doc(alias = "store_real")]
pub fn write_real(&mut self, value: real) -> std::io::Result<()> {
#[cfg(feature = "double-precision")] #[cfg_attr(published_docs, doc(cfg(feature = "double-precision")))]
self.fa.store_double(value);
#[cfg(not(feature = "double-precision"))] #[cfg_attr(published_docs, doc(cfg(not(feature = "double-precision"))))]
self.fa.store_float(value);
self.clear_file_length();
self.check_error()?;
Ok(())
}
#[doc(alias = "store_string")]
pub fn write_gstring(&mut self, value: impl AsArg<GString>) -> std::io::Result<()> {
arg_into_ref!(value);
self.fa.store_string(value);
self.clear_file_length();
self.check_error()?;
Ok(())
}
#[doc(alias = "store_pascal_string")]
pub fn write_pascal_string(&mut self, value: impl AsArg<GString>) -> std::io::Result<()> {
arg_into_ref!(value);
self.fa.store_pascal_string(value);
self.clear_file_length();
self.check_error()?;
Ok(())
}
#[doc(alias = "store_line")]
pub fn write_gstring_line(&mut self, value: impl AsArg<GString>) -> std::io::Result<()> {
arg_into_ref!(value);
self.fa.store_line(value);
self.clear_file_length();
self.check_error()?;
Ok(())
}
#[doc(alias = "store_csv_line")]
pub fn write_csv_line(
&mut self,
values: &PackedStringArray,
delim: impl AsArg<GString>,
) -> std::io::Result<()> {
arg_into_ref!(delim);
self.fa.store_csv_line_ex(values).delim(delim).done();
self.clear_file_length();
self.check_error()?;
Ok(())
}
#[doc(alias = "store_var")]
pub fn write_variant(&mut self, value: Variant, full_objects: bool) -> std::io::Result<()> {
self.fa
.store_var_ex(&value)
.full_objects(full_objects)
.done();
self.clear_file_length();
self.check_error()?;
Ok(())
}
pub fn set_big_endian(&mut self, value: bool) {
self.fa.set_big_endian(value);
}
pub fn is_big_endian(&self) -> bool {
self.fa.is_big_endian()
}
#[doc(alias = "get_path")]
pub fn path(&self) -> GString {
self.fa.get_path()
}
#[doc(alias = "get_path_absolute")]
pub fn path_absolute(&self) -> GString {
self.fa.get_path_absolute()
}
#[doc(alias = "get_position")]
pub fn position(&self) -> u64 {
self.fa.get_position()
}
#[doc(alias = "get_length")]
pub fn length(&self) -> u64 {
self.fa.get_length()
}
pub fn eof_reached(&self) -> bool {
self.fa.eof_reached()
}
fn check_error(&self) -> Result<(), std::io::Error> {
let error = self.fa.get_error();
if error == Error::OK {
return Ok(());
}
Err(std::io::Error::other(format!("GodotError: {error:?}")))
}
fn check_file_length(&mut self) -> u64 {
if let Some(length) = self.file_length {
return length;
}
let file_length = self.fa.get_length();
self.file_length = Some(file_length);
file_length
}
fn clear_file_length(&mut self) {
self.file_length = None;
}
fn from_inner(fa: Gd<FileAccess>) -> Self {
let file_length = Some(fa.get_length());
Self {
fa,
buffer: vec![0; Self::BUFFER_SIZE],
last_buffer_size: 0,
write_buffer: PackedByteArray::new(),
file_length,
}
}
fn extend_write_buffer(&mut self, len: usize) {
if self.write_buffer.len() >= len {
return;
}
self.write_buffer.resize(len)
}
fn pack_into_write_buffer(&mut self, buf: &[u8]) {
self.extend_write_buffer(buf.len());
let write_slice = self.write_buffer.as_mut_slice();
write_slice[0..buf.len()].copy_from_slice(buf);
}
}
impl Read for GFile {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let length = self.check_file_length();
let position = self.fa.get_position();
if position >= length {
return Ok(0);
}
let remaining_bytes = (length - position) as usize;
let bytes_to_read = cmp::min(buf.len(), remaining_bytes);
if bytes_to_read == 0 {
return Ok(0);
}
let gd_buffer = self.fa.get_buffer(bytes_to_read as i64);
let bytes_read = gd_buffer.len();
buf[0..bytes_read].copy_from_slice(gd_buffer.as_slice());
self.check_error()?;
Ok(bytes_read)
}
}
impl Write for GFile {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.pack_into_write_buffer(buf);
self.fa
.store_buffer(&self.write_buffer.subarray(0..buf.len()));
self.clear_file_length();
self.check_error()?;
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
self.fa.flush();
self.check_error()?;
Ok(())
}
}
impl Seek for GFile {
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
match pos {
SeekFrom::Start(position) => {
self.fa.seek(position);
self.check_error()?;
Ok(position)
}
SeekFrom::End(offset) => {
if (self.check_file_length() as i64) < offset {
return Err(std::io::Error::new(
ErrorKind::InvalidInput,
"Position can't be set before the file beginning",
));
}
self.fa.seek_end_ex().position(offset).done();
self.check_error()?;
Ok(self.fa.get_position())
}
SeekFrom::Current(offset) => {
let new_pos = self.fa.get_position() as i64 + offset;
if new_pos < 0 {
return Err(std::io::Error::new(
ErrorKind::InvalidInput,
"Position can't be set before the file beginning",
));
}
let new_pos = new_pos as u64;
self.fa.seek(new_pos);
self.check_error()?;
Ok(new_pos)
}
}
}
}
impl BufRead for GFile {
fn fill_buf(&mut self) -> std::io::Result<&[u8]> {
let remaining_bytes = self.check_file_length() - self.fa.get_position();
let buffer_read_size = cmp::min(remaining_bytes as usize, Self::BUFFER_SIZE);
self.last_buffer_size = buffer_read_size;
self.buffer = vec![0; Self::BUFFER_SIZE];
let gd_buffer = self.fa.get_buffer(buffer_read_size as i64);
self.check_error()?;
let read_buffer = &mut self.buffer[0..gd_buffer.len()];
read_buffer.copy_from_slice(gd_buffer.as_slice());
Ok(read_buffer)
}
fn consume(&mut self, amt: usize) {
let offset = (self.last_buffer_size - amt) as i64;
let pos = SeekFrom::Current(-offset);
self.seek(pos).expect("failed to consume bytes during read");
}
}