1use std::path::PathBuf;
8use std::sync::Arc;
9
10#[derive(Debug, Clone, Copy, Default)]
12pub struct FileExtent {
13 pub sector: u32,
15 pub length: u64,
17}
18
19impl core::fmt::Display for FileExtent {
20 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
21 if self.is_empty() {
22 write!(f, "empty")
23 } else {
24 write!(f, "sector {} ({} bytes)", self.sector, self.length)
25 }
26 }
27}
28
29impl FileExtent {
30 pub fn new(sector: u32, length: u64) -> Self {
32 Self { sector, length }
33 }
34
35 pub fn is_empty(&self) -> bool {
37 self.length == 0
38 }
39
40 pub fn sector_count(&self, sector_size: usize) -> u32 {
42 if self.length == 0 {
43 0
44 } else {
45 ((self.length + sector_size as u64 - 1) / sector_size as u64) as u32
46 }
47 }
48}
49
50pub enum FileData {
52 Buffer(Vec<u8>),
54 Path(PathBuf),
56}
57
58impl std::fmt::Debug for FileData {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 match self {
61 Self::Buffer(b) => write!(f, "Buffer({} bytes)", b.len()),
62 Self::Path(p) => write!(f, "Path({:?})", p),
63 }
64 }
65}
66
67impl core::fmt::Display for FileData {
68 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
69 match self {
70 Self::Buffer(b) => write!(f, "buffer ({} bytes)", b.len()),
71 Self::Path(p) => write!(f, "path ({})", p.display()),
72 }
73 }
74}
75
76impl FileData {
77 pub fn size(&self) -> std::io::Result<u64> {
79 match self {
80 Self::Buffer(b) => Ok(b.len() as u64),
81 Self::Path(p) => Ok(std::fs::metadata(p)?.len()),
82 }
83 }
84
85 pub fn read_all(&self) -> std::io::Result<Vec<u8>> {
87 match self {
88 Self::Buffer(b) => Ok(b.clone()),
89 Self::Path(p) => std::fs::read(p),
90 }
91 }
92}
93
94#[derive(Debug)]
96pub struct FileEntry {
97 pub name: Arc<String>,
99 pub extent: FileExtent,
101 pub data: FileData,
103 pub unique_id: u64,
105}
106
107impl core::fmt::Display for FileEntry {
108 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
109 write!(f, "{}", self.name)
110 }
111}
112
113impl FileEntry {
114 pub fn from_buffer(name: impl Into<String>, data: Vec<u8>) -> Self {
116 Self {
117 name: Arc::new(name.into()),
118 extent: FileExtent::default(),
119 data: FileData::Buffer(data),
120 unique_id: 0,
121 }
122 }
123
124 pub fn from_path(name: impl Into<String>, path: PathBuf) -> Self {
126 Self {
127 name: Arc::new(name.into()),
128 extent: FileExtent::default(),
129 data: FileData::Path(path),
130 unique_id: 0,
131 }
132 }
133
134 pub fn size(&self) -> std::io::Result<u64> {
136 self.data.size()
137 }
138}
139
140#[derive(Debug)]
142pub struct Directory {
143 pub name: Arc<String>,
145 pub files: Vec<FileEntry>,
147 pub subdirs: Vec<Directory>,
149 pub unique_id: u64,
151 pub udf_icb_location: u32,
153 pub iso_extent: FileExtent,
155}
156
157impl core::fmt::Display for Directory {
158 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
159 let name = if self.name.is_empty() { "/" } else { &self.name };
160 write!(f, "{} ({} files, {} subdirs)", name, self.files.len(), self.subdirs.len())
161 }
162}
163
164impl Directory {
165 pub fn new(name: impl Into<String>) -> Self {
167 Self {
168 name: Arc::new(name.into()),
169 files: Vec::new(),
170 subdirs: Vec::new(),
171 unique_id: 0,
172 udf_icb_location: 0,
173 iso_extent: FileExtent::default(),
174 }
175 }
176
177 pub fn root() -> Self {
179 Self::new("")
180 }
181
182 pub fn add_file(&mut self, file: FileEntry) {
184 self.files.push(file);
185 }
186
187 pub fn add_subdir(&mut self, dir: Directory) {
189 self.subdirs.push(dir);
190 }
191
192 pub fn find_file(&self, name: &str) -> Option<&FileEntry> {
194 self.files.iter().find(|f| f.name.as_str() == name)
195 }
196
197 pub fn find_file_mut(&mut self, name: &str) -> Option<&mut FileEntry> {
199 self.files.iter_mut().find(|f| f.name.as_str() == name)
200 }
201
202 pub fn find_subdir(&self, name: &str) -> Option<&Directory> {
204 self.subdirs.iter().find(|d| d.name.as_str() == name)
205 }
206
207 pub fn find_subdir_mut(&mut self, name: &str) -> Option<&mut Directory> {
209 self.subdirs.iter_mut().find(|d| d.name.as_str() == name)
210 }
211
212 pub fn total_files(&self) -> usize {
214 self.files.len() + self.subdirs.iter().map(|d| d.total_files()).sum::<usize>()
215 }
216
217 pub fn total_dirs(&self) -> usize {
219 1 + self.subdirs.iter().map(|d| d.total_dirs()).sum::<usize>()
220 }
221
222 pub fn iter_files(&self) -> Vec<&FileEntry> {
224 let mut result: Vec<&FileEntry> = self.files.iter().collect();
225 for subdir in &self.subdirs {
226 result.extend(subdir.iter_files());
227 }
228 result
229 }
230
231 pub fn sort(&mut self) {
233 self.files.sort_by(|a, b| a.name.cmp(&b.name));
234 self.subdirs.sort_by(|a, b| a.name.cmp(&b.name));
235 for subdir in &mut self.subdirs {
236 subdir.sort();
237 }
238 }
239}
240
241#[derive(Debug)]
243pub struct FileTree {
244 pub root: Directory,
246}
247
248impl core::fmt::Display for FileTree {
249 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
250 write!(f, "{} files, {} directories", self.total_files(), self.total_dirs())
251 }
252}
253
254impl Default for FileTree {
255 fn default() -> Self {
256 Self::new()
257 }
258}
259
260impl FileTree {
261 pub fn new() -> Self {
263 Self {
264 root: Directory::root(),
265 }
266 }
267
268 pub fn add_file(&mut self, file: FileEntry) {
270 self.root.add_file(file);
271 }
272
273 pub fn add_dir(&mut self, dir: Directory) {
275 self.root.add_subdir(dir);
276 }
277
278 pub fn find_file(&self, path: &str) -> Option<&FileEntry> {
280 let parts: Vec<&str> = path.split('/').collect();
281 if parts.is_empty() {
282 return None;
283 }
284
285 let mut current = &self.root;
286 for (i, part) in parts.iter().enumerate() {
287 if i == parts.len() - 1 {
288 return current.find_file(part);
290 } else {
291 current = current.find_subdir(part)?;
293 }
294 }
295 None
296 }
297
298 pub fn total_files(&self) -> usize {
300 self.root.total_files()
301 }
302
303 pub fn total_dirs(&self) -> usize {
305 self.root.total_dirs()
306 }
307
308 pub fn sort(&mut self) {
310 self.root.sort();
311 }
312
313 pub fn from_fs(path: &std::path::Path) -> std::io::Result<Self> {
315 let mut tree = Self::new();
316 tree.root = Self::read_dir_recursive(path)?;
317 tree.root.name = Arc::new(String::new()); Ok(tree)
319 }
320
321 fn read_dir_recursive(path: &std::path::Path) -> std::io::Result<Directory> {
322 let name = path
323 .file_name()
324 .and_then(|n| n.to_str())
325 .unwrap_or("")
326 .to_string();
327
328 let mut dir = Directory::new(name);
329
330 for entry in std::fs::read_dir(path)? {
331 let entry = entry?;
332 let file_type = entry.file_type()?;
333 let entry_name = entry.file_name().to_string_lossy().to_string();
334
335 if file_type.is_file() {
336 dir.add_file(FileEntry::from_path(entry_name, entry.path()));
337 } else if file_type.is_dir() {
338 dir.add_subdir(Self::read_dir_recursive(&entry.path())?);
339 }
340 }
342
343 dir.sort();
345 Ok(dir)
346 }
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352
353 #[test]
354 fn test_file_extent() {
355 let extent = FileExtent::new(100, 4096);
356 assert_eq!(extent.sector, 100);
357 assert_eq!(extent.length, 4096);
358 assert_eq!(extent.sector_count(2048), 2);
359
360 let empty = FileExtent::default();
361 assert!(empty.is_empty());
362 assert_eq!(empty.sector_count(2048), 0);
363 }
364
365 #[test]
366 fn test_directory_tree() {
367 let mut tree = FileTree::new();
368
369 tree.add_file(FileEntry::from_buffer("readme.txt", b"Hello".to_vec()));
371
372 let mut subdir = Directory::new("docs");
374 subdir.add_file(FileEntry::from_buffer(
375 "guide.txt",
376 b"Guide content".to_vec(),
377 ));
378 tree.add_dir(subdir);
379
380 assert_eq!(tree.total_files(), 2);
381 assert_eq!(tree.total_dirs(), 2);
382
383 assert!(tree.find_file("readme.txt").is_some());
385 assert!(tree.find_file("docs/guide.txt").is_some());
386 assert!(tree.find_file("nonexistent.txt").is_none());
387 }
388}