1use anyhow::{Context, Result};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::fs;
5use std::path::{Path, PathBuf};
6
7pub const CACHE_DIR: &str = ".omni";
8pub const MANIFEST_FILE: &str = "manifest.json";
9pub const STATE_FILE: &str = "state.bin";
10pub const BM25_FILE: &str = "bm25.bin";
11
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
13pub struct FileFingerprint {
14 pub mtime_ms: u64,
15 pub size_bytes: u64,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize, Default)]
19pub struct IndexManifest {
20 pub tool_version: String,
21 pub root: Option<String>,
22 pub files: HashMap<String, FileFingerprint>,
23}
24
25pub fn cache_dir(root: &Path) -> PathBuf {
26 root.join(CACHE_DIR)
27}
28
29pub fn ensure_cache_dir(root: &Path) -> Result<PathBuf> {
30 let dir = cache_dir(root);
31 fs::create_dir_all(&dir)
32 .with_context(|| format!("Failed to create cache dir: {}", dir.display()))?;
33 Ok(dir)
34}
35
36pub fn manifest_path(root: &Path) -> PathBuf {
37 cache_dir(root).join(MANIFEST_FILE)
38}
39
40pub fn state_path(root: &Path) -> PathBuf {
41 cache_dir(root).join(STATE_FILE)
42}
43
44pub fn bm25_path(root: &Path) -> PathBuf {
45 cache_dir(root).join(BM25_FILE)
46}
47
48pub fn load_manifest(root: &Path) -> Result<Option<IndexManifest>> {
49 let path = manifest_path(root);
50 if !path.exists() {
51 return Ok(None);
52 }
53 let data =
54 fs::read(&path).with_context(|| format!("Failed to read manifest: {}", path.display()))?;
55 let manifest: IndexManifest = serde_json::from_slice(&data)
56 .with_context(|| format!("Failed to parse manifest: {}", path.display()))?;
57 Ok(Some(manifest))
58}
59
60pub fn save_manifest(root: &Path, manifest: &IndexManifest) -> Result<()> {
61 ensure_cache_dir(root)?;
62 let path = manifest_path(root);
63 let data = serde_json::to_vec_pretty(manifest)?;
64 fs::write(&path, data)
65 .with_context(|| format!("Failed to write manifest: {}", path.display()))?;
66 Ok(())
67}
68
69pub fn clear_cache(root: &Path) -> Result<()> {
70 let dir = cache_dir(root);
71 if dir.exists() {
72 fs::remove_dir_all(&dir)
73 .with_context(|| format!("Failed to remove cache dir: {}", dir.display()))?;
74 }
75 Ok(())
76}