use std::cell::RefCell;
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use std::collections::VecDeque;
use std::str;
use sequoia_openpgp as openpgp;
use openpgp::Fingerprint;
use openpgp::Result;
use openpgp::packet::UserID;
use crate::store::StoreError;
use crate::store::UserIDQueryParams;
use super::TRACE;
pub struct UserIDIndex {
by_userid: BTreeMap<UserID, BTreeSet<Fingerprint>>,
by_email: RefCell<BTreeMap<String, BTreeSet<Fingerprint>>>,
by_email_pending: RefCell<VecDeque<(UserID, Fingerprint)>>,
}
impl Default for UserIDIndex {
fn default() -> Self {
UserIDIndex {
by_userid: Default::default(),
by_email: RefCell::new(Default::default()),
by_email_pending: RefCell::new(VecDeque::new()),
}
}
}
impl UserIDIndex {
pub fn new() -> Self {
Self::default()
}
pub fn insert<I>(&mut self, fpr: &Fingerprint, userids: I)
where I: Iterator<Item=UserID>
{
for userid in userids {
self.by_userid.entry(userid.clone())
.or_default()
.insert(fpr.clone());
self.by_email_pending.borrow_mut()
.push_back((userid, fpr.clone()));
}
}
fn execute_pending_insertions(&self) {
for (userid, fpr) in self.by_email_pending.borrow_mut().drain(..) {
if let Ok(Some(email)) = userid.email_normalized() {
self.by_email.borrow_mut()
.entry(email)
.or_default()
.insert(fpr.clone());
}
}
}
pub fn select_userid(&self, params: &UserIDQueryParams, pattern: &str)
-> Result<Vec<Fingerprint>>
{
tracer!(TRACE, "UserIDIndex::select_userid");
t!("params: {:?}, pattern: {:?}", params, pattern);
let mut matches = match params {
UserIDQueryParams {
anchor_start: true,
anchor_end: true,
email: false,
ignore_case: false,
} => {
let userid = UserID::from(pattern);
self.by_userid.get(&userid)
.ok_or_else(|| {
StoreError::NoMatches(pattern.into())
})?
.iter()
.cloned()
.collect()
}
UserIDQueryParams {
anchor_start: true,
anchor_end: true,
email: true,
ignore_case: false,
} => {
self.execute_pending_insertions();
self.by_email.borrow()
.get(pattern)
.ok_or_else(|| {
StoreError::NoMatches(pattern.into())
})?
.iter()
.cloned()
.collect()
}
UserIDQueryParams {
anchor_start,
anchor_end,
email,
ignore_case,
} => {
let mut pattern = pattern;
let _pattern;
if *ignore_case {
_pattern = pattern.to_lowercase();
pattern = &_pattern[..];
}
let check = |userid: &str| -> bool {
let mut userid = userid;
let _userid: String;
if *ignore_case {
_userid = userid.to_lowercase();
userid = &_userid[..];
}
t!("Considering if {:?} matches {:?} \
(anchors: {}, {}, ignore case: {})",
pattern, userid, anchor_start, anchor_end,
ignore_case);
if match (*anchor_start, *anchor_end) {
(true, true) => userid == pattern,
(true, false) => userid.starts_with(pattern),
(false, true) => userid.ends_with(pattern),
(false, false) => userid.contains(pattern),
}
{
t!("*** {:?} matches {:?} (anchors: {}, {})",
pattern, userid, *anchor_start, anchor_end);
true
} else {
false
}
};
if *email {
self.execute_pending_insertions();
self.by_email
.borrow()
.iter()
.filter_map(|(email, matches)| {
if check(email) {
Some(matches.iter())
} else {
None
}
})
.flatten()
.cloned()
.collect::<Vec<Fingerprint>>()
} else {
self.by_userid
.iter()
.filter_map(|(userid, matches)| {
let userid = str::from_utf8(userid.value()).ok()?;
if check(userid) {
Some(matches.iter())
} else {
None
}
})
.flatten()
.cloned()
.collect::<Vec<Fingerprint>>()
}
}
};
if matches.is_empty() {
return Err(StoreError::NoMatches(pattern.into()).into());
}
matches.sort();
matches.dedup();
Ok(matches)
}
}