use crate::error::{CdError, CdResult};
use crate::options::CdOptions;
use crate::tree::{Directory, FileExtent, FileTree};
#[derive(Debug)]
pub struct LayoutManager {
sector_size: usize,
next_file_sector: u32,
next_udf_block: u32,
next_unique_id: u64,
}
impl LayoutManager {
pub fn new(sector_size: usize) -> Self {
Self {
sector_size,
next_file_sector: 0,
next_udf_block: 0,
next_unique_id: 16, }
}
pub fn layout_files(
&mut self,
tree: &mut FileTree,
options: &CdOptions,
) -> CdResult<LayoutInfo> {
let vds_end = self.calculate_vds_end(options);
let udf_partition_start = 257;
let udf_metadata_sectors = self.estimate_udf_metadata_sectors(tree);
self.next_udf_block = udf_metadata_sectors;
self.next_file_sector = udf_partition_start + udf_metadata_sectors;
self.assign_file_extents(&mut tree.root)?;
self.assign_unique_ids(&mut tree.root);
let file_data_end = self.next_file_sector;
Ok(LayoutInfo {
vds_end,
udf_partition_start,
udf_metadata_sectors,
file_data_start: udf_partition_start + udf_metadata_sectors,
file_data_end,
total_sectors: file_data_end + 100, })
}
fn calculate_vds_end(&self, options: &CdOptions) -> u32 {
let mut sector = 16;
if options.iso.enabled {
sector += 1;
}
if options.iso.joliet.is_some() {
sector += 1;
}
if options.iso.long_filenames {
sector += 1;
}
if options.boot.is_some() {
sector += 1;
}
sector += 1;
sector
}
fn estimate_udf_metadata_sectors(&self, tree: &FileTree) -> u32 {
let total_dirs = tree.total_dirs();
let total_files = tree.total_files();
let estimated = (total_dirs * 2 + total_files / 50 + 10) as u32;
estimated.max(20)
}
fn assign_file_extents(&mut self, dir: &mut Directory) -> CdResult<()> {
for file in &mut dir.files {
let size = file.size().map_err(CdError::Io)?;
if size == 0 {
file.extent = FileExtent::new(0, 0);
} else {
file.extent = FileExtent::new(self.next_file_sector, size);
let sectors = file.extent.sector_count(self.sector_size);
self.next_file_sector += sectors;
}
}
for subdir in &mut dir.subdirs {
self.assign_file_extents(subdir)?;
}
Ok(())
}
fn assign_unique_ids(&mut self, dir: &mut Directory) {
dir.unique_id = self.next_unique_id;
self.next_unique_id += 1;
for file in &mut dir.files {
file.unique_id = self.next_unique_id;
self.next_unique_id += 1;
}
for subdir in &mut dir.subdirs {
self.assign_unique_ids(subdir);
}
}
pub fn allocate_udf_block(&mut self) -> u32 {
let block = self.next_udf_block;
self.next_udf_block += 1;
block
}
pub fn next_unique_id(&mut self) -> u64 {
let id = self.next_unique_id;
self.next_unique_id += 1;
id
}
}
#[derive(Debug, Clone)]
pub struct LayoutInfo {
pub vds_end: u32,
pub udf_partition_start: u32,
pub udf_metadata_sectors: u32,
pub file_data_start: u32,
pub file_data_end: u32,
pub total_sectors: u32,
}
impl core::fmt::Display for LayoutInfo {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"layout: {} total sectors (files at sectors {}-{})",
self.total_sectors, self.file_data_start, self.file_data_end
)
}
}
impl LayoutInfo {
pub fn udf_partition_length(&self) -> u32 {
self.total_sectors - self.udf_partition_start
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tree::FileEntry;
#[test]
fn test_layout_empty_tree() {
let mut tree = FileTree::new();
let options = CdOptions::default();
let mut layout = LayoutManager::new(2048);
let info = layout.layout_files(&mut tree, &options).unwrap();
assert!(info.file_data_end >= info.file_data_start);
}
#[test]
fn test_layout_with_files() {
let mut tree = FileTree::new();
tree.add_file(FileEntry::from_buffer("test.txt", vec![0u8; 4096]));
tree.add_file(FileEntry::from_buffer("small.txt", vec![0u8; 100]));
let options = CdOptions::default();
let mut layout = LayoutManager::new(2048);
let info = layout.layout_files(&mut tree, &options).unwrap();
let file1 = tree.root.files.get(0).unwrap();
assert!(file1.extent.sector > 0);
assert_eq!(file1.extent.length, 4096);
let file2 = tree.root.files.get(1).unwrap();
assert!(file2.extent.sector > file1.extent.sector);
}
#[test]
fn test_layout_zero_size_file() {
let mut tree = FileTree::new();
tree.add_file(FileEntry::from_buffer("empty.txt", vec![]));
let options = CdOptions::default();
let mut layout = LayoutManager::new(2048);
layout.layout_files(&mut tree, &options).unwrap();
let file = tree.root.files.get(0).unwrap();
assert_eq!(file.extent.sector, 0);
assert_eq!(file.extent.length, 0);
}
}