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 opts = ForkExportOptions::default();
66 export_file_with_fork_opts(fs, entry, dest_dir, out_name, mode, opts)
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub struct ForkExportOptions {
72 pub finder_info_sidecar: bool,
74}
75
76impl Default for ForkExportOptions {
77 fn default() -> Self {
78 ForkExportOptions {
79 finder_info_sidecar: true,
80 }
81 }
82}
83
84pub fn export_file_with_fork_opts(
86 fs: &mut dyn Filesystem,
87 entry: &FileEntry,
88 dest_dir: &Path,
89 out_name: &str,
90 mode: ResourceForkMode,
91 opts: ForkExportOptions,
92) -> Result<u64> {
93 let safe = out_name;
94 let has_rsrc = entry.resource_fork_size.map(|s| s > 0).unwrap_or(false);
95 let type_code = entry.type_code.unwrap_or([0; 4]);
96 let creator_code = entry.creator_code.unwrap_or([0; 4]);
97 let dates = entry
100 .mac_dates
101 .map(|(created, modified, _backup)| MacFileDates { created, modified })
102 .unwrap_or_default();
103
104 let read_data = |fs: &mut dyn Filesystem| -> Result<Vec<u8>> {
105 fs.read_file(entry, usize::MAX)
106 .with_context(|| format!("reading '{}'", entry.name))
107 };
108 let read_rsrc = |fs: &mut dyn Filesystem| -> Result<Vec<u8>> {
109 let mut buf = Vec::new();
110 fs.write_resource_fork_to(entry, &mut buf)
111 .with_context(|| format!("reading resource fork of '{}'", entry.name))?;
112 Ok(buf)
113 };
114
115 if mode == ResourceForkMode::MacBinary && has_rsrc {
116 let data = read_data(fs)?;
117 let rsrc = read_rsrc(fs)?;
118 let mb =
119 resource_fork::build_macbinary(safe, &type_code, &creator_code, dates, &data, &rsrc);
120 let out = dest_dir.join(format!("{safe}.bin"));
121 std::fs::write(&out, &mb).with_context(|| format!("writing {}", out.display()))?;
122 return Ok(data.len() as u64 + rsrc.len() as u64);
123 }
124
125 if mode == ResourceForkMode::BinHex {
126 let data = read_data(fs)?;
127 let rsrc = if has_rsrc { read_rsrc(fs)? } else { Vec::new() };
128 let bh = binhex::BinHexFile {
129 name: entry.name.clone(),
130 type_code,
131 creator_code,
132 flags: 0,
133 data_fork: data,
134 resource_fork: rsrc,
135 };
136 let text = binhex::build_binhex(&bh);
137 let out = dest_dir.join(format!("{safe}.hqx"));
138 std::fs::write(&out, text.as_bytes())
139 .with_context(|| format!("writing {}", out.display()))?;
140 return Ok(bh.data_fork.len() as u64 + bh.resource_fork.len() as u64);
141 }
142
143 let out = dest_dir.join(safe);
145 let mut f =
146 std::fs::File::create(&out).with_context(|| format!("creating {}", out.display()))?;
147 let written = fs
148 .write_file_to(entry, &mut f)
149 .with_context(|| format!("extracting '{}'", entry.name))?;
150 drop(f);
151 apply_host_mtime(&out, entry);
158
159 let mut extra = 0u64;
163 let has_finfo = opts.finder_info_sidecar && (type_code != [0; 4] || creator_code != [0; 4]);
164 match mode {
165 ResourceForkMode::Native if has_rsrc => {
166 let rsrc = read_rsrc(fs)?;
167 let p = out.join("..namedfork/rsrc");
168 std::fs::write(&p, &rsrc)
169 .with_context(|| format!("writing native resource fork of '{}'", entry.name))?;
170 extra = rsrc.len() as u64;
171 }
172 ResourceForkMode::AppleDouble if has_rsrc || has_finfo => {
173 let rsrc = if has_rsrc { read_rsrc(fs)? } else { Vec::new() };
174 let ad = resource_fork::build_appledouble(&type_code, &creator_code, dates, &rsrc);
175 let p = dest_dir.join(format!("._{safe}"));
176 std::fs::write(&p, &ad)
177 .with_context(|| format!("writing AppleDouble sidecar of '{}'", entry.name))?;
178 extra = rsrc.len() as u64;
179 }
180 ResourceForkMode::SeparateRsrc if has_rsrc => {
181 let rsrc = read_rsrc(fs)?;
182 let p = dest_dir.join(format!("{safe}.rsrc"));
183 std::fs::write(&p, &rsrc)
184 .with_context(|| format!("writing .rsrc sidecar of '{}'", entry.name))?;
185 extra = rsrc.len() as u64;
186 }
187 _ => {}
188 }
189
190 Ok(written + extra)
191}
192
193fn apply_host_mtime(path: &Path, entry: &FileEntry) {
198 let Some(secs) = entry.modified_unix else {
199 return;
200 };
201 let ft = filetime::FileTime::from_unix_time(secs as i64, 0);
202 let _ = filetime::set_file_times(path, ft, ft);
203}