diff options
Diffstat (limited to 'src/cmd/list_search.rs')
-rw-r--r-- | src/cmd/list_search.rs | 342 |
1 files changed, 342 insertions, 0 deletions
diff --git a/src/cmd/list_search.rs b/src/cmd/list_search.rs new file mode 100644 index 0000000..999cb10 --- /dev/null +++ b/src/cmd/list_search.rs @@ -0,0 +1,342 @@ +use std::cmp::min; +use std::ops::RangeBounds; + +use crate::cmd::{open_submaildir, MailStorage}; +use crate::de_jmhoffmann_jwebmail_mailstorage::{ + Bound, Call_ListSearch, ListSearch_Args_direction, Sort, Sort_direction, Sort_parameter, +}; +use crate::rfc822::{me_to_lmh, parse_mail_addrs}; + +use chrono::DateTime; +use log::warn; +use mailparse::MailHeader; + +fn get_from_or_sender(mhs: Vec<MailHeader>) -> String { + for mh in mhs { + if mh.get_key_ref().eq_ignore_ascii_case("sender") { + let addrs = parse_mail_addrs(&mh).unwrap_or_default(); + return addrs[0].address.clone(); + } + if mh.get_key_ref().eq_ignore_ascii_case("from") { + let addrs = parse_mail_addrs(&mh).unwrap_or_default(); + if addrs.len() == 1 { + return addrs[0].address.clone(); + } + } + } + String::new() +} + +fn get_subject(mhs: Vec<MailHeader>) -> String { + for mh in mhs { + if mh.get_key_ref().eq_ignore_ascii_case("subject") { + return mh.get_value(); + } + } + String::new() +} + +fn search(entries: &mut Vec<maildir::MailEntry>, search_key: &str) { + entries.retain_mut(|m| { + if let Ok(headers_vec) = m.headers() { + for mail_header in headers_vec { + if mail_header.get_value().contains(search_key) { + return true; + } + } + false + } else { + true + } + }); +} + +enum TotalOrderMailEntries { + Date(Vec<(i64, String, maildir::MailEntry)>), + Size(Vec<(u64, String, maildir::MailEntry)>), + Sender(Vec<(String, String, maildir::MailEntry)>), + Subject(Vec<(String, String, maildir::MailEntry)>), +} + +fn transform_vec<T, U, F>(v: Vec<T>, f: F) -> Vec<U> +where + F: Fn(T) -> U, +{ + let mut res = Vec::with_capacity(v.len()); + for t in v { + res.push(f(t)); + } + res +} + +fn sort_by_keys<A, B, C>(s: &mut [(A, B, C)], reverse: bool) +where + A: PartialOrd, + B: Ord, +{ + s.sort_unstable_by(|x, y| { + let res = match x.0.partial_cmp(&y.0) { + None | Some(std::cmp::Ordering::Equal) => x.1.cmp(&y.1), + Some(ord) => ord, + }; + if reverse { + res.reverse() + } else { + res + } + }); +} + +fn sort_by(entries: Vec<maildir::MailEntry>, sortby: Sort) -> TotalOrderMailEntries { + match sortby.parameter { + Sort_parameter::date => { + let mut res = transform_vec(entries, |mut a| { + (a.received().unwrap_or_default(), a.id().to_owned(), a) + }); + sort_by_keys(&mut res, sortby.direction == Sort_direction::desc); + TotalOrderMailEntries::Date(res) + } + Sort_parameter::size => { + let mut res = transform_vec(entries, |a| { + ( + a.path().metadata().map_or(0, |m| m.len()), + a.id().to_owned(), + a, + ) + }); + sort_by_keys(&mut res, sortby.direction == Sort_direction::desc); + TotalOrderMailEntries::Size(res) + } + Sort_parameter::sender => { + let mut res = transform_vec(entries, |mut a| { + ( + get_from_or_sender(a.headers().unwrap_or_default()), + a.id().to_owned(), + a, + ) + }); + sort_by_keys(&mut res, sortby.direction == Sort_direction::desc); + TotalOrderMailEntries::Sender(res) + } + Sort_parameter::subject => { + let mut res = transform_vec(entries, |mut a| { + ( + get_subject(a.headers().unwrap_or_default()), + a.id().to_owned(), + a, + ) + }); + sort_by_keys(&mut res, sortby.direction == Sort_direction::desc); + TotalOrderMailEntries::Subject(res) + } + } +} + +fn shrink_to_range<T, R>(v: &mut Vec<T>, r: R) +where + R: RangeBounds<usize>, +{ + let mut new: Vec<T> = v.drain(r).collect(); + std::mem::swap(v, &mut new); +} + +fn limit( + entries: &mut TotalOrderMailEntries, + b: Option<Bound>, + direction: ListSearch_Args_direction, + sort_direction: Sort_direction, + limit: usize, +) -> (bool, bool) { + if let Some(bound) = b { + match entries { + TotalOrderMailEntries::Date(v) => { + let start_date = DateTime::parse_from_rfc3339(&bound.param) + .unwrap() + .timestamp(); + let r = v.binary_search_by(|a| match sort_direction { + Sort_direction::asc => (a.0, &a.1).cmp(&(start_date, &bound.id)), + Sort_direction::desc => (start_date, &bound.id).cmp(&(a.0, &a.1)), + }); + let len = v.len(); + match (r, direction) { + (Ok(i), ListSearch_Args_direction::after) => { + shrink_to_range(v, i + 1..min(i + 1 + limit, len)); + (false, i + 1 + limit >= len) + } + (Err(i), ListSearch_Args_direction::after) => { + shrink_to_range(v, i..min(i + limit, len)); + (i == 0, i + limit >= len) + } + (Ok(i), ListSearch_Args_direction::before) => { + shrink_to_range(v, i.saturating_sub(limit)..i); + (i.saturating_sub(limit) == 0, false) + } + (Err(i), ListSearch_Args_direction::before) => { + shrink_to_range(v, i.saturating_sub(limit)..i); + (i.saturating_sub(limit) == 0, i == len) + } + } + } + TotalOrderMailEntries::Size(v) => { + let start_size = bound + .param + .parse() + .expect("start mark param must be an integer when sorting by size"); + let r = v.binary_search_by(|a| match sort_direction { + Sort_direction::asc => (a.0, &a.1).cmp(&(start_size, &bound.id)), + Sort_direction::desc => (start_size, &bound.id).cmp(&(a.0, &a.1)), + }); + let len = v.len(); + match (r, direction) { + (Ok(i), ListSearch_Args_direction::after) => { + shrink_to_range(v, i + 1..min(i + 1 + limit, len)); + (false, i + 1 + limit >= len) + } + (Err(i), ListSearch_Args_direction::after) => { + shrink_to_range(v, i..min(i + limit, len)); + (i == 0, i + limit >= len) + } + (Ok(i), ListSearch_Args_direction::before) => { + shrink_to_range(v, i.saturating_sub(limit)..i); + (i.saturating_sub(limit) == 0, false) + } + (Err(i), ListSearch_Args_direction::before) => { + shrink_to_range(v, i.saturating_sub(limit)..i); + (i.saturating_sub(limit) == 0, i == len) + } + } + } + TotalOrderMailEntries::Sender(v) => { + let r = v.binary_search_by(|a| match sort_direction { + Sort_direction::asc => (&a.0, &a.1).cmp(&(&bound.param, &bound.id)), + Sort_direction::desc => (&bound.param, &bound.id).cmp(&(&a.0, &a.1)), + }); + let len = v.len(); + match (r, direction) { + (Ok(i), ListSearch_Args_direction::after) => { + shrink_to_range(v, i + 1..min(i + 1 + limit, len)); + (false, i + 1 + limit >= len) + } + (Err(i), ListSearch_Args_direction::after) => { + shrink_to_range(v, i..min(i + limit, len)); + (i == 0, i + limit >= len) + } + (Ok(i), ListSearch_Args_direction::before) => { + shrink_to_range(v, i.saturating_sub(limit)..i); + (i.saturating_sub(limit) == 0, false) + } + (Err(i), ListSearch_Args_direction::before) => { + shrink_to_range(v, i.saturating_sub(limit)..i); + (i.saturating_sub(limit) == 0, i == len) + } + } + } + TotalOrderMailEntries::Subject(v) => { + let r = v.binary_search_by(|a| match sort_direction { + Sort_direction::asc => (&a.0, &a.1).cmp(&(&bound.param, &bound.id)), + Sort_direction::desc => (&bound.param, &bound.id).cmp(&(&a.0, &a.1)), + }); + let len = v.len(); + match (r, direction) { + (Ok(i), ListSearch_Args_direction::after) => { + shrink_to_range(v, i + 1..min(i + 1 + limit, len)); + (false, i + 1 + limit >= len) + } + (Err(i), ListSearch_Args_direction::after) => { + shrink_to_range(v, i..min(i + limit, len)); + (i == 0, i + limit >= len) + } + (Ok(i), ListSearch_Args_direction::before) => { + shrink_to_range(v, i.saturating_sub(limit)..i); + (i.saturating_sub(limit) == 0, false) + } + (Err(i), ListSearch_Args_direction::before) => { + shrink_to_range(v, i.saturating_sub(limit)..i); + (i.saturating_sub(limit) == 0, i == len) + } + } + } + } + } else { + assert!(direction == ListSearch_Args_direction::after); + let len = match entries { + TotalOrderMailEntries::Date(v) => { + let l = v.len(); + v.truncate(limit); + l + } + TotalOrderMailEntries::Size(v) => { + let l = v.len(); + v.truncate(limit); + l + } + TotalOrderMailEntries::Sender(v) => { + let l = v.len(); + v.truncate(limit); + l + } + TotalOrderMailEntries::Subject(v) => { + let l = v.len(); + v.truncate(limit); + l + } + }; + (true, limit >= len) + } +} + +pub fn list_search( + ms: &MailStorage, + call: &mut dyn Call_ListSearch, + folder: String, + bound: Option<Bound>, + direction: ListSearch_Args_direction, + limit_num: i64, + sort: Sort, + search_key: Option<String>, +) -> varlink::Result<()> { + if let Some(path) = ms.maildir_path.read().unwrap().clone() { + let md = open_submaildir(path, &folder); + + for r in md.list_new() { + match r { + Err(e) => warn!("{}", e), + Ok(me) => { + if let Err(e) = md.move_new_to_cur(me.id()) { + warn!("{}", e); + } + } + }; + } + + let mut a: Vec<_> = md.list_cur().filter_map(std::result::Result::ok).collect(); + if let Some(search_key) = search_key { + search(&mut a, &search_key); + } + let mut b = sort_by(a, sort.clone()); + let (first, last) = limit(&mut b, bound, direction, sort.direction, limit_num as usize); + + let mail_heads = match b { + TotalOrderMailEntries::Date(mut v) => v + .drain(..) + .filter_map(|me| me_to_lmh(me.2).map_err(|e| warn!("{}", e)).ok()) + .collect(), + TotalOrderMailEntries::Size(mut v) => v + .drain(..) + .filter_map(|me| me_to_lmh(me.2).map_err(|e| warn!("{}", e)).ok()) + .collect(), + TotalOrderMailEntries::Sender(mut v) => v + .drain(..) + .filter_map(|me| me_to_lmh(me.2).map_err(|e| warn!("{}", e)).ok()) + .collect(), + TotalOrderMailEntries::Subject(mut v) => v + .drain(..) + .filter_map(|me| me_to_lmh(me.2).map_err(|e| warn!("{}", e)).ok()) + .collect(), + }; + + call.reply(mail_heads, first, last) + } else { + call.reply_not_initialized() + } +} |