use std::{
collections::HashMap,
sync::{Arc, Mutex},
thread::sleep,
time::Duration,
};
use r2d2::PooledConnection;
use r2d2_sqlite::SqliteConnectionManager;
use uuid::Uuid;
use crate::{
config::SearchConfig,
error::Result,
events::{MxId, Profile, SerializedEvent},
index::IndexSearcher,
Database,
};
static BUSY_RETRY: usize = 10;
static BUSY_SLEEP: Duration = Duration::from_millis(10);
#[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize)]
pub struct SearchResult {
pub score: f32,
pub event_source: SerializedEvent,
pub events_before: Vec<SerializedEvent>,
pub events_after: Vec<SerializedEvent>,
pub profile_info: HashMap<MxId, Profile>,
}
#[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize)]
pub struct SearchBatch {
pub count: usize,
pub results: Vec<SearchResult>,
pub next_batch: Option<Uuid>,
}
pub struct Searcher {
pub(crate) inner: IndexSearcher,
pub(crate) database: Arc<Mutex<PooledConnection<SqliteConnectionManager>>>,
}
impl Searcher {
pub fn search(&self, term: &str, config: &SearchConfig) -> Result<SearchBatch> {
let search_result = self.inner.search(term, config)?;
if search_result.results.is_empty() {
return Ok(SearchBatch {
count: 0,
next_batch: search_result.next_batch,
results: vec![],
});
}
let mut retry = 0;
let events = loop {
match Database::load_events(
&self.database.lock().unwrap(),
&search_result.results,
config.before_limit,
config.after_limit,
config.order_by_recency,
) {
Ok(e) => break e,
Err(e) => match e {
rusqlite::Error::SqliteFailure(sql_error, _) => {
if sql_error.code == rusqlite::ffi::ErrorCode::DatabaseBusy
&& retry < BUSY_RETRY
{
retry += 1;
sleep(BUSY_SLEEP);
continue;
} else {
return Err(e.into());
}
}
e => return Err(e.into()),
},
}
};
Ok(SearchBatch {
count: search_result.count,
next_batch: search_result.next_batch,
results: events,
})
}
}