use core::ffi::CStr;
use core::fmt;
use flipperzero_sys as sys;
use crate::furi::string::FuriString;
pub(crate) const DEFAULT_BUF_SIZE: usize = 64;
pub type Result<T> = core::result::Result<T, Error>;
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub enum Error {
NotReady,
Exists,
NotExists,
InvalidParameter,
Denied,
InvalidName,
Internal,
NotImplemented,
AlreadyOpen,
WriteZero,
#[non_exhaustive]
#[doc(hidden)]
Uncategorized(sys::FS_Error),
}
impl Error {
pub fn to_sys(&self) -> Option<sys::FS_Error> {
match self {
Self::NotReady => Some(sys::FSE_NOT_READY),
Self::Exists => Some(sys::FSE_EXIST),
Self::NotExists => Some(sys::FSE_NOT_EXIST),
Self::InvalidParameter => Some(sys::FSE_INVALID_PARAMETER),
Self::Denied => Some(sys::FSE_DENIED),
Self::InvalidName => Some(sys::FSE_INVALID_NAME),
Self::Internal => Some(sys::FSE_INTERNAL),
Self::NotImplemented => Some(sys::FSE_NOT_IMPLEMENTED),
Self::AlreadyOpen => Some(sys::FSE_ALREADY_OPEN),
Self::Uncategorized(error_code) => Some(*error_code),
_ => None,
}
}
pub fn from_sys(err: sys::FS_Error) -> Option<Self> {
match err {
sys::FSE_OK => None,
sys::FSE_NOT_READY => Some(Self::NotReady),
sys::FSE_EXIST => Some(Self::Exists),
sys::FSE_NOT_EXIST => Some(Self::NotExists),
sys::FSE_INVALID_PARAMETER => Some(Self::InvalidParameter),
sys::FSE_DENIED => Some(Self::Denied),
sys::FSE_INVALID_NAME => Some(Self::InvalidName),
sys::FSE_INTERNAL => Some(Self::Internal),
sys::FSE_NOT_IMPLEMENTED => Some(Self::NotImplemented),
sys::FSE_ALREADY_OPEN => Some(Self::AlreadyOpen),
error_code => Some(Self::Uncategorized(error_code)),
}
}
pub fn description(&self) -> &CStr {
unsafe { CStr::from_ptr(sys::filesystem_api_error_get_desc(self.to_sys().unwrap())) }
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.description().to_bytes().escape_ascii().fmt(f)
}
}
impl ufmt::uDisplay for Error {
fn fmt<W>(&self, f: &mut ufmt::Formatter<'_, W>) -> core::result::Result<(), W::Error>
where
W: ufmt::uWrite + ?Sized,
{
for c in self.description().to_bytes().escape_ascii() {
f.write_char(c as char)?;
}
Ok(())
}
}
pub trait Read {
fn read(&mut self, buf: &mut [u8]) -> Result<usize>;
fn read_to_string(&mut self, string: &mut FuriString) -> Result<usize> {
default_read_to_string(self, string)
}
}
pub(crate) fn default_read_to_string<R: Read + ?Sized>(
r: &mut R,
string: &mut FuriString,
) -> Result<usize> {
let mut total_bytes_read = 0;
let mut buf = [0u8; DEFAULT_BUF_SIZE];
loop {
let bytes_read = r.read(&mut buf)?;
if bytes_read == 0 {
break;
}
total_bytes_read += bytes_read;
for ch in buf[0..bytes_read].iter().copied() {
string.push(ch as char);
}
}
Ok(total_bytes_read)
}
pub trait Seek {
fn seek(&mut self, pos: SeekFrom) -> Result<usize>;
fn rewind(&mut self) -> Result<()> {
self.seek(SeekFrom::Start(0))?;
Ok(())
}
fn stream_len(&mut self) -> Result<usize> {
let old_pos = self.stream_position()?;
let len = self.seek(SeekFrom::End(0))?;
if old_pos != len {
self.seek(SeekFrom::Start(
old_pos.try_into().map_err(|_| Error::InvalidParameter)?,
))?;
}
Ok(len)
}
fn stream_position(&mut self) -> Result<usize> {
self.seek(SeekFrom::Current(0))
}
}
pub trait Write {
fn write(&mut self, buf: &[u8]) -> Result<usize>;
fn flush(&mut self) -> Result<()>;
fn write_all(&mut self, mut buf: &[u8]) -> Result<()> {
while !buf.is_empty() {
match self.write(buf) {
Ok(0) => return Err(Error::WriteZero),
Ok(n) => buf = &buf[n..],
Err(e) => return Err(e),
}
}
Ok(())
}
}
pub enum SeekFrom {
Start(u64),
End(i64),
Current(i64),
}