1use crate::error::Result;
2use anyhow::Context;
3use std::fs;
4use std::path::Path;
5
6pub fn write_file<P: AsRef<Path>, D: AsRef<[u8]>>(path: P, data: D) -> Result<()> {
8 let path = path.as_ref();
9 if let Some(parent) = path.parent() {
10 fs::create_dir_all(parent).with_context(|| format!("创建父目录失败: {:?}", parent))?;
11 }
12 fs::write(path, data).with_context(|| format!("写入文件失败: {:?}", path))
13}
14
15pub fn read_file<P: AsRef<Path>>(path: P) -> Result<Vec<u8>> {
17 let path = path.as_ref();
18 fs::read(path).with_context(|| format!("读取文件失败: {:?}", path))
19}
20
21#[cfg(feature = "mmap")]
22pub mod mmap {
23 use crate::error::Result;
24 use anyhow::Context;
25 use memmap2::Mmap;
26 use std::fs;
27 use std::fs::File;
28 use std::path::Path;
29
30 pub unsafe fn map_file<P: AsRef<Path>>(path: P) -> Result<Mmap> {
40 let path = path.as_ref();
41 let file = File::open(path).with_context(|| format!("打开文件失败: {:?}", path))?;
42 let mmap = Mmap::map(&file).with_context(|| format!("内存映射失败: {:?}", path))?;
43 Ok(mmap)
44 }
45
46 pub unsafe fn map_file_mut<P: AsRef<Path>>(path: P) -> Result<memmap2::MmapMut> {
55 let path = path.as_ref();
56 let file = fs::OpenOptions::new()
57 .read(true)
58 .write(true)
59 .open(path)
60 .with_context(|| format!("打开文件失败: {:?}", path))?;
61 let mmap = memmap2::MmapMut::map_mut(&file)
62 .with_context(|| format!("内存映射失败: {:?}", path))?;
63 Ok(mmap)
64 }
65}
66
67#[cfg(feature = "async")]
68pub mod async_io {
69 use crate::error::Result;
70 use anyhow::Context;
71 use std::path::Path;
72 use tokio::fs;
73
74 pub async fn write_file_async<P: AsRef<Path>, D: AsRef<[u8]>>(path: P, data: D) -> Result<()> {
76 let path = path.as_ref();
77 if let Some(parent) = path.parent() {
78 fs::create_dir_all(parent)
79 .await
80 .with_context(|| format!("创建父目录失败: {:?}", parent))?;
81 }
82 fs::write(path, data)
83 .await
84 .with_context(|| format!("写入文件失败: {:?}", path))
85 }
86
87 pub async fn read_file_async<P: AsRef<Path>>(path: P) -> Result<Vec<u8>> {
89 let path = path.as_ref();
90 fs::read(path)
91 .await
92 .with_context(|| format!("读取文件失败: {:?}", path))
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 fn tmp_file(tag: &str) -> std::path::PathBuf {
101 std::env::temp_dir().join(format!(
102 "xtap_core_lib_io_test_{}_{}",
103 tag,
104 std::process::id()
105 ))
106 }
107
108 #[test]
109 fn write_then_read_roundtrip() {
110 let p = tmp_file("roundtrip");
111 let data = b"hello xtap core lib";
112 write_file(&p, data).expect("写入应成功");
113 let read = read_file(&p).expect("读取应成功");
114 assert_eq!(read, data);
115 std::fs::remove_file(&p).ok();
116 }
117
118 #[test]
119 fn write_creates_parent_dirs() {
120 let p = tmp_file("nested").join("a/b/c/data.bin");
121 write_file(&p, vec![1u8, 2, 3]).expect("写入应自动创建父目录");
122 assert!(p.exists());
123 assert_eq!(read_file(&p).unwrap(), vec![1u8, 2, 3]);
124 let _ = std::fs::remove_dir_all(tmp_file("nested"));
125 }
126
127 #[test]
128 fn read_missing_file_errors() {
129 let p = tmp_file("missing");
130 let _ = std::fs::remove_file(&p);
131 assert!(read_file(&p).is_err(), "读取不存在的文件应返回 Err");
132 }
133
134 #[cfg(feature = "mmap")]
135 #[test]
136 fn mmap_roundtrip() {
137 let p = tmp_file("mmap");
138 write_file(&p, vec![7u8; 64]).unwrap();
139 let mmap = unsafe { super::mmap::map_file(&p) }.expect("mmap 读取应成功");
141 assert_eq!(mmap.len(), 64);
142 assert!(mmap.iter().all(|&b| b == 7));
143 std::fs::remove_file(&p).ok();
144 }
145}