summaryrefslogtreecommitdiff
path: root/src/cmd/list_search.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/list_search.rs')
-rw-r--r--src/cmd/list_search.rs342
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()
+ }
+}