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) -> 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) -> 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, 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(v: Vec, f: F) -> Vec 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(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, 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(v: &mut Vec, r: R) where R: RangeBounds, { let mut new: Vec = v.drain(r).collect(); std::mem::swap(v, &mut new); } fn limit( entries: &mut TotalOrderMailEntries, b: Option, 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, direction: ListSearch_Args_direction, limit_num: i64, sort: Sort, search_key: Option, ) -> 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() } }