use std::fs::{self, File};
use std::io::{Seek, Write};
use std::path::Path;
use anyhow::{Context, Result};
use isomage::{TreeNode, cat_node, detect_and_parse_filesystem, udf::parse_udf};
pub fn open(path: &Path) -> Result<(File, TreeNode)> {
let mut file = File::open(path).with_context(|| format!("open ISO {}", path.display()))?;
let root = if let Ok(root) = parse_udf(&mut file) {
root
} else {
file.rewind()
.with_context(|| format!("rewind ISO {}", path.display()))?;
detect_and_parse_filesystem(&mut file, &path.to_string_lossy())
.map_err(|e| anyhow::anyhow!("parse ISO {}: {e}", path.display()))?
};
Ok((file, root))
}
pub fn find_install_wim(root: &TreeNode) -> Option<&TreeNode> {
if let Some(node) = root.find_node("sources/install.wim") {
return Some(node);
}
let sources = root
.children
.iter()
.find(|c| c.is_directory && c.name.eq_ignore_ascii_case("sources"))?;
sources
.children
.iter()
.find(|c| !c.is_directory && c.name.eq_ignore_ascii_case("install.wim"))
}
pub fn total_file_size(node: &TreeNode) -> Result<u64> {
if node.is_directory {
node.children.iter().try_fold(0_u64, |size, child| {
let child_size = total_file_size(child)?;
size.checked_add(child_size)
.ok_or_else(|| anyhow::anyhow!("ISO file size exceeds u64"))
})
} else {
Ok(node.size)
}
}
pub fn cat(iso: &mut File, node: &TreeNode, writer: &mut impl Write) -> Result<()> {
cat_node(iso, node, writer).map_err(|e| anyhow::anyhow!("stream file from ISO: {e}"))
}
pub fn copy_tree(
iso: &mut File,
node: &TreeNode,
rel: &str,
dest_root: &Path,
skip: &dyn Fn(&str) -> bool,
rep: &crate::progress::Reporter,
) -> Result<()> {
if !rel.is_empty() {
rep.set_message(rel);
if skip(rel) {
tracing::debug!("skipping {rel} (handled separately)");
return Ok(());
}
}
let dest = if rel.is_empty() {
dest_root.to_path_buf()
} else {
dest_root.join(rel)
};
if node.is_directory {
fs::create_dir_all(&dest).with_context(|| format!("mkdir {}", dest.display()))?;
for child in &node.children {
let child_rel = if rel.is_empty() {
child.name.clone()
} else {
format!("{rel}/{}", child.name)
};
copy_tree(iso, child, &child_rel, dest_root, skip, rep)?;
}
} else {
if let Some(parent) = dest.parent() {
fs::create_dir_all(parent).with_context(|| format!("mkdir {}", parent.display()))?;
}
let mut out = File::create(&dest).with_context(|| format!("create {}", dest.display()))?;
cat(iso, node, &mut out)?;
out.sync_all()
.with_context(|| format!("sync {}", dest.display()))?;
tracing::debug!("copied {} ({} bytes)", rel, node.size);
}
Ok(())
}
pub fn copy_tree_to_fat(
iso: &mut File,
node: &TreeNode,
rel: &str,
dest_root: &crate::disk::FatDir<'_>,
skip: &dyn Fn(&str) -> bool,
rep: &crate::progress::Reporter,
) -> Result<()> {
if !rel.is_empty() {
rep.set_message(rel);
if skip(rel) {
tracing::debug!("skipping {rel} (handled separately)");
return Ok(());
}
}
if node.is_directory {
if !rel.is_empty() {
dest_root
.create_dir(rel)
.with_context(|| format!("mkdir {rel} in image"))?;
}
for child in &node.children {
let child_rel = if rel.is_empty() {
child.name.clone()
} else {
format!("{rel}/{}", child.name)
};
copy_tree_to_fat(iso, child, &child_rel, dest_root, skip, rep)?;
}
} else {
let mut out = crate::disk::create_image_file(dest_root, rel)
.with_context(|| format!("create {rel} in image"))?;
cat(iso, node, &mut out)?;
out.flush()
.with_context(|| format!("flush {rel} in image"))?;
tracing::debug!("copied {} ({} bytes)", rel, node.size);
}
Ok(())
}