1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
//! Resuming a run from a snapshot of the process.
//!
//! The pass driver (in `retex-cli`) forks the process when LaTeX begins
//! the document: everything before is the same in every pass, so later
//! passes resume from that point instead of running the preamble again.
//! The engine's part is the hook that marks the point
//! ([`Engine::snapshot_hook`]) and the output files: they are flushed
//! before the fork, and a resumed process reopens them, cut back to their
//! length at the snapshot.
use std::io::{Seek, SeekFrom};
use std::path::PathBuf;
use std::rc::Rc;
use crate::Engine;
use crate::input::{Level, LineSource};
/// The snapshot hook (see [`Engine::snapshot_hook`]).
pub type Hook = Box<dyn FnOnce(&mut Engine)>;
/// New contents of the input files being read (see
/// [`Engine::reloaded_inputs`]).
pub struct Reload(Vec<(usize, Rc<[u8]>)>);
/// An output file open when the snapshot was taken.
#[derive(Clone, Debug)]
pub struct OpenOutput {
/// The file.
pub path: PathBuf,
/// Its length at the snapshot.
pub len: u64,
/// The `\write` stream, or `None` for the log file.
stream: Option<u32>,
}
impl Engine {
/// Flushes all output and lists the output files that are open.
pub fn snapshot_outputs(&mut self) -> Vec<OpenOutput> {
self.printer.flush();
if let Some(b) = self.backend.as_mut() {
// A failure shows when the file is reopened.
let _ = b.suspend();
}
let mut out = Vec::new();
if self.printer.log_opened()
&& let Some(path) = &self.files.log_name
{
out.push(OpenOutput {
path: path.clone(),
len: std::fs::metadata(path).map_or(0, |m| m.len()),
stream: None,
});
}
for n in self.printer.open_write_streams() {
if let Some(Some(path)) = self.write_paths.get(n as usize) {
out.push(OpenOutput {
path: path.clone(),
len: std::fs::metadata(path).map_or(0, |m| m.len()),
stream: Some(n),
});
}
}
out
}
/// In a process resumed from a snapshot: reopens the output files
/// listed by [`snapshot_outputs`](Self::snapshot_outputs), truncated to
/// their length at the snapshot.
pub fn resume_outputs(&mut self, outputs: &[OpenOutput]) -> std::io::Result<()> {
if let Some(b) = self.backend.as_mut() {
b.resume()?;
}
for o in outputs {
let mut f = std::fs::OpenOptions::new().write(true).open(&o.path)?;
f.set_len(o.len)?;
f.seek(SeekFrom::End(0))?;
match o.stream {
None => self
.printer
.set_log(Box::new(std::io::BufWriter::with_capacity(1 << 16, f))),
Some(n) => {
let w = crate::files::RecordingWriter::resume(
f,
o.path.clone(),
self.written_sink.clone(),
);
self.printer.open_write(n, Box::new(w));
}
}
}
Ok(())
}
/// The input files being read, from the outermost.
pub fn open_input_files(&self) -> Vec<PathBuf> {
self.file_levels().map(|(_, path, _, _)| path).collect()
}
/// The text levels reading files: stack index, path, content and
/// number of bytes read.
fn file_levels(&self) -> impl Iterator<Item = (usize, PathBuf, &[u8], usize)> {
self.input
.stack
.iter()
.enumerate()
.filter_map(|(i, l)| match l {
Level::Text(t) => match &t.source {
LineSource::File { data, pos } => {
let path = PathBuf::from(String::from_utf8_lossy(&t.name).into_owned());
Some((i, path, &data[..], *pos))
}
_ => None,
},
Level::Tokens(_) => None,
})
}
/// Reads the input files being read again, for a process resumed from
/// a snapshot after they were edited (`--watch`): the part already read
/// must be unchanged; the rest may differ. Returns the first file
/// whose read part changed (or that cannot be read) as the error.
pub fn reloaded_inputs(&self) -> Result<Reload, PathBuf> {
let mut out = Vec::new();
for (i, path, data, pos) in self.file_levels() {
match std::fs::read(&path) {
Ok(new) if new.get(..pos) == data.get(..pos) => out.push((i, Rc::from(new))),
_ => return Err(path),
}
}
Ok(Reload(out))
}
/// Continues reading the input files with the contents of `reload`.
pub fn reload_inputs(&mut self, reload: Reload) {
for (i, new) in reload.0 {
if let Some(Level::Text(t)) = self.input.stack.get_mut(i)
&& let LineSource::File { data, .. } = &mut t.source
{
*data = new;
}
}
}
/// Runs the snapshot hook if `cs` is the control sequence it waits for.
#[inline]
pub(crate) fn check_snapshot(&mut self) {
if self.snapshot_cs.is_some_and(|cs| cs == self.cur_cs) {
self.snapshot_cs = None;
if let Some(hook) = self.snapshot_hook.take() {
hook(self);
}
}
}
}