magi-code 0.96.1

Repository-aware CLI coding agent for terminal work
Documentation
use anyhow::Context;
use std::{
    collections::HashMap,
    fs,
    path::{Path, PathBuf},
    sync::{Arc, Mutex},
    time::{Duration, Instant, SystemTime},
};

#[derive(Debug)]
pub(super) struct FsCache {
    ttl: Duration,
    entries: Mutex<HashMap<PathBuf, CacheEntry>>,
}

#[derive(Debug)]
struct DirectoryState {
    dir: PathBuf,
    modified: SystemTime,
}

#[derive(Debug, Clone)]
struct CacheEntry {
    modified: SystemTime,
    inserted: Instant,
    listing: Arc<[DirectoryListingEntry]>,
}

#[derive(Debug, Clone)]
pub(super) struct DirectoryListingEntry {
    pub(super) name: String,
    pub(super) file_type: fs::FileType,
}

#[derive(Debug, Clone)]
pub(super) struct DirectoryListing {
    pub(super) entries: Arc<[DirectoryListingEntry]>,
}

impl Default for FsCache {
    fn default() -> Self {
        Self::with_ttl(Duration::from_secs(2))
    }
}

impl FsCache {
    pub(super) fn with_ttl(ttl: Duration) -> Self {
        Self {
            ttl,
            entries: Mutex::new(HashMap::new()),
        }
    }

    pub(super) fn read_dir_listing(&self, dir: &Path) -> anyhow::Result<DirectoryListing> {
        let state = self.state_for_dir(dir)?;
        if let Some(entries) = self.get_valid(&state)? {
            return Ok(DirectoryListing { entries });
        }

        let entries = Arc::from(read_directory_entries(&state.dir)?);
        self.store(state, Arc::clone(&entries))?;
        Ok(DirectoryListing { entries })
    }

    pub(super) fn len(&self) -> anyhow::Result<usize> {
        self.entries
            .lock()
            .map(|entries| entries.len())
            .map_err(|_| anyhow::anyhow!("fs cache lock poisoned"))
    }

    fn state_for_dir(&self, dir: &Path) -> anyhow::Result<DirectoryState> {
        let canonical = dir
            .canonicalize()
            .with_context(|| format!("failed to canonicalize directory '{}'", dir.display()))?;
        let modified = fs::metadata(&canonical)
            .with_context(|| format!("failed to read metadata for '{}'", canonical.display()))?
            .modified()
            .with_context(|| {
                format!(
                    "failed to read modified time for directory '{}'",
                    canonical.display()
                )
            })?;
        Ok(DirectoryState {
            dir: canonical,
            modified,
        })
    }

    fn get_valid(
        &self,
        state: &DirectoryState,
    ) -> anyhow::Result<Option<Arc<[DirectoryListingEntry]>>> {
        let mut entries = self
            .entries
            .lock()
            .map_err(|_| anyhow::anyhow!("fs cache lock poisoned"))?;
        entries.retain(|_, entry| entry.inserted.elapsed() <= self.ttl);
        Ok(entries.get(&state.dir).and_then(|entry| {
            (entry.modified == state.modified).then(|| Arc::clone(&entry.listing))
        }))
    }

    fn store(
        &self,
        state: DirectoryState,
        listing: Arc<[DirectoryListingEntry]>,
    ) -> anyhow::Result<()> {
        let mut entries = self
            .entries
            .lock()
            .map_err(|_| anyhow::anyhow!("fs cache lock poisoned"))?;
        entries.retain(|_, entry| entry.inserted.elapsed() <= self.ttl);
        entries.insert(
            state.dir,
            CacheEntry {
                modified: state.modified,
                inserted: Instant::now(),
                listing,
            },
        );
        Ok(())
    }
}

fn read_directory_entries(dir: &Path) -> anyhow::Result<Vec<DirectoryListingEntry>> {
    let mut entries = Vec::new();
    for entry in fs::read_dir(dir)
        .with_context(|| format!("failed to read directory '{}'", dir.display()))?
    {
        let entry = entry
            .with_context(|| format!("failed to read entry in directory '{}'", dir.display()))?;
        let path = entry.path();
        let file_type = entry
            .file_type()
            .with_context(|| format!("failed to read file type for '{}'", path.display()))?;
        entries.push(DirectoryListingEntry {
            name: entry.file_name().to_string_lossy().into_owned(),
            file_type,
        });
    }
    Ok(entries)
}

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

    #[test]
    #[ignore = "release-mode filesystem cache measurement; run with --release --ignored --nocapture"]
    fn filesystem_cache_large_directory_measurement() {
        let temp = tempfile::TempDir::new().unwrap();
        for index in 0..10_000 {
            fs::write(temp.path().join(format!("entry-{index:05}.txt")), "x").unwrap();
        }
        let cache = FsCache::default();
        let first = cache.read_dir_listing(temp.path()).unwrap();
        let started = Instant::now();
        let second = cache.read_dir_listing(temp.path()).unwrap();
        let elapsed = started.elapsed();
        let retained_name_bytes = second
            .entries
            .iter()
            .map(|entry| entry.name.len())
            .sum::<usize>();

        eprintln!(
            "fs_cache entries={} retained_paths={} retained_name_bytes={} hit_us={} shared_listing={} cloned_entries=0",
            second.entries.len(),
            cache.len().unwrap(),
            retained_name_bytes,
            elapsed.as_micros(),
            Arc::ptr_eq(&first.entries, &second.entries),
        );
    }
}