use super::*; pub struct DirectoryListing { children: Vec, length: u32, selected: Option, name_buffer: CircularPathBuffer, } impl DirectoryListing { pub fn from_path(path: &Path, base: &Path) -> Result { macro_rules! continue_on_err { ($result:expr) => { match $result { Ok(v) => v, Err(_) => continue} }; } let mut children = Vec::new(); if let Ok(iter_dir) = std::fs::read_dir(path) { for (i, entry_result) in iter_dir.enumerate() { if i == u16::MAX as usize { eprintln!("Warning, {path:?} contains more than 65536 entries.") }; let entry = continue_on_err!(entry_result); let path = continue_on_err!(remove_base(&entry.path(), &base)); let byte_path = path.as_os_str().as_encoded_bytes(); if byte_path.len() > 255 { continue; }; if filename_dot_prefixed(&path) { continue; } let metadata = continue_on_err!(std::fs::metadata(&entry.path())); children.push( DirectoryChild { byte_path: Vec::from(byte_path), entry_type: if metadata.is_file() { EntryType::File } else if metadata.is_dir() { EntryType::Directory } else { continue; }, } ) } children.sort_unstable(); let length = u32::try_from(children.len()).unwrap_or(u32::MAX); let selected = None; let name_buffer = CircularPathBuffer::new(); Ok(Self { children, length, selected, name_buffer } ) } else { Err(()) } } pub fn get(&self, index: u32) -> Option<&DirectoryChild> { self.children.get(index as usize) } pub fn length(&self) -> u32 { self.length } pub fn selected(&self) -> u32 { self.selected.unwrap_or(0) } pub fn set_selected(&mut self, index: u32) { if let Some(info) = self.get(index) { self.name_buffer.populate(&info.byte_path.clone()); self.selected = Some(index); } else { self.name_buffer.clear(); self.selected = None; } } pub fn child_name(&mut self) -> &mut CircularPathBuffer { &mut self.name_buffer } pub fn child_type(&self) -> Option { self.selected.and_then(|s| self.get(s).and_then(|i| Some(i.entry_type))) } pub fn child_path(&self) -> Option { self.selected.and_then(|s| self.get(s).and_then(|i| { Some(bytes_to_path(&i.byte_path)) })) } } pub fn remove_base(absolute_path: &Path, base_path: &Path) -> Result { if let Ok(relative) = absolute_path.strip_prefix(base_path) { let mut baseless_path = PathBuf::from("/"); for component in relative.components() { match component { Component::Normal(s) => baseless_path.push(s), Component::ParentDir => return Err(()), Component::CurDir => continue, Component::RootDir => continue, Component::Prefix(_) => continue, } } return Ok(baseless_path); } return Err(()); } // Returns true if a dot character directly follows the right-most // forward-slash character in the path. fn filename_dot_prefixed(path: &Path) -> bool { let bytes = path.as_os_str().as_encoded_bytes(); // Find position of final forward-slash byte. let mut final_slash = None; for (i, byte) in bytes.iter().enumerate() { if *byte == b'/' { final_slash = Some(i) } } if let Some(i) = final_slash { if let Some(b'.') = bytes.get(i+1) { return true; } } return false; }