hermes_core/directories/
mmap.rs1use std::io;
6use std::ops::Range;
7use std::path::{Path, PathBuf};
8use std::sync::Arc;
9
10use async_trait::async_trait;
11use memmap2::Mmap;
12
13use super::{
14 Directory, DirectoryWriter, FileHandle, FileStreamingWriter, OwnedBytes, StreamingWriter,
15};
16
17pub struct MmapDirectory {
31 root: PathBuf,
32 label: super::IndexLabel,
33}
34
35impl MmapDirectory {
36 pub fn new(root: impl AsRef<Path>) -> Self {
38 Self {
39 root: root.as_ref().to_path_buf(),
40 label: super::IndexLabel::default(),
41 }
42 }
43
44 pub fn root(&self) -> &Path {
46 &self.root
47 }
48
49 fn resolve(&self, path: &Path) -> PathBuf {
50 self.root.join(path)
51 }
52
53 fn mmap_file(&self, path: &Path) -> io::Result<Arc<Mmap>> {
55 let full_path = self.resolve(path);
56 let file = std::fs::File::open(&full_path)?;
57 let mmap = unsafe { Mmap::map(&file)? };
58 Ok(Arc::new(mmap))
59 }
60}
61
62impl Clone for MmapDirectory {
63 fn clone(&self) -> Self {
64 Self {
65 root: self.root.clone(),
66 label: self.label.clone(),
68 }
69 }
70}
71
72#[async_trait]
73impl Directory for MmapDirectory {
74 async fn exists(&self, path: &Path) -> io::Result<bool> {
75 let full_path = self.resolve(path);
76 Ok(tokio::fs::try_exists(&full_path).await.unwrap_or(false))
77 }
78
79 async fn file_size(&self, path: &Path) -> io::Result<u64> {
80 let full_path = self.resolve(path);
81 let metadata = tokio::fs::metadata(&full_path).await?;
82 Ok(metadata.len())
83 }
84
85 async fn open_read(&self, path: &Path) -> io::Result<FileHandle> {
86 let mmap = self.mmap_file(path)?;
87 Ok(FileHandle::from_bytes(OwnedBytes::from_mmap(mmap)))
89 }
90
91 async fn read_range(&self, path: &Path, range: Range<u64>) -> io::Result<OwnedBytes> {
92 let mmap = self.mmap_file(path)?;
93 let start = range.start as usize;
94 let end = range.end as usize;
95
96 if end > mmap.len() {
97 return Err(io::Error::new(
98 io::ErrorKind::InvalidInput,
99 format!("Range {}..{} exceeds file size {}", start, end, mmap.len()),
100 ));
101 }
102
103 Ok(OwnedBytes::from_mmap_range(mmap, start..end))
105 }
106
107 async fn list_files(&self, prefix: &Path) -> io::Result<Vec<PathBuf>> {
108 let full_path = self.resolve(prefix);
109 let mut entries = tokio::fs::read_dir(&full_path).await?;
110 let mut files = Vec::new();
111
112 while let Some(entry) = entries.next_entry().await? {
113 if entry.file_type().await?.is_file() {
114 files.push(entry.path().strip_prefix(&self.root).unwrap().to_path_buf());
115 }
116 }
117
118 Ok(files)
119 }
120
121 async fn open_lazy(&self, path: &Path) -> io::Result<FileHandle> {
122 self.open_read(path).await
125 }
126
127 fn set_index_label(&self, label: &str) {
128 self.label.set(label);
129 }
130}
131
132#[async_trait]
133impl DirectoryWriter for MmapDirectory {
134 async fn write(&self, path: &Path, data: &[u8]) -> io::Result<()> {
135 let full_path = self.resolve(path);
136
137 if let Some(parent) = full_path.parent() {
139 tokio::fs::create_dir_all(parent).await?;
140 }
141
142 tokio::fs::write(&full_path, data).await
143 }
144
145 async fn delete(&self, path: &Path) -> io::Result<()> {
146 let full_path = self.resolve(path);
147 tokio::fs::remove_file(&full_path).await
148 }
149
150 async fn rename(&self, from: &Path, to: &Path) -> io::Result<()> {
151 let from_path = self.resolve(from);
152 let to_path = self.resolve(to);
153 tokio::fs::rename(&from_path, &to_path).await
154 }
155
156 async fn sync(&self) -> io::Result<()> {
157 let dir = std::fs::File::open(&self.root)?;
159 dir.sync_all()?;
160 Ok(())
161 }
162
163 async fn streaming_writer(&self, path: &Path) -> io::Result<Box<dyn StreamingWriter>> {
164 let full_path = self.resolve(path);
165 if let Some(parent) = full_path.parent() {
166 tokio::fs::create_dir_all(parent).await?;
167 }
168 let file = std::fs::File::create(&full_path)?;
169 Ok(Box::new(FileStreamingWriter::new(file)))
170 }
171
172 async fn streaming_writer_cold(&self, path: &Path) -> io::Result<Box<dyn StreamingWriter>> {
173 let full_path = self.resolve(path);
174 if let Some(parent) = full_path.parent() {
175 tokio::fs::create_dir_all(parent).await?;
176 }
177 let file = std::fs::File::create(&full_path)?;
178 Ok(Box::new(super::ColdStreamingWriter::new(
179 file,
180 self.label.get(),
181 )))
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188 use tempfile::TempDir;
189
190 #[tokio::test]
191 async fn test_mmap_directory_basic() {
192 let temp_dir = TempDir::new().unwrap();
193 let dir = MmapDirectory::new(temp_dir.path());
194
195 let test_data = b"Hello, mmap world!";
197 dir.write(Path::new("test.txt"), test_data).await.unwrap();
198
199 assert!(dir.exists(Path::new("test.txt")).await.unwrap());
201 assert!(!dir.exists(Path::new("nonexistent.txt")).await.unwrap());
202
203 assert_eq!(
205 dir.file_size(Path::new("test.txt")).await.unwrap(),
206 test_data.len() as u64
207 );
208
209 let slice = dir.open_read(Path::new("test.txt")).await.unwrap();
211 let bytes = slice.read_bytes().await.unwrap();
212 assert_eq!(bytes.as_slice(), test_data);
213
214 let range_bytes = dir.read_range(Path::new("test.txt"), 7..12).await.unwrap();
216 assert_eq!(range_bytes.as_slice(), b"mmap ");
217 }
218
219 #[tokio::test]
220 async fn test_mmap_directory_lazy_handle() {
221 let temp_dir = TempDir::new().unwrap();
222 let dir = MmapDirectory::new(temp_dir.path());
223
224 let data: Vec<u8> = (0..1000).map(|i| (i % 256) as u8).collect();
226 dir.write(Path::new("large.bin"), &data).await.unwrap();
227
228 let handle = dir.open_lazy(Path::new("large.bin")).await.unwrap();
230 assert_eq!(handle.len(), 1000);
231 assert!(handle.is_sync());
232
233 let range1 = handle.read_bytes_range(0..100).await.unwrap();
235 assert_eq!(range1.len(), 100);
236 assert_eq!(range1.as_slice(), &data[0..100]);
237
238 let range2 = handle.read_bytes_range_sync(500..600).unwrap();
240 assert_eq!(range2.as_slice(), &data[500..600]);
241 }
242}