search first ultra minimal implementation
This commit is contained in:
parent
ac8fb89d56
commit
35591ff060
4 changed files with 141 additions and 5 deletions
|
@ -136,15 +136,17 @@ impl<'a> SelectedContext<'a> {
|
||||||
|
|
||||||
pub async fn search(
|
pub async fn search(
|
||||||
self,
|
self,
|
||||||
_charset: &Option<Charset<'a>>,
|
charset: &Option<Charset<'a>>,
|
||||||
_criteria: &SearchKey<'a>,
|
criteria: &SearchKey<'a>,
|
||||||
_uid: &bool,
|
uid: &bool,
|
||||||
) -> Result<(Response<'static>, flow::Transition)> {
|
) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
|
let found = self.mailbox.search(charset, criteria, *uid).await?;
|
||||||
Ok((
|
Ok((
|
||||||
Response::build()
|
Response::build()
|
||||||
.to_req(self.req)
|
.to_req(self.req)
|
||||||
.message("Not implemented")
|
.set_body(found)
|
||||||
.bad()?,
|
.message("SEARCH completed")
|
||||||
|
.ok()?,
|
||||||
flow::Transition::None,
|
flow::Transition::None,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,15 +5,18 @@ use anyhow::{anyhow, bail, Error, Result};
|
||||||
|
|
||||||
use futures::stream::{FuturesOrdered, StreamExt};
|
use futures::stream::{FuturesOrdered, StreamExt};
|
||||||
|
|
||||||
|
use imap_codec::imap_types::core::Charset;
|
||||||
use imap_codec::imap_types::fetch::{MacroOrMessageDataItemNames, MessageDataItem};
|
use imap_codec::imap_types::fetch::{MacroOrMessageDataItemNames, MessageDataItem};
|
||||||
use imap_codec::imap_types::flag::{Flag, FlagFetch, FlagPerm, StoreResponse, StoreType};
|
use imap_codec::imap_types::flag::{Flag, FlagFetch, FlagPerm, StoreResponse, StoreType};
|
||||||
use imap_codec::imap_types::response::{Code, Data, Status};
|
use imap_codec::imap_types::response::{Code, Data, Status};
|
||||||
|
use imap_codec::imap_types::search::SearchKey;
|
||||||
use imap_codec::imap_types::sequence::{self, SequenceSet};
|
use imap_codec::imap_types::sequence::{self, SequenceSet};
|
||||||
|
|
||||||
use crate::imap::attributes::AttributesProxy;
|
use crate::imap::attributes::AttributesProxy;
|
||||||
use crate::imap::flags;
|
use crate::imap::flags;
|
||||||
use crate::imap::mail_view::SeenFlag;
|
use crate::imap::mail_view::SeenFlag;
|
||||||
use crate::imap::response::Body;
|
use crate::imap::response::Body;
|
||||||
|
use crate::imap::search;
|
||||||
use crate::imap::selectors::MailSelectionBuilder;
|
use crate::imap::selectors::MailSelectionBuilder;
|
||||||
use crate::mail::mailbox::Mailbox;
|
use crate::mail::mailbox::Mailbox;
|
||||||
use crate::mail::uidindex::{ImapUid, ImapUidvalidity, UidIndex};
|
use crate::mail::uidindex::{ImapUid, ImapUidvalidity, UidIndex};
|
||||||
|
@ -308,6 +311,7 @@ impl MailboxView {
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|mv| mv.filter(&ap).ok().map(|(body, seen)| (mv, body, seen)))
|
.filter_map(|mv| mv.filter(&ap).ok().map(|(body, seen)| (mv, body, seen)))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
// Register seen flags
|
// Register seen flags
|
||||||
let future_flags = filtered_view
|
let future_flags = filtered_view
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -333,6 +337,22 @@ impl MailboxView {
|
||||||
Ok(command_body)
|
Ok(command_body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A very naive search implementation...
|
||||||
|
pub async fn search<'a>(
|
||||||
|
&self,
|
||||||
|
_charset: &Option<Charset<'a>>,
|
||||||
|
search_key: &SearchKey<'a>,
|
||||||
|
uid: bool,
|
||||||
|
) -> Result<Vec<Body<'static>>> {
|
||||||
|
let (seq_set, seq_type) = search::Criteria(search_key).to_sequence_set();
|
||||||
|
let mailids = MailIdentifiersList(self.get_mail_ids(&seq_set, seq_type.is_uid())?);
|
||||||
|
let mail_u32 = match uid {
|
||||||
|
true => mailids.uids(),
|
||||||
|
_ => mailids.ids(),
|
||||||
|
};
|
||||||
|
Ok(vec![Body::Data(Data::Search(mail_u32))])
|
||||||
|
}
|
||||||
|
|
||||||
// ----
|
// ----
|
||||||
|
|
||||||
// Gets the IMAP ID, the IMAP UIDs and, the Aerogramme UUIDs of mails identified by a SequenceSet of
|
// Gets the IMAP ID, the IMAP UIDs and, the Aerogramme UUIDs of mails identified by a SequenceSet of
|
||||||
|
@ -525,6 +545,12 @@ pub struct MailIdentifiers {
|
||||||
pub struct MailIdentifiersList(Vec<MailIdentifiers>);
|
pub struct MailIdentifiersList(Vec<MailIdentifiers>);
|
||||||
|
|
||||||
impl MailIdentifiersList {
|
impl MailIdentifiersList {
|
||||||
|
fn ids(&self) -> Vec<NonZeroU32> {
|
||||||
|
self.0.iter().map(|mi| mi.i).collect()
|
||||||
|
}
|
||||||
|
fn uids(&self) -> Vec<ImapUid> {
|
||||||
|
self.0.iter().map(|mi| mi.uid).collect()
|
||||||
|
}
|
||||||
fn uuids(&self) -> Vec<UniqueIdent> {
|
fn uuids(&self) -> Vec<UniqueIdent> {
|
||||||
self.0.iter().map(|mi| mi.uuid).collect()
|
self.0.iter().map(|mi| mi.uuid).collect()
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ mod mail_view;
|
||||||
mod mailbox_view;
|
mod mailbox_view;
|
||||||
mod mime_view;
|
mod mime_view;
|
||||||
mod response;
|
mod response;
|
||||||
|
mod search;
|
||||||
mod selectors;
|
mod selectors;
|
||||||
mod session;
|
mod session;
|
||||||
|
|
||||||
|
|
107
src/imap/search.rs
Normal file
107
src/imap/search.rs
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
use imap_codec::imap_types::core::NonEmptyVec;
|
||||||
|
use imap_codec::imap_types::search::SearchKey;
|
||||||
|
use imap_codec::imap_types::sequence::{SeqOrUid, Sequence, SequenceSet};
|
||||||
|
use std::num::NonZeroU32;
|
||||||
|
|
||||||
|
pub enum SeqType {
|
||||||
|
Undefined,
|
||||||
|
NonUid,
|
||||||
|
Uid,
|
||||||
|
}
|
||||||
|
impl SeqType {
|
||||||
|
pub fn is_uid(&self) -> bool {
|
||||||
|
matches!(self, Self::Uid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Criteria<'a>(pub &'a SearchKey<'a>);
|
||||||
|
impl<'a> Criteria<'a> {
|
||||||
|
/// Returns a set of email identifiers that is greater or equal
|
||||||
|
/// to the set of emails to return
|
||||||
|
pub fn to_sequence_set(&self) -> (SequenceSet, SeqType) {
|
||||||
|
match self.0 {
|
||||||
|
SearchKey::All => (sequence_set_all(), SeqType::Undefined),
|
||||||
|
SearchKey::SequenceSet(seq_set) => (seq_set.clone(), SeqType::NonUid),
|
||||||
|
SearchKey::Uid(seq_set) => (seq_set.clone(), SeqType::Uid),
|
||||||
|
SearchKey::Not(_inner) => {
|
||||||
|
tracing::debug!(
|
||||||
|
"using NOT in a search request is slow: it selects all identifiers"
|
||||||
|
);
|
||||||
|
(sequence_set_all(), SeqType::Undefined)
|
||||||
|
}
|
||||||
|
SearchKey::Or(left, right) => {
|
||||||
|
tracing::debug!("using OR in a search request is slow: no deduplication is done");
|
||||||
|
let (base, base_seqtype) = Self(&left).to_sequence_set();
|
||||||
|
let (ext, ext_seqtype) = Self(&right).to_sequence_set();
|
||||||
|
|
||||||
|
// Check if we have a UID/ID conflict in fetching: now we don't know how to handle them
|
||||||
|
match (base_seqtype, ext_seqtype) {
|
||||||
|
(SeqType::Uid, SeqType::NonUid) | (SeqType::NonUid, SeqType::Uid) => {
|
||||||
|
(sequence_set_all(), SeqType::Undefined)
|
||||||
|
}
|
||||||
|
(SeqType::Undefined, x) | (x, _) => {
|
||||||
|
let mut new_vec = base.0.into_inner();
|
||||||
|
new_vec.extend_from_slice(ext.0.as_ref());
|
||||||
|
let seq = SequenceSet(
|
||||||
|
NonEmptyVec::try_from(new_vec)
|
||||||
|
.expect("merging non empty vec lead to non empty vec"),
|
||||||
|
);
|
||||||
|
(seq, x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SearchKey::And(search_list) => {
|
||||||
|
tracing::debug!(
|
||||||
|
"using AND in a search request is slow: no intersection is performed"
|
||||||
|
);
|
||||||
|
search_list
|
||||||
|
.as_ref()
|
||||||
|
.iter()
|
||||||
|
.map(|crit| Self(&crit).to_sequence_set())
|
||||||
|
.min_by(|(x, _), (y, _)| {
|
||||||
|
let x_size = approx_sequence_set_size(x);
|
||||||
|
let y_size = approx_sequence_set_size(y);
|
||||||
|
x_size.cmp(&y_size)
|
||||||
|
})
|
||||||
|
.unwrap_or((sequence_set_all(), SeqType::Undefined))
|
||||||
|
}
|
||||||
|
_ => (sequence_set_all(), SeqType::Undefined),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn need_meta(&self) {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn need_body(&self) {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sequence_set_all() -> SequenceSet {
|
||||||
|
SequenceSet::from(Sequence::Range(
|
||||||
|
SeqOrUid::Value(NonZeroU32::MIN),
|
||||||
|
SeqOrUid::Asterisk,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is wrong as sequences can overlap
|
||||||
|
fn approx_sequence_set_size(seq_set: &SequenceSet) -> u64 {
|
||||||
|
seq_set.0.as_ref().iter().fold(0u64, |acc, seq| {
|
||||||
|
acc.saturating_add(approx_sequence_size(seq))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is wrong as sequence UID can have holes,
|
||||||
|
// as we don't know the number of messages in the mailbox also
|
||||||
|
fn approx_sequence_size(seq: &Sequence) -> u64 {
|
||||||
|
match seq {
|
||||||
|
Sequence::Single(_) => 1,
|
||||||
|
Sequence::Range(SeqOrUid::Asterisk, _) | Sequence::Range(_, SeqOrUid::Asterisk) => u64::MAX,
|
||||||
|
Sequence::Range(SeqOrUid::Value(x1), SeqOrUid::Value(x2)) => {
|
||||||
|
let x2 = x2.get() as i64;
|
||||||
|
let x1 = x1.get() as i64;
|
||||||
|
(x2 - x1).abs().try_into().unwrap_or(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue