1#![warn(clippy::pedantic)]
10#![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)]
18#![warn(clippy::non_ascii_literal)]
19#![warn(clippy::unimplemented)]
20#![warn(clippy::use_self)]
21#![warn(clippy::string_slice)]
22#![warn(clippy::clone_on_ref_ptr)]
23
24extern crate llmosafe;
25
26pub mod archive;
27pub mod bloom;
28pub mod builder;
29pub mod cache_policy;
31pub mod config;
32pub mod decompress;
33pub mod error;
35pub mod executor;
36pub mod format;
37#[cfg(feature = "notify")]
38pub mod idle;
39pub mod neg_cache;
41pub mod planner;
42pub mod posting;
43pub mod posting_cache;
45pub mod reader;
46pub mod regex_pool;
48pub mod scanner;
49pub mod string_pool;
50pub mod trigram;
51pub mod varint;
52#[cfg(feature = "notify")]
53pub mod watcher;
54
55#[cfg(feature = "notify")]
56pub use crate::builder::Builder;
57#[cfg(feature = "notify")]
58pub use crate::format::Beacon;
59#[cfg(feature = "notify")]
60pub use crate::idle::IdleTracker;
61#[cfg(feature = "notify")]
62pub use crate::watcher::Watcher;
63
64#[cfg(feature = "notify")]
75pub fn run_daemon(path: &std::path::Path) -> crate::error::Result<()> {
76 use std::fs;
77 use std::sync::Arc;
78 use std::sync::atomic::{AtomicBool, Ordering};
79 use std::time::Duration;
80
81 let root = path.canonicalize().map_err(crate::error::Error::Io)?;
82
83 println!("ixd: watching {}...", root.display());
84
85 let mut builder = Builder::new(&root)?;
86
87 let ix_dir = root.join(".ix");
89 let index_file = ix_dir.join("shard.ix");
90 if index_file.exists() {
91 println!("ixd: existing index found, performing startup update...");
92 builder.build()?;
96 } else {
97 builder.build()?;
98 }
99
100 println!(
101 "ixd: initial index ready ({} files, {} trigrams)",
102 builder.files_len(),
103 builder.trigrams_len()
104 );
105
106 let mut watcher = Watcher::new(&root);
107 let rx = watcher.start()?;
108
109 let ix_dir = root.join(".ix");
110 if !ix_dir.exists() {
111 fs::create_dir_all(&ix_dir)?;
112 }
113 let mut beacon = Beacon::new(&root);
114 beacon.write_to(&ix_dir)?;
115
116 let mut idle = IdleTracker::new();
117
118 let running = Arc::new(AtomicBool::new(true));
119 let r = std::sync::Arc::clone(&running);
120 ctrlc::set_handler(move || {
121 r.store(false, Ordering::SeqCst);
122 })
123 .expect("Error setting Ctrl-C handler");
124
125 while running.load(Ordering::SeqCst) {
126 match rx.recv_timeout(Duration::from_secs(5)) {
127 Ok(changed_files) => {
128 println!(
129 "ixd: {} files changed, updating index...",
130 changed_files.len()
131 );
132
133 beacon.status = "indexing".to_string();
134 beacon.last_event_at = std::time::SystemTime::now()
135 .duration_since(std::time::UNIX_EPOCH)
136 .unwrap_or_default()
137 .as_secs();
138 let _ = beacon.write_to(&ix_dir);
139
140 idle.record_change();
141 builder.update(&changed_files)?;
142
143 beacon.status = "idle".to_string();
144 let _ = beacon.write_to(&ix_dir);
145 }
146 Err(crossbeam_channel::RecvTimeoutError::Timeout) => {}
147 Err(crossbeam_channel::RecvTimeoutError::Disconnected) => break,
148 }
149 }
150
151 println!("ixd: shutting down");
152 let _ = fs::remove_file(ix_dir.join("beacon.json"));
153 watcher.stop();
154 Ok(())
155}