rsconstruct 0.9.78

Rust based fast build system
//! `rsconstruct cache` command handlers.
//!
//! Split out of `main.rs`'s `run()`, where this was a 136-line inline match
//! arm — by far the largest, and the specific example finding 6 names. The
//! command bodies live here; `run()` keeps only the dispatch.

use anyhow::Result;
use std::fs;

use crate::builder::Builder;
use crate::cli::CacheAction;
use crate::{checksum, color, json_output, webcache};

/// Handle every `rsconstruct cache <action>` subcommand.
pub fn handle(ctx: &crate::build_context::BuildContext, action: CacheAction) -> Result<()> {
        match action {
            CacheAction::Clear => {
                // Delete .rsconstruct directory directly — must work even if the
                // database is corrupted and Builder::new() would fail.
                let rsconstruct_dir = std::path::Path::new(".rsconstruct");
                if rsconstruct_dir.exists() {
                    crate::errors::ctx(fs::remove_dir_all(rsconstruct_dir), "Failed to remove .rsconstruct directory")?;
                }
                if json_output::is_json_mode() {
                    println!("{}", serde_json::json!({ "cleared": true }));
                } else {
                    crate::output::info("Cache cleared.");
                }
            }
            CacheAction::Size => {
                let builder = Builder::new(ctx)?;
                let (bytes, count) = builder.object_store().size();
                if json_output::is_json_mode() {
                    let out = serde_json::json!({ "bytes": bytes, "objects": count });
                    println!("{}", serde_json::to_string_pretty(&out)?);
                } else {
                    println!("Cache size: {} ({} objects)", humansize::format_size(bytes, humansize::BINARY), count);
                }
            }
            CacheAction::Trim => {
                let builder = Builder::new(ctx)?;
                let (bytes, count) = builder.object_store().trim()?;
                // The object store is not the only thing that grows
                // without bound: mtime rows for deleted files and expired
                // HTTP responses accumulate forever otherwise.
                let mtime_pruned = checksum::prune_mtime_cache(ctx)?;
                let web_pruned = webcache::prune(ctx.webcache_ttl_secs())?;
                if json_output::is_json_mode() {
                    let out = serde_json::json!({
                        "removed_bytes": bytes,
                        "removed_objects": count,
                        "removed_mtime_entries": mtime_pruned,
                        "removed_webcache_entries": web_pruned,
                    });
                    println!("{}", serde_json::to_string_pretty(&out)?);
                } else {
                    println!("Removed {bytes} bytes ({count} unreferenced objects)");
                    println!("Removed {mtime_pruned} mtime entries for deleted files");
                    println!("Removed {web_pruned} expired webcache entries");
                }
            }
            CacheAction::RemoveStale => {
                let builder = Builder::new(ctx)?;
                let valid_keys = builder.valid_cache_keys(ctx)?;
                let stale_count = builder.object_store().remove_stale(&valid_keys)?;
                let (bytes, trim_count) = builder.object_store().trim()?;
                if json_output::is_json_mode() {
                    let out = serde_json::json!({
                        "stale_index_entries_removed": stale_count,
                        "orphaned_bytes_removed": bytes,
                        "orphaned_objects_removed": trim_count,
                    });
                    println!("{}", serde_json::to_string_pretty(&out)?);
                } else {
                    println!("Removed {stale_count} stale index entries");
                    if trim_count > 0 {
                        println!("Removed {bytes} bytes ({trim_count} orphaned objects)");
                    }
                }
            }
            CacheAction::List => {
                let builder = Builder::new(ctx)?;
                let entries = builder.object_store().list();
                println!("{}", serde_json::to_string_pretty(&entries)?);
            }
            CacheAction::Stats => {
                let builder = Builder::new(ctx)?;
                let stats = builder.object_store().stats_by_processor();

                if stats.is_empty() {
                    println!("Cache is empty.");
                } else if json_output::is_json_mode() {
                    println!("{}", serde_json::to_string_pretty(&stats)?);
                } else {
                    let mut total_entries = 0usize;
                    let mut total_outputs = 0usize;
                    let mut total_bytes = 0u64;
                    for (processor, proc_stats) in &stats {
                        total_entries += proc_stats.entry_count;
                        total_outputs += proc_stats.output_count;
                        total_bytes += proc_stats.output_bytes;
                        println!("{}: {} entries, {} outputs, {}",
                            color::bold(processor),
                            proc_stats.entry_count,
                            proc_stats.output_count,
                            humansize::format_size(proc_stats.output_bytes, humansize::BINARY));
                    }
                    println!();
                    println!("{}: {} entries, {} outputs, {}",
                        color::bold("Total"),
                        total_entries,
                        total_outputs,
                        humansize::format_size(total_bytes, humansize::BINARY));
                }
            }
            CacheAction::Stale => {
                let builder = Builder::new(ctx)?;
                let valid_keys = builder.valid_cache_keys(ctx)?;
                let entries = builder.object_store().list();
                let mut current_count = 0usize;
                let mut stale_count = 0usize;
                if json_output::is_json_mode() {
                    let mut rows = Vec::with_capacity(entries.len());
                    for entry in &entries {
                        let is_current = valid_keys.contains(&entry.cache_key);
                        if is_current { current_count += 1; } else { stale_count += 1; }
                        rows.push(serde_json::json!({
                            "cache_key": entry.cache_key,
                            "status": if is_current { "current" } else { "stale" },
                        }));
                    }
                    let out = serde_json::json!({
                        "entries": rows,
                        "summary": { "current": current_count, "stale": stale_count },
                    });
                    println!("{}", serde_json::to_string_pretty(&out)?);
                } else {
                    for entry in &entries {
                        if valid_keys.contains(&entry.cache_key) {
                            println!("{} {}", color::green("current"), entry.cache_key);
                            current_count += 1;
                        } else {
                            println!("{} {}", color::yellow("stale"), entry.cache_key);
                            stale_count += 1;
                        }
                    }
                    println!();
                    println!("{}: {} current, {} stale",
                        color::bold("Summary"), current_count, stale_count);
                }
            }
        }
    Ok(())
}