lorefs 0.1.0

A lightweight, high-performance Rust native SDK for File-First Agent Memory.
Documentation
use std::fs::{self, OpenOptions};
use std::io::Write;
use std::path::{Path, PathBuf};
use walkdir::WalkDir;
use crate::error::{MemoryError, Result};

pub struct FileManager {
    base_dir: PathBuf,
}

impl FileManager {
    pub fn new(base_dir: PathBuf) -> Result<Self> {
        if !base_dir.exists() {
            fs::create_dir_all(&base_dir)?;
            fs::create_dir_all(base_dir.join("system"))?;
        }
        Ok(Self { base_dir })
    }

    pub fn write_file(&self, relative_path: &str, content: &str) -> Result<()> {
        let full_path = self.base_dir.join(relative_path);
        if let Some(parent) = full_path.parent() {
            fs::create_dir_all(parent)?;
        }
        fs::write(full_path, content)?;
        Ok(())
    }

    pub fn append_to_file(&self, relative_path: &str, content: &str) -> Result<()> {
        let full_path = self.base_dir.join(relative_path);
        if let Some(parent) = full_path.parent() {
            fs::create_dir_all(parent)?;
        }
        let mut file = OpenOptions::new()
            .create(true)
            .append(true)
            .open(full_path)?;
        
        writeln!(file, "{}", content)?;
        Ok(())
    }

    pub fn read_file(&self, relative_path: &str) -> Result<String> {
        let full_path = self.base_dir.join(relative_path);
        if !full_path.exists() {
            return Err(MemoryError::FileNotFound(relative_path.to_string()));
        }
        Ok(fs::read_to_string(full_path)?)
    }

    pub fn get_tree(&self) -> String {
        let mut tree = String::new();
        for entry in WalkDir::new(&self.base_dir)
            .sort_by_file_name()
            .into_iter()
            .filter_map(|e| e.ok()) {
            let path = entry.path();
            let relative = path.strip_prefix(&self.base_dir).unwrap_or(path);
            let depth = entry.depth();
            
            if depth == 0 {
                tree.push_str(&format!("{}/\n", self.base_dir.display()));
                continue;
            }

            let indent = "  ".repeat(depth);
            let name = relative.file_name().unwrap_or_default().to_string_lossy();
            
            if entry.file_type().is_dir() {
                tree.push_str(&format!("{}{} /\n", indent, name));
            } else {
                tree.push_str(&format!("{}{} \n", indent, name));
            }
        }
        tree
    }

    pub fn search(&self, pattern: &str) -> Result<Vec<(String, String)>> {
        let mut results = Vec::new();
        for entry in WalkDir::new(&self.base_dir)
            .into_iter()
            .filter_map(|e| e.ok())
            .filter(|e| e.file_type().is_file()) {
            
            let content = fs::read_to_string(entry.path())?;
            if content.contains(pattern) {
                let relative = entry.path().strip_prefix(&self.base_dir)
                    .unwrap_or(entry.path())
                    .to_string_lossy()
                    .to_string();
                results.push((relative, content));
            }
        }
        Ok(results)
    }

    pub fn base_path(&self) -> &Path {
        &self.base_dir
    }
}