use alloc::vec::Vec;
use super::options::{CreationFeatures, FormatOptions};
use super::{File, InputFiles};
use crate::file::EntryType;
#[derive(Debug, Clone, Default)]
pub struct SizeBreakdown {
pub system_area: u64,
pub volume_descriptors: u64,
pub path_tables: u64,
pub directory_records: u64,
pub continuation_areas: u64,
pub file_data: u64,
pub boot_catalog: u64,
}
#[derive(Debug, Clone)]
pub struct IsoSizeEstimate {
pub minimum_sectors: u64,
pub breakdown: SizeBreakdown,
}
impl IsoSizeEstimate {
pub fn minimum_bytes(&self) -> u64 {
self.minimum_sectors * 2048
}
}
struct TreeStats {
dir_count: u64,
file_count: u64,
total_file_bytes: u64,
dir_name_bytes: u64,
dir_record_bytes: u64,
}
fn align_to_sector(bytes: u64, sector_size: u64) -> u64 {
bytes.div_ceil(sector_size)
}
fn walk_files_stats(
files: &[File],
entry_types: &[EntryType],
sector_size: u64,
stats: &mut TreeStats,
) {
let has_rrip = entry_types.iter().any(|e| e.supports_rrip());
let dot_dotdot_size: u64 = if has_rrip {
256 + 128
} else {
34 + 34
};
let mut dir_total = dot_dotdot_size;
for file in files {
match file {
File::File { name, contents } => {
stats.file_count += 1;
if !contents.is_empty() {
stats.total_file_bytes +=
align_to_sector(contents.len() as u64, sector_size) * sector_size;
}
let name_len = estimate_converted_name_len(name, entry_types);
let record_size = if has_rrip {
let su_size = 68 + name.len() as u64;
let base = 33 + name_len;
let padded = (base + 1) & !1;
(padded + su_size).min(256)
} else {
let base = 33 + name_len;
(base + 1) & !1
};
dir_total += record_size;
}
File::Directory { name, children } => {
stats.dir_count += 1;
let name_len = estimate_converted_name_len(name, entry_types);
stats.dir_name_bytes += name_len;
let record_size = if has_rrip {
let su_size = 68 + name.len() as u64;
let base = 33 + name_len;
let padded = (base + 1) & !1;
(padded + su_size).min(256)
} else {
let base = 33 + name_len;
(base + 1) & !1
};
dir_total += record_size;
walk_files_stats(children, entry_types, sector_size, stats);
}
}
}
stats.dir_record_bytes += align_to_sector(dir_total, sector_size) * sector_size;
}
fn estimate_converted_name_len(name: &str, entry_types: &[EntryType]) -> u64 {
match entry_types.first() {
Some(EntryType::Level1 { .. }) => {
let has_dot = name.contains('.');
if has_dot {
let dot_pos = name.find('.').unwrap();
let basename = dot_pos.min(8);
let ext = (name.len() - dot_pos - 1).min(3);
(basename + 1 + ext + 2) as u64 } else {
(name.len().min(8) + 2) as u64 }
}
Some(EntryType::Level2 { .. }) => {
(name.len().min(30) + 2) as u64
}
Some(EntryType::Level3 { .. }) => {
name.len().min(207) as u64
}
Some(EntryType::Joliet { .. }) => {
let code_units: usize = name.encode_utf16().count();
(code_units.min(103) * 2) as u64
}
None => name.len() as u64,
}
}
pub fn estimate(files: &InputFiles, options: &FormatOptions) -> IsoSizeEstimate {
let sector_size = options.sector_size as u64;
let features = &options.features;
let entry_types = build_entry_types(features);
let num_entry_types = entry_types.len() as u64;
let mut breakdown = SizeBreakdown {
system_area: 16 * sector_size,
..SizeBreakdown::default()
};
let mut vd_count: u64 = 1; if features.el_torito.is_some() {
vd_count += 1; }
if features.long_filenames {
vd_count += 1; }
if features.joliet.is_some() {
vd_count += 1; }
vd_count += 1; breakdown.volume_descriptors = vd_count * sector_size;
let mut stats = TreeStats {
dir_count: 0,
file_count: 0,
total_file_bytes: 0,
dir_name_bytes: 0,
dir_record_bytes: 0,
};
walk_files_stats(&files.files, &entry_types, sector_size, &mut stats);
stats.dir_count += 1;
let pt_root_size = 10u64;
let pt_dir_size = stats.dir_count.saturating_sub(1).saturating_mul(8)
+ stats.dir_name_bytes
+ stats.dir_count.saturating_sub(1); let pt_size_bytes = pt_root_size + pt_dir_size;
let pt_sectors = align_to_sector(pt_size_bytes, sector_size);
breakdown.path_tables = pt_sectors * 2 * num_entry_types * sector_size;
breakdown.directory_records = stats.dir_record_bytes * num_entry_types;
let has_rrip = entry_types.iter().any(|e| e.supports_rrip());
if has_rrip {
breakdown.continuation_areas = stats.dir_count * sector_size;
}
breakdown.file_data = stats.total_file_bytes;
if features.el_torito.is_some() {
breakdown.boot_catalog = sector_size;
}
let total_bytes = breakdown.system_area
+ breakdown.volume_descriptors
+ breakdown.path_tables
+ breakdown.directory_records
+ breakdown.continuation_areas
+ breakdown.file_data
+ breakdown.boot_catalog;
let minimum_sectors = align_to_sector(total_bytes, sector_size);
IsoSizeEstimate {
minimum_sectors,
breakdown,
}
}
fn build_entry_types(features: &CreationFeatures) -> Vec<EntryType> {
let mut entry_types = Vec::new();
entry_types.push(features.filenames.into());
if features.long_filenames {
entry_types.push(EntryType::Level3 {
supports_lowercase: true,
supports_rrip: false,
});
}
if let Some(joliet) = features.joliet {
entry_types.push(joliet.into());
}
entry_types
}