summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJannis M. Hoffmann <jannis@fehcom.de>2024-12-08 16:03:30 +0100
committerJannis M. Hoffmann <jannis@fehcom.de>2024-12-08 16:03:30 +0100
commitf5b98066b6e474bbe13051ff0a56944a562fc243 (patch)
tree99d69c23dc5a204deaf745a2e979b64ebd059519
parent83ce24c22eff6c778adcdb067b5fc4e8940d808d (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.
-rw-r--r--Cargo.lock2
-rw-r--r--Cargo.toml2
-rw-r--r--src/cmd.rs23
-rw-r--r--src/cmd/add_folder.rs2
-rw-r--r--src/cmd/list.rs145
-rw-r--r--src/cmd/list_search.rs342
-rw-r--r--src/cmd/search.rs17
-rw-r--r--src/de.jmhoffmann.jwebmail.mail-storage.varlink31
-rw-r--r--src/de_jmhoffmann_jwebmail_mail-storage.rs112
-rw-r--r--src/main.rs5
-rw-r--r--src/rfc822.rs49
11 files changed, 529 insertions, 201 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 3d582b0..663fd6b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -206,7 +206,7 @@ dependencies = [
[[package]]
name = "jwebmail-extract"
-version = "0.8.1"
+version = "0.9.0"
dependencies = [
"base64",
"chrono",
diff --git a/Cargo.toml b/Cargo.toml
index 40ee19c..2c9acb7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "jwebmail-extract"
-version = "0.8.1"
+version = "0.9.0"
authors = ["Jannis M. Hoffmann <jannis@fehcom.de>"]
edition = "2021"
rust-version = "1.80"
diff --git a/src/cmd.rs b/src/cmd.rs
index 49ba70e..2f2bf47 100644
--- a/src/cmd.rs
+++ b/src/cmd.rs
@@ -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)?,
})
}