tauri-plugin-indexer 0.1.1

a custom indexer (if the version is 1.0.0 then the plugin is ready)
Documentation
use crate::indexer::{
    indexer::trie::{TrieNode, WordMeta},
    utils::{tokenizer::tokenize, word_utils::get_word_half},
};
use crate::indexer::config::config::index_dir;
use anyhow::Result;
use data_encoding::BASE32_NOPAD;
use std::collections::HashMap;
use std::path::PathBuf;
use std::fs;
use rust_stemmers::{Algorithm, Stemmer};
use std::io::BufReader;
use std::time::Instant;

pub fn rank_documents(results: Vec<(HashMap<String, WordMeta>, String)>) -> Vec<(String, f32)> {
    let mut doc_scores: HashMap<String, f32> = HashMap::new();
    let mut doc_occurrences: HashMap<String, usize> = HashMap::new();
    let files_metadata = match fs::read_to_string(PathBuf::from(index_dir()).join("doc_indexes/files_metadata.json")) {
        Ok(data) => data,
        Err(e) => {
            eprintln!("Error reading files_metadata.json: {}", e);
            return Vec::new(); // Return an empty result if the file cannot be read
        }
    };
    let files_metadata: HashMap<String, serde_json::Value> = match serde_json::from_str(&files_metadata) {
        Ok(data) => data,
        Err(e) => {
            eprintln!("Error parsing files_metadata.json: {}", e);
            return Vec::new(); // Return an empty result if the JSON is invalid
        }
    };
    // println!("Files metadata: {:#?}", files_metadata);

    for (doc_map, _found_word) in results {
        for (doc_id, word_meta) in doc_map {
            *doc_scores.entry(doc_id.clone()).or_insert(0.0) += word_meta.weight;
            *doc_occurrences.entry(doc_id).or_insert(0) += 1;
        }
    }

    // OPTIONAL: boost documents that appeared in multiple word matches
    for (doc_id, occurrences) in &doc_occurrences {
        if let Some(score) = doc_scores.get_mut(doc_id) {
            *score *= *occurrences as f32; // boost by the number of intersections
        }
    }

    // Sort by score descending
    let mut scored_docs: Vec<(String, f32)> = doc_scores.into_iter().collect();
    scored_docs.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
    // Replace doc_id with the corresponding path from files_metadata
    for doc in &mut scored_docs {
        if let Some(metadata) = files_metadata.get(&doc.0) {
            if let Some(path) = metadata.get("path").and_then(|p| p.as_str()) {
                doc.0 = path.to_string();
            }
        }
    }
    scored_docs
}
fn fuzzy_search<'a>(
    node: &'a TrieNode,
    chars: &[char],
    idx: usize,
    allowed_errors: usize,
    current_word: String,  // Track the current word being reconstructed
) -> Option<(String, &'a TrieNode)> {
    if idx == chars.len() {
        // If we've processed all characters, return the current word and the node
        return Some((current_word, node));
    }
    
    let ch = chars[idx];
    
    // Exact match path
    if let Some(child) = node.children.get(&ch) {
        if let Some((reconstructed_word, result)) = fuzzy_search(child, chars, idx + 1, allowed_errors, current_word.clone() + &ch.to_string()) {
            return Some((reconstructed_word, result));
        }
    }
    
    if allowed_errors == 0 {
        return None;
    }
    
    // Try substitution
    for (other_ch, child) in &node.children {
        if *other_ch != ch {
            if let Some((reconstructed_word, result)) = fuzzy_search(child, chars, idx + 1, allowed_errors - 1, current_word.clone() + &other_ch.to_string()) {
                return Some((reconstructed_word, result));
            }
        }
    }
    
    // Try deletion (skip this character)
    if let Some((reconstructed_word, result)) = fuzzy_search(node, chars, idx + 1, allowed_errors - 1, current_word.clone()) {
        return Some((reconstructed_word, result));
    }
    
    // Try insertion (extra character)
    for (_other_ch, child) in &node.children {
        if let Some((reconstructed_word, result)) = fuzzy_search(child, chars, idx, allowed_errors - 1, current_word.clone()) {
            return Some((reconstructed_word, result));
        }
    }
    
    None
}
pub fn search_word(word: &str) -> Result<Option<(HashMap<String, WordMeta>, String)>> {
    let stemmer = Stemmer::create(Algorithm::English);
    let stemmed_word = stemmer.stem(word).to_string();
    if stemmed_word.is_empty() {
        return Ok(None);
    }

    let word_half = get_word_half(&stemmed_word);
    let encoded_name = BASE32_NOPAD.encode(word_half.as_bytes());
    let mut filename = PathBuf::from(index_dir());
    for ch in encoded_name.chars().take(4) {
        filename.push(ch.to_string());
    }
    filename.push(format!("{}.json", encoded_name));
    println!("encoded_name: {}", encoded_name);
    if !filename.exists() {
        return Ok(None);
    }

    let file_data: HashMap<String, TrieNode> = {
        let file = fs::File::open(&filename)?;
        let reader = BufReader::new(file);
        serde_json::from_reader(reader)?
    };

    let trie = file_data.get(&word_half)
        .ok_or_else(|| anyhow::anyhow!("No index for word half {}", word_half))?;

    let current = trie;
    let chars: Vec<char> = stemmed_word.chars().collect();
    
    // Perform fuzzy search with allowed distance of 2
    let result = fuzzy_search(current, &chars, 0, 2, String::new())
        .ok_or_else(|| anyhow::anyhow!("Word not found with allowed distance"))?;

    if let Some(doc_ids) = result.1.doc_ids.clone() {
        Ok(Some((doc_ids, result.0)))
    } else {
        Err(anyhow::anyhow!("No doc_ids found for the word"))
    }
}
pub fn search_query(query: &str) {
    let start_time = Instant::now(); // Start the timer
    let words: Vec<String> = tokenize(query);
    let results: Vec<(HashMap<String, WordMeta>, String)> = words.iter()
    .filter_map(|word| match search_word(word) {
        Ok(Some((doc_ids, found_word))) => {
            println!("Found word: {}", found_word); // Print the word found
            Some((doc_ids, found_word)) // Include the word in the results
        }
        Ok(None) => None, // Handle the case where the result is None
        Err(e) => {
            eprintln!("Error searching word '{}': {}", word, e);
            None
        }
    })
    .collect();
    let ranked = rank_documents(results);
    let elapsed_time = start_time.elapsed();
    println!("Search completed in: {:?}", elapsed_time);
    println!("Ranked documents: {:#?}", ranked.iter().take(10).collect::<Vec<_>>());
}