2025-09-11 14:00:33 -04:00
|
|
|
use std::sync::{Arc, Mutex, mpsc};
|
|
|
|
|
use std::thread;
|
2025-09-11 14:32:04 -04:00
|
|
|
use std::time::Instant;
|
2025-09-12 00:31:52 -04:00
|
|
|
use std::process::Command;
|
2025-09-11 14:00:33 -04:00
|
|
|
use walkdir::WalkDir;
|
2025-09-11 16:32:21 -04:00
|
|
|
use rusqlite::{Connection, params};
|
2025-09-11 14:00:33 -04:00
|
|
|
|
2025-09-11 18:36:42 -04:00
|
|
|
use crate::file_handling::{load_existing_files, analyze_files_for_batch_update, process_batch_updates_files_only, process_batch_inserts_files_only, process_text_indexing};
|
2025-09-11 16:32:21 -04:00
|
|
|
use crate::config::Config;
|
2025-09-11 14:00:33 -04:00
|
|
|
|
2025-09-11 23:15:50 -04:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub struct SearchResultRow {
|
|
|
|
|
pub values: Vec<String>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub struct SearchResult {
|
|
|
|
|
pub columns: Vec<String>,
|
|
|
|
|
pub rows: Vec<SearchResultRow>,
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-11 14:00:33 -04:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub enum IndexingStatus {
|
|
|
|
|
Idle,
|
2025-09-11 18:36:42 -04:00
|
|
|
RunningFileIndex {
|
|
|
|
|
files_processed: usize,
|
|
|
|
|
total_files: Option<usize>,
|
|
|
|
|
current_file: Option<String>,
|
|
|
|
|
start_time: Instant,
|
|
|
|
|
},
|
|
|
|
|
RunningTextIndex {
|
2025-09-11 14:00:33 -04:00
|
|
|
files_processed: usize,
|
|
|
|
|
total_files: Option<usize>,
|
|
|
|
|
current_file: Option<String>,
|
|
|
|
|
start_time: Instant,
|
|
|
|
|
},
|
|
|
|
|
Stopping,
|
|
|
|
|
Error(String),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub enum IndexingCommand {
|
|
|
|
|
Start {
|
|
|
|
|
path: String,
|
|
|
|
|
db_path: String,
|
2025-09-11 16:32:21 -04:00
|
|
|
config: Config,
|
2025-09-11 14:00:33 -04:00
|
|
|
},
|
|
|
|
|
Stop,
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-11 18:36:42 -04:00
|
|
|
#[derive(Debug)]
|
2025-09-11 14:00:33 -04:00
|
|
|
pub struct IndexingService {
|
|
|
|
|
status: Arc<Mutex<IndexingStatus>>,
|
|
|
|
|
command_tx: mpsc::Sender<IndexingCommand>,
|
2025-09-11 18:36:42 -04:00
|
|
|
db_connection: Arc<Mutex<Option<Arc<Mutex<Connection>>>>>,
|
2025-09-11 14:00:33 -04:00
|
|
|
_handle: thread::JoinHandle<()>,
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-11 16:32:21 -04:00
|
|
|
/// Set process priority for background operation
|
|
|
|
|
// fn set_background_priority() {
|
|
|
|
|
// #[cfg(windows)]
|
|
|
|
|
// {
|
|
|
|
|
// use std::os::windows::raw::HANDLE;
|
|
|
|
|
|
|
|
|
|
// // Windows implementation
|
|
|
|
|
// extern "system" {
|
|
|
|
|
// fn GetCurrentProcess() -> HANDLE;
|
|
|
|
|
// fn SetPriorityClass(hprocess: HANDLE, dwpriorityclass: u32) -> i32;
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// const BELOW_NORMAL_PRIORITY_CLASS: u32 = 0x00004000;
|
|
|
|
|
// unsafe {
|
|
|
|
|
// SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS);
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// #[cfg(unix)]
|
|
|
|
|
// {
|
|
|
|
|
// // Unix implementation
|
|
|
|
|
// use std::os::unix::process::CommandExt;
|
|
|
|
|
// unsafe {
|
|
|
|
|
// libc::nice(10); // Lower priority
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
|
2025-09-11 14:00:33 -04:00
|
|
|
impl IndexingService {
|
|
|
|
|
pub fn new() -> Self {
|
|
|
|
|
let status = Arc::new(Mutex::new(IndexingStatus::Idle));
|
|
|
|
|
let (command_tx, command_rx) = mpsc::channel();
|
2025-09-11 18:36:42 -04:00
|
|
|
let db_connection = Arc::new(Mutex::new(None));
|
2025-09-11 14:00:33 -04:00
|
|
|
|
|
|
|
|
let status_clone = status.clone();
|
2025-09-11 18:36:42 -04:00
|
|
|
let db_connection_clone = db_connection.clone();
|
2025-09-11 14:00:33 -04:00
|
|
|
let handle = thread::spawn(move || {
|
2025-09-11 18:36:42 -04:00
|
|
|
Self::indexing_thread(status_clone, command_rx, db_connection_clone);
|
2025-09-11 14:00:33 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
IndexingService {
|
|
|
|
|
status,
|
|
|
|
|
command_tx,
|
2025-09-11 18:36:42 -04:00
|
|
|
db_connection,
|
2025-09-11 14:00:33 -04:00
|
|
|
_handle: handle,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-11 16:32:21 -04:00
|
|
|
pub fn start_indexing(&self, path: String, db_path: String, config: Config) -> Result<(), String> {
|
2025-09-11 14:00:33 -04:00
|
|
|
self.command_tx
|
2025-09-11 16:32:21 -04:00
|
|
|
.send(IndexingCommand::Start { path, db_path, config })
|
2025-09-11 14:00:33 -04:00
|
|
|
.map_err(|e| format!("Failed to send start command: {}", e))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn stop_indexing(&self) -> Result<(), String> {
|
2025-09-11 18:36:42 -04:00
|
|
|
// First send the stop command
|
2025-09-11 14:00:33 -04:00
|
|
|
self.command_tx
|
|
|
|
|
.send(IndexingCommand::Stop)
|
2025-09-11 18:36:42 -04:00
|
|
|
.map_err(|e| format!("Failed to send stop command: {}", e))?;
|
|
|
|
|
|
|
|
|
|
// Wait for indexing to transition to stopping state
|
|
|
|
|
let mut attempts = 0;
|
|
|
|
|
while attempts < 50 { // Wait up to 5 seconds
|
|
|
|
|
match self.get_status() {
|
|
|
|
|
IndexingStatus::Stopping => break,
|
|
|
|
|
IndexingStatus::Idle => return Ok(()), // Already stopped
|
|
|
|
|
IndexingStatus::Error(_) => return Ok(()), // Consider error state as stopped
|
|
|
|
|
_ => {
|
|
|
|
|
std::thread::sleep(std::time::Duration::from_millis(100));
|
|
|
|
|
attempts += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Flush and close database connection if it exists
|
|
|
|
|
if let Ok(mut db_opt) = self.db_connection.lock() {
|
|
|
|
|
if let Some(db_conn_arc) = db_opt.take() {
|
|
|
|
|
if let Ok(conn) = db_conn_arc.lock() {
|
|
|
|
|
// Re-enable journal mode and synchronous writes for proper flushing
|
|
|
|
|
let _ = conn.execute_batch(
|
|
|
|
|
"PRAGMA journal_mode = DELETE;
|
|
|
|
|
PRAGMA synchronous = FULL;"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Force a checkpoint to flush any remaining WAL data
|
|
|
|
|
let _ = conn.execute("PRAGMA wal_checkpoint(FULL);", ());
|
|
|
|
|
|
|
|
|
|
// Explicitly close the connection by dropping it
|
|
|
|
|
drop(conn);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
2025-09-11 14:00:33 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_status(&self) -> IndexingStatus {
|
|
|
|
|
self.status.lock().unwrap().clone()
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-11 18:36:42 -04:00
|
|
|
/// Force graceful shutdown - used for signal handling
|
|
|
|
|
pub fn graceful_shutdown(&self) -> Result<(), String> {
|
|
|
|
|
self.stop_indexing()
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-11 23:15:50 -04:00
|
|
|
/// Execute a search query against the database
|
|
|
|
|
pub fn execute_search(&self, db_path: &str, query: &str) -> Result<Vec<SearchResult>, String> {
|
|
|
|
|
let conn = Connection::open(db_path)
|
2025-09-12 00:31:52 -04:00
|
|
|
.map_err(|e| {
|
|
|
|
|
if e.to_string().contains("corrupt") || e.to_string().contains("malformed") {
|
|
|
|
|
format!("DATABASE_CORRUPTED: {}", e)
|
|
|
|
|
} else {
|
|
|
|
|
format!("Failed to open database: {}", e)
|
|
|
|
|
}
|
|
|
|
|
})?;
|
2025-09-11 23:15:50 -04:00
|
|
|
|
|
|
|
|
let mut stmt = conn.prepare(query)
|
2025-09-12 00:31:52 -04:00
|
|
|
.map_err(|e| {
|
|
|
|
|
let error_msg = e.to_string();
|
|
|
|
|
if error_msg.contains("malformed") || error_msg.contains("corrupt") || error_msg.contains("database disk image is malformed") {
|
|
|
|
|
format!("DATABASE_CORRUPTED: {}", error_msg)
|
|
|
|
|
} else if error_msg.contains("fts5: syntax error") {
|
|
|
|
|
format!("Search syntax error: The search term contains characters that cannot be processed. Please try a simpler search term.")
|
|
|
|
|
} else {
|
|
|
|
|
format!("Failed to prepare query: {}", error_msg)
|
|
|
|
|
}
|
|
|
|
|
})?;
|
2025-09-11 23:15:50 -04:00
|
|
|
|
|
|
|
|
let column_count = stmt.column_count();
|
|
|
|
|
let column_names: Vec<String> = (0..column_count)
|
|
|
|
|
.map(|i| stmt.column_name(i).unwrap_or("").to_string())
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
let rows = stmt.query_map([], |row| {
|
|
|
|
|
let mut values = Vec::new();
|
|
|
|
|
for i in 0..column_count {
|
|
|
|
|
let value = match row.get_ref(i)? {
|
|
|
|
|
rusqlite::types::ValueRef::Null => "NULL".to_string(),
|
|
|
|
|
rusqlite::types::ValueRef::Integer(i) => i.to_string(),
|
|
|
|
|
rusqlite::types::ValueRef::Real(f) => f.to_string(),
|
|
|
|
|
rusqlite::types::ValueRef::Text(t) => String::from_utf8_lossy(t).to_string(),
|
|
|
|
|
rusqlite::types::ValueRef::Blob(b) => format!("BLOB({} bytes)", b.len()),
|
|
|
|
|
};
|
|
|
|
|
values.push(value);
|
|
|
|
|
}
|
|
|
|
|
Ok(SearchResultRow { values })
|
|
|
|
|
})
|
2025-09-12 00:31:52 -04:00
|
|
|
.map_err(|e| {
|
|
|
|
|
let error_msg = e.to_string();
|
|
|
|
|
if error_msg.contains("malformed") || error_msg.contains("corrupt") || error_msg.contains("database disk image is malformed") {
|
|
|
|
|
format!("DATABASE_CORRUPTED: {}", error_msg)
|
|
|
|
|
} else if error_msg.contains("fts5: syntax error") {
|
|
|
|
|
format!("Search syntax error: The search term contains characters that cannot be processed. Please try a simpler search term.")
|
|
|
|
|
} else {
|
|
|
|
|
format!("Failed to execute query: {}", error_msg)
|
|
|
|
|
}
|
|
|
|
|
})?;
|
2025-09-11 23:15:50 -04:00
|
|
|
|
|
|
|
|
let mut results = Vec::new();
|
|
|
|
|
for row in rows {
|
|
|
|
|
match row {
|
|
|
|
|
Ok(search_row) => results.push(search_row),
|
2025-09-12 00:31:52 -04:00
|
|
|
Err(e) => {
|
|
|
|
|
let error_msg = e.to_string();
|
|
|
|
|
if error_msg.contains("malformed") || error_msg.contains("corrupt") || error_msg.contains("database disk image is malformed") {
|
|
|
|
|
return Err(format!("DATABASE_CORRUPTED: {}", error_msg));
|
|
|
|
|
} else if error_msg.contains("fts5: syntax error") {
|
|
|
|
|
return Err(format!("Search syntax error: The search term contains characters that cannot be processed. Please try a simpler search term."));
|
|
|
|
|
} else {
|
|
|
|
|
return Err(format!("Error reading row: {}", error_msg));
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-11 23:15:50 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(vec![SearchResult {
|
|
|
|
|
columns: column_names,
|
|
|
|
|
rows: results,
|
|
|
|
|
}])
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-12 00:31:52 -04:00
|
|
|
/// Open file explorer to the directory containing the specified file path
|
|
|
|
|
pub fn open_file_explorer(&self, file_path: &str) -> Result<(), String> {
|
|
|
|
|
#[cfg(windows)]
|
|
|
|
|
{
|
|
|
|
|
Command::new("explorer")
|
|
|
|
|
.arg("/select,")
|
|
|
|
|
.arg(file_path)
|
|
|
|
|
.spawn()
|
|
|
|
|
.map_err(|e| format!("Failed to open file explorer: {}", e))?;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
|
{
|
|
|
|
|
Command::new("open")
|
|
|
|
|
.arg("-R")
|
|
|
|
|
.arg(file_path)
|
|
|
|
|
.spawn()
|
|
|
|
|
.map_err(|e| format!("Failed to open file explorer: {}", e))?;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(target_os = "linux")]
|
|
|
|
|
{
|
|
|
|
|
let path = std::path::Path::new(file_path);
|
|
|
|
|
let dir_path = if path.is_file() {
|
|
|
|
|
path.parent().unwrap_or(path)
|
|
|
|
|
} else {
|
|
|
|
|
path
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Try different file managers
|
|
|
|
|
let managers = ["xdg-open", "nautilus", "dolphin", "thunar", "pcmanfm"];
|
|
|
|
|
let mut success = false;
|
|
|
|
|
|
|
|
|
|
for manager in &managers {
|
|
|
|
|
if let Ok(_) = Command::new(manager)
|
|
|
|
|
.arg(dir_path)
|
|
|
|
|
.spawn() {
|
|
|
|
|
success = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !success {
|
|
|
|
|
return Err("No suitable file manager found".to_string());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Clean up UNC prefixes from existing database entries
|
|
|
|
|
pub fn clean_unc_prefixes(&self, db_path: &str) -> Result<(), String> {
|
|
|
|
|
let conn = Connection::open(db_path)
|
|
|
|
|
.map_err(|e| format!("Failed to open database: {}", e))?;
|
|
|
|
|
|
|
|
|
|
// Clean UNC prefixes from files table
|
|
|
|
|
conn.execute(
|
|
|
|
|
"UPDATE files SET path = SUBSTR(path, 5) WHERE path LIKE '\\\\?\\%'",
|
|
|
|
|
(),
|
|
|
|
|
).map_err(|e| format!("Failed to update files table: {}", e))?;
|
|
|
|
|
|
|
|
|
|
// Clean UNC prefixes from searchabletext table
|
|
|
|
|
conn.execute(
|
|
|
|
|
"UPDATE searchabletext SET path = SUBSTR(path, 5) WHERE path LIKE '\\\\?\\%'",
|
|
|
|
|
(),
|
|
|
|
|
).map_err(|e| format!("Failed to update searchabletext table: {}", e))?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Check if the database is corrupted or malformed
|
|
|
|
|
pub fn check_database_health(&self, db_path: &str) -> Result<bool, String> {
|
|
|
|
|
match Connection::open(db_path) {
|
|
|
|
|
Ok(conn) => {
|
|
|
|
|
// Try to run integrity check
|
|
|
|
|
match conn.prepare("PRAGMA integrity_check") {
|
|
|
|
|
Ok(mut stmt) => {
|
|
|
|
|
match stmt.query_row([], |row| {
|
|
|
|
|
let result: String = row.get(0)?;
|
|
|
|
|
Ok(result == "ok")
|
|
|
|
|
}) {
|
|
|
|
|
Ok(is_ok) => Ok(is_ok),
|
|
|
|
|
Err(_) => Ok(false)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
Err(_) => Ok(false)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
Err(_) => Ok(false)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-11 16:32:21 -04:00
|
|
|
/// Check if configuration changes require index recreation
|
|
|
|
|
pub fn check_config_validation(&self, db_path: &str, config: &Config, indexing_path: &str) -> Result<Option<Vec<String>>, String> {
|
|
|
|
|
let conn = Connection::open(db_path)
|
|
|
|
|
.map_err(|e| format!("Failed to open database: {}", e))?;
|
|
|
|
|
|
|
|
|
|
// Create config validation table if it doesn't exist
|
|
|
|
|
conn.execute(
|
|
|
|
|
"CREATE TABLE IF NOT EXISTS config_validation (
|
|
|
|
|
key TEXT PRIMARY KEY,
|
|
|
|
|
value TEXT NOT NULL);",
|
|
|
|
|
(),
|
|
|
|
|
).map_err(|e| format!("Failed to create config_validation table: {}", e))?;
|
|
|
|
|
|
|
|
|
|
Self::validate_config(&conn, config, indexing_path)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Stop indexing and delete the database file for a clean rebuild
|
|
|
|
|
pub fn delete_index_for_rebuild(&self, db_path: &str) -> Result<(), String> {
|
|
|
|
|
// Stop any running indexing first
|
|
|
|
|
self.stop_indexing()
|
|
|
|
|
.map_err(|e| format!("Failed to stop indexing: {}", e))?;
|
|
|
|
|
|
|
|
|
|
// Wait for indexing to actually stop
|
|
|
|
|
let mut attempts = 0;
|
|
|
|
|
while attempts < 50 { // Wait up to 5 seconds
|
|
|
|
|
match self.get_status() {
|
|
|
|
|
IndexingStatus::Idle => break,
|
2025-09-11 18:36:42 -04:00
|
|
|
IndexingStatus::Stopping | IndexingStatus::RunningFileIndex { .. } | IndexingStatus::RunningTextIndex { .. } => {
|
2025-09-11 16:32:21 -04:00
|
|
|
std::thread::sleep(std::time::Duration::from_millis(100));
|
|
|
|
|
attempts += 1;
|
|
|
|
|
}
|
|
|
|
|
IndexingStatus::Error(_) => break, // Consider error state as stopped
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Delete the database file
|
|
|
|
|
if std::path::Path::new(db_path).exists() {
|
|
|
|
|
std::fs::remove_file(db_path)
|
|
|
|
|
.map_err(|e| format!("Failed to delete database file: {}", e))?;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-11 18:36:42 -04:00
|
|
|
fn indexing_thread(
|
|
|
|
|
status: Arc<Mutex<IndexingStatus>>,
|
|
|
|
|
command_rx: mpsc::Receiver<IndexingCommand>,
|
|
|
|
|
db_connection: Arc<Mutex<Option<Arc<Mutex<Connection>>>>>
|
|
|
|
|
) {
|
2025-09-11 14:00:33 -04:00
|
|
|
let stop_flag = Arc::new(Mutex::new(false));
|
2025-09-11 14:32:04 -04:00
|
|
|
let mut indexing_handle: Option<thread::JoinHandle<()>> = None;
|
2025-09-11 14:00:33 -04:00
|
|
|
|
|
|
|
|
while let Ok(command) = command_rx.recv() {
|
|
|
|
|
match command {
|
2025-09-11 16:32:21 -04:00
|
|
|
IndexingCommand::Start { path, db_path, config } => {
|
2025-09-11 18:36:42 -04:00
|
|
|
if matches!(*status.lock().unwrap(), IndexingStatus::RunningFileIndex { .. } | IndexingStatus::RunningTextIndex { .. }) {
|
2025-09-11 14:00:33 -04:00
|
|
|
continue; // Already running
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-11 14:32:04 -04:00
|
|
|
// Join any previous indexing thread
|
|
|
|
|
if let Some(handle) = indexing_handle.take() {
|
|
|
|
|
let _ = handle.join();
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-11 14:00:33 -04:00
|
|
|
*stop_flag.lock().unwrap() = false;
|
2025-09-11 18:36:42 -04:00
|
|
|
*status.lock().unwrap() = IndexingStatus::RunningFileIndex {
|
2025-09-11 14:00:33 -04:00
|
|
|
files_processed: 0,
|
|
|
|
|
total_files: None,
|
|
|
|
|
current_file: None,
|
|
|
|
|
start_time: Instant::now(),
|
|
|
|
|
};
|
|
|
|
|
|
2025-09-11 14:32:04 -04:00
|
|
|
// Run indexing in a separate thread
|
|
|
|
|
let status_clone = status.clone();
|
|
|
|
|
let stop_flag_clone = stop_flag.clone();
|
|
|
|
|
let path_owned = path.clone();
|
|
|
|
|
let db_path_owned = db_path.clone();
|
2025-09-11 16:32:21 -04:00
|
|
|
let config_owned = config.clone();
|
2025-09-11 14:32:04 -04:00
|
|
|
|
2025-09-11 18:36:42 -04:00
|
|
|
let db_connection_clone = db_connection.clone();
|
2025-09-11 14:32:04 -04:00
|
|
|
indexing_handle = Some(thread::spawn(move || {
|
2025-09-11 18:36:42 -04:00
|
|
|
if let Err(e) = Self::run_indexing(&status_clone, &path_owned, &db_path_owned, &stop_flag_clone, &config_owned, &db_connection_clone) {
|
2025-09-11 14:32:04 -04:00
|
|
|
*status_clone.lock().unwrap() = IndexingStatus::Error(e);
|
|
|
|
|
} else {
|
|
|
|
|
// Only set to Idle if we weren't stopped
|
|
|
|
|
if !*stop_flag_clone.lock().unwrap() {
|
|
|
|
|
*status_clone.lock().unwrap() = IndexingStatus::Idle;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-11 18:36:42 -04:00
|
|
|
|
|
|
|
|
// Clear the database connection when indexing completes
|
|
|
|
|
if let Ok(mut db_opt) = db_connection_clone.lock() {
|
|
|
|
|
*db_opt = None;
|
|
|
|
|
}
|
2025-09-11 14:32:04 -04:00
|
|
|
}));
|
2025-09-11 14:00:33 -04:00
|
|
|
}
|
|
|
|
|
IndexingCommand::Stop => {
|
2025-09-11 18:36:42 -04:00
|
|
|
if matches!(*status.lock().unwrap(), IndexingStatus::RunningFileIndex { .. } | IndexingStatus::RunningTextIndex { .. }) {
|
2025-09-11 14:00:33 -04:00
|
|
|
*status.lock().unwrap() = IndexingStatus::Stopping;
|
|
|
|
|
*stop_flag.lock().unwrap() = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-11 14:32:04 -04:00
|
|
|
|
|
|
|
|
// Clean up any remaining indexing thread
|
|
|
|
|
if let Some(handle) = indexing_handle {
|
|
|
|
|
let _ = handle.join();
|
|
|
|
|
}
|
2025-09-11 14:00:33 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn run_indexing(
|
|
|
|
|
status: &Arc<Mutex<IndexingStatus>>,
|
|
|
|
|
path: &str,
|
|
|
|
|
db_path: &str,
|
|
|
|
|
stop_flag: &Arc<Mutex<bool>>,
|
2025-09-11 16:32:21 -04:00
|
|
|
config: &Config,
|
2025-09-11 18:36:42 -04:00
|
|
|
db_connection: &Arc<Mutex<Option<Arc<Mutex<Connection>>>>>,
|
2025-09-11 14:00:33 -04:00
|
|
|
) -> Result<(), String> {
|
|
|
|
|
// Set up database
|
|
|
|
|
let conn = Connection::open(db_path)
|
|
|
|
|
.map_err(|e| format!("Failed to open database: {}", e))?;
|
|
|
|
|
|
|
|
|
|
conn.execute_batch(
|
|
|
|
|
"PRAGMA journal_mode = OFF;
|
|
|
|
|
PRAGMA synchronous = 0;
|
|
|
|
|
PRAGMA cache_size = 10000;
|
|
|
|
|
PRAGMA temp_store = MEMORY;",
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| format!("Failed to set PRAGMA: {}", e))?;
|
|
|
|
|
|
|
|
|
|
conn.execute(
|
|
|
|
|
"CREATE TABLE IF NOT EXISTS files (
|
|
|
|
|
name TEXT,
|
|
|
|
|
path TEXT,
|
|
|
|
|
size INTEGER,
|
|
|
|
|
moddate INTEGER,
|
|
|
|
|
hash BLOB);",
|
|
|
|
|
(),
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| format!("Failed to create files table: {}", e))?;
|
|
|
|
|
|
2025-09-11 18:36:42 -04:00
|
|
|
let create_fts_sql = format!(
|
|
|
|
|
"CREATE VIRTUAL TABLE IF NOT EXISTS searchabletext USING fts5 (name, path, text, tokenize = '{}');",
|
|
|
|
|
config.processing.tokenize
|
|
|
|
|
);
|
|
|
|
|
conn.execute(&create_fts_sql, ())
|
|
|
|
|
.map_err(|e| format!("Failed to create searchabletext table: {}", e))?;
|
2025-09-11 14:00:33 -04:00
|
|
|
|
2025-09-11 16:32:21 -04:00
|
|
|
conn.execute(
|
|
|
|
|
"CREATE TABLE IF NOT EXISTS config_validation (
|
|
|
|
|
key TEXT PRIMARY KEY,
|
|
|
|
|
value TEXT NOT NULL);",
|
|
|
|
|
(),
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| format!("Failed to create config_validation table: {}", e))?;
|
|
|
|
|
|
|
|
|
|
// Update configuration (for new installations or when no validation issues)
|
|
|
|
|
Self::update_config(&conn, config, path)?;
|
|
|
|
|
|
2025-09-11 14:32:04 -04:00
|
|
|
// Load existing files from database for incremental indexing
|
|
|
|
|
let existing_files = {
|
|
|
|
|
let conn_ref = &conn;
|
|
|
|
|
load_existing_files(conn_ref)
|
|
|
|
|
.map_err(|e| format!("Failed to load existing files: {}", e))?
|
|
|
|
|
};
|
|
|
|
|
|
2025-09-11 14:00:33 -04:00
|
|
|
let conn_mutex = Arc::new(Mutex::new(conn));
|
2025-09-11 18:36:42 -04:00
|
|
|
|
|
|
|
|
// Store the database connection for proper cleanup on stop
|
|
|
|
|
if let Ok(mut db_opt) = db_connection.lock() {
|
|
|
|
|
*db_opt = Some(conn_mutex.clone());
|
|
|
|
|
}
|
2025-09-11 14:00:33 -04:00
|
|
|
|
2025-09-11 14:32:04 -04:00
|
|
|
// Collect all file entries
|
|
|
|
|
let walker = WalkDir::new(path).into_iter();
|
|
|
|
|
let entries: Vec<_> = walker
|
2025-09-11 14:00:33 -04:00
|
|
|
.filter_map(|entry| entry.ok())
|
|
|
|
|
.filter(|entry| !entry.metadata().map(|m| m.is_dir()).unwrap_or(true))
|
2025-09-11 14:32:04 -04:00
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
let total_file_count = entries.len();
|
2025-09-11 14:00:33 -04:00
|
|
|
|
|
|
|
|
// Update status with total file count
|
|
|
|
|
if let Ok(mut status_guard) = status.lock() {
|
2025-09-11 18:36:42 -04:00
|
|
|
if let IndexingStatus::RunningFileIndex { ref mut total_files, .. } = *status_guard {
|
2025-09-11 14:00:33 -04:00
|
|
|
*total_files = Some(total_file_count);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-11 14:32:04 -04:00
|
|
|
// Analyze which files need updates vs inserts
|
|
|
|
|
let batch_update = analyze_files_for_batch_update(&entries, &existing_files);
|
2025-09-11 14:00:33 -04:00
|
|
|
|
2025-09-11 14:32:04 -04:00
|
|
|
let total_work = batch_update.files_to_update.len() + batch_update.files_to_insert.len();
|
2025-09-11 14:00:33 -04:00
|
|
|
|
2025-09-11 14:32:04 -04:00
|
|
|
// Update status to show actual work needed
|
|
|
|
|
if let Ok(mut status_guard) = status.lock() {
|
2025-09-11 18:36:42 -04:00
|
|
|
if let IndexingStatus::RunningFileIndex { ref mut total_files, .. } = *status_guard {
|
2025-09-11 14:32:04 -04:00
|
|
|
*total_files = Some(total_work);
|
2025-09-11 14:00:33 -04:00
|
|
|
}
|
2025-09-11 14:32:04 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut work_completed = 0;
|
2025-09-11 14:00:33 -04:00
|
|
|
|
2025-09-11 14:32:04 -04:00
|
|
|
// Process updated files in batches
|
|
|
|
|
if !batch_update.files_to_update.is_empty() {
|
2025-09-11 14:00:33 -04:00
|
|
|
if let Ok(mut status_guard) = status.lock() {
|
2025-09-11 18:36:42 -04:00
|
|
|
if let IndexingStatus::RunningFileIndex { ref mut current_file, .. } = *status_guard {
|
2025-09-11 14:32:04 -04:00
|
|
|
*current_file = Some(format!("Updating {} modified files...", batch_update.files_to_update.len()));
|
2025-09-11 14:00:33 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-11 16:32:21 -04:00
|
|
|
// Create status callback to update current file
|
|
|
|
|
let status_clone_1 = status.clone();
|
2025-09-11 14:32:04 -04:00
|
|
|
let status_callback = Box::new(move |file_status: &str| {
|
2025-09-11 16:32:21 -04:00
|
|
|
if let Ok(mut status_guard) = status_clone_1.lock() {
|
2025-09-11 18:36:42 -04:00
|
|
|
if let IndexingStatus::RunningFileIndex { ref mut current_file, .. } = *status_guard {
|
2025-09-11 14:32:04 -04:00
|
|
|
*current_file = Some(file_status.to_string());
|
2025-09-11 16:32:21 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Create progress callback to update files_processed
|
|
|
|
|
let status_clone_2 = status.clone();
|
|
|
|
|
let base_work_completed = work_completed;
|
|
|
|
|
let progress_callback = Box::new(move |current_index: usize| {
|
|
|
|
|
if let Ok(mut status_guard) = status_clone_2.lock() {
|
2025-09-11 18:36:42 -04:00
|
|
|
if let IndexingStatus::RunningFileIndex { ref mut files_processed, .. } = *status_guard {
|
2025-09-11 16:32:21 -04:00
|
|
|
*files_processed = base_work_completed + current_index;
|
2025-09-11 14:32:04 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-09-11 14:00:33 -04:00
|
|
|
|
2025-09-11 18:36:42 -04:00
|
|
|
if let Err(e) = process_batch_updates_files_only(&conn_mutex, &batch_update.files_to_update, &stop_flag, Some(status_callback), Some(progress_callback), config) {
|
2025-09-11 14:32:04 -04:00
|
|
|
return Err(format!("Failed to process batch updates: {}", e));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
work_completed += batch_update.files_to_update.len();
|
|
|
|
|
|
|
|
|
|
if let Ok(mut status_guard) = status.lock() {
|
2025-09-11 18:36:42 -04:00
|
|
|
if let IndexingStatus::RunningFileIndex { ref mut files_processed, .. } = *status_guard {
|
2025-09-11 14:32:04 -04:00
|
|
|
*files_processed = work_completed;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check for stop signal
|
|
|
|
|
if *stop_flag.lock().unwrap() {
|
|
|
|
|
if let Ok(mut status_guard) = status.lock() {
|
|
|
|
|
*status_guard = IndexingStatus::Idle;
|
|
|
|
|
}
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Process new files in batches
|
|
|
|
|
if !batch_update.files_to_insert.is_empty() {
|
|
|
|
|
if let Ok(mut status_guard) = status.lock() {
|
2025-09-11 18:36:42 -04:00
|
|
|
if let IndexingStatus::RunningFileIndex { ref mut current_file, .. } = *status_guard {
|
2025-09-11 14:32:04 -04:00
|
|
|
*current_file = Some(format!("Indexing {} new files...", batch_update.files_to_insert.len()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-11 16:32:21 -04:00
|
|
|
// Create status callback for inserts
|
|
|
|
|
let status_clone_3 = status.clone();
|
2025-09-11 14:32:04 -04:00
|
|
|
let status_callback = Box::new(move |file_status: &str| {
|
2025-09-11 16:32:21 -04:00
|
|
|
if let Ok(mut status_guard) = status_clone_3.lock() {
|
2025-09-11 18:36:42 -04:00
|
|
|
if let IndexingStatus::RunningFileIndex { ref mut current_file, .. } = *status_guard {
|
2025-09-11 14:32:04 -04:00
|
|
|
*current_file = Some(file_status.to_string());
|
2025-09-11 16:32:21 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Create progress callback for inserts
|
|
|
|
|
let status_clone_4 = status.clone();
|
|
|
|
|
let base_work_completed = work_completed;
|
|
|
|
|
let progress_callback = Box::new(move |current_index: usize| {
|
|
|
|
|
if let Ok(mut status_guard) = status_clone_4.lock() {
|
2025-09-11 18:36:42 -04:00
|
|
|
if let IndexingStatus::RunningFileIndex { ref mut files_processed, .. } = *status_guard {
|
2025-09-11 16:32:21 -04:00
|
|
|
*files_processed = base_work_completed + current_index;
|
2025-09-11 14:32:04 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2025-09-11 18:36:42 -04:00
|
|
|
if let Err(e) = process_batch_inserts_files_only(&conn_mutex, &batch_update.files_to_insert, &stop_flag, Some(status_callback), Some(progress_callback), config) {
|
2025-09-11 14:32:04 -04:00
|
|
|
return Err(format!("Failed to process batch inserts: {}", e));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
work_completed += batch_update.files_to_insert.len();
|
|
|
|
|
|
|
|
|
|
if let Ok(mut status_guard) = status.lock() {
|
2025-09-11 18:36:42 -04:00
|
|
|
if let IndexingStatus::RunningFileIndex { ref mut files_processed, .. } = *status_guard {
|
2025-09-11 14:32:04 -04:00
|
|
|
*files_processed = work_completed;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-11 18:36:42 -04:00
|
|
|
// If no incremental work was needed, show completion status for file indexing phase
|
2025-09-11 14:32:04 -04:00
|
|
|
if total_work == 0 {
|
|
|
|
|
if let Ok(mut status_guard) = status.lock() {
|
2025-09-11 18:36:42 -04:00
|
|
|
if let IndexingStatus::RunningFileIndex { ref mut current_file, ref mut files_processed, .. } = *status_guard {
|
|
|
|
|
*current_file = Some("File index is up to date".to_string());
|
2025-09-11 14:32:04 -04:00
|
|
|
*files_processed = total_file_count;
|
2025-09-11 14:00:33 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-11 18:36:42 -04:00
|
|
|
// Check for stop signal before starting text indexing
|
|
|
|
|
if *stop_flag.lock().unwrap() {
|
|
|
|
|
if let Ok(mut status_guard) = status.lock() {
|
|
|
|
|
*status_guard = IndexingStatus::Idle;
|
|
|
|
|
}
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Phase 2: Text indexing
|
|
|
|
|
if let Ok(mut status_guard) = status.lock() {
|
|
|
|
|
*status_guard = IndexingStatus::RunningTextIndex {
|
|
|
|
|
files_processed: 0,
|
|
|
|
|
total_files: None,
|
|
|
|
|
current_file: Some("Starting text indexing...".to_string()),
|
|
|
|
|
start_time: Instant::now(),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create status callback for text indexing
|
|
|
|
|
let status_clone_5 = status.clone();
|
|
|
|
|
let text_status_callback = Box::new(move |file_status: &str| {
|
|
|
|
|
if let Ok(mut status_guard) = status_clone_5.lock() {
|
|
|
|
|
if let IndexingStatus::RunningTextIndex { ref mut current_file, .. } = *status_guard {
|
|
|
|
|
*current_file = Some(file_status.to_string());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Create progress callback for text indexing
|
|
|
|
|
let status_clone_6 = status.clone();
|
|
|
|
|
let text_progress_callback = Box::new(move |current_index: usize| {
|
|
|
|
|
if let Ok(mut status_guard) = status_clone_6.lock() {
|
|
|
|
|
if let IndexingStatus::RunningTextIndex { ref mut files_processed, .. } = *status_guard {
|
|
|
|
|
*files_processed = current_index;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Process text indexing
|
|
|
|
|
if let Err(e) = process_text_indexing(&conn_mutex, &stop_flag, Some(text_status_callback), Some(text_progress_callback), config) {
|
|
|
|
|
return Err(format!("Failed to process text indexing: {}", e));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Mark text indexing as complete
|
|
|
|
|
if let Ok(mut status_guard) = status.lock() {
|
|
|
|
|
if let IndexingStatus::RunningTextIndex { ref mut current_file, .. } = *status_guard {
|
|
|
|
|
*current_file = Some("Text indexing complete".to_string());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-11 14:00:33 -04:00
|
|
|
Ok(())
|
|
|
|
|
}
|
2025-09-11 16:32:21 -04:00
|
|
|
|
|
|
|
|
/// Validates configuration against stored values and returns validation results.
|
|
|
|
|
/// Critical configuration changes that require index recreation:
|
|
|
|
|
/// - hash_length: affects file hash computation, invalidates existing file metadata
|
|
|
|
|
/// - indexing_path: changes the scope of indexed files
|
2025-09-11 18:36:42 -04:00
|
|
|
/// - tokenize: changes FTS5 tokenization, invalidates text search index
|
2025-09-11 16:32:21 -04:00
|
|
|
fn validate_config(conn: &Connection, config: &Config, indexing_path: &str) -> Result<Option<Vec<String>>, String> {
|
|
|
|
|
// Critical configuration values that require index recreation
|
|
|
|
|
let hash_length = config.processing.hash_length.to_string();
|
2025-09-11 18:36:42 -04:00
|
|
|
let tokenize = config.processing.tokenize.clone();
|
2025-09-12 00:31:52 -04:00
|
|
|
let normalized_path = {
|
|
|
|
|
let path = std::path::Path::new(indexing_path)
|
|
|
|
|
.canonicalize()
|
|
|
|
|
.unwrap_or_else(|_| std::path::PathBuf::from(indexing_path))
|
|
|
|
|
.to_string_lossy()
|
|
|
|
|
.to_string();
|
|
|
|
|
// Remove Windows UNC prefix \\?\
|
|
|
|
|
if path.starts_with("\\\\?\\") {
|
|
|
|
|
path[4..].to_string()
|
|
|
|
|
} else {
|
|
|
|
|
path
|
|
|
|
|
}
|
|
|
|
|
};
|
2025-09-11 16:32:21 -04:00
|
|
|
|
|
|
|
|
// Check stored configuration values
|
|
|
|
|
let mut stored_hash_length: Option<String> = None;
|
|
|
|
|
let mut stored_indexing_path: Option<String> = None;
|
2025-09-11 18:36:42 -04:00
|
|
|
let mut stored_tokenize: Option<String> = None;
|
2025-09-11 16:32:21 -04:00
|
|
|
|
2025-09-11 18:36:42 -04:00
|
|
|
if let Ok(mut stmt) = conn.prepare("SELECT key, value FROM config_validation WHERE key IN ('hash_length', 'indexing_path', 'tokenize')") {
|
2025-09-11 16:32:21 -04:00
|
|
|
if let Ok(rows) = stmt.query_map([], |row| {
|
|
|
|
|
let key: String = row.get(0)?;
|
|
|
|
|
let value: String = row.get(1)?;
|
|
|
|
|
Ok((key, value))
|
|
|
|
|
}) {
|
|
|
|
|
for row in rows.flatten() {
|
|
|
|
|
match row.0.as_str() {
|
|
|
|
|
"hash_length" => stored_hash_length = Some(row.1),
|
|
|
|
|
"indexing_path" => stored_indexing_path = Some(row.1),
|
2025-09-11 18:36:42 -04:00
|
|
|
"tokenize" => stored_tokenize = Some(row.1),
|
2025-09-11 16:32:21 -04:00
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if configuration is invalid
|
|
|
|
|
let hash_length_changed = stored_hash_length.as_ref().map_or(false, |stored| stored != &hash_length);
|
|
|
|
|
let indexing_path_changed = stored_indexing_path.as_ref().map_or(false, |stored| stored != &normalized_path);
|
2025-09-11 18:36:42 -04:00
|
|
|
let tokenize_changed = stored_tokenize.as_ref().map_or(false, |stored| stored != &tokenize);
|
2025-09-11 16:32:21 -04:00
|
|
|
|
2025-09-11 18:36:42 -04:00
|
|
|
if hash_length_changed || indexing_path_changed || tokenize_changed {
|
2025-09-11 16:32:21 -04:00
|
|
|
let mut changes = Vec::new();
|
|
|
|
|
if hash_length_changed {
|
|
|
|
|
changes.push(format!("hash_length: {} -> {}",
|
|
|
|
|
stored_hash_length.unwrap_or_else(|| "unknown".to_string()), hash_length));
|
|
|
|
|
}
|
|
|
|
|
if indexing_path_changed {
|
|
|
|
|
changes.push(format!("indexing_path: {} -> {}",
|
|
|
|
|
stored_indexing_path.unwrap_or_else(|| "unknown".to_string()), normalized_path));
|
|
|
|
|
}
|
2025-09-11 18:36:42 -04:00
|
|
|
if tokenize_changed {
|
|
|
|
|
changes.push(format!("tokenize: {} -> {}",
|
|
|
|
|
stored_tokenize.unwrap_or_else(|| "unknown".to_string()), tokenize));
|
|
|
|
|
}
|
2025-09-11 16:32:21 -04:00
|
|
|
|
|
|
|
|
return Ok(Some(changes));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// No configuration changes detected
|
|
|
|
|
Ok(None)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Updates stored configuration values without clearing the index
|
|
|
|
|
fn update_config(conn: &Connection, config: &Config, indexing_path: &str) -> Result<(), String> {
|
|
|
|
|
let hash_length = config.processing.hash_length.to_string();
|
2025-09-11 18:36:42 -04:00
|
|
|
let tokenize = config.processing.tokenize.clone();
|
2025-09-12 00:31:52 -04:00
|
|
|
let normalized_path = {
|
|
|
|
|
let path = std::path::Path::new(indexing_path)
|
|
|
|
|
.canonicalize()
|
|
|
|
|
.unwrap_or_else(|_| std::path::PathBuf::from(indexing_path))
|
|
|
|
|
.to_string_lossy()
|
|
|
|
|
.to_string();
|
|
|
|
|
// Remove Windows UNC prefix \\?\
|
|
|
|
|
if path.starts_with("\\\\?\\") {
|
|
|
|
|
path[4..].to_string()
|
|
|
|
|
} else {
|
|
|
|
|
path
|
|
|
|
|
}
|
|
|
|
|
};
|
2025-09-11 16:32:21 -04:00
|
|
|
|
|
|
|
|
// Update stored configuration values
|
|
|
|
|
conn.execute(
|
|
|
|
|
"INSERT OR REPLACE INTO config_validation (key, value) VALUES ('hash_length', ?1)",
|
|
|
|
|
params![hash_length],
|
|
|
|
|
).map_err(|e| format!("Failed to store hash_length config: {}", e))?;
|
|
|
|
|
|
|
|
|
|
conn.execute(
|
|
|
|
|
"INSERT OR REPLACE INTO config_validation (key, value) VALUES ('indexing_path', ?1)",
|
|
|
|
|
params![normalized_path],
|
|
|
|
|
).map_err(|e| format!("Failed to store indexing_path config: {}", e))?;
|
2025-09-11 18:36:42 -04:00
|
|
|
|
|
|
|
|
conn.execute(
|
|
|
|
|
"INSERT OR REPLACE INTO config_validation (key, value) VALUES ('tokenize', ?1)",
|
|
|
|
|
params![tokenize],
|
|
|
|
|
).map_err(|e| format!("Failed to store tokenize config: {}", e))?;
|
2025-09-11 16:32:21 -04:00
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2025-09-11 14:00:33 -04:00
|
|
|
}
|
|
|
|
|
|
2025-09-11 18:36:42 -04:00
|
|
|
impl Drop for IndexingService {
|
|
|
|
|
fn drop(&mut self) {
|
|
|
|
|
// Ensure graceful shutdown when the service is dropped
|
|
|
|
|
let _ = self.stop_indexing();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-11 14:00:33 -04:00
|
|
|
impl Default for IndexingService {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self::new()
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-11 14:32:04 -04:00
|
|
|
|