xtap-core-lib 0.5.2

星TAP实验室通用 Rust 基座:跨平台路径/IO/硬件/MCP HTTP 服务/egui 主题与字体(features 按需裁剪)
Documentation
use crate::error::Result;
use anyhow::Context;
use std::fs;
use std::path::Path;

/// 写入文件,自动创建父目录
pub fn write_file<P: AsRef<Path>, D: AsRef<[u8]>>(path: P, data: D) -> Result<()> {
    let path = path.as_ref();
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent).with_context(|| format!("创建父目录失败: {:?}", parent))?;
    }
    fs::write(path, data).with_context(|| format!("写入文件失败: {:?}", path))
}

/// 安全读取文件
pub fn read_file<P: AsRef<Path>>(path: P) -> Result<Vec<u8>> {
    let path = path.as_ref();
    fs::read(path).with_context(|| format!("读取文件失败: {:?}", path))
}

#[cfg(feature = "mmap")]
pub mod mmap {
    use crate::error::Result;
    use anyhow::Context;
    use memmap2::Mmap;
    use std::fs;
    use std::fs::File;
    use std::path::Path;

    /// 内存映射读取文件(零拷贝高性能)
    /// 适用于大文件处理,如 RAW 图片、向量索引等
    ///
    /// # Safety
    ///
    /// 调用方必须保证 `path` 指向一个有效且可读的文件,且在返回的
    /// `Mmap` 存活期间不得以破坏内存映射的方式修改或删除该文件
    /// (例如截断、替换文件内容或撤销文件句柄的底层存储)。映射区域的生命周期
    /// 与返回值绑定,跨线程使用时需自行保证同步。
    pub unsafe fn map_file<P: AsRef<Path>>(path: P) -> Result<Mmap> {
        let path = path.as_ref();
        let file = File::open(path).with_context(|| format!("打开文件失败: {:?}", path))?;
        let mmap = Mmap::map(&file).with_context(|| format!("内存映射失败: {:?}", path))?;
        Ok(mmap)
    }

    /// 内存映射写入文件
    ///
    /// # Safety
    ///
    /// 调用方必须保证 `path` 指向一个有效、存在且**可写**的文件(`MmapMut` 无法
    /// 扩容,若文件小于将要写入的字节数会发生越界写入)。返回的
    /// `MmapMut` 存活期间,不得有其他线程/进程并发写入该文件,
    /// 亦不得以破坏映射的方式修改或删除底层文件。
    pub unsafe fn map_file_mut<P: AsRef<Path>>(path: P) -> Result<memmap2::MmapMut> {
        let path = path.as_ref();
        let file = fs::OpenOptions::new()
            .read(true)
            .write(true)
            .open(path)
            .with_context(|| format!("打开文件失败: {:?}", path))?;
        let mmap = memmap2::MmapMut::map_mut(&file)
            .with_context(|| format!("内存映射失败: {:?}", path))?;
        Ok(mmap)
    }
}

#[cfg(feature = "async")]
pub mod async_io {
    use crate::error::Result;
    use anyhow::Context;
    use std::path::Path;
    use tokio::fs;

    /// 异步写入文件,自动创建父目录
    pub async fn write_file_async<P: AsRef<Path>, D: AsRef<[u8]>>(path: P, data: D) -> Result<()> {
        let path = path.as_ref();
        if let Some(parent) = path.parent() {
            fs::create_dir_all(parent)
                .await
                .with_context(|| format!("创建父目录失败: {:?}", parent))?;
        }
        fs::write(path, data)
            .await
            .with_context(|| format!("写入文件失败: {:?}", path))
    }

    /// 异步读取文件
    pub async fn read_file_async<P: AsRef<Path>>(path: P) -> Result<Vec<u8>> {
        let path = path.as_ref();
        fs::read(path)
            .await
            .with_context(|| format!("读取文件失败: {:?}", path))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn tmp_file(tag: &str) -> std::path::PathBuf {
        std::env::temp_dir().join(format!(
            "xtap_core_lib_io_test_{}_{}",
            tag,
            std::process::id()
        ))
    }

    #[test]
    fn write_then_read_roundtrip() {
        let p = tmp_file("roundtrip");
        let data = b"hello xtap core lib";
        write_file(&p, data).expect("写入应成功");
        let read = read_file(&p).expect("读取应成功");
        assert_eq!(read, data);
        std::fs::remove_file(&p).ok();
    }

    #[test]
    fn write_creates_parent_dirs() {
        let p = tmp_file("nested").join("a/b/c/data.bin");
        write_file(&p, vec![1u8, 2, 3]).expect("写入应自动创建父目录");
        assert!(p.exists());
        assert_eq!(read_file(&p).unwrap(), vec![1u8, 2, 3]);
        let _ = std::fs::remove_dir_all(tmp_file("nested"));
    }

    #[test]
    fn read_missing_file_errors() {
        let p = tmp_file("missing");
        let _ = std::fs::remove_file(&p);
        assert!(read_file(&p).is_err(), "读取不存在的文件应返回 Err");
    }

    #[cfg(feature = "mmap")]
    #[test]
    fn mmap_roundtrip() {
        let p = tmp_file("mmap");
        write_file(&p, vec![7u8; 64]).unwrap();
        // SAFETY: 测试中 p 有效可读,映射期间无并发修改
        let mmap = unsafe { super::mmap::map_file(&p) }.expect("mmap 读取应成功");
        assert_eq!(mmap.len(), 64);
        assert!(mmap.iter().all(|&b| b == 7));
        std::fs::remove_file(&p).ok();
    }
}