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
6// Lint configuration — Tier 1 security hardened, pedantic with pragmatic allow
7#![warn(clippy::pedantic)]
8#![allow(clippy::cast_possible_truncation)] // 64-bit target; try_from guards runtime
9#![allow(clippy::cast_possible_wrap)] // controlled internal values
10#![allow(clippy::cast_sign_loss)] // controlled internal values
11#![allow(clippy::module_name_repetitions)] // module::Type is idiomatic
12#![allow(clippy::too_many_lines)] // scanner/executor are well-structured
13#![warn(missing_docs)] // enterprise: every public item documented
14#![cfg_attr(test, allow(missing_docs))] // tests: no doc needed for test fns
15#![warn(clippy::semicolon_if_nothing_returned)]
16#![warn(clippy::non_ascii_literal)]
17#![warn(clippy::unimplemented)]
18#![warn(clippy::use_self)]
19#![warn(clippy::string_slice)]
20#![warn(clippy::clone_on_ref_ptr)]
21
22extern crate llmosafe;
23
24pub mod archive;
25pub mod bloom;
26pub mod builder;
27pub mod config;
28pub mod decompress;
29/// Error types for the ix crate.
30pub mod error;
31pub mod executor;
32pub mod format;
33#[cfg(feature = "notify")]
34pub mod idle;
35pub mod planner;
36pub mod posting;
37pub mod reader;
38pub mod scanner;
39pub mod string_pool;
40pub mod trigram;
41pub mod varint;
42#[cfg(feature = "notify")]
43pub mod watcher;
44
45#[cfg(feature = "notify")]
46pub use crate::builder::Builder;
47#[cfg(feature = "notify")]
48pub use crate::format::Beacon;
49#[cfg(feature = "notify")]
50pub use crate::idle::IdleTracker;
51#[cfg(feature = "notify")]
52pub use crate::watcher::Watcher;
53
54/// Run the daemon watching the given directory for changes and rebuilding the index.
55///
56/// # Errors
57///
58/// Returns an error if the root cannot be canonicalized, the index cannot be built,
59/// or the file watcher fails.
60///
61/// # Panics
62///
63/// Panics if the Ctrl-C handler cannot be set (which is extremely rare).
64#[cfg(feature = "notify")]
65pub fn run_daemon(path: &std::path::Path) -> crate::error::Result<()> {
66    use std::fs;
67    use std::sync::Arc;
68    use std::sync::atomic::{AtomicBool, Ordering};
69    use std::time::Duration;
70
71    let root = path.canonicalize().map_err(crate::error::Error::Io)?;
72
73    println!("ixd: watching {}...", root.display());
74
75    let mut builder = Builder::new(&root)?;
76
77    // Lazy startup: if index exists, just update. Otherwise build.
78    let ix_dir = root.join(".ix");
79    let index_file = ix_dir.join("shard.ix");
80    if index_file.exists() {
81        println!("ixd: existing index found, performing startup update...");
82        // In a real implementation we would load the 'files' table from the shard
83        // to know which files to check for changes. For now we just build to satisfy
84        // the current Builder API, but we've already optimized Builder's memory.
85        builder.build()?;
86    } else {
87        builder.build()?;
88    }
89
90    println!(
91        "ixd: initial index ready ({} files, {} trigrams)",
92        builder.files_len(),
93        builder.trigrams_len()
94    );
95
96    let mut watcher = Watcher::new(&root);
97    let rx = watcher.start()?;
98
99    let ix_dir = root.join(".ix");
100    if !ix_dir.exists() {
101        fs::create_dir_all(&ix_dir)?;
102    }
103    let mut beacon = Beacon::new(&root);
104    beacon.write_to(&ix_dir)?;
105
106    let mut idle = IdleTracker::new();
107
108    let running = Arc::new(AtomicBool::new(true));
109    let r = std::sync::Arc::clone(&running);
110    ctrlc::set_handler(move || {
111        r.store(false, Ordering::SeqCst);
112    })
113    .expect("Error setting Ctrl-C handler");
114
115    while running.load(Ordering::SeqCst) {
116        match rx.recv_timeout(Duration::from_secs(5)) {
117            Ok(changed_files) => {
118                println!(
119                    "ixd: {} files changed, updating index...",
120                    changed_files.len()
121                );
122
123                beacon.status = "indexing".to_string();
124                beacon.last_event_at = std::time::SystemTime::now()
125                    .duration_since(std::time::UNIX_EPOCH)
126                    .unwrap_or_default()
127                    .as_secs();
128                let _ = beacon.write_to(&ix_dir);
129
130                idle.record_change();
131                builder.update(&changed_files)?;
132
133                beacon.status = "idle".to_string();
134                let _ = beacon.write_to(&ix_dir);
135            }
136            Err(crossbeam_channel::RecvTimeoutError::Timeout) => {}
137            Err(crossbeam_channel::RecvTimeoutError::Disconnected) => break,
138        }
139    }
140
141    println!("ixd: shutting down");
142    let _ = fs::remove_file(ix_dir.join("beacon.json"));
143    watcher.stop();
144    Ok(())
145}