1use crate::error::{CdError, CdResult};
26use crate::options::CdOptions;
27use crate::tree::{Directory, FileExtent, FileTree};
28
29#[derive(Debug)]
31pub struct LayoutManager {
32 sector_size: usize,
34 next_file_sector: u32,
36 next_udf_block: u32,
38 next_unique_id: u64,
40}
41
42impl LayoutManager {
43 pub fn new(sector_size: usize) -> Self {
45 Self {
46 sector_size,
47 next_file_sector: 0,
50 next_udf_block: 0,
51 next_unique_id: 16, }
53 }
54
55 pub fn layout_files(
61 &mut self,
62 tree: &mut FileTree,
63 options: &CdOptions,
64 ) -> CdResult<LayoutInfo> {
65 let vds_end = self.calculate_vds_end(options);
67
68 let udf_partition_start = 257;
70
71 let udf_metadata_sectors = self.estimate_udf_metadata_sectors(tree);
74
75 self.next_udf_block = udf_metadata_sectors;
77 self.next_file_sector = udf_partition_start + udf_metadata_sectors;
78
79 self.assign_file_extents(&mut tree.root)?;
81
82 self.assign_unique_ids(&mut tree.root);
84
85 let file_data_end = self.next_file_sector;
86
87 Ok(LayoutInfo {
88 vds_end,
89 udf_partition_start,
90 udf_metadata_sectors,
91 file_data_start: udf_partition_start + udf_metadata_sectors,
92 file_data_end,
93 total_sectors: file_data_end + 100, })
95 }
96
97 fn calculate_vds_end(&self, options: &CdOptions) -> u32 {
99 let mut sector = 16; if options.iso.enabled {
103 sector += 1;
104 }
105
106 if options.iso.joliet.is_some() {
112 sector += 1;
113 }
114
115 if options.iso.long_filenames {
117 sector += 1;
118 }
119
120 if options.boot.is_some() {
122 sector += 1;
123 }
124
125 sector += 1;
127
128 sector
129 }
130
131 fn estimate_udf_metadata_sectors(&self, tree: &FileTree) -> u32 {
133 let total_dirs = tree.total_dirs();
139 let total_files = tree.total_files();
140
141 let estimated = (total_dirs * 2 + total_files / 50 + 10) as u32;
144
145 estimated.max(20)
147 }
148
149 fn assign_file_extents(&mut self, dir: &mut Directory) -> CdResult<()> {
151 for file in &mut dir.files {
153 let size = file.size().map_err(CdError::Io)?;
154
155 if size == 0 {
156 file.extent = FileExtent::new(0, 0);
158 } else {
159 file.extent = FileExtent::new(self.next_file_sector, size);
160 let sectors = file.extent.sector_count(self.sector_size);
161 self.next_file_sector += sectors;
162 }
163 }
164
165 for subdir in &mut dir.subdirs {
167 self.assign_file_extents(subdir)?;
168 }
169
170 Ok(())
171 }
172
173 fn assign_unique_ids(&mut self, dir: &mut Directory) {
175 dir.unique_id = self.next_unique_id;
176 self.next_unique_id += 1;
177
178 for file in &mut dir.files {
179 file.unique_id = self.next_unique_id;
180 self.next_unique_id += 1;
181 }
182
183 for subdir in &mut dir.subdirs {
184 self.assign_unique_ids(subdir);
185 }
186 }
187
188 pub fn allocate_udf_block(&mut self) -> u32 {
190 let block = self.next_udf_block;
191 self.next_udf_block += 1;
192 block
193 }
194
195 pub fn next_unique_id(&mut self) -> u64 {
197 let id = self.next_unique_id;
198 self.next_unique_id += 1;
199 id
200 }
201}
202
203#[derive(Debug, Clone)]
205pub struct LayoutInfo {
206 pub vds_end: u32,
208 pub udf_partition_start: u32,
210 pub udf_metadata_sectors: u32,
212 pub file_data_start: u32,
214 pub file_data_end: u32,
216 pub total_sectors: u32,
218}
219
220impl core::fmt::Display for LayoutInfo {
221 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
222 write!(
223 f,
224 "layout: {} total sectors (files at sectors {}-{})",
225 self.total_sectors, self.file_data_start, self.file_data_end
226 )
227 }
228}
229
230impl LayoutInfo {
231 pub fn udf_partition_length(&self) -> u32 {
233 self.total_sectors - self.udf_partition_start
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240 use crate::tree::FileEntry;
241
242 #[test]
243 fn test_layout_empty_tree() {
244 let mut tree = FileTree::new();
245 let options = CdOptions::default();
246 let mut layout = LayoutManager::new(2048);
247
248 let info = layout.layout_files(&mut tree, &options).unwrap();
249 assert!(info.file_data_end >= info.file_data_start);
250 }
251
252 #[test]
253 fn test_layout_with_files() {
254 let mut tree = FileTree::new();
255 tree.add_file(FileEntry::from_buffer("test.txt", vec![0u8; 4096]));
256 tree.add_file(FileEntry::from_buffer("small.txt", vec![0u8; 100]));
257
258 let options = CdOptions::default();
259 let mut layout = LayoutManager::new(2048);
260
261 let info = layout.layout_files(&mut tree, &options).unwrap();
262
263 let file1 = tree.root.files.get(0).unwrap();
265 assert!(file1.extent.sector > 0);
266 assert_eq!(file1.extent.length, 4096);
267
268 let file2 = tree.root.files.get(1).unwrap();
270 assert!(file2.extent.sector > file1.extent.sector);
271 }
272
273 #[test]
274 fn test_layout_zero_size_file() {
275 let mut tree = FileTree::new();
276 tree.add_file(FileEntry::from_buffer("empty.txt", vec![]));
277
278 let options = CdOptions::default();
279 let mut layout = LayoutManager::new(2048);
280
281 layout.layout_files(&mut tree, &options).unwrap();
282
283 let file = tree.root.files.get(0).unwrap();
284 assert_eq!(file.extent.sector, 0);
285 assert_eq!(file.extent.length, 0);
286 }
287}