use alloc::{string::String, vec::Vec};
use super::{
bisync,
directory_entry::{DirectoryEntryChain, FileAttributes},
error::ExFatError,
file::{FileDetails, Metadata},
file_system::FileSystem,
io::BlockDevice,
upcase_table::UpcaseTable,
utils::encode_utf16_upcase_and_hash,
};
pub(crate) trait DirectoryEntryFilter {
fn hash(&self, file_name_hash: u16, file_attributes: FileAttributes) -> bool;
fn file_name(&self, file_name: &[u16], upcase_table: &UpcaseTable) -> bool;
}
pub(crate) struct AllPassFilter {}
impl DirectoryEntryFilter for AllPassFilter {
fn hash(&self, _file_name_hash: u16, _file_attributes: FileAttributes) -> bool {
true
}
fn file_name(&self, _file_name: &[u16], _upcase_table: &UpcaseTable) -> bool {
true
}
}
pub(crate) struct ExactNameFilter {
file_name: Vec<u16>,
file_name_hash: u16,
file_attributes: Option<FileAttributes>,
}
impl ExactNameFilter {
pub(crate) fn new(
file_name_str: &str,
upcase_table: &UpcaseTable,
file_attributes: Option<FileAttributes>,
) -> Self {
let (file_name, file_name_hash) = encode_utf16_upcase_and_hash(file_name_str, upcase_table);
Self {
file_name,
file_name_hash,
file_attributes,
}
}
}
impl DirectoryEntryFilter for ExactNameFilter {
fn hash(&self, file_name_hash: u16, file_attributes: FileAttributes) -> bool {
match self.file_attributes {
Some(attributes) => {
self.file_name_hash == file_name_hash && file_attributes.contains(attributes)
}
None => self.file_name_hash == file_name_hash,
}
}
fn file_name(&self, file_name: &[u16], upcase_table: &UpcaseTable) -> bool {
for (left, right) in self.file_name.iter().zip(file_name.iter()) {
let upcased = upcase_table.upcase(*right);
if *left != upcased {
return false;
}
}
true
}
}
#[bisync]
pub(crate) async fn get_leaf_file_entry<D: BlockDevice, const N: usize>(
fs: &mut FileSystem<D, N>,
path: &str,
file_attributes: Option<FileAttributes>,
) -> Result<Option<FileDetails>, ExFatError<D>> {
let mut splits = path
.split(['/', '\\'])
.filter(|part| !part.is_empty())
.map(|c| c.trim())
.peekable();
let mut cluster_id = fs.fs.first_cluster_of_root_dir;
while let Some(part) = splits.next() {
let is_last = splits.peek().is_none();
let attributes = if is_last {
file_attributes
} else {
Some(FileAttributes::Directory)
};
let filter = ExactNameFilter::new(part, &fs.upcase_table, attributes);
let mut entries = DirectoryEntryChain::new(cluster_id, &fs.fs);
let file_details = entries.next_file_entry(fs, &filter).await?;
match file_details {
Some(file_details) => {
if is_last {
return Ok(Some(file_details));
} else {
if file_details.attributes.contains(FileAttributes::Directory) {
cluster_id = file_details.first_cluster
} else {
return Ok(None);
}
}
}
None => return Ok(None),
}
}
Ok(None)
}
fn is_root_directory(path: &str) -> bool {
let mut splits = path
.split(['/', '\\'])
.filter(|part| !part.is_empty())
.map(|c| c.trim())
.peekable();
splits.peek().is_none()
}
#[bisync]
pub(crate) async fn directory_list<D: BlockDevice, const N: usize>(
fs: &mut FileSystem<D, N>,
path: &str,
) -> Result<DirectoryIterator, ExFatError<D>> {
let cluster_id = if is_root_directory(path) {
fs.fs.first_cluster_of_root_dir
} else {
match get_leaf_file_entry(fs, path, Some(FileAttributes::Directory)).await? {
Some(file_details) => {
if file_details.attributes.contains(FileAttributes::Directory) {
file_details.first_cluster
} else {
return Err(ExFatError::DirectoryNotFound);
}
}
None => return Err(ExFatError::DirectoryNotFound),
}
};
let entries = DirectoryEntryChain::new(cluster_id, &fs.fs);
Ok(DirectoryIterator { entries })
}
pub struct DirectoryIterator {
entries: DirectoryEntryChain,
}
#[derive(Debug)]
pub struct DirectoryEntry {
details: FileDetails,
}
impl DirectoryEntry {
pub fn file_name(&self) -> String {
self.details.name.clone()
}
pub fn metadata(&self) -> Metadata {
Metadata {
details: self.details.clone(),
}
}
}
impl DirectoryIterator {
#[bisync]
pub async fn next_entry<D: BlockDevice, const N: usize>(
&mut self,
fs: &mut FileSystem<D, N>,
) -> Result<Option<DirectoryEntry>, ExFatError<D>> {
let filter = AllPassFilter {};
Ok(self
.entries
.next_file_entry(fs, &filter)
.await?
.map(|x| DirectoryEntry { details: x.clone() }))
}
}