1use std::{
4 collections::HashMap,
5 io::{BufReader, Write},
6 sync::{
7 Arc, Weak,
8 atomic::{AtomicBool, Ordering},
9 },
10};
11
12use anyhow::{Result, ensure};
13
14use crate::{
15 engine::{ReviewId, Side},
16 source::{CheckedPair, Snapshot},
17 storage::{FileIndex, copy_checked},
18};
19
20pub(crate) mod domain;
21pub(crate) mod literal;
22pub(crate) mod search;
23mod search_read;
24pub use literal::Direction as SearchDirection;
25pub(crate) use search_read::SearchStart;
26pub use search_read::{SearchMatch, SearchRead};
27
28#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30pub struct SourceRange {
31 pub review: ReviewId,
32 pub file: usize,
33 pub side: Side,
34 pub first: u32,
35 pub last: u32,
36}
37
38pub(crate) struct LineSelection {
39 pub range: SourceRange,
40 pub anchor: u32,
41 pub extending: bool,
42 pub checked: bool,
43}
44
45enum Source {
46 Patch(Arc<FileIndex>),
47 Full {
48 snapshot: Arc<Snapshot>,
49 valid: Arc<AtomicBool>,
50 },
51}
52
53#[derive(Default)]
54pub(crate) struct SourceLeases {
55 files: HashMap<usize, Weak<AtomicBool>>,
56}
57
58impl SourceLeases {
59 pub(crate) fn lease(&mut self, file: usize) -> Arc<AtomicBool> {
60 self.files.retain(|_, lease| lease.strong_count() != 0);
61 if let Some(lease) = self.files.get(&file).and_then(Weak::upgrade) {
62 return lease;
63 }
64 let lease = Arc::new(AtomicBool::new(true));
65 self.files.insert(file, Arc::downgrade(&lease));
66 lease
67 }
68
69 pub(crate) fn invalidate(&mut self, file: usize) {
70 if let Some(lease) = self.files.remove(&file).and_then(|lease| lease.upgrade()) {
71 lease.store(false, Ordering::Release);
72 }
73 }
74}
75
76pub struct SourceRead {
79 range: SourceRange,
80 source: Source,
81 active: Arc<AtomicBool>,
82}
83
84impl SourceRead {
85 pub(crate) fn new(
86 range: SourceRange,
87 file: Arc<FileIndex>,
88 pair: Option<(&Arc<CheckedPair>, Arc<AtomicBool>)>,
89 active: Arc<AtomicBool>,
90 ) -> Result<Self> {
91 ensure!(
92 range.first != 0 && range.first <= range.last,
93 "Invalid source line range"
94 );
95 ensure!(
96 file.id == range.file,
97 "Source range belongs to another file"
98 );
99 let source = if let Some((pair, valid)) = pair {
100 ensure!(
101 Arc::ptr_eq(pair.file(), &file),
102 "Full source belongs to another file occurrence"
103 );
104 let snapshot = match range.side {
105 Side::Old => pair.old(),
106 Side::New => pair.new_source(),
107 };
108 let snapshot =
109 snapshot.ok_or_else(|| anyhow::anyhow!("The requested side has no source"))?;
110 ensure!(
111 range.last <= snapshot.lines(),
112 "Source range is outside the full file"
113 );
114 Source::Full {
115 snapshot: snapshot.clone(),
116 valid,
117 }
118 } else {
119 Source::Patch(file)
120 };
121 Ok(Self {
122 range,
123 source,
124 active,
125 })
126 }
127
128 pub fn range(&self) -> SourceRange {
129 self.range
130 }
131
132 pub fn check(&self) -> Result<()> {
134 ensure!(
135 self.active.load(Ordering::Acquire),
136 "Source read belongs to a closed review"
137 );
138 if let Source::Full { valid, .. } = &self.source {
139 ensure!(
140 valid.load(Ordering::Acquire),
141 "Checked source is no longer available"
142 );
143 }
144 Ok(())
145 }
146
147 pub fn write_to(
152 &self,
153 output: &mut impl Write,
154 cancel: &impl Fn() -> Result<()>,
155 ) -> Result<u64> {
156 let check = || {
157 self.check()?;
158 cancel()
159 };
160 check()?;
161 let written = match &self.source {
162 Source::Patch(file) => file.write_source_lines(
163 self.range.side == Side::Old,
164 self.range.first..=self.range.last,
165 output,
166 &check,
167 )?,
168 Source::Full { snapshot, .. } => {
169 let reader = snapshot.uncached_range(self.range.first - 1..self.range.last)?;
170 let len = reader.remaining();
171 copy_checked(
172 &mut BufReader::with_capacity(64 * 1024, reader),
173 output,
174 len,
175 &check,
176 )?;
177 len
178 }
179 };
180 check()?;
181 Ok(written)
182 }
183}