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> {
79 if self.sync_all {
80 self.mmap.flush()
81 } else {
82 self.mmap.flush_async()
83 }
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 #![allow(clippy::unwrap_used)]
90 use super::*;
91 use std::sync::{Arc, Mutex};
92 use std::{fs::File, io::Read, vec::Vec};
93 use tempfile::NamedTempFile;
94
95 #[tokio::test]
96 async fn test_rand_file_pusher() {
97 let temp_file = NamedTempFile::new().unwrap();
99 let file_path = temp_file.path();
100
101 let mut pusher = MmapFilePusher::new(&temp_file.reopen().unwrap().into(), 10, false)
103 .await
104 .unwrap();
105
106 let data = b"234";
108 let range = 2..5;
109 pusher.push(&range, data[..].into()).unwrap();
110 pusher.flush().unwrap();
111
112 let mut file_content = Vec::new();
114 File::open(file_path)
115 .unwrap()
116 .read_to_end(&mut file_content)
117 .unwrap();
118 assert_eq!(file_content, b"\0\x00234\0\0\0\0\0");
119 }
120
121 #[tokio::test]
122 async fn test_debug_and_sync_all_flush() {
123 let temp_file = NamedTempFile::new().unwrap();
125 let file_path = temp_file.path().to_path_buf();
126 let mut pusher = MmapFilePusher::new(&temp_file.reopen().unwrap().into(), 10, true)
127 .await
128 .unwrap();
129 let _ = format!("{pusher:?}");
130 pusher.push(&(2..5), b"234"[..].into()).unwrap();
131 pusher.flush().unwrap();
132 let mut file_content = Vec::new();
133 File::open(&file_path)
134 .unwrap()
135 .read_to_end(&mut file_content)
136 .unwrap();
137 assert_eq!(file_content, b"\0\x00234\0\0\0\0\0");
138 }
139
140 #[tokio::test]
141 async fn test_listener_invoked_on_push() {
142 let temp_file = NamedTempFile::new().unwrap();
144 let mut pusher = MmapFilePusher::new(&temp_file.reopen().unwrap().into(), 10, false)
145 .await
146 .unwrap();
147 let seen = Arc::new(Mutex::new(None::<ProgressEntry>));
148 let seen2 = seen.clone();
149 pusher.set_listener(Box::new(move |r| {
150 *seen2.lock().unwrap() = Some(r);
151 }));
152 pusher.push(&(2..5), b"234"[..].into()).unwrap();
153 assert_eq!(*seen.lock().unwrap(), Some(2..5));
154 }
155
156 #[tokio::test]
157 async fn test_mmap_pusher_zero_length_file() {
158 let temp_file = NamedTempFile::new().unwrap();
162 let result = MmapFilePusher::new(&temp_file.reopen().unwrap().into(), 0, false).await;
163 assert!(
164 result.is_ok(),
165 "zero-length download should construct an mmap pusher, got: {result:?}"
166 );
167 }
168}