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.div_ceil(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() {
160 "/"
161 } else {
162 &self.name
163 };
164 write!(
165 f,
166 "{} ({} files, {} subdirs)",
167 name,
168 self.files.len(),
169 self.subdirs.len()
170 )
171 }
172}
173
174impl Directory {
175 pub fn new(name: impl Into<String>) -> Self {
177 Self {
178 name: Arc::new(name.into()),
179 files: Vec::new(),
180 subdirs: Vec::new(),
181 unique_id: 0,
182 udf_icb_location: 0,
183 iso_extent: FileExtent::default(),
184 }
185 }
186
187 pub fn root() -> Self {
189 Self::new("")
190 }
191
192 pub fn add_file(&mut self, file: FileEntry) {
194 self.files.push(file);
195 }
196
197 pub fn add_subdir(&mut self, dir: Directory) {
199 self.subdirs.push(dir);
200 }
201
202 pub fn find_file(&self, name: &str) -> Option<&FileEntry> {
204 self.files.iter().find(|f| f.name.as_str() == name)
205 }
206
207 pub fn find_file_mut(&mut self, name: &str) -> Option<&mut FileEntry> {
209 self.files.iter_mut().find(|f| f.name.as_str() == name)
210 }
211
212 pub fn find_subdir(&self, name: &str) -> Option<&Directory> {
214 self.subdirs.iter().find(|d| d.name.as_str() == name)
215 }
216
217 pub fn find_subdir_mut(&mut self, name: &str) -> Option<&mut Directory> {
219 self.subdirs.iter_mut().find(|d| d.name.as_str() == name)
220 }
221
222 pub fn total_files(&self) -> usize {
224 self.files.len() + self.subdirs.iter().map(|d| d.total_files()).sum::<usize>()
225 }
226
227 pub fn total_dirs(&self) -> usize {
229 1 + self.subdirs.iter().map(|d| d.total_dirs()).sum::<usize>()
230 }
231
232 pub fn iter_files(&self) -> Vec<&FileEntry> {
234 let mut result: Vec<&FileEntry> = self.files.iter().collect();
235 for subdir in &self.subdirs {
236 result.extend(subdir.iter_files());
237 }
238 result
239 }
240
241 pub fn sort(&mut self) {
243 self.files.sort_by(|a, b| a.name.cmp(&b.name));
244 self.subdirs.sort_by(|a, b| a.name.cmp(&b.name));
245 for subdir in &mut self.subdirs {
246 subdir.sort();
247 }
248 }
249}
250
251#[derive(Debug)]
253pub struct FileTree {
254 pub root: Directory,
256}
257
258impl core::fmt::Display for FileTree {
259 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
260 write!(
261 f,
262 "{} files, {} directories",
263 self.total_files(),
264 self.total_dirs()
265 )
266 }
267}
268
269impl Default for FileTree {
270 fn default() -> Self {
271 Self::new()
272 }
273}
274
275impl FileTree {
276 pub fn new() -> Self {
278 Self {
279 root: Directory::root(),
280 }
281 }
282
283 pub fn add_file(&mut self, file: FileEntry) {
285 self.root.add_file(file);
286 }
287
288 pub fn add_dir(&mut self, dir: Directory) {
290 self.root.add_subdir(dir);
291 }
292
293 pub fn find_file(&self, path: &str) -> Option<&FileEntry> {
295 let parts: Vec<&str> = path.split('/').collect();
296 if parts.is_empty() {
297 return None;
298 }
299
300 let mut current = &self.root;
301 for (i, part) in parts.iter().enumerate() {
302 if i == parts.len() - 1 {
303 return current.find_file(part);
305 } else {
306 current = current.find_subdir(part)?;
308 }
309 }
310 None
311 }
312
313 pub fn total_files(&self) -> usize {
315 self.root.total_files()
316 }
317
318 pub fn total_dirs(&self) -> usize {
320 self.root.total_dirs()
321 }
322
323 pub fn sort(&mut self) {
325 self.root.sort();
326 }
327
328 pub fn from_fs(path: &std::path::Path) -> std::io::Result<Self> {
330 let mut tree = Self::new();
331 tree.root = Self::read_dir_recursive(path)?;
332 tree.root.name = Arc::new(String::new()); Ok(tree)
334 }
335
336 fn read_dir_recursive(path: &std::path::Path) -> std::io::Result<Directory> {
337 let name = path
338 .file_name()
339 .and_then(|n| n.to_str())
340 .unwrap_or("")
341 .to_string();
342
343 let mut dir = Directory::new(name);
344
345 for entry in std::fs::read_dir(path)? {
346 let entry = entry?;
347 let file_type = entry.file_type()?;
348 let entry_name = entry.file_name().to_string_lossy().to_string();
349
350 if file_type.is_file() {
351 dir.add_file(FileEntry::from_path(entry_name, entry.path()));
352 } else if file_type.is_dir() {
353 dir.add_subdir(Self::read_dir_recursive(&entry.path())?);
354 }
355 }
357
358 dir.sort();
360 Ok(dir)
361 }
362}
363
364#[cfg(test)]
365mod tests {
366 use super::*;
367
368 #[test]
369 fn test_file_extent() {
370 let extent = FileExtent::new(100, 4096);
371 assert_eq!(extent.sector, 100);
372 assert_eq!(extent.length, 4096);
373 assert_eq!(extent.sector_count(2048), 2);
374
375 let empty = FileExtent::default();
376 assert!(empty.is_empty());
377 assert_eq!(empty.sector_count(2048), 0);
378 }
379
380 #[test]
381 fn test_directory_tree() {
382 let mut tree = FileTree::new();
383
384 tree.add_file(FileEntry::from_buffer("readme.txt", b"Hello".to_vec()));
386
387 let mut subdir = Directory::new("docs");
389 subdir.add_file(FileEntry::from_buffer(
390 "guide.txt",
391 b"Guide content".to_vec(),
392 ));
393 tree.add_dir(subdir);
394
395 assert_eq!(tree.total_files(), 2);
396 assert_eq!(tree.total_dirs(), 2);
397
398 assert!(tree.find_file("readme.txt").is_some());
400 assert!(tree.find_file("docs/guide.txt").is_some());
401 assert!(tree.find_file("nonexistent.txt").is_none());
402 }
403}