1#![warn(clippy::pedantic)]
48#![allow(clippy::cast_possible_truncation)] #![allow(clippy::cast_possible_wrap)] #![allow(clippy::cast_sign_loss)] #![allow(clippy::module_name_repetitions)] #![allow(clippy::too_many_lines)] #![warn(missing_docs)] #![cfg_attr(test, allow(missing_docs))] #![warn(clippy::semicolon_if_nothing_returned)]
56#![warn(clippy::non_ascii_literal)]
57#![warn(clippy::unimplemented)]
58#![warn(clippy::use_self)]
59#![warn(clippy::string_slice)]
60#![warn(clippy::clone_on_ref_ptr)]
61
62extern crate llmosafe;
63
64pub mod archive;
65pub mod bloom;
66pub mod builder;
67pub mod cache_policy;
69pub mod config;
70#[cfg(feature = "notify")]
71pub mod daemon_sock;
72pub mod decompress;
73pub mod error;
75pub mod executor;
76pub mod format;
77#[cfg(feature = "notify")]
78pub mod idle;
79pub mod neg_cache;
81pub mod planner;
82pub mod posting;
83pub mod posting_cache;
85pub mod reader;
86pub mod regex_pool;
88pub mod scanner;
89pub mod string_pool;
90pub mod trigram;
91pub mod varint;
92#[cfg(feature = "notify")]
93pub mod watcher;
94
95#[cfg(feature = "notify")]
96pub use crate::builder::Builder;
97#[cfg(feature = "notify")]
98pub use crate::daemon_sock::{
99 ClientMessage, DaemonClient, DaemonServer, FileChange, FileOp, ServerMessage,
100};
101#[cfg(feature = "notify")]
102pub use crate::format::Beacon;
103#[cfg(feature = "notify")]
104pub use crate::idle::IdleTracker;
105#[cfg(feature = "notify")]
106pub use crate::watcher::Watcher;
107
108#[cfg(feature = "notify")]
119pub fn run_daemon(path: &std::path::Path) -> crate::error::Result<()> {
120 use std::fs;
121 use std::sync::Arc;
122 use std::sync::atomic::{AtomicBool, Ordering};
123 use std::time::Duration;
124
125 let root = path.canonicalize().map_err(crate::error::Error::Io)?;
126
127 println!("ixd: watching {}...", root.display());
128
129 let mut builder = Builder::new(&root)?;
130
131 let ix_dir = root.join(".ix");
133 let index_file = ix_dir.join("shard.ix");
134 if index_file.exists() {
135 println!("ixd: existing index found, performing startup update...");
136 builder.build()?;
140 } else {
141 builder.build()?;
142 }
143
144 println!(
145 "ixd: initial index ready ({} files, {} trigrams)",
146 builder.files_len(),
147 builder.trigrams_len()
148 );
149
150 let mut watcher = Watcher::new(&root);
151 let rx = watcher.start()?;
152
153 let ix_dir = root.join(".ix");
154 if !ix_dir.exists() {
155 fs::create_dir_all(&ix_dir)?;
156 }
157 let mut beacon = Beacon::new(&root);
158 beacon.write_to(&ix_dir)?;
159
160 let mut idle = IdleTracker::new();
161
162 let running = Arc::new(AtomicBool::new(true));
163 let r = std::sync::Arc::clone(&running);
164 if let Err(e) = ctrlc::set_handler(move || {
165 r.store(false, Ordering::SeqCst);
166 }) {
167 eprintln!("[ix] warning: could not set Ctrl-C handler: {e}");
168 }
169
170 while running.load(Ordering::SeqCst) {
171 match rx.recv_timeout(Duration::from_secs(5)) {
172 Ok(changed_files) => {
173 println!(
174 "ixd: {} files changed, updating index...",
175 changed_files.len()
176 );
177
178 beacon.status = "indexing".to_string();
179 beacon.last_event_at = std::time::SystemTime::now()
180 .duration_since(std::time::UNIX_EPOCH)
181 .unwrap_or_default()
182 .as_secs();
183 let _ = beacon.write_to(&ix_dir);
184
185 idle.record_change();
186 builder.update(&changed_files)?;
187
188 beacon.status = "idle".to_string();
189 let _ = beacon.write_to(&ix_dir);
190 }
191 Err(crossbeam_channel::RecvTimeoutError::Timeout) => {}
192 Err(crossbeam_channel::RecvTimeoutError::Disconnected) => break,
193 }
194 }
195
196 println!("ixd: shutting down");
197 let _ = fs::remove_file(ix_dir.join("beacon.json"));
198 watcher.stop();
199 Ok(())
200}