#![deny(missing_docs)]
extern crate alloc;
use alloc::string::String;
use alloc::vec::Vec;
use core::cmp::min;
use core::ops::Range;
use core_io::io::{Error, ErrorKind, Read, Result, Seek, SeekFrom, Write};
use fatfs::{Dir, FileSystem, FsOptions};
use rlvgl_core::fs::BlockDevice;
pub struct FatfsBlockStream<'a, BD: BlockDevice> {
bd: &'a mut BD,
pos: u64,
total: u64,
sector: usize,
buf: Vec<u8>,
buf_lba: u64,
}
impl<'a, BD: BlockDevice> FatfsBlockStream<'a, BD> {
pub fn new(bd: &'a mut BD) -> Self {
let sector = bd.block_size();
Self {
bd,
pos: 0,
total: (bd.num_blocks() as u64) * (sector as u64),
sector,
buf: alloc::vec![0u8; sector],
buf_lba: u64::MAX, }
}
fn cur_lba_off(&self) -> (u64, usize) {
let lba = self.pos / (self.sector as u64);
let off = (self.pos % (self.sector as u64)) as usize;
(lba, off)
}
fn load_lba(&mut self, lba: u64) -> Result<()> {
if self.buf_lba == lba {
return Ok(());
}
self.bd
.read_blocks(lba, &mut self.buf)
.map_err(|_| Error::from(ErrorKind::Other))?;
self.buf_lba = lba;
Ok(())
}
}
impl<'a, BD: BlockDevice> Read for FatfsBlockStream<'a, BD> {
fn read(&mut self, mut out: &mut [u8]) -> Result<usize> {
if self.pos >= self.total {
return Ok(0);
}
let mut read_total = 0usize;
while !out.is_empty() && self.pos < self.total {
let (lba, off) = self.cur_lba_off();
self.load_lba(lba)?;
let avail = min(self.sector - off, out.len());
let tail = min(avail as u64, self.total - self.pos) as usize;
out[..tail].copy_from_slice(&self.buf[off..off + tail]);
out = &mut out[tail..];
self.pos += tail as u64;
read_total += tail;
}
Ok(read_total)
}
}
impl<'a, BD: BlockDevice> Seek for FatfsBlockStream<'a, BD> {
fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
let new_pos = match pos {
SeekFrom::Start(n) => n,
SeekFrom::End(off) => {
if off >= 0 {
self.total.saturating_add(off as u64)
} else {
self.total.saturating_sub((-off) as u64)
}
}
SeekFrom::Current(off) => {
if off >= 0 {
self.pos.saturating_add(off as u64)
} else {
self.pos.saturating_sub((-off) as u64)
}
}
};
self.pos = new_pos.min(self.total);
Ok(self.pos)
}
}
impl<'a, BD: BlockDevice> Write for FatfsBlockStream<'a, BD> {
fn write(&mut self, buf: &[u8]) -> Result<usize> {
if buf.is_empty() {
return Ok(0);
}
let mut written = 0usize;
while written < buf.len() && self.pos < self.total {
let (lba, off) = self.cur_lba_off();
self.load_lba(lba)?;
let avail = min(self.sector - off, buf.len() - written);
let tail = min(avail as u64, self.total - self.pos) as usize;
self.buf[off..off + tail].copy_from_slice(&buf[written..written + tail]);
self.bd
.write_blocks(lba, &self.buf)
.map_err(|_| Error::from(ErrorKind::Other))?;
self.buf_lba = lba;
self.pos += tail as u64;
written += tail;
}
Ok(written)
}
fn flush(&mut self) -> Result<()> {
self.bd.flush().map_err(|_| Error::from(ErrorKind::Other))
}
}
pub fn mount_and_list_assets<BD: BlockDevice>(bd: &mut BD) -> Result<Vec<String>> {
let stream = FatfsBlockStream::new(bd);
let fs = FileSystem::new(stream, FsOptions::new().read_only(true))
.map_err(|_| Error::from(ErrorKind::Other))?;
let root = fs.root_dir();
let mut out: Vec<String> = Vec::new();
let mut list_dir = |d: Dir<_>| -> Result<()> {
for r in d.iter() {
let e = r.map_err(|_| Error::from(ErrorKind::Other))?;
out.push(e.file_name().to_string());
}
Ok(())
};
match root.open_dir("assets") {
Ok(dir) => list_dir(dir)?,
Err(_) => list_dir(root)?,
}
Ok(out)
}