use std::path::PathBuf;
use std::sync::Arc;
#[derive(Debug, Clone, Copy, Default)]
pub struct FileExtent {
pub sector: u32,
pub length: u64,
}
impl core::fmt::Display for FileExtent {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if self.is_empty() {
write!(f, "empty")
} else {
write!(f, "sector {} ({} bytes)", self.sector, self.length)
}
}
}
impl FileExtent {
pub fn new(sector: u32, length: u64) -> Self {
Self { sector, length }
}
pub fn is_empty(&self) -> bool {
self.length == 0
}
pub fn sector_count(&self, sector_size: usize) -> u32 {
if self.length == 0 {
0
} else {
self.length.div_ceil(sector_size as u64) as u32
}
}
}
pub enum FileData {
Buffer(Vec<u8>),
Path(PathBuf),
}
impl std::fmt::Debug for FileData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Buffer(b) => write!(f, "Buffer({} bytes)", b.len()),
Self::Path(p) => write!(f, "Path({:?})", p),
}
}
}
impl core::fmt::Display for FileData {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Buffer(b) => write!(f, "buffer ({} bytes)", b.len()),
Self::Path(p) => write!(f, "path ({})", p.display()),
}
}
}
impl FileData {
pub fn size(&self) -> std::io::Result<u64> {
match self {
Self::Buffer(b) => Ok(b.len() as u64),
Self::Path(p) => Ok(std::fs::metadata(p)?.len()),
}
}
pub fn read_all(&self) -> std::io::Result<Vec<u8>> {
match self {
Self::Buffer(b) => Ok(b.clone()),
Self::Path(p) => std::fs::read(p),
}
}
}
#[derive(Debug)]
pub struct FileEntry {
pub name: Arc<String>,
pub extent: FileExtent,
pub data: FileData,
pub unique_id: u64,
}
impl core::fmt::Display for FileEntry {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.name)
}
}
impl FileEntry {
pub fn from_buffer(name: impl Into<String>, data: Vec<u8>) -> Self {
Self {
name: Arc::new(name.into()),
extent: FileExtent::default(),
data: FileData::Buffer(data),
unique_id: 0,
}
}
pub fn from_path(name: impl Into<String>, path: PathBuf) -> Self {
Self {
name: Arc::new(name.into()),
extent: FileExtent::default(),
data: FileData::Path(path),
unique_id: 0,
}
}
pub fn size(&self) -> std::io::Result<u64> {
self.data.size()
}
}
#[derive(Debug)]
pub struct Directory {
pub name: Arc<String>,
pub files: Vec<FileEntry>,
pub subdirs: Vec<Directory>,
pub unique_id: u64,
pub udf_icb_location: u32,
pub iso_extent: FileExtent,
}
impl core::fmt::Display for Directory {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let name = if self.name.is_empty() {
"/"
} else {
&self.name
};
write!(
f,
"{} ({} files, {} subdirs)",
name,
self.files.len(),
self.subdirs.len()
)
}
}
impl Directory {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: Arc::new(name.into()),
files: Vec::new(),
subdirs: Vec::new(),
unique_id: 0,
udf_icb_location: 0,
iso_extent: FileExtent::default(),
}
}
pub fn root() -> Self {
Self::new("")
}
pub fn add_file(&mut self, file: FileEntry) {
self.files.push(file);
}
pub fn add_subdir(&mut self, dir: Directory) {
self.subdirs.push(dir);
}
pub fn find_file(&self, name: &str) -> Option<&FileEntry> {
self.files.iter().find(|f| f.name.as_str() == name)
}
pub fn find_file_mut(&mut self, name: &str) -> Option<&mut FileEntry> {
self.files.iter_mut().find(|f| f.name.as_str() == name)
}
pub fn find_subdir(&self, name: &str) -> Option<&Directory> {
self.subdirs.iter().find(|d| d.name.as_str() == name)
}
pub fn find_subdir_mut(&mut self, name: &str) -> Option<&mut Directory> {
self.subdirs.iter_mut().find(|d| d.name.as_str() == name)
}
pub fn total_files(&self) -> usize {
self.files.len() + self.subdirs.iter().map(|d| d.total_files()).sum::<usize>()
}
pub fn total_dirs(&self) -> usize {
1 + self.subdirs.iter().map(|d| d.total_dirs()).sum::<usize>()
}
pub fn iter_files(&self) -> Vec<&FileEntry> {
let mut result: Vec<&FileEntry> = self.files.iter().collect();
for subdir in &self.subdirs {
result.extend(subdir.iter_files());
}
result
}
pub fn sort(&mut self) {
self.files.sort_by(|a, b| a.name.cmp(&b.name));
self.subdirs.sort_by(|a, b| a.name.cmp(&b.name));
for subdir in &mut self.subdirs {
subdir.sort();
}
}
}
#[derive(Debug)]
pub struct FileTree {
pub root: Directory,
}
impl core::fmt::Display for FileTree {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"{} files, {} directories",
self.total_files(),
self.total_dirs()
)
}
}
impl Default for FileTree {
fn default() -> Self {
Self::new()
}
}
impl FileTree {
pub fn new() -> Self {
Self {
root: Directory::root(),
}
}
pub fn add_file(&mut self, file: FileEntry) {
self.root.add_file(file);
}
pub fn add_dir(&mut self, dir: Directory) {
self.root.add_subdir(dir);
}
pub fn find_file(&self, path: &str) -> Option<&FileEntry> {
let parts: Vec<&str> = path.split('/').collect();
if parts.is_empty() {
return None;
}
let mut current = &self.root;
for (i, part) in parts.iter().enumerate() {
if i == parts.len() - 1 {
return current.find_file(part);
} else {
current = current.find_subdir(part)?;
}
}
None
}
pub fn total_files(&self) -> usize {
self.root.total_files()
}
pub fn total_dirs(&self) -> usize {
self.root.total_dirs()
}
pub fn sort(&mut self) {
self.root.sort();
}
pub fn from_fs(path: &std::path::Path) -> std::io::Result<Self> {
let mut tree = Self::new();
tree.root = Self::read_dir_recursive(path)?;
tree.root.name = Arc::new(String::new()); Ok(tree)
}
fn read_dir_recursive(path: &std::path::Path) -> std::io::Result<Directory> {
let name = path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("")
.to_string();
let mut dir = Directory::new(name);
for entry in std::fs::read_dir(path)? {
let entry = entry?;
let file_type = entry.file_type()?;
let entry_name = entry.file_name().to_string_lossy().to_string();
if file_type.is_file() {
dir.add_file(FileEntry::from_path(entry_name, entry.path()));
} else if file_type.is_dir() {
dir.add_subdir(Self::read_dir_recursive(&entry.path())?);
}
}
dir.sort();
Ok(dir)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_file_extent() {
let extent = FileExtent::new(100, 4096);
assert_eq!(extent.sector, 100);
assert_eq!(extent.length, 4096);
assert_eq!(extent.sector_count(2048), 2);
let empty = FileExtent::default();
assert!(empty.is_empty());
assert_eq!(empty.sector_count(2048), 0);
}
#[test]
fn test_directory_tree() {
let mut tree = FileTree::new();
tree.add_file(FileEntry::from_buffer("readme.txt", b"Hello".to_vec()));
let mut subdir = Directory::new("docs");
subdir.add_file(FileEntry::from_buffer(
"guide.txt",
b"Guide content".to_vec(),
));
tree.add_dir(subdir);
assert_eq!(tree.total_files(), 2);
assert_eq!(tree.total_dirs(), 2);
assert!(tree.find_file("readme.txt").is_some());
assert!(tree.find_file("docs/guide.txt").is_some());
assert!(tree.find_file("nonexistent.txt").is_none());
}
}