Skip to main content

diffs_rs/
source.rs

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/// Immutable bytes and disk-backed line boundaries. Lines include their original terminators.
21#[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    /// Zero-based half-open line range, including the original line terminators.
91    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
184/// One session's body and index files, shared by all completed snapshots.
185pub 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}