1use std::path::Path;
23
24use anyhow::{Context, Result};
25
26use crate::binhex;
27use crate::resource_fork::{self, MacFileDates, ResourceForkMode};
28use disk_core::entry::FileEntry;
29use disk_core::filesystem::Filesystem;
30
31pub fn safe_name(entry: &FileEntry) -> String {
34 resource_fork::sanitize_filename(&entry.name)
35}
36
37pub fn primary_output_path(
40 entry: &FileEntry,
41 dest_dir: &Path,
42 out_name: &str,
43 mode: ResourceForkMode,
44) -> std::path::PathBuf {
45 let has_rsrc = entry.resource_fork_size.map(|s| s > 0).unwrap_or(false);
46 match mode {
47 ResourceForkMode::MacBinary if has_rsrc => dest_dir.join(format!("{out_name}.bin")),
48 ResourceForkMode::BinHex => dest_dir.join(format!("{out_name}.hqx")),
49 _ => dest_dir.join(out_name),
50 }
51}
52
53pub fn export_file_with_fork(
59 fs: &mut dyn Filesystem,
60 entry: &FileEntry,
61 dest_dir: &Path,
62 out_name: &str,
63 mode: ResourceForkMode,
64) -> Result<u64> {
65 let safe = out_name;
66 let has_rsrc = entry.resource_fork_size.map(|s| s > 0).unwrap_or(false);
67 let type_code = entry.type_code.unwrap_or([0; 4]);
68 let creator_code = entry.creator_code.unwrap_or([0; 4]);
69 let dates = entry
72 .mac_dates
73 .map(|(created, modified, _backup)| MacFileDates { created, modified })
74 .unwrap_or_default();
75
76 let read_data = |fs: &mut dyn Filesystem| -> Result<Vec<u8>> {
77 fs.read_file(entry, usize::MAX)
78 .with_context(|| format!("reading '{}'", entry.name))
79 };
80 let read_rsrc = |fs: &mut dyn Filesystem| -> Result<Vec<u8>> {
81 let mut buf = Vec::new();
82 fs.write_resource_fork_to(entry, &mut buf)
83 .with_context(|| format!("reading resource fork of '{}'", entry.name))?;
84 Ok(buf)
85 };
86
87 if mode == ResourceForkMode::MacBinary && has_rsrc {
88 let data = read_data(fs)?;
89 let rsrc = read_rsrc(fs)?;
90 let mb =
91 resource_fork::build_macbinary(safe, &type_code, &creator_code, dates, &data, &rsrc);
92 let out = dest_dir.join(format!("{safe}.bin"));
93 std::fs::write(&out, &mb).with_context(|| format!("writing {}", out.display()))?;
94 return Ok(data.len() as u64 + rsrc.len() as u64);
95 }
96
97 if mode == ResourceForkMode::BinHex {
98 let data = read_data(fs)?;
99 let rsrc = if has_rsrc { read_rsrc(fs)? } else { Vec::new() };
100 let bh = binhex::BinHexFile {
101 name: entry.name.clone(),
102 type_code,
103 creator_code,
104 flags: 0,
105 data_fork: data,
106 resource_fork: rsrc,
107 };
108 let text = binhex::build_binhex(&bh);
109 let out = dest_dir.join(format!("{safe}.hqx"));
110 std::fs::write(&out, text.as_bytes())
111 .with_context(|| format!("writing {}", out.display()))?;
112 return Ok(bh.data_fork.len() as u64 + bh.resource_fork.len() as u64);
113 }
114
115 let out = dest_dir.join(safe);
117 let mut f =
118 std::fs::File::create(&out).with_context(|| format!("creating {}", out.display()))?;
119 let written = fs
120 .write_file_to(entry, &mut f)
121 .with_context(|| format!("extracting '{}'", entry.name))?;
122 drop(f);
123 apply_host_mtime(&out, entry);
130
131 let mut extra = 0u64;
135 let has_finfo = type_code != [0; 4] || creator_code != [0; 4];
136 match mode {
137 ResourceForkMode::Native if has_rsrc => {
138 let rsrc = read_rsrc(fs)?;
139 let p = out.join("..namedfork/rsrc");
140 std::fs::write(&p, &rsrc)
141 .with_context(|| format!("writing native resource fork of '{}'", entry.name))?;
142 extra = rsrc.len() as u64;
143 }
144 ResourceForkMode::AppleDouble if has_rsrc || has_finfo => {
145 let rsrc = if has_rsrc { read_rsrc(fs)? } else { Vec::new() };
146 let ad = resource_fork::build_appledouble(&type_code, &creator_code, dates, &rsrc);
147 let p = dest_dir.join(format!("._{safe}"));
148 std::fs::write(&p, &ad)
149 .with_context(|| format!("writing AppleDouble sidecar of '{}'", entry.name))?;
150 extra = rsrc.len() as u64;
151 }
152 ResourceForkMode::SeparateRsrc if has_rsrc => {
153 let rsrc = read_rsrc(fs)?;
154 let p = dest_dir.join(format!("{safe}.rsrc"));
155 std::fs::write(&p, &rsrc)
156 .with_context(|| format!("writing .rsrc sidecar of '{}'", entry.name))?;
157 extra = rsrc.len() as u64;
158 }
159 _ => {}
160 }
161
162 Ok(written + extra)
163}
164
165fn apply_host_mtime(path: &Path, entry: &FileEntry) {
170 let Some(secs) = entry.modified_unix else {
171 return;
172 };
173 let ft = filetime::FileTime::from_unix_time(secs as i64, 0);
174 let _ = filetime::set_file_times(path, ft, ft);
175}