streampager/
buffer_cache.rs1use std::borrow::Cow;
4use std::fs::File as StdFile;
5use std::io::{Read, Seek, SeekFrom};
6use std::path::{Path, PathBuf};
7
8use lru::LruCache;
9
10use crate::buffer::Buffer;
11use crate::error::Error;
12
13pub(crate) struct BufferCache {
14 path: PathBuf,
15 file: Option<StdFile>,
16 cache: LruCache<usize, Buffer>,
17 block_size: usize,
18}
19
20impl BufferCache {
21 pub(crate) fn new<P: AsRef<Path>>(path: P, block_size: usize, capacity: usize) -> Self {
22 let path = path.as_ref();
23 BufferCache {
24 path: path.to_path_buf(),
25 file: None,
26 cache: LruCache::new(capacity),
27 block_size,
28 }
29 }
30
31 pub(crate) fn clear(&mut self) {
32 self.cache.clear();
33 self.file = None;
34 }
35
36 fn open_file(&mut self) -> Result<(), Error> {
37 if self.file.is_none() {
38 self.file = Some(StdFile::open(&self.path)?);
39 }
40 Ok(())
41 }
42
43 fn get_buffer(&mut self, start: usize, end: usize) -> Result<Option<&mut Buffer>, Error> {
44 let block_index = start / self.block_size;
45 let block_offset = start % self.block_size;
46 self.open_file()?;
47 if let Some(buffer) = self.cache.get_mut(&block_index) {
48 fill_buffer(
49 self.file.as_mut().expect("file is open"),
50 buffer,
51 start,
52 block_offset,
53 end - start,
54 )?;
55 } else {
56 let mut buffer = Buffer::new(self.block_size);
57 fill_buffer(
58 self.file.as_mut().expect("file is open"),
59 &mut buffer,
60 block_index * self.block_size,
61 0,
62 self.block_size,
63 )?;
64 self.cache.put(block_index, buffer);
65 }
66 Ok(self.cache.get_mut(&block_index))
67 }
68
69 fn get_data(&mut self, start: usize, end: usize) -> Result<&[u8], Error> {
70 let block_size = self.block_size;
71 if let Some(buffer) = self.get_buffer(start, end)? {
72 let data = buffer.read();
73 let data_start = data.len().min(start % block_size);
74 let data_end = (data.len()).min((end - 1) % block_size + 1);
75 Ok(&data[data_start..data_end])
76 } else {
77 Ok(&[])
78 }
79 }
80
81 pub(crate) fn with_slice<T, F>(
82 &mut self,
83 start: usize,
84 end: usize,
85 mut call: F,
86 ) -> Result<T, Error>
87 where
88 F: FnMut(Cow<'_, [u8]>) -> T,
89 {
90 let start_block = start / self.block_size;
91 let end_block = (end - 1) / self.block_size;
92 if start_block == end_block {
93 Ok(call(Cow::Borrowed(self.get_data(start, end)?)))
94 } else {
95 let mut v = Vec::with_capacity(end - start);
98 let first_end = (start_block + 1) * self.block_size;
99 let first_slice = self.get_data(start, first_end)?;
100 v.extend_from_slice(first_slice);
101 v.resize(first_end - start, 0);
102 for b in start_block + 1..end_block {
103 let block_start = b * self.block_size;
104 let block_end = block_start + self.block_size;
105 let block_slice = self.get_data(block_start, block_end)?;
106 v.extend_from_slice(block_slice);
107 v.resize(block_end - start, 0);
108 }
109 let end_start = end_block * self.block_size;
110 let end_slice = self.get_data(end_start, end)?;
111 v.extend_from_slice(end_slice);
112 v.resize(end - start, 0);
113 Ok(call(Cow::Owned(v)))
114 }
115 }
116}
117
118fn fill_buffer(
119 file: &mut StdFile,
120 buffer: &mut Buffer,
121 file_offset: usize,
122 buffer_offset: usize,
123 len: usize,
124) -> Result<(), Error> {
125 if buffer_offset + len <= buffer.available() {
126 return Ok(());
127 }
128 let mut write = buffer.write();
129 if file.seek(SeekFrom::Start(file_offset as u64)).is_err() {
130 return Ok(());
132 }
133 loop {
134 match file.read(&mut write) {
135 Ok(0) => {
136 break;
138 }
139 Ok(len) => {
140 write.written(len);
142 break;
143 }
144 Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => {}
145 Err(e) => {
146 return Err(e.into());
147 }
148 }
149 }
150 Ok(())
151}
152
153#[cfg(test)]
154mod test {
155 use super::*;
156 use std::io::Write;
157 use tempfile::NamedTempFile;
158
159 #[test]
160 fn basic() -> Result<(), Error> {
161 let mut t = NamedTempFile::new()?;
162 write!(t, "HERE IS SOME DATA")?;
163 t.flush()?;
164 let mut c = BufferCache::new(t.path(), 4, 2);
165 let mut read_range = |start, end| c.with_slice(start, end, |data| data.into_owned());
166 assert_eq!(read_range(0, 4)?.as_slice(), b"HERE");
167 assert_eq!(read_range(5, 7)?.as_slice(), b"IS");
168 assert_eq!(read_range(3, 9)?.as_slice(), b"E IS S");
169 assert_eq!(read_range(0, 17)?.as_slice(), b"HERE IS SOME DATA");
170 assert_eq!(read_range(0, 20)?.as_slice(), b"HERE IS SOME DATA\0\0\0");
171 Ok(())
172 }
173}