1use std::io::{self, Read as _, Seek as _, SeekFrom, Write as _};
13use std::ops::Range;
14use std::path::Path;
15
16#[derive(Debug)]
18pub struct SparseCache {
19 file: std::fs::File,
20 ranges: Vec<Range<u64>>,
22 total_size: u64,
23}
24
25impl SparseCache {
26 pub fn new(total_size: u64, spool_dir: &Path) -> io::Result<Self> {
33 let file = tempfile::tempfile_in(spool_dir)?;
34 file.set_len(total_size)?;
37 Ok(Self {
38 file,
39 ranges: Vec::new(),
40 total_size,
41 })
42 }
43
44 pub fn missing_ranges(&self, offset: u64, size: u64) -> Vec<Range<u64>> {
49 let end = offset.saturating_add(size).min(self.total_size);
50 if offset >= end {
51 return Vec::new();
52 }
53
54 let mut missing = Vec::new();
55 let mut cursor = offset;
56
57 for populated in &self.ranges {
58 if populated.end <= cursor {
59 continue;
60 }
61 if populated.start >= end {
62 break;
63 }
64 if populated.start > cursor {
65 missing.push(cursor..populated.start.min(end));
66 }
67 cursor = populated.end;
68 if cursor >= end {
69 break;
70 }
71 }
72
73 if cursor < end {
74 missing.push(cursor..end);
75 }
76
77 missing
78 }
79
80 pub fn write_at(&mut self, offset: u64, data: &[u8]) -> io::Result<()> {
82 if data.is_empty() {
83 return Ok(());
84 }
85 self.file.seek(SeekFrom::Start(offset))?;
86 self.file.write_all(data)?;
87
88 let new_range = offset..offset + data.len() as u64;
89 self.insert_range(new_range);
90 Ok(())
91 }
92
93 pub fn read_at(&mut self, offset: u64, size: u64) -> io::Result<Vec<u8>> {
99 if offset >= self.total_size {
100 return Ok(Vec::new());
101 }
102 let read_len = size.min(self.total_size - offset) as usize;
103 let mut buf = vec![0u8; read_len];
104 self.file.seek(SeekFrom::Start(offset))?;
105 self.file.read_exact(&mut buf)?;
106 Ok(buf)
107 }
108
109 fn insert_range(&mut self, new: Range<u64>) {
112 let mut start = new.start;
113 let mut end = new.end;
114
115 self.ranges.retain(|r| {
117 if r.end < start || r.start > end {
118 true
119 } else {
120 start = start.min(r.start);
121 end = end.max(r.end);
122 false
123 }
124 });
125
126 let pos = self
128 .ranges
129 .binary_search_by(|r| r.start.cmp(&start))
130 .unwrap_or_else(|p| p);
131 self.ranges.insert(pos, start..end);
132 }
133
134 #[cfg(test)]
135 pub fn populated_ranges(&self) -> &[Range<u64>] {
136 &self.ranges
137 }
138}
139
140#[cfg(test)]
141#[allow(clippy::single_range_in_vec_init)] mod tests {
143 use super::*;
144 use std::path::PathBuf;
145
146 fn spool() -> PathBuf {
149 std::env::temp_dir()
150 }
151
152 #[test]
153 fn missing_ranges_empty_cache() {
154 let cache = SparseCache::new(1000, &spool()).unwrap();
155 assert_eq!(cache.missing_ranges(0, 100), vec![0..100]);
156 assert_eq!(cache.missing_ranges(500, 100), vec![500..600]);
157 }
158
159 #[test]
160 fn missing_ranges_full_hit() {
161 let mut cache = SparseCache::new(1000, &spool()).unwrap();
162 cache.write_at(0, &[0u8; 500]).unwrap();
163 assert_eq!(cache.missing_ranges(100, 200), Vec::<Range<u64>>::new());
164 assert_eq!(cache.missing_ranges(0, 500), Vec::<Range<u64>>::new());
165 }
166
167 #[test]
168 fn missing_ranges_partial_hit_at_start() {
169 let mut cache = SparseCache::new(1000, &spool()).unwrap();
170 cache.write_at(0, &[0u8; 100]).unwrap();
172 assert_eq!(cache.missing_ranges(0, 200), vec![100..200]);
174 }
175
176 #[test]
177 fn missing_ranges_partial_hit_at_end() {
178 let mut cache = SparseCache::new(1000, &spool()).unwrap();
179 cache.write_at(100, &[0u8; 100]).unwrap();
181 assert_eq!(cache.missing_ranges(0, 200), vec![0..100]);
183 }
184
185 #[test]
186 fn missing_ranges_gap_in_middle() {
187 let mut cache = SparseCache::new(1000, &spool()).unwrap();
188 cache.write_at(0, &[0u8; 100]).unwrap();
189 cache.write_at(200, &[0u8; 100]).unwrap();
190 assert_eq!(cache.missing_ranges(0, 300), vec![100..200]);
192 }
193
194 #[test]
195 fn missing_ranges_multiple_gaps() {
196 let mut cache = SparseCache::new(1000, &spool()).unwrap();
197 cache.write_at(100, &[0u8; 50]).unwrap();
198 cache.write_at(300, &[0u8; 50]).unwrap();
199 assert_eq!(
201 cache.missing_ranges(0, 400),
202 vec![0..100, 150..300, 350..400]
203 );
204 }
205
206 #[test]
207 fn missing_ranges_clips_to_total_size() {
208 let mut cache = SparseCache::new(500, &spool()).unwrap();
209 assert_eq!(cache.missing_ranges(400, 1000), vec![400..500]);
211 cache.write_at(400, &[0u8; 100]).unwrap();
212 assert_eq!(cache.missing_ranges(400, 1000), Vec::<Range<u64>>::new());
213 }
214
215 #[test]
216 fn missing_ranges_offset_past_end() {
217 let cache = SparseCache::new(100, &spool()).unwrap();
218 assert_eq!(cache.missing_ranges(200, 100), Vec::<Range<u64>>::new());
219 }
220
221 #[test]
222 fn adjacent_ranges_merge() {
223 let mut cache = SparseCache::new(1000, &spool()).unwrap();
224 cache.write_at(0, &[0u8; 100]).unwrap();
225 cache.write_at(100, &[0u8; 100]).unwrap();
226 assert_eq!(cache.populated_ranges(), &[0..200]);
227 }
228
229 #[test]
230 fn overlapping_ranges_merge() {
231 let mut cache = SparseCache::new(1000, &spool()).unwrap();
232 cache.write_at(0, &[0u8; 100]).unwrap();
233 cache.write_at(50, &[0u8; 100]).unwrap();
234 assert_eq!(cache.populated_ranges(), &[0..150]);
235 }
236
237 #[test]
238 fn disjoint_ranges_preserved() {
239 let mut cache = SparseCache::new(1000, &spool()).unwrap();
240 cache.write_at(0, &[0u8; 100]).unwrap();
241 cache.write_at(500, &[0u8; 100]).unwrap();
242 assert_eq!(cache.populated_ranges(), &[0..100, 500..600]);
243 }
244
245 #[test]
246 fn insertion_sorted() {
247 let mut cache = SparseCache::new(1000, &spool()).unwrap();
248 cache.write_at(500, &[0u8; 100]).unwrap();
249 cache.write_at(0, &[0u8; 100]).unwrap();
250 cache.write_at(300, &[0u8; 50]).unwrap();
251 assert_eq!(cache.populated_ranges(), &[0..100, 300..350, 500..600]);
252 }
253
254 #[test]
255 fn write_read_roundtrip() {
256 let mut cache = SparseCache::new(1000, &spool()).unwrap();
257 let data: Vec<u8> = (0..200).map(|i| (i % 256) as u8).collect();
258 cache.write_at(100, &data).unwrap();
259 let read = cache.read_at(100, 200).unwrap();
260 assert_eq!(read, data);
261 }
262
263 #[test]
264 fn read_at_clips_to_total_size() {
265 let mut cache = SparseCache::new(150, &spool()).unwrap();
266 cache.write_at(100, &[0u8; 50]).unwrap();
267 let read = cache.read_at(100, 200).unwrap();
269 assert_eq!(read.len(), 50);
270 }
271
272 #[test]
273 fn read_at_past_end_returns_empty() {
274 let mut cache = SparseCache::new(100, &spool()).unwrap();
275 let read = cache.read_at(200, 50).unwrap();
276 assert_eq!(read, Vec::<u8>::new());
277 }
278
279 #[test]
280 fn three_way_merge() {
281 let mut cache = SparseCache::new(1000, &spool()).unwrap();
283 cache.write_at(0, &[0u8; 100]).unwrap();
284 cache.write_at(200, &[0u8; 100]).unwrap();
285 cache.write_at(100, &[0u8; 100]).unwrap();
286 assert_eq!(cache.populated_ranges(), &[0..300]);
287 }
288}