1use std::{
2 io::{BufReader, Read, Write},
3 ops::Range,
4 sync::Arc,
5};
6
7use anyhow::{Context, Result, ensure};
8
9use crate::storage::{ByteStore, FileIndex, ReadWindow, StoreReader, StoreWriter};
10
11mod correspondence;
12pub(crate) use correspondence::Gap;
13pub use correspondence::{CheckedPair, check_pair};
14
15#[cfg(test)]
16mod tests;
17
18const CHUNK: usize = 64 * 1024;
19
20#[derive(Debug)]
22pub struct Snapshot {
23 body: Arc<ByteStore>,
24 index: Arc<ByteStore>,
25 body_start: u64,
26 index_start: u64,
27 len: u64,
28 lines: u32,
29 final_newline: bool,
30}
31
32impl Snapshot {
33 pub(crate) fn uncached_lines(&self) -> SnapshotReader<'_> {
34 SnapshotReader {
35 source: self,
36 offsets: ReadWindow::within(
37 self.index.clone(),
38 self.index_start..self.index_start + (u64::from(self.lines) + 1) * 8,
39 ),
40 body: ReadWindow::within(
41 self.body.clone(),
42 self.body_start..self.body_start + self.len,
43 ),
44 }
45 }
46
47 pub(crate) fn stored_line(
48 &self,
49 number: u32,
50 ) -> Result<(&ByteStore, crate::storage::SourceRef)> {
51 let line = number
52 .checked_sub(1)
53 .context("Source line numbers start at one")?;
54 let start = self.offset(line)?;
55 let end = self.offset(number)?;
56 let len = usize::try_from(
57 end.checked_sub(start)
58 .context("Invalid source line index")?,
59 )?;
60 Ok((
61 &self.body,
62 self.body.source_ref(self.body_start + start, len)?,
63 ))
64 }
65 pub fn len(&self) -> u64 {
66 self.len
67 }
68 pub fn is_empty(&self) -> bool {
69 self.len == 0
70 }
71 pub fn lines(&self) -> u32 {
72 self.lines
73 }
74 pub fn final_newline(&self) -> bool {
75 self.final_newline
76 }
77
78 fn offset(&self, line: u32) -> Result<u64> {
79 ensure!(line <= self.lines, "Source line is outside the snapshot");
80 let bytes = self.index.read(self.index_start + u64::from(line) * 8, 8)?;
81 Ok(u64::from_le_bytes(bytes.try_into().map_err(|_| {
82 anyhow::anyhow!("Invalid source line index")
83 })?))
84 }
85
86 pub fn reader(&self) -> Result<StoreReader> {
87 self.body.reader(self.body_start, self.len)
88 }
89
90 pub fn range(&self, lines: Range<u32>) -> Result<StoreReader> {
92 ensure!(lines.start <= lines.end, "Invalid source line range");
93 let start = self.offset(lines.start)?;
94 let end = self.offset(lines.end)?;
95 ensure!(start <= end && end <= self.len, "Invalid source line index");
96 self.body.reader(self.body_start + start, end - start)
97 }
98
99 fn uncached_offset(&self, line: u32) -> Result<u64> {
100 ensure!(line <= self.lines, "Source line is outside the snapshot");
101 let mut bytes = [0; 8];
102 self.index
103 .reader(self.index_start + u64::from(line) * 8, 8)?
104 .read_exact(&mut bytes)?;
105 Ok(u64::from_le_bytes(bytes))
106 }
107
108 pub(crate) fn uncached_range(&self, lines: Range<u32>) -> Result<StoreReader> {
109 ensure!(lines.start <= lines.end, "Invalid source line range");
110 let start = self.uncached_offset(lines.start)?;
111 let end = self.uncached_offset(lines.end)?;
112 ensure!(start <= end && end <= self.len, "Invalid source line index");
113 self.body.reader(self.body_start + start, end - start)
114 }
115
116 fn line_cursor(&self, line: u32) -> Result<LineCursor<'_>> {
117 let offset = self.uncached_offset(line)?;
118 let offsets = self.index.reader(
119 self.index_start + (u64::from(line) + 1) * 8,
120 u64::from(self.lines - line) * 8,
121 )?;
122 Ok(LineCursor {
123 source: self,
124 offset,
125 offsets: BufReader::with_capacity(CHUNK, offsets),
126 body: BufReader::with_capacity(
127 CHUNK,
128 self.body
129 .reader(self.body_start + offset, self.len - offset)?,
130 ),
131 })
132 }
133}
134
135pub(crate) struct SnapshotReader<'a> {
136 source: &'a Snapshot,
137 offsets: ReadWindow,
138 body: ReadWindow,
139}
140
141impl SnapshotReader<'_> {
142 pub(crate) fn line(&mut self, number: u32, from: u64) -> Result<&mut ReadWindow> {
143 ensure!(
144 number != 0 && number <= self.source.lines,
145 "Source line is outside the snapshot"
146 );
147 let index = self.source.index_start + u64::from(number - 1) * 8;
148 let mut bytes = [0; 16];
149 self.offsets
150 .range(index..index + 16)?
151 .read_exact(&mut bytes)?;
152 let start = u64::from_le_bytes(bytes[..8].try_into()?);
153 let end = u64::from_le_bytes(bytes[8..].try_into()?);
154 ensure!(
155 start <= end && end <= self.source.len,
156 "Invalid source line index"
157 );
158 ensure!(from <= end - start, "Search starts outside the source line");
159 self.body
160 .range(self.source.body_start + start + from..self.source.body_start + end)
161 }
162}
163
164struct LineCursor<'a> {
165 source: &'a Snapshot,
166 offset: u64,
167 offsets: BufReader<StoreReader>,
168 body: BufReader<StoreReader>,
169}
170
171impl LineCursor<'_> {
172 fn next(&mut self) -> Result<std::io::Take<&mut BufReader<StoreReader>>> {
173 let mut bytes = [0; 8];
174 self.offsets.read_exact(&mut bytes)?;
175 let end = u64::from_le_bytes(bytes);
176 let len = end
177 .checked_sub(self.offset)
178 .context("Invalid source line index")?;
179 self.offset = end;
180 Ok(self.body.by_ref().take(len))
181 }
182}
183
184pub struct SourceStore {
186 body: StoreWriter,
187 index: StoreWriter,
188}
189
190impl SourceStore {
191 pub(crate) fn named_sharing(store: &ByteStore) -> Result<(Self, tempfile::TempPath)> {
192 let (body, path) = StoreWriter::named_sharing(store)?;
193 Ok((
194 Self {
195 body,
196 index: StoreWriter::sharing(store)?,
197 },
198 path,
199 ))
200 }
201 pub fn sharing(store: &ByteStore) -> Result<Self> {
202 Ok(Self {
203 body: StoreWriter::sharing(store)?,
204 index: StoreWriter::sharing(store)?,
205 })
206 }
207
208 pub fn bytes(&self) -> u64 {
209 self.body.store().len() + self.index.store().len()
210 }
211
212 pub fn capture(
213 &mut self,
214 input: impl Read,
215 check: &impl Fn() -> Result<()>,
216 ) -> Result<Arc<Snapshot>> {
217 let before = self.checkpoint();
218 let result = self.capture_inner(input, check);
219 if result.is_err() {
220 self.rollback(before)?;
221 }
222 result
223 }
224
225 pub(crate) fn checkpoint(&self) -> (u64, u64) {
226 (self.body.store().len(), self.index.store().len())
227 }
228
229 pub(crate) fn rollback(&mut self, position: (u64, u64)) -> Result<()> {
230 self.body.rollback(position.0)?;
231 self.index.rollback(position.1)
232 }
233
234 fn capture_inner(
235 &mut self,
236 mut input: impl Read,
237 check: &impl Fn() -> Result<()>,
238 ) -> Result<Arc<Snapshot>> {
239 let (body_start, index_start) = self.checkpoint();
240 let mut offsets = std::io::BufWriter::with_capacity(CHUNK, &mut self.index);
241 offsets.write_all(&0u64.to_le_bytes())?;
242 let mut buf = [0; CHUNK];
243 let (mut len, mut lines, mut final_newline) = (0u64, 0u32, false);
244 loop {
245 check()?;
246 let count = input.read(&mut buf)?;
247 if count == 0 {
248 break;
249 }
250 self.body.append(&buf[..count])?;
251 for (i, byte) in buf[..count].iter().enumerate() {
252 if *byte == b'\n' {
253 lines = lines
254 .checked_add(1)
255 .context("Source line count exceeds the supported range")?;
256 offsets.write_all(&(len + i as u64 + 1).to_le_bytes())?;
257 }
258 }
259 len = len
260 .checked_add(count as u64)
261 .context("Source length overflow")?;
262 final_newline = buf[count - 1] == b'\n';
263 }
264 if len != 0 && !final_newline {
265 lines = lines
266 .checked_add(1)
267 .context("Source line count exceeds the supported range")?;
268 offsets.write_all(&len.to_le_bytes())?;
269 }
270 offsets.flush()?;
271 drop(offsets);
272 check()?;
273 Ok(Arc::new(Snapshot {
274 body: self.body.store(),
275 index: self.index.store(),
276 body_start,
277 index_start,
278 len,
279 lines,
280 final_newline,
281 }))
282 }
283}
284
285struct ByteComparison<'a> {
286 a: Vec<u8>,
287 b: Vec<u8>,
288 check: &'a dyn Fn() -> Result<()>,
289}
290
291impl<'a> ByteComparison<'a> {
292 fn new(check: &'a dyn Fn() -> Result<()>) -> Self {
293 Self {
294 a: vec![0; CHUNK],
295 b: vec![0; CHUNK],
296 check,
297 }
298 }
299
300 fn equal(&mut self, mut left: impl Read, mut right: impl Read) -> Result<()> {
301 let Self { a, b, check } = self;
302 loop {
303 check()?;
304 let count = left.read(a)?;
305 if count == 0 {
306 ensure!(
307 right.read(&mut b[..1])? == 0,
308 "Source does not match this diff: extra bytes"
309 );
310 return Ok(());
311 }
312 right
313 .read_exact(&mut b[..count])
314 .context("Source does not match this diff: missing bytes")?;
315 ensure!(
316 a[..count] == b[..count],
317 "Source does not match this diff: different bytes"
318 );
319 }
320 }
321}