1use std::io::{Seek, SeekFrom};
12use std::path::PathBuf;
13use std::rc::Rc;
14
15use crate::Engine;
16use crate::input::{Level, LineSource};
17
18pub type Hook = Box<dyn FnOnce(&mut Engine)>;
20
21pub struct Reload(Vec<(usize, Rc<[u8]>)>);
24
25#[derive(Clone, Debug)]
27pub struct OpenOutput {
28 pub path: PathBuf,
30 pub len: u64,
32 stream: Option<u32>,
34}
35
36impl Engine {
37 pub fn snapshot_outputs(&mut self) -> Vec<OpenOutput> {
39 self.printer.flush();
40 if let Some(b) = self.backend.as_mut() {
41 let _ = b.suspend();
43 }
44 let mut out = Vec::new();
45 if self.printer.log_opened()
46 && let Some(path) = &self.files.log_name
47 {
48 out.push(OpenOutput {
49 path: path.clone(),
50 len: std::fs::metadata(path).map_or(0, |m| m.len()),
51 stream: None,
52 });
53 }
54 for n in self.printer.open_write_streams() {
55 if let Some(Some(path)) = self.write_paths.get(n as usize) {
56 out.push(OpenOutput {
57 path: path.clone(),
58 len: std::fs::metadata(path).map_or(0, |m| m.len()),
59 stream: Some(n),
60 });
61 }
62 }
63 out
64 }
65
66 pub fn resume_outputs(&mut self, outputs: &[OpenOutput]) -> std::io::Result<()> {
70 if let Some(b) = self.backend.as_mut() {
71 b.resume()?;
72 }
73 for o in outputs {
74 let mut f = std::fs::OpenOptions::new().write(true).open(&o.path)?;
75 f.set_len(o.len)?;
76 f.seek(SeekFrom::End(0))?;
77 match o.stream {
78 None => self
79 .printer
80 .set_log(Box::new(std::io::BufWriter::with_capacity(1 << 16, f))),
81 Some(n) => {
82 let w = crate::files::RecordingWriter::resume(
83 f,
84 o.path.clone(),
85 self.written_sink.clone(),
86 );
87 self.printer.open_write(n, Box::new(w));
88 }
89 }
90 }
91 Ok(())
92 }
93
94 pub fn open_input_files(&self) -> Vec<PathBuf> {
96 self.file_levels().map(|(_, path, _, _)| path).collect()
97 }
98
99 fn file_levels(&self) -> impl Iterator<Item = (usize, PathBuf, &[u8], usize)> {
102 self.input
103 .stack
104 .iter()
105 .enumerate()
106 .filter_map(|(i, l)| match l {
107 Level::Text(t) => match &t.source {
108 LineSource::File { data, pos } => {
109 let path = PathBuf::from(String::from_utf8_lossy(&t.name).into_owned());
110 Some((i, path, &data[..], *pos))
111 }
112 _ => None,
113 },
114 Level::Tokens(_) => None,
115 })
116 }
117
118 pub fn reloaded_inputs(&self) -> Result<Reload, PathBuf> {
123 let mut out = Vec::new();
124 for (i, path, data, pos) in self.file_levels() {
125 match std::fs::read(&path) {
126 Ok(new) if new.get(..pos) == data.get(..pos) => out.push((i, Rc::from(new))),
127 _ => return Err(path),
128 }
129 }
130 Ok(Reload(out))
131 }
132
133 pub fn reload_inputs(&mut self, reload: Reload) {
135 for (i, new) in reload.0 {
136 if let Some(Level::Text(t)) = self.input.stack.get_mut(i)
137 && let LineSource::File { data, .. } = &mut t.source
138 {
139 *data = new;
140 }
141 }
142 }
143
144 #[inline]
146 pub(crate) fn check_snapshot(&mut self) {
147 if self.snapshot_cs.is_some_and(|cs| cs == self.cur_cs) {
148 self.snapshot_cs = None;
149 if let Some(hook) = self.snapshot_hook.take() {
150 hook(self);
151 }
152 }
153 }
154}