use std::path::PathBuf;
use lunar_lib::{
database::{DatabaseEntry, Db, DbIdIterExt, Entry, TransactionError},
define_db,
id::Id,
iterator_ext::{IntoIteratorExtensions, IteratorExtensions},
log::trace,
paths::data_dir,
};
mod tx_extensions;
pub(crate) use tx_extensions::*;
#[cfg(debug_assertions)]
pub mod validator;
define_db!(Library {
fn path() -> Option<PathBuf> {
Some(data_dir().join("library_data"))
}
});
pub trait SeleneEntryExt<T: DatabaseEntry> {
fn to_db_entry(self) -> Entry<T>;
}
impl<T: DatabaseEntry> SeleneEntryExt<T> for crate::library::Entry<T> {
fn to_db_entry(self) -> Entry<T> {
self.into()
}
}
pub trait Searchable: DatabaseEntry {
const SEARCH_INDEX: &'static str;
fn search_name(&self) -> Option<&str>;
fn build_search_index(db: &Db<Self::DbInner>) -> Result<(), TransactionError> {
db.index::<Self>(Self::SEARCH_INDEX).rebuild(|tree, entry| {
if let Some(title) = entry.search_name().map(str::to_ascii_lowercase) {
let mut key = [0u8; 8];
let bytes = title.as_bytes();
let len = bytes.len().min(8);
key[..len].copy_from_slice(&bytes[..len]);
tree.fetch_and_update(key, |old| match old {
Some(v) => {
let mut new_value = v.to_vec();
new_value.extend_from_slice(&*entry.id());
Some(new_value)
}
None => Some(entry.id().to_vec()),
})?;
}
Ok(())
})
}
fn search(
db: &Db<Self::DbInner>,
query: &str,
limit: usize,
offset: usize,
) -> Result<Vec<Entry<Self>>, TransactionError> {
trace!(
"Searching index {} with query '{query}'",
Self::SEARCH_INDEX
);
let query = query.to_ascii_lowercase();
let len = query.len().min(8);
let key = &query.as_bytes()[..len];
let mut items = db
.index::<Self>(Self::SEARCH_INDEX)
.scan_prefix(key)
.try_map(|i| -> Result<Vec<Id<Self>>, TransactionError> {
let (_, v) = i.map_err(TransactionError::from)?;
Ok(v.chunks_exact(32)
.map(|chunk| Id::<Self>::from(<[u8; 32]>::try_from(chunk).unwrap()))
.collect())
})?
.flatten()
.db_get(db)?;
items.retain(|item| {
item.search_name()
.expect("Items without names cant be indexed")
.to_ascii_lowercase()
.starts_with(&query)
});
{
let result = items
.iter()
.map(|t| t.search_name().unwrap())
.join_to_string(", ");
trace!("Search found: {result}");
}
Ok(items.into_iter().skip(offset).take(limit).to_vec())
}
}