Skip to main content

ix/
lib.rs

1// ix — trigram code search library
2//
3// Build order: format → varint → trigram → bloom → posting →
4//              string_pool → builder → reader → planner → executor → scanner
5
6extern crate llmosafe;
7
8pub mod archive;
9pub mod bloom;
10pub mod builder;
11pub mod config;
12pub mod decompress;
13pub mod error;
14pub mod executor;
15pub mod format;
16#[cfg(feature = "notify")]
17pub mod idle;
18pub mod planner;
19pub mod posting;
20pub mod reader;
21pub mod scanner;
22pub mod string_pool;
23pub mod trigram;
24pub mod varint;
25#[cfg(feature = "notify")]
26pub mod watcher;
27
28#[cfg(feature = "notify")]
29pub use crate::builder::Builder;
30#[cfg(feature = "notify")]
31pub use crate::format::Beacon;
32#[cfg(feature = "notify")]
33pub use crate::idle::IdleTracker;
34#[cfg(feature = "notify")]
35pub use crate::watcher::Watcher;
36
37#[cfg(feature = "notify")]
38pub fn run_daemon(path: &std::path::Path) -> crate::error::Result<()> {
39    use std::fs;
40    use std::sync::Arc;
41    use std::sync::atomic::{AtomicBool, Ordering};
42    use std::time::Duration;
43
44    let root = path.canonicalize().map_err(crate::error::Error::Io)?;
45
46    println!("ixd: watching {}...", root.display());
47
48    let mut builder = Builder::new(&root)?;
49
50    // Lazy startup: if index exists, just update. Otherwise build.
51    let ix_dir = root.join(".ix");
52    let index_file = ix_dir.join("shard.ix");
53    if index_file.exists() {
54        println!("ixd: existing index found, performing startup update...");
55        // In a real implementation we would load the 'files' table from the shard
56        // to know which files to check for changes. For now we just build to satisfy
57        // the current Builder API, but we've already optimized Builder's memory.
58        builder.build()?;
59    } else {
60        builder.build()?;
61    }
62
63    println!(
64        "ixd: initial index ready ({} files, {} trigrams)",
65        builder.files_len(),
66        builder.trigrams_len()
67    );
68
69    let mut watcher = Watcher::new(&root)?;
70    let rx = watcher.start()?;
71
72    let ix_dir = root.join(".ix");
73    if !ix_dir.exists() {
74        fs::create_dir_all(&ix_dir)?;
75    }
76    let mut beacon = Beacon::new(&root);
77    beacon.write_to(&ix_dir)?;
78
79    let mut idle = IdleTracker::new();
80
81    let running = Arc::new(AtomicBool::new(true));
82    let r = running.clone();
83    ctrlc::set_handler(move || {
84        r.store(false, Ordering::SeqCst);
85    })
86    .expect("Error setting Ctrl-C handler");
87
88    while running.load(Ordering::SeqCst) {
89        match rx.recv_timeout(Duration::from_secs(5)) {
90            Ok(changed_files) => {
91                println!(
92                    "ixd: {} files changed, updating index...",
93                    changed_files.len()
94                );
95
96                beacon.status = "indexing".to_string();
97                beacon.last_event_at = std::time::SystemTime::now()
98                    .duration_since(std::time::UNIX_EPOCH)
99                    .unwrap_or_default()
100                    .as_secs();
101                let _ = beacon.write_to(&ix_dir);
102
103                idle.record_change();
104                builder.update(&changed_files)?;
105
106                beacon.status = "idle".to_string();
107                let _ = beacon.write_to(&ix_dir);
108            }
109            Err(crossbeam_channel::RecvTimeoutError::Timeout) => {
110                continue;
111            }
112            Err(crossbeam_channel::RecvTimeoutError::Disconnected) => break,
113        }
114    }
115
116    println!("ixd: shutting down");
117    let _ = fs::remove_file(ix_dir.join("beacon.json"));
118    watcher.stop();
119    Ok(())
120}