use std::path::{Path, PathBuf};
use diesel::{query_dsl, ExpressionMethods, QueryDsl, RunQueryDsl, TextExpressionMethods};
use openpgp_cert_d::{CertD, MergeResult};
use rpgpie::key::Certificate;
use crate::schema::{certs, emails, keyids, subkeys, userids};
#[rustfmt::skip]
mod schema;
mod db;
mod lookup;
mod model;
mod util;
const PRELOAD: bool = true;
const DB_FILENAME: &str = "_rpgpie.sqlite";
pub struct Store {
certd: CertD,
db_path: PathBuf,
}
impl Store {
pub fn new() -> Result<Self, Error> {
Self::with_base_dir(CertD::user_configured_store_path()?)
}
pub fn with_base_dir(base: impl AsRef<Path>) -> Result<Self, Error> {
let p: &Path = base.as_ref();
if !p.exists() {
std::fs::create_dir_all(p).ok();
}
let certd = CertD::with_base_dir(base)?;
let mut db_path = certd.base_dir().to_path_buf();
db_path.push(DB_FILENAME);
log::debug!("Store::new with db path {:?}", db_path);
let store = Self { certd, db_path };
store.init(PRELOAD)?;
Ok(store)
}
pub fn insert(&self, certificate: &Certificate) -> Result<(), Error> {
let serialized: Vec<u8> = certificate.try_into()?;
let fp = certificate.fingerprint();
match self
.certd
.insert(&hex::encode(fp), serialized, false, |this, old| {
if old.is_none() {
Ok(MergeResult::Data(this))
} else {
log::warn!("Merging/updating is not yet implemented");
Err(openpgp_cert_d::Error::Other(
"Merging/updating is not yet implemented".into(),
))
}
}) {
Ok((tag, _)) => {
let mut conn = self.conn()?;
Self::cache_update(certificate, tag, &mut conn)
}
Err(e) => Err(Error::Message(format!("Database insert failed {:?}", e))),
}
}
pub fn get_by_primary_fingerprint(
&self,
fingerprint: &str,
) -> Result<Option<Certificate>, Error> {
let mut conn = self.conn()?;
self.get_by_primary_with_conn(fingerprint, &mut conn)
}
pub fn search_by_fingerprint(&self, fingerprint: &str) -> Result<Vec<Certificate>, Error> {
let mut res = vec![];
let mut conn = self.conn()?;
if let Ok(Some(primary)) = self.get_by_primary_with_conn(fingerprint, &mut conn) {
res.push(primary);
}
let matches = subkeys::table
.inner_join(certs::table)
.filter(subkeys::fp.eq(fingerprint.to_ascii_lowercase()))
.select(certs::fp)
.load::<String>(&mut conn)?;
matches
.iter()
.flat_map(|fp| self.get_by_primary_with_conn(fp, &mut conn))
.flatten()
.for_each(|c| res.push(c));
Ok(res)
}
pub fn search_by_key_id(&self, key_id: &str) -> Result<Vec<Certificate>, Error> {
let mut conn = self.conn()?;
let matches = keyids::table
.inner_join(certs::table)
.filter(keyids::keyid.eq(key_id.to_ascii_lowercase()))
.select(certs::fp)
.load::<String>(&mut conn)?;
let res = matches
.iter()
.flat_map(|fp| self.get_by_primary_with_conn(fp, &mut conn))
.flatten()
.collect();
Ok(res)
}
pub fn search_by_email(&self, email: &str) -> Result<Vec<Certificate>, Error> {
let mut conn = self.conn()?;
let matches = emails::table
.inner_join(certs::table)
.filter(emails::email.eq(email))
.select(certs::fp)
.load::<String>(&mut conn)?;
let res = matches
.iter()
.flat_map(|fp| self.get_by_primary_with_conn(fp, &mut conn))
.flatten()
.collect();
Ok(res)
}
pub fn search_exact_user_id(&self, user_id: &str) -> Result<Vec<Certificate>, Error> {
let mut conn = self.conn()?;
let matches = userids::table
.inner_join(certs::table)
.filter(userids::userid.eq(user_id))
.select(certs::fp)
.load::<String>(&mut conn)?;
let res = matches
.iter()
.flat_map(|fp| self.get_by_primary_with_conn(fp, &mut conn))
.flatten()
.collect();
Ok(res)
}
pub fn search_like_user_id(&self, like: &str) -> Result<Vec<Certificate>, Error> {
let mut conn = self.conn()?;
let matches = query_dsl::methods::GroupByDsl::group_by(
userids::table
.inner_join(certs::table)
.filter(userids::userid.like(like))
.select(certs::fp),
certs::id,
)
.load::<String>(&mut conn)?;
let res = matches
.iter()
.flat_map(|fp| self.get_by_primary_with_conn(fp, &mut conn))
.flatten()
.collect();
Ok(res)
}
pub fn poll(&self, idents: &[String]) -> Result<Vec<Certificate>, Error> {
let mut conn = self.conn()?;
Ok(lookup::get_keyservers(idents, &mut conn))
}
}
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum Error {
#[error("rpgpie error: {0}")]
Rpgpie(rpgpie::Error),
#[error("Database error")]
Database(diesel::result::Error),
#[error("cert-d error: {0}")]
Certd(openpgp_cert_d::Error),
#[error("Internal error: {0}")]
Message(String),
}
impl From<rpgpie::Error> for Error {
fn from(value: rpgpie::Error) -> Self {
Error::Rpgpie(value)
}
}
impl From<diesel::result::Error> for Error {
fn from(value: diesel::result::Error) -> Self {
Error::Database(value)
}
}
impl From<openpgp_cert_d::Error> for Error {
fn from(value: openpgp_cert_d::Error) -> Self {
Error::Certd(value)
}
}