1use std::fs::{File, Metadata};
2use std::io::ErrorKind::InvalidInput;
3use std::io::{self, Error, Read, Seek, SeekFrom, Write};
4use std::ops::Range;
5
6pub struct FileRegion<'a> {
7 file: &'a File,
8 range: Range<u64>,
9}
10
11impl<'a> FileRegion<'a> {
12 pub fn file_metadata(&self) -> io::Result<Metadata> {
13 self.file.metadata()
14 }
15
16 pub fn range(self) -> Range<u64> {
17 self.range
18 }
19
20 pub fn len(&self) -> u64 {
21 self.range.end - self.range.start
22 }
23
24 pub fn is_empty(&self) -> bool {
25 self.range.is_empty()
26 }
27
28 pub fn new(file: &File, range: Range<u64>) -> FileRegion {
29 FileRegion { file, range }
30 }
31
32 pub fn is_valid(&self) -> io::Result<bool> {
33 let metadata = self.file.metadata()?;
34 let file_len = metadata.len();
35 Ok(self.range.start <= file_len && self.range.end <= file_len)
36 }
37
38 pub fn from_file(file: &'a File) -> io::Result<FileRegion<'a>> {
39 let range = 0..file.metadata()?.len();
40 Ok(FileRegion { file, range })
41 }
42
43 pub fn subregion(self, range: Range<u64>) -> io::Result<FileRegion<'a>> {
46 let start = {
47 let s = self.range.start.checked_add(range.start);
48 s.ok_or_else(|| Error::new(InvalidInput, "subregion start overflow"))?
49 };
50 let end = {
51 let e = self.range.start.checked_add(range.end);
52 e.ok_or_else(|| Error::new(InvalidInput, "subregion end overflow"))?
53 };
54 if start > self.range.end {
55 return Err(Error::new(InvalidInput, "subregion start exceeds parent"));
56 }
57 if end > self.range.end {
58 return Err(Error::new(InvalidInput, "subregion end exceeds parent"));
59 }
60 Ok(FileRegion {
61 file: self.file,
62 range: start..end,
63 })
64 }
65
66 pub fn read(&mut self, offset: u64, buf: &mut [u8]) -> io::Result<usize> {
67 self.file.seek(SeekFrom::Start(self.range.start + offset))?;
68 let max = self.len().saturating_sub(offset);
69 (&mut self.file).take(max).read(buf)
70 }
71
72 pub fn write(&mut self, offset: u64, buf: &[u8]) -> io::Result<usize> {
73 self.file.seek(SeekFrom::Start(self.range.start + offset))?;
74 let x = self.len().saturating_sub(offset).try_into();
75 let max: usize = x.map_err(|_| Error::new(InvalidInput, "offset too large"))?;
76 let buf_max = max.min(buf.len());
77 self.file.write(&buf[..buf_max])
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use std::io::{Read, Seek, SeekFrom, Write};
84 use tempfile::tempfile;
85
86 use super::FileRegion;
87
88 #[test]
89 fn test_from_file() {
90 let mut file = tempfile().unwrap();
91 file.write_all(b"Hello, World!").unwrap();
92 file.flush().unwrap();
93 let fr = FileRegion::from_file(&file).unwrap();
94 assert_eq!(fr.range(), 0..13);
95 }
96
97 #[test]
98 fn test_is_valid_true() {
99 let mut file = tempfile().unwrap();
100 file.write_all(b"0123456789").unwrap();
101 file.flush().unwrap();
102 let fr = FileRegion::from_file(&file).unwrap();
103 assert!(fr.is_valid().unwrap());
104 }
105
106 #[test]
107 fn test_is_valid_false_1() {
108 let file = tempfile().unwrap();
109 let fr = FileRegion::new(&file, 0..7);
110 assert!(!fr.is_valid().unwrap());
111 }
112
113 #[test]
114 fn test_is_valid_false_2() {
115 let mut file = tempfile().unwrap();
116 file.write_all(b"0123456789").unwrap();
117 file.flush().unwrap();
118 let fr = FileRegion::new(&file, 0..11);
119 assert!(!fr.is_valid().unwrap());
120 }
121
122 #[test]
123 fn test_subregion() {
124 let file = tempfile().unwrap();
125 let fr = FileRegion::new(&file, 100..2100);
126 let sub = fr.subregion(200..600).unwrap();
127 assert_eq!(sub.range(), 300..700);
128 }
129
130 #[test]
131 fn test_write_within_region() {
132 let mut file = tempfile().unwrap();
133 file.write_all(&[0; 200]).unwrap();
134
135 {
136 let mut fr = FileRegion::new(&file, 100..120);
137 let written = fr.write(0, b"enshittification").unwrap();
138 assert_eq!(written, 16);
139 }
140
141 file.seek(SeekFrom::Start(0)).unwrap();
142 let mut content = vec![0; 200];
143 file.read_exact(&mut content).unwrap();
144
145 assert_eq!(content[..100], [0; 100]);
146 assert_eq!(&content[100..116], b"enshittification");
147 assert_eq!(content[116..], [0; 84]);
148 }
149
150 #[test]
151 fn test_write_beyond_region() {
152 let mut file = tempfile().unwrap();
153 file.write_all(&[0; 200]).unwrap();
154
155 {
156 let mut fr = FileRegion::new(&file, 100..110);
157 let written = fr.write(0, b"enshittification").unwrap();
158 assert_eq!(written, 10);
159 }
160
161 file.seek(SeekFrom::Start(0)).unwrap();
162 let mut content = vec![0; 200];
163 file.read_exact(&mut content).unwrap();
164
165 assert_eq!(content[..100], [0; 100]);
166 assert_eq!(&content[100..110], b"enshittifi");
167 assert_eq!(content[110..], [0; 90]);
168 }
169
170 #[test]
171 fn test_example() {
172 let mut file = tempfile().unwrap();
173 file.write_all(b"Hello, FileRegion.").unwrap();
174
175 let mut region = FileRegion::new(&file, 7..16);
176 let mut buffer = [0; 9];
177 region.read(0, &mut buffer).unwrap();
178 assert_eq!(&buffer, b"FileRegio");
179
180 region.write(0, b"01234").unwrap();
181
182 let mut content = String::new();
183 file.seek(SeekFrom::Start(0)).unwrap();
184 file.read_to_string(&mut content).unwrap();
185 assert_eq!(content, "Hello, 01234egion.");
186 }
187}