1mod handle;
27mod lock;
28
29use std::{
30 cmp::Ordering,
31 fs::{File, Metadata},
32 hash::{Hash, Hasher},
33 io::{self, Read, Seek, Write},
34 path::Path,
35};
36
37use handle::PortableFileHandle;
38use lock::Lock;
39
40type Arc<T> = std::sync::Arc<T>;
41
42#[derive(Debug)]
45struct FileImpl {
46 file: File,
47 state_lock: Lock<()>,
48}
49
50#[derive(Debug, Clone)]
61pub struct CloneableFile {
62 inner: Arc<FileImpl>,
63}
64
65impl CloneableFile {
66 pub fn open<P: AsRef<Path>>(path: P) -> io::Result<CloneableFile> {
68 File::open(path).map(CloneableFile::from)
69 }
70
71 fn with_lock<T>(&self, func: impl FnOnce(&File) -> T) -> T {
73 lock::acquire(&self.inner.state_lock, |_| func(&self.inner.file))
74 }
75
76 pub fn create<P: AsRef<Path>>(path: P) -> io::Result<CloneableFile> {
78 File::create(path).map(CloneableFile::from)
79 }
80
81 pub fn metadata(&self) -> io::Result<Metadata> {
83 self.with_lock(|f| f.metadata())
84 }
85
86 pub fn set_len(&self, size: u64) -> io::Result<()> {
89 self.with_lock(|f| f.set_len(size))
90 }
91
92 pub fn try_clone(&self) -> io::Result<CloneableFile> {
96 self.with_lock(|f| f.try_clone().map(CloneableFile::from))
97 }
98
99 fn portable_fd(&self) -> PortableFileHandle {
100 handle::portable_file_handle(&self.inner.file)
101 }
102}
103
104impl From<File> for CloneableFile {
105 fn from(file: File) -> Self {
106 CloneableFile {
107 inner: Arc::new(FileImpl {
108 file,
109 state_lock: Lock::new(()),
110 }),
111 }
112 }
113}
114
115impl Read for CloneableFile {
116 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
117 self.with_lock(|mut f| f.read(buf))
118 }
119}
120
121impl Read for &CloneableFile {
122 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
123 self.with_lock(|mut f| f.read(buf))
124 }
125}
126
127impl Write for CloneableFile {
128 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
129 self.with_lock(|mut f| f.write(buf))
130 }
131
132 fn flush(&mut self) -> io::Result<()> {
133 self.with_lock(|mut f| f.flush())
134 }
135}
136
137impl Write for &CloneableFile {
138 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
139 self.with_lock(|mut f| f.write(buf))
140 }
141
142 fn flush(&mut self) -> io::Result<()> {
143 self.with_lock(|mut f| f.flush())
144 }
145}
146
147impl Seek for CloneableFile {
148 fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
149 self.with_lock(|mut f| f.seek(pos))
150 }
151}
152
153impl Seek for &CloneableFile {
154 fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
155 self.with_lock(|mut f| f.seek(pos))
156 }
157}
158
159impl PartialEq for CloneableFile {
160 fn eq(&self, other: &Self) -> bool {
161 self.portable_fd().eq(&other.portable_fd())
162 }
163}
164
165impl Eq for CloneableFile {}
166
167impl Hash for CloneableFile {
168 fn hash<H: Hasher>(&self, state: &mut H) {
169 self.portable_fd().hash(state)
170 }
171}
172
173impl PartialOrd for CloneableFile {
174 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
175 self.portable_fd().partial_cmp(&other.portable_fd())
176 }
177}
178
179impl Ord for CloneableFile {
180 fn cmp(&self, other: &Self) -> Ordering {
181 self.portable_fd().cmp(&other.portable_fd())
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use std::{
188 cmp::Ordering,
189 collections::{BTreeMap, HashMap},
190 };
191
192 use super::*;
193
194 fn is_read_write_seek<T: io::Read + io::Write + io::Seek>(_: T) {}
195 fn is_send_sync<T: Send + Sync>(_: T) {}
196
197 fn cloneable_tempfile() -> io::Result<CloneableFile> {
198 tempfile::tempfile().map(CloneableFile::from)
199 }
200
201 #[test]
202 fn cloneable_file_is_usable() -> io::Result<()> {
203 let f = cloneable_tempfile()?;
204 let f2 = f.clone();
205 is_read_write_seek(f);
206 is_read_write_seek(&f2);
207 is_send_sync(f2);
208 is_send_sync(tempfile::tempfile()?);
209 Ok(())
210 }
211
212 #[test]
213 fn equality() -> io::Result<()> {
214 let f1 = cloneable_tempfile()?;
215 let f2 = cloneable_tempfile()?;
216 assert_ne!(&f1, &f2);
217 assert_eq!(&f2, &f2);
218 assert_eq!(&f1, &f1);
219 Ok(())
220 }
221
222 #[test]
223 fn ordering() -> io::Result<()> {
224 let f1 = cloneable_tempfile()?;
225 let f2 = cloneable_tempfile()?;
226 assert!(matches!(f1.cmp(&f2), Ordering::Greater | Ordering::Less));
227 Ok(())
228 }
229
230 #[test]
231 fn do_ops_with_lock_acquired() -> io::Result<()> {
232 let f = cloneable_tempfile()?;
233 f.with_lock(|mut f| {
234 f.write_all(b"Hello,")?;
235 f.write_all(b"qq world!\n")?;
236 f.sync_all()?;
237 Ok(())
238 })
239 }
240
241 #[test]
242 fn can_use_with_btreemap() -> io::Result<()> {
243 let mut map = BTreeMap::new();
244 let f1 = cloneable_tempfile()?;
245 let f2 = cloneable_tempfile()?;
246 map.insert(f1.clone(), 1);
247 map.insert(f2, 2);
248 assert_eq!(map.len(), 2);
249 assert_eq!(map[&f1], 1);
250 Ok(())
251 }
252
253 #[test]
254 fn can_use_with_hashmap() -> io::Result<()> {
255 let mut map = HashMap::new();
256 let f1 = cloneable_tempfile()?;
257 let f2 = cloneable_tempfile()?;
258 map.insert(f1.clone(), 1);
259 map.insert(f2, 2);
260 assert_eq!(map.len(), 2);
261 assert_eq!(map[&f1], 1);
262 Ok(())
263 }
264}