use std::path::Path;
use anyhow::{Context, Result};
use crate::binhex;
use crate::resource_fork::{self, MacFileDates, ResourceForkMode};
use disk_core::entry::FileEntry;
use disk_core::filesystem::Filesystem;
pub fn safe_name(entry: &FileEntry) -> String {
resource_fork::sanitize_filename(&entry.name)
}
pub fn primary_output_path(
entry: &FileEntry,
dest_dir: &Path,
out_name: &str,
mode: ResourceForkMode,
) -> std::path::PathBuf {
let has_rsrc = entry.resource_fork_size.map(|s| s > 0).unwrap_or(false);
match mode {
ResourceForkMode::MacBinary if has_rsrc => dest_dir.join(format!("{out_name}.bin")),
ResourceForkMode::BinHex => dest_dir.join(format!("{out_name}.hqx")),
_ => dest_dir.join(out_name),
}
}
pub fn export_file_with_fork(
fs: &mut dyn Filesystem,
entry: &FileEntry,
dest_dir: &Path,
out_name: &str,
mode: ResourceForkMode,
) -> Result<u64> {
let opts = ForkExportOptions::default();
export_file_with_fork_opts(fs, entry, dest_dir, out_name, mode, opts)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ForkExportOptions {
pub finder_info_sidecar: bool,
}
impl Default for ForkExportOptions {
fn default() -> Self {
ForkExportOptions {
finder_info_sidecar: true,
}
}
}
pub fn export_file_with_fork_opts(
fs: &mut dyn Filesystem,
entry: &FileEntry,
dest_dir: &Path,
out_name: &str,
mode: ResourceForkMode,
opts: ForkExportOptions,
) -> Result<u64> {
let safe = out_name;
let has_rsrc = entry.resource_fork_size.map(|s| s > 0).unwrap_or(false);
let type_code = entry.type_code.unwrap_or([0; 4]);
let creator_code = entry.creator_code.unwrap_or([0; 4]);
let dates = entry
.mac_dates
.map(|(created, modified, _backup)| MacFileDates { created, modified })
.unwrap_or_default();
let read_data = |fs: &mut dyn Filesystem| -> Result<Vec<u8>> {
fs.read_file(entry, usize::MAX)
.with_context(|| format!("reading '{}'", entry.name))
};
let read_rsrc = |fs: &mut dyn Filesystem| -> Result<Vec<u8>> {
let mut buf = Vec::new();
fs.write_resource_fork_to(entry, &mut buf)
.with_context(|| format!("reading resource fork of '{}'", entry.name))?;
Ok(buf)
};
if mode == ResourceForkMode::MacBinary && has_rsrc {
let data = read_data(fs)?;
let rsrc = read_rsrc(fs)?;
let mb =
resource_fork::build_macbinary(safe, &type_code, &creator_code, dates, &data, &rsrc);
let out = dest_dir.join(format!("{safe}.bin"));
std::fs::write(&out, &mb).with_context(|| format!("writing {}", out.display()))?;
return Ok(data.len() as u64 + rsrc.len() as u64);
}
if mode == ResourceForkMode::BinHex {
let data = read_data(fs)?;
let rsrc = if has_rsrc { read_rsrc(fs)? } else { Vec::new() };
let bh = binhex::BinHexFile {
name: entry.name.clone(),
type_code,
creator_code,
flags: 0,
data_fork: data,
resource_fork: rsrc,
};
let text = binhex::build_binhex(&bh);
let out = dest_dir.join(format!("{safe}.hqx"));
std::fs::write(&out, text.as_bytes())
.with_context(|| format!("writing {}", out.display()))?;
return Ok(bh.data_fork.len() as u64 + bh.resource_fork.len() as u64);
}
let out = dest_dir.join(safe);
let mut f =
std::fs::File::create(&out).with_context(|| format!("creating {}", out.display()))?;
let written = fs
.write_file_to(entry, &mut f)
.with_context(|| format!("extracting '{}'", entry.name))?;
drop(f);
apply_host_mtime(&out, entry);
let mut extra = 0u64;
let has_finfo = opts.finder_info_sidecar && (type_code != [0; 4] || creator_code != [0; 4]);
match mode {
ResourceForkMode::Native if has_rsrc => {
let rsrc = read_rsrc(fs)?;
let p = out.join("..namedfork/rsrc");
std::fs::write(&p, &rsrc)
.with_context(|| format!("writing native resource fork of '{}'", entry.name))?;
extra = rsrc.len() as u64;
}
ResourceForkMode::AppleDouble if has_rsrc || has_finfo => {
let rsrc = if has_rsrc { read_rsrc(fs)? } else { Vec::new() };
let ad = resource_fork::build_appledouble(&type_code, &creator_code, dates, &rsrc);
let p = dest_dir.join(format!("._{safe}"));
std::fs::write(&p, &ad)
.with_context(|| format!("writing AppleDouble sidecar of '{}'", entry.name))?;
extra = rsrc.len() as u64;
}
ResourceForkMode::SeparateRsrc if has_rsrc => {
let rsrc = read_rsrc(fs)?;
let p = dest_dir.join(format!("{safe}.rsrc"));
std::fs::write(&p, &rsrc)
.with_context(|| format!("writing .rsrc sidecar of '{}'", entry.name))?;
extra = rsrc.len() as u64;
}
_ => {}
}
Ok(written + extra)
}
fn apply_host_mtime(path: &Path, entry: &FileEntry) {
let Some(secs) = entry.modified_unix else {
return;
};
let ft = filetime::FileTime::from_unix_time(secs as i64, 0);
let _ = filetime::set_file_times(path, ft, ft);
}