use std::path::{Path, PathBuf};
use dirs::config_dir;
use nucleo::{Config, Matcher, Utf32Str};
use serde::{Deserialize, Serialize};
use crate::identity::Identity;
#[derive(Serialize, Deserialize)]
pub struct Database {
pub keys: Vec<Identity>
}
impl Database {
pub fn load_or_create(path: &Path) -> anyhow::Result<Self> {
if !database_exists(path) {
let d = Database {
keys: vec![]
};
write_database(d, path)?;
}
load_database(path)
}
pub fn write(self, path: &Path) -> anyhow::Result<()> {
write_database(self, path)
}
pub fn fuzzy_search(&self, term: String) -> Vec<Identity> {
if self.keys.is_empty() { return vec![] }
let mut m = Matcher::new(Config::DEFAULT);
let haystacks = self.keys.iter().map(|u| {
(u.clone(), format!("{:?} {}", u, u.keys.pk().unwrap_or("<pk_unknown>".to_string())))
}).collect::<Vec<_>>();
let mut scores = haystacks.iter().map(|h| {
(
m.fuzzy_match(Utf32Str::Ascii(h.1.as_bytes()), Utf32Str::Ascii(term.as_bytes())).unwrap_or(0),
h.0.clone()
)
}).collect::<Vec<_>>();
scores.sort_by_key(|u| u.0);
scores.reverse();
let mut best_matches = vec![scores[0].clone()];
for score in &scores[1..] {
if score.0 == best_matches[0].0 {
best_matches.push(score.clone());
}
}
best_matches.iter().map(|u| u.1.clone()).collect()
}
}
fn database_exists(path: &Path) -> bool {
path.exists()
}
fn load_database(path: &Path) -> anyhow::Result<Database> {
let f = std::fs::read_to_string(path)?;
let data: Database = toml::from_str(&f)?;
Ok(data)
}
fn write_database(d: Database, path: &Path) -> anyhow::Result<()> {
let s = toml::to_string(&d)?;
let s_with_warning = format!(
"# This file is autogenerated; do not edit! sage v{}\n{}",
env!("CARGO_PKG_VERSION"),
s
);
std::fs::write(path, s_with_warning)?;
Ok(())
}
pub fn db_default() -> PathBuf {
config_dir().unwrap().join("sage.toml")
}