Skip to main content

xtap_core_lib/
io.rs

1use crate::error::Result;
2use anyhow::Context;
3use std::fs;
4use std::path::Path;
5
6/// 写入文件,自动创建父目录
7pub 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
15/// 安全读取文件
16pub 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    /// 内存映射读取文件(零拷贝高性能)
31    /// 适用于大文件处理,如 RAW 图片、向量索引等
32    ///
33    /// # Safety
34    ///
35    /// 调用方必须保证 `path` 指向一个有效且可读的文件,且在返回的
36    /// `Mmap` 存活期间不得以破坏内存映射的方式修改或删除该文件
37    /// (例如截断、替换文件内容或撤销文件句柄的底层存储)。映射区域的生命周期
38    /// 与返回值绑定,跨线程使用时需自行保证同步。
39    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    /// 内存映射写入文件
47    ///
48    /// # Safety
49    ///
50    /// 调用方必须保证 `path` 指向一个有效、存在且**可写**的文件(`MmapMut` 无法
51    /// 扩容,若文件小于将要写入的字节数会发生越界写入)。返回的
52    /// `MmapMut` 存活期间,不得有其他线程/进程并发写入该文件,
53    /// 亦不得以破坏映射的方式修改或删除底层文件。
54    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    /// 异步写入文件,自动创建父目录
75    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    /// 异步读取文件
88    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        // SAFETY: 测试中 p 有效可读,映射期间无并发修改
140        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}