1use crate::{ProgressEntry, ProgressListener, Pusher};
4use bytes::Bytes;
5use memmap2::MmapMut;
6
7#[cfg(unix)]
8use std::os::unix::io::AsRawFd;
9#[cfg(windows)]
10use std::os::windows::io::AsRawHandle;
11
12pub struct MmapFilePusher {
19 mmap: MmapMut,
20 sync_all: bool,
21 listener: Option<ProgressListener>,
22}
23
24impl std::fmt::Debug for MmapFilePusher {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 f.debug_struct("MmapFilePusher")
27 .field("sync_all", &self.sync_all)
28 .finish_non_exhaustive()
29 }
30}
31impl MmapFilePusher {
32 pub async fn new(file: &tokio::fs::File, size: u64, sync_all: bool) -> std::io::Result<Self> {
36 file.set_len(size).await?;
37 #[cfg(unix)]
44 let raw = file.as_raw_fd();
45 #[cfg(windows)]
46 let raw = file.as_raw_handle() as usize;
47 let mmap = tokio::task::spawn_blocking(move || unsafe {
48 #[cfg(unix)]
49 let desc = raw;
50 #[cfg(windows)]
51 let desc = raw as std::os::windows::io::RawHandle;
52 MmapMut::map_mut(desc)
53 })
54 .await
55 .map_err(std::io::Error::other)??;
56 Ok(Self {
57 mmap,
58 sync_all,
59 listener: None,
60 })
61 }
62}
63impl Pusher for MmapFilePusher {
64 type Error = std::io::Error;
65
66 fn set_listener(&mut self, cb: ProgressListener) {
67 self.listener = Some(cb);
68 }
69
70 fn push(&mut self, range: &ProgressEntry, bytes: Bytes) -> Result<(), (Self::Error, Bytes)> {
71 #[allow(clippy::cast_possible_truncation)]
72 self.mmap[range.start as usize..range.end as usize].copy_from_slice(&bytes);
73 if let Some(l) = &mut self.listener {
74 l(range.clone());
75 }
76 Ok(())
77 }
78 fn flush(&mut self) -> Result<(), Self::Error> {
85 if self.sync_all {
86 self.mmap.flush()
87 } else {
88 Ok(())
89 }
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 #![allow(clippy::unwrap_used)]
96 use super::*;
97 use std::sync::{Arc, Mutex};
98 use std::{fs::File, io::Read, vec::Vec};
99 use tempfile::NamedTempFile;
100
101 #[tokio::test]
102 async fn test_rand_file_pusher() {
103 let temp_file = NamedTempFile::new().unwrap();
105 let file_path = temp_file.path();
106
107 let mut pusher = MmapFilePusher::new(&temp_file.reopen().unwrap().into(), 10, false)
109 .await
110 .unwrap();
111
112 let data = b"234";
114 let range = 2..5;
115 pusher.push(&range, data[..].into()).unwrap();
116 pusher.flush().unwrap();
117
118 let mut file_content = Vec::new();
120 File::open(file_path)
121 .unwrap()
122 .read_to_end(&mut file_content)
123 .unwrap();
124 assert_eq!(file_content, b"\0\x00234\0\0\0\0\0");
125 }
126
127 #[tokio::test]
128 async fn test_debug_and_sync_all_flush() {
129 let temp_file = NamedTempFile::new().unwrap();
131 let file_path = temp_file.path().to_path_buf();
132 let mut pusher = MmapFilePusher::new(&temp_file.reopen().unwrap().into(), 10, true)
133 .await
134 .unwrap();
135 let _ = format!("{pusher:?}");
136 pusher.push(&(2..5), b"234"[..].into()).unwrap();
137 pusher.flush().unwrap();
138 let mut file_content = Vec::new();
139 File::open(&file_path)
140 .unwrap()
141 .read_to_end(&mut file_content)
142 .unwrap();
143 assert_eq!(file_content, b"\0\x00234\0\0\0\0\0");
144 }
145
146 #[tokio::test]
147 async fn test_listener_invoked_on_push() {
148 let temp_file = NamedTempFile::new().unwrap();
150 let mut pusher = MmapFilePusher::new(&temp_file.reopen().unwrap().into(), 10, false)
151 .await
152 .unwrap();
153 let seen = Arc::new(Mutex::new(None::<ProgressEntry>));
154 let seen2 = seen.clone();
155 pusher.set_listener(Box::new(move |r| {
156 *seen2.lock().unwrap() = Some(r);
157 }));
158 pusher.push(&(2..5), b"234"[..].into()).unwrap();
159 assert_eq!(*seen.lock().unwrap(), Some(2..5));
160 }
161
162 #[tokio::test]
163 async fn test_mmap_pusher_zero_length_file() {
164 let temp_file = NamedTempFile::new().unwrap();
168 let result = MmapFilePusher::new(&temp_file.reopen().unwrap().into(), 0, false).await;
169 assert!(
170 result.is_ok(),
171 "zero-length download should construct an mmap pusher, got: {result:?}"
172 );
173 }
174}