diff options
author | Jannis M. Hoffmann <jannis@fehcom.de> | 2024-12-08 16:03:30 +0100 |
---|---|---|
committer | Jannis M. Hoffmann <jannis@fehcom.de> | 2024-12-08 16:03:30 +0100 |
commit | f5b98066b6e474bbe13051ff0a56944a562fc243 (patch) | |
tree | 99d69c23dc5a204deaf745a2e979b64ebd059519 /src | |
parent | 83ce24c22eff6c778adcdb067b5fc4e8940d808d (diff) |
update for mail-storage version 1.1
This adds the new method ListSearch and deprecates the methods List and
Search. This also redoes pagination which is now keyset based instead of
offset based.
https://blog.sequinstream.com/keyset-cursors-not-offsets-for-postgres-pagination/
This version also adds an optimization so that reading only the header
is required when only header data is required.
Diffstat (limited to 'src')
-rw-r--r-- | src/cmd.rs | 23 | ||||
-rw-r--r-- | src/cmd/add_folder.rs | 2 | ||||
-rw-r--r-- | src/cmd/list.rs | 145 | ||||
-rw-r--r-- | src/cmd/list_search.rs | 342 | ||||
-rw-r--r-- | src/cmd/search.rs | 17 | ||||
-rw-r--r-- | src/de.jmhoffmann.jwebmail.mail-storage.varlink | 31 | ||||
-rw-r--r-- | src/de_jmhoffmann_jwebmail_mail-storage.rs | 112 | ||||
-rw-r--r-- | src/main.rs | 5 | ||||
-rw-r--r-- | src/rfc822.rs | 49 |
9 files changed, 527 insertions, 199 deletions
@@ -7,22 +7,20 @@ use maildir::Maildir; mod add_folder; mod folders; mod init; -mod list; +mod list_search; mod r#move; mod raw; mod remove; -mod search; mod show; mod stats; use add_folder::add_folder; use folders::folders; use init::init; -use list::list; +use list_search::list_search; use r#move::r#move; use raw::raw; use remove::remove; -use search::search; use show::show; use stats::stats; @@ -75,7 +73,20 @@ impl VarlinkInterface for MailStorage { end: i64, sort: Sort, ) -> varlink::Result<()> { - list(self, call, folder, start, end, sort) + todo!() + } + + fn list_search( + &self, + call: &mut dyn Call_ListSearch, + folder: String, + bound: Option<Bound>, + direction: ListSearch_Args_direction, + limit: i64, + sort: Sort, + search: Option<String>, + ) -> varlink::Result<()> { + list_search(self, call, folder, bound, direction, limit, sort, search) } fn r#move( @@ -113,7 +124,7 @@ impl VarlinkInterface for MailStorage { folder: String, pattern: String, ) -> varlink::Result<()> { - search(self, call, folder, pattern) + todo!() } fn show(&self, call: &mut dyn Call_Show, folder: String, mid: String) -> varlink::Result<()> { diff --git a/src/cmd/add_folder.rs b/src/cmd/add_folder.rs index 07ad455..0d640af 100644 --- a/src/cmd/add_folder.rs +++ b/src/cmd/add_folder.rs @@ -1,7 +1,5 @@ use std::fs::create_dir; -use varlink; - use crate::cmd::MailStorage; use crate::de_jmhoffmann_jwebmail_mailstorage::{AddFolder_Reply_status, Call_AddFolder}; diff --git a/src/cmd/list.rs b/src/cmd/list.rs deleted file mode 100644 index 8aeaf97..0000000 --- a/src/cmd/list.rs +++ /dev/null @@ -1,145 +0,0 @@ -use std::cmp::Reverse; - -use log::warn; - -use crate::cmd::{open_submaildir, MailStorage}; -use crate::de_jmhoffmann_jwebmail_mailstorage::{ - Call_List, ListMailHeader, MailHeader, Sort, Sort_direction, Sort_parameter, -}; -use crate::rfc822::me_to_lmh; - -fn from_or_sender(mh: &MailHeader) -> &str { - if mh.written_from.is_empty() { - warn!("mail without from"); - panic!() - } - if mh.written_from.len() == 1 { - &mh.written_from[0].address - } else { - &mh.sender.as_ref().unwrap().address - } -} - -fn mid_to_rec_time(mid: &str) -> f64 { - let Some(dec) = mid.find('.') else { - warn!("Invaild mail-id {}", mid); - return 0.0; - }; - let Some(sep) = mid[dec + 1..].find('.') else { - return 0.0; - }; - mid[..dec + 1 + sep] - .parse() - .or_else(|_| mid[..dec].parse()) - .unwrap_or(0.0) -} - -fn sort_by_and_take( - mut entries: Vec<maildir::MailEntry>, - sortby: Sort, - s: usize, - e: usize, -) -> Vec<ListMailHeader> { - match sortby.parameter { - Sort_parameter::date => match sortby.direction { - Sort_direction::asc => { - entries.sort_by(|a, b| { - mid_to_rec_time(a.id()) - .partial_cmp(&mid_to_rec_time(b.id())) - .unwrap() - }); - entries - .drain(s..e) - .filter_map(|me| me_to_lmh(me).map_err(|e| warn!("{}", e)).ok()) - .collect() - } - Sort_direction::desc => { - entries.sort_by(|b, a| { - mid_to_rec_time(a.id()) - .partial_cmp(&mid_to_rec_time(b.id())) - .unwrap() - }); - entries - .drain(s..e) - .filter_map(|me| me_to_lmh(me).map_err(|e| warn!("{}", e)).ok()) - .collect() - } - }, - Sort_parameter::size => match sortby.direction { - Sort_direction::asc => { - entries.sort_by_cached_key(|a| a.path().metadata().map_or(0, |m| m.len())); - entries - .drain(s..e) - .filter_map(|me| me_to_lmh(me).map_err(|e| warn!("{}", e)).ok()) - .collect() - } - Sort_direction::desc => { - entries.sort_by_cached_key(|a| Reverse(a.path().metadata().map_or(0, |m| m.len()))); - entries - .drain(s..e) - .filter_map(|me| me_to_lmh(me).map_err(|e| warn!("{}", e)).ok()) - .collect() - } - }, - Sort_parameter::subject => { - let mut x: Vec<ListMailHeader> = entries - .drain(..) - .filter_map(|me| me_to_lmh(me).map_err(|e| warn!("{}", e)).ok()) - .collect(); - match sortby.direction { - Sort_direction::asc => x.sort_by(|a, b| a.header.subject.cmp(&b.header.subject)), - Sort_direction::desc => x.sort_by(|b, a| a.header.subject.cmp(&b.header.subject)), - } - x.drain(s..e).collect() - } - Sort_parameter::sender => { - let mut x: Vec<ListMailHeader> = entries - .drain(..) - .filter_map(|me| me_to_lmh(me).map_err(|e| warn!("{}", e)).ok()) - .collect(); - match sortby.direction { - Sort_direction::asc => { - x.sort_by(|a, b| from_or_sender(&a.header).cmp(from_or_sender(&b.header))) - } - Sort_direction::desc => { - x.sort_by(|b, a| from_or_sender(&a.header).cmp(from_or_sender(&b.header))) - } - } - x.drain(s..e).collect() - } - } -} - -pub fn list( - ms: &MailStorage, - call: &mut dyn Call_List, - folder: String, - start: i64, - end: i64, - sort: Sort, -) -> 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 a: Vec<_> = md.list_cur().filter_map(std::result::Result::ok).collect(); - let start = std::cmp::min(a.len(), start as usize); - let end = std::cmp::min(a.len(), end as usize); - - let mail_heads = sort_by_and_take(a, sort, start, end); - - call.reply(mail_heads) - } else { - call.reply_not_initialized() - } -} 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() + } +} diff --git a/src/cmd/search.rs b/src/cmd/search.rs deleted file mode 100644 index 00cc192..0000000 --- a/src/cmd/search.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::cmd::{open_submaildir, MailStorage}; -use crate::de_jmhoffmann_jwebmail_mailstorage::Call_Search; - -pub fn search( - ms: &MailStorage, - call: &mut dyn Call_Search, - folder: String, - _pattern: String, -) -> varlink::Result<()> { - if let Some(p) = ms.maildir_path.read().unwrap().clone() { - let md = open_submaildir(p, &folder); - - todo!(); - } else { - call.reply_not_initialized() - } -} diff --git a/src/de.jmhoffmann.jwebmail.mail-storage.varlink b/src/de.jmhoffmann.jwebmail.mail-storage.varlink index f61f624..206c3df 100644 --- a/src/de.jmhoffmann.jwebmail.mail-storage.varlink +++ b/src/de.jmhoffmann.jwebmail.mail-storage.varlink @@ -60,22 +60,51 @@ type MIMEPart ( type Sort ( direction: (asc, desc), - parameter: (date, size, sender) + parameter: (date, size, sender, subject) +) + +type Bound ( + param: string, + id: string ) method Init(unix_user: string, mailbox_path: string) -> () + +# deprecated: use ListSearch instead method List(folder: string, start: int, end: int, sort: Sort) -> (mail_heads: []ListMailHeader) + method Stats(folder: string) -> (mail_count: int, unread_count: int, byte_size: int) + method Show(folder: string, mid: string) -> (mail: Mail) + # body is base64 encoded method Raw(folder: string, mid: string, path: ?string) -> (header: MIMEHeader, body: string) + +# deprecated: use ListSearch instead method Search(folder: string, pattern: string) -> (found: []ListMailHeader) + method Folders() -> (folders: []string) + method Move(mid: string, from_folder: string, to_folder: string) -> () + method Remove(folder: string, mid: string) -> () + method AddFolder(name: string) -> (status: (created, skiped)) +method ListSearch( + folder: string, + bound: ?Bound, + direction: (after, before), + limit: int, + sort: Sort, + search: ?string +) -> ( + mail_heads: []ListMailHeader, + first: bool, + last: bool +) + error NotInitialized() error InvalidFolder(folder: string) diff --git a/src/de_jmhoffmann_jwebmail_mail-storage.rs b/src/de_jmhoffmann_jwebmail_mail-storage.rs index bf3b148..4c0486d 100644 --- a/src/de_jmhoffmann_jwebmail_mail-storage.rs +++ b/src/de_jmhoffmann_jwebmail_mail-storage.rs @@ -314,6 +314,11 @@ pub trait VarlinkCallError: varlink::CallTrait { } impl<'a> VarlinkCallError for varlink::Call<'a> {} #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct r#Bound { + pub r#param: String, + pub r#id: String, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub struct r#ListMailHeader { pub r#byte_size: i64, pub r#unread: bool, @@ -493,6 +498,47 @@ pub trait Call_List: VarlinkCallError { } impl<'a> Call_List for varlink::Call<'a> {} #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub enum r#ListSearch_Args_direction { + r#after, + r#before, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct ListSearch_Reply { + pub r#mail_heads: Vec<ListMailHeader>, + pub r#first: bool, + pub r#last: bool, +} +impl varlink::VarlinkReply for ListSearch_Reply {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct ListSearch_Args { + pub r#folder: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub r#bound: Option<Bound>, + pub r#direction: ListSearch_Args_direction, + pub r#limit: i64, + pub r#sort: Sort, + #[serde(skip_serializing_if = "Option::is_none")] + pub r#search: Option<String>, +} +pub trait Call_ListSearch: VarlinkCallError { + fn reply( + &mut self, + r#mail_heads: Vec<ListMailHeader>, + r#first: bool, + r#last: bool, + ) -> varlink::Result<()> { + self.reply_struct( + ListSearch_Reply { + r#mail_heads, + r#first, + r#last, + } + .into(), + ) + } +} +impl<'a> Call_ListSearch for varlink::Call<'a> {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub struct Move_Reply {} impl varlink::VarlinkReply for Move_Reply {} #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] @@ -618,6 +664,16 @@ pub trait VarlinkInterface { r#end: i64, r#sort: Sort, ) -> varlink::Result<()>; + fn list_search( + &self, + call: &mut dyn Call_ListSearch, + r#folder: String, + r#bound: Option<Bound>, + r#direction: ListSearch_Args_direction, + r#limit: i64, + r#sort: Sort, + r#search: Option<String>, + ) -> varlink::Result<()>; fn r#move( &self, call: &mut dyn Call_Move, @@ -677,6 +733,15 @@ pub trait VarlinkClientInterface { r#end: i64, r#sort: Sort, ) -> varlink::MethodCall<List_Args, List_Reply, Error>; + fn list_search( + &mut self, + r#folder: String, + r#bound: Option<Bound>, + r#direction: ListSearch_Args_direction, + r#limit: i64, + r#sort: Sort, + r#search: Option<String>, + ) -> varlink::MethodCall<ListSearch_Args, ListSearch_Reply, Error>; fn r#move( &mut self, r#mid: String, @@ -766,6 +831,28 @@ impl VarlinkClientInterface for VarlinkClient { }, ) } + fn list_search( + &mut self, + r#folder: String, + r#bound: Option<Bound>, + r#direction: ListSearch_Args_direction, + r#limit: i64, + r#sort: Sort, + r#search: Option<String>, + ) -> varlink::MethodCall<ListSearch_Args, ListSearch_Reply, Error> { + varlink::MethodCall::<ListSearch_Args, ListSearch_Reply, Error>::new( + self.connection.clone(), + "de.jmhoffmann.jwebmail.mail-storage.ListSearch", + ListSearch_Args { + r#folder, + r#bound, + r#direction, + r#limit, + r#sort, + r#search, + }, + ) + } fn r#move( &mut self, r#mid: String, @@ -852,7 +939,7 @@ pub fn new(inner: Box<dyn VarlinkInterface + Send + Sync>) -> VarlinkInterfacePr } impl varlink::Interface for VarlinkInterfaceProxy { fn get_description(&self) -> &'static str { - "interface de.jmhoffmann.jwebmail.mail-storage\n\n\ntype MIMEHeader (\n mime_type: (main_type: string, sub_type: string),\n content_dispo: (none, inline, attachment),\n file_name: ?string\n)\n\ntype MailAddr (\n name: ?string,\n address: string\n)\n\n# send_date is of ISO 8601 date-time format\ntype MailHeader (\n send_date: string,\n written_from: []MailAddr,\n sender: ?MailAddr,\n reply_to: []MailAddr,\n send_to: []MailAddr,\n cc: []MailAddr,\n bcc: []MailAddr,\n subject: string,\n comments: []string,\n keywords: []string,\n mime: MIMEHeader\n)\n\ntype ListMailHeader (\n byte_size: int,\n unread: bool,\n rec_date: string,\n mid: string,\n header: MailHeader\n)\n\ntype Multipart (\n preamble: ?string,\n parts: []MIMEPart,\n epilogue: ?string\n)\n\n# exactly one of these fileds must be present\ntype MailBody (\n discrete: ?string,\n multipart: ?Multipart,\n mail: ?Mail\n)\n\ntype Mail (\n head: MailHeader,\n body: MailBody\n)\n\ntype MIMEPart (\n mime_header: MIMEHeader,\n body: MailBody\n)\n\ntype Sort (\n direction: (asc, desc),\n parameter: (date, size, sender)\n)\n\n\nmethod Init(unix_user: string, mailbox_path: string) -> ()\nmethod List(folder: string, start: int, end: int, sort: Sort) -> (mail_heads: []ListMailHeader)\nmethod Stats(folder: string) -> (mail_count: int, unread_count: int, byte_size: int)\nmethod Show(folder: string, mid: string) -> (mail: Mail)\n# body is base64 encoded\nmethod Raw(folder: string, mid: string, path: ?string) -> (header: MIMEHeader, body: string)\nmethod Search(folder: string, pattern: string) -> (found: []ListMailHeader)\nmethod Folders() -> (folders: []string)\nmethod Move(mid: string, from_folder: string, to_folder: string) -> ()\nmethod Remove(folder: string, mid: string) -> ()\nmethod AddFolder(name: string) -> (status: (created, skiped))\n\n\nerror NotInitialized()\nerror InvalidFolder(folder: string)\nerror InvalidMID(folder: string, mid: string)\nerror InvalidPathInMail(folder: string, mid: string, path: string)\nerror InvalidSearchPattern(pattern: string)\nerror InvalidUser(unix_user: string)\nerror InvalidMailbox(path: string, not_a_mailbox: bool, user_mismatch: bool)\n" + "interface de.jmhoffmann.jwebmail.mail-storage\n\n\ntype MIMEHeader (\n mime_type: (main_type: string, sub_type: string),\n content_dispo: (none, inline, attachment),\n file_name: ?string\n)\n\ntype MailAddr (\n name: ?string,\n address: string\n)\n\n# send_date is of ISO 8601 date-time format\ntype MailHeader (\n send_date: string,\n written_from: []MailAddr,\n sender: ?MailAddr,\n reply_to: []MailAddr,\n send_to: []MailAddr,\n cc: []MailAddr,\n bcc: []MailAddr,\n subject: string,\n comments: []string,\n keywords: []string,\n mime: MIMEHeader\n)\n\ntype ListMailHeader (\n byte_size: int,\n unread: bool,\n rec_date: string,\n mid: string,\n header: MailHeader\n)\n\ntype Multipart (\n preamble: ?string,\n parts: []MIMEPart,\n epilogue: ?string\n)\n\n# exactly one of these fileds must be present\ntype MailBody (\n discrete: ?string,\n multipart: ?Multipart,\n mail: ?Mail\n)\n\ntype Mail (\n head: MailHeader,\n body: MailBody\n)\n\ntype MIMEPart (\n mime_header: MIMEHeader,\n body: MailBody\n)\n\ntype Sort (\n direction: (asc, desc),\n parameter: (date, size, sender, subject)\n)\n\ntype Bound (\n param: string,\n id: string\n)\n\n\nmethod Init(unix_user: string, mailbox_path: string) -> ()\n\n# deprecated: use ListSearch instead\nmethod List(folder: string, start: int, end: int, sort: Sort) -> (mail_heads: []ListMailHeader)\n\nmethod Stats(folder: string) -> (mail_count: int, unread_count: int, byte_size: int)\n\nmethod Show(folder: string, mid: string) -> (mail: Mail)\n\n# body is base64 encoded\nmethod Raw(folder: string, mid: string, path: ?string) -> (header: MIMEHeader, body: string)\n\n# deprecated: use ListSearch instead\nmethod Search(folder: string, pattern: string) -> (found: []ListMailHeader)\n\nmethod Folders() -> (folders: []string)\n\nmethod Move(mid: string, from_folder: string, to_folder: string) -> ()\n\nmethod Remove(folder: string, mid: string) -> ()\n\nmethod AddFolder(name: string) -> (status: (created, skiped))\n\nmethod ListSearch(\n folder: string,\n bound: ?Bound,\n direction: (after, before),\n limit: int,\n sort: Sort,\n search: ?string\n) -> (\n mail_heads: []ListMailHeader,\n first: bool,\n last: bool\n)\n\n\nerror NotInitialized()\nerror InvalidFolder(folder: string)\nerror InvalidMID(folder: string, mid: string)\nerror InvalidPathInMail(folder: string, mid: string, path: string)\nerror InvalidSearchPattern(pattern: string)\nerror InvalidUser(unix_user: string)\nerror InvalidMailbox(path: string, not_a_mailbox: bool, user_mismatch: bool)\n" } fn get_name(&self) -> &'static str { "de.jmhoffmann.jwebmail.mail-storage" @@ -926,6 +1013,29 @@ impl varlink::Interface for VarlinkInterfaceProxy { call.reply_invalid_parameter("parameters".into()) } } + "de.jmhoffmann.jwebmail.mail-storage.ListSearch" => { + if let Some(args) = req.parameters.clone() { + let args: ListSearch_Args = match serde_json::from_value(args) { + Ok(v) => v, + Err(e) => { + let es = format!("{}", e); + let _ = call.reply_invalid_parameter(es.clone()); + return Err(varlink::context!(varlink::ErrorKind::SerdeJsonDe(es))); + } + }; + self.inner.list_search( + call as &mut dyn Call_ListSearch, + args.r#folder, + args.r#bound, + args.r#direction, + args.r#limit, + args.r#sort, + args.r#search, + ) + } else { + call.reply_invalid_parameter("parameters".into()) + } + } "de.jmhoffmann.jwebmail.mail-storage.Move" => { if let Some(args) = req.parameters.clone() { let args: Move_Args = match serde_json::from_value(args) { diff --git a/src/main.rs b/src/main.rs index 5ab99e9..a8dfcc9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,7 +28,7 @@ fn main() { let service = VarlinkService::new( "de.jmhoffmann.jwebmail.mail-storage.varlink", "jwebmails storage service", - "1.0.1", + "1.1.0", "https://fehcom.de/cgit/jwebmail2/", vec![Box::new(myinterface)], ); @@ -37,7 +37,6 @@ fn main() { let lfds = u8::from_str(&listen_fds).expect("env variable `LISTEN_FDS` must be an integer"); if lfds < 1 { log::error!("No file descriptor to receive commands from!"); - return; } else if lfds == 1 { let uds = unsafe { UnixStream::from_raw_fd(3) }; accept_con(&service, uds); @@ -69,7 +68,7 @@ fn accept_con(service: &VarlinkService, mut uds: UnixStream) { iface = i; match br.fill_buf() { Err(_) => break, - Ok(buf) if buf.is_empty() => break, + Ok([]) => break, _ => {} } } diff --git a/src/rfc822.rs b/src/rfc822.rs index a8fa5c3..b8bad6a 100644 --- a/src/rfc822.rs +++ b/src/rfc822.rs @@ -7,7 +7,7 @@ use mailparse::{ use crate::de_jmhoffmann_jwebmail_mailstorage::MIMEHeader_content_dispo as CD; use crate::de_jmhoffmann_jwebmail_mailstorage::*; -fn parse_mail_addrs( +pub fn parse_mail_addrs( inp: &mailparse::MailHeader, ) -> std::result::Result<Vec<MailAddr>, MailParseError> { let mut mal = addrparse_header(inp)?; @@ -36,25 +36,17 @@ fn parse_mail_addrs( // ---------------- -fn get_received(me: &mut maildir::MailEntry) -> i64 { - me.received().unwrap_or_else(|_| { - let mut id = me.id(); - id = &id[..id.find('.').unwrap()]; - id.parse().unwrap_or_default() - }) -} - pub fn me_to_lmh( mut me: maildir::MailEntry, ) -> std::result::Result<ListMailHeader, MailEntryError> { let lmh = ListMailHeader { byte_size: me.path().metadata()?.len() as i64, unread: !me.is_seen(), - rec_date: DateTime::from_timestamp(get_received(&mut me), 0) + rec_date: DateTime::from_timestamp(me.received().unwrap_or_default(), 0) .unwrap() .to_rfc3339(), mid: me.id().to_owned(), - header: parse_mail_header(&me.parsed()?)?, + header: parse_mail_header(&me.headers()?)?, }; Ok(lmh) @@ -104,18 +96,11 @@ pub fn parse_mail_content(v: &ParsedMail) -> MIMEHeader { } } -fn parse_mail_header(pm: &ParsedMail) -> std::result::Result<MailHeader, MailParseError> { - let v = &pm.headers; - - let mut val = pm.ctype.mimetype.clone(); - if let Some(i) = val.find(';') { - val.truncate(i); - } - let j = val.find('/').unwrap(); - let sub_type = val.split_off(j + 1); - val.pop(); - let main_type = val; - +fn parse_mail_header( + v: &Vec<mailparse::MailHeader>, +) -> std::result::Result<MailHeader, MailParseError> { + let mut sub_type = String::new(); + let mut main_type = String::new(); let mut file_name = None; let mut content_dispo = CD::none; let mut cc = vec![]; @@ -138,6 +123,15 @@ fn parse_mail_header(pm: &ParsedMail) -> std::result::Result<MailHeader, MailPar key.make_ascii_lowercase(); match key.as_str() { + "content-type" => { + if let Some(i) = val.find(';') { + val.truncate(i); + } + let j = val.find('/').unwrap(); + sub_type = val.split_off(j + 1); + val.pop(); + main_type = val; + } "date" => { send_date = DateTime::from_timestamp(dateparse(&val)?, 0) .unwrap() @@ -187,6 +181,13 @@ fn parse_mail_header(pm: &ParsedMail) -> std::result::Result<MailHeader, MailPar key.clear(); } + + // this is not correct for multipart/digest + if main_type.is_empty() || sub_type.is_empty() { + main_type = "text".to_string(); + sub_type = "plain".to_string(); + } + Ok(MailHeader { written_from, sender, @@ -362,7 +363,7 @@ fn strip_comments(s: &mut String) { pub fn parsed_mail_to_mail(pm: ParsedMail) -> std::result::Result<Mail, MailParseError> { Ok(Mail { - head: parse_mail_header(&pm)?, + head: parse_mail_header(&pm.headers)?, body: parse_mail_body(&pm)?, }) } |