diff --git a/melib/src/backends/jmap.rs b/melib/src/backends/jmap.rs index 890e09b77..0e4603a5f 100644 --- a/melib/src/backends/jmap.rs +++ b/melib/src/backends/jmap.rs @@ -190,12 +190,10 @@ impl MailBackend for JmapType { let handle = { let tx = w.tx(); let closure = move |_work_context| { - tx.send(AsyncStatus::Payload( - protocol::get_message_list(&connection, &folders.read().unwrap()[&folder_hash]) - .and_then(|ids| { - protocol::get_message(&connection, std::dbg!(&ids).as_slice()) - }), - )) + tx.send(AsyncStatus::Payload(protocol::get( + &connection, + &folders.read().unwrap()[&folder_hash], + ))) .unwrap(); tx.send(AsyncStatus::Finished).unwrap(); }; diff --git a/melib/src/backends/jmap/objects/email.rs b/melib/src/backends/jmap/objects/email.rs index c97d98418..06e317bdb 100644 --- a/melib/src/backends/jmap/objects/email.rs +++ b/melib/src/backends/jmap/objects/email.rs @@ -399,7 +399,6 @@ impl EmailGet { } } - _impl!(get_call: Get); _impl!(body_properties: Vec); _impl!(fetch_text_body_values: bool); _impl!(fetch_html_body_values: bool); @@ -410,8 +409,8 @@ impl EmailGet { #[derive(Serialize, Deserialize, Default, Debug)] #[serde(rename_all = "camelCase")] pub struct EmailFilterCondition { - #[serde(skip_serializing_if = "Vec::is_empty")] - pub in_mailboxes: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub in_mailbox: Option, #[serde(skip_serializing_if = "Vec::is_empty")] pub in_mailbox_other_than: Vec, #[serde(skip_serializing_if = "String::is_empty")] @@ -455,7 +454,11 @@ pub struct EmailFilterCondition { } impl EmailFilterCondition { - _impl!(in_mailboxes: Vec); + pub fn new() -> Self { + Self::default() + } + + _impl!(in_mailbox: Option); _impl!(in_mailbox_other_than: Vec); _impl!(before: UtcDate); _impl!(after: UtcDate); diff --git a/melib/src/backends/jmap/objects/mailbox.rs b/melib/src/backends/jmap/objects/mailbox.rs index f3d828cfe..1c41e392b 100644 --- a/melib/src/backends/jmap/objects/mailbox.rs +++ b/melib/src/backends/jmap/objects/mailbox.rs @@ -60,7 +60,12 @@ pub struct MailboxGet { #[serde(flatten)] pub get_call: Get, } +impl MailboxGet { + pub fn new(get_call: Get) -> Self { + MailboxGet { get_call } + } +} impl Method for MailboxGet { - const NAME: &'static str = "Mailbox/query"; + const NAME: &'static str = "Mailbox/get"; } diff --git a/melib/src/backends/jmap/protocol.rs b/melib/src/backends/jmap/protocol.rs index d205c5359..d027d5658 100644 --- a/melib/src/backends/jmap/protocol.rs +++ b/melib/src/backends/jmap/protocol.rs @@ -80,7 +80,7 @@ impl Request { } } - pub fn add_call, O: Object>(&mut self, call: M) -> usize { + pub fn add_call, O: Object>(&mut self, call: &M) -> usize { let seq = get_request_no!(self.request_no); self.method_calls .push(serde_json::to_value((M::NAME, call, &format!("m{}", seq))).unwrap()); @@ -176,10 +176,7 @@ pub struct JsonResponse<'a> { pub fn get_message_list(conn: &JmapConnection, folder: &JmapFolder) -> Result> { let seq = get_request_no!(conn.request_no); let email_call: EmailQueryCall = EmailQueryCall { - filter: EmailFilterCondition { - in_mailboxes: vec![folder.id.clone()], - ..Default::default() - }, + filter: EmailFilterCondition::new().in_mailbox(Some(folder.id.clone())), collapse_threads: false, position: 0, fetch_threads: true, @@ -201,7 +198,7 @@ pub fn get_message_list(conn: &JmapConnection, folder: &JmapFolder) -> Result Result>())) + .ids(Some(JmapArgument::value( + ids.iter().cloned().collect::>(), + ))) .account_id(conn.account_id.lock().unwrap().clone()), ); let mut req = Request::new(conn.request_no.clone()); - req.add_call(email_call); + req.add_call(&email_call); let res = conn .client .lock() @@ -330,3 +329,60 @@ pub fn get_message(conn: &JmapConnection, ids: &[String]) -> Result Result> { + let email_query_call: EmailQueryCall = EmailQueryCall { + filter: EmailFilterCondition::new().in_mailbox(Some(folder.id.clone())), + collapse_threads: false, + position: 0, + fetch_threads: true, + fetch_messages: true, + fetch_message_properties: vec![ + MessageProperty::ThreadId, + MessageProperty::MailboxId, + MessageProperty::IsUnread, + MessageProperty::IsFlagged, + MessageProperty::IsAnswered, + MessageProperty::IsDraft, + MessageProperty::HasAttachment, + MessageProperty::From, + MessageProperty::To, + MessageProperty::Subject, + MessageProperty::Date, + MessageProperty::Preview, + ], + }; + + let mut req = Request::new(conn.request_no.clone()); + let prev_seq = req.add_call(&email_query_call); + + let email_call: EmailGet = EmailGet::new( + Get::new() + .ids(Some(JmapArgument::reference( + prev_seq, + &email_query_call, + "/ids", + ))) + .account_id(conn.account_id.lock().unwrap().clone()), + ); + + req.add_call(&email_call); + + let res = conn + .client + .lock() + .unwrap() + .post("https://jmap-proxy.local/jmap/fc32dffe-14e7-11ea-a277-2477037a1804/") + .json(&req) + .send(); + + let res_text = res?.text()?; + + let mut v: MethodResponse = serde_json::from_str(&res_text).unwrap(); + let e = GetResponse::::try_from(v.method_responses.pop().unwrap())?; + let GetResponse:: { list, .. } = e; + Ok(list + .into_iter() + .map(std::convert::Into::into) + .collect::>()) +} diff --git a/melib/src/backends/jmap/rfc8620.rs b/melib/src/backends/jmap/rfc8620.rs index 14f6d0ccd..3bdac1bc9 100644 --- a/melib/src/backends/jmap/rfc8620.rs +++ b/melib/src/backends/jmap/rfc8620.rs @@ -21,13 +21,16 @@ use super::Id; use core::marker::PhantomData; -use serde::{de::DeserializeOwned, Serialize}; +use serde::de::DeserializeOwned; +use serde::ser::{Serialize, SerializeStruct, Serializer}; use serde_json::{value::RawValue, Value}; mod filters; pub use filters::*; mod comparator; pub use comparator::*; +mod argument; +pub use argument::*; use super::protocol::{Method, Response}; use std::collections::HashMap; @@ -102,7 +105,7 @@ pub struct Account { extra_properties: HashMap, } -#[derive(Deserialize, Serialize, Debug)] +#[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Get where @@ -111,7 +114,8 @@ where #[serde(skip_serializing_if = "String::is_empty")] pub account_id: String, #[serde(skip_serializing_if = "Option::is_none")] - pub ids: Option>, + #[serde(flatten)] + pub ids: Option>>, #[serde(skip_serializing_if = "Option::is_none")] pub properties: Option>, #[serde(skip)] @@ -131,9 +135,65 @@ where } } _impl!(account_id: String); - _impl!(ids: Option>); + _impl!(ids: Option>>); _impl!(properties: Option>); } + +impl Serialize for Get { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut fields_no = 0; + if !self.account_id.is_empty() { + fields_no += 1; + } + if !self.ids.is_none() { + fields_no += 1; + } + if !self.properties.is_none() { + fields_no += 1; + } + + let mut state = serializer.serialize_struct("Get", fields_no)?; + if !self.account_id.is_empty() { + state.serialize_field("accountId", &self.account_id)?; + } + match self.ids.as_ref() { + None => {} + Some(JmapArgument::Value(ref v)) => state.serialize_field("ids", v)?, + Some(JmapArgument::ResultReference { + ref result_of, + ref name, + ref path, + }) => { + #[derive(Serialize)] + #[serde(rename_all = "camelCase")] + struct A<'a> { + result_of: &'a str, + name: &'a str, + path: &'a str, + } + + state.serialize_field( + "#ids", + &A { + result_of, + name, + path, + }, + )?; + } + } + + if !self.properties.is_none() { + state.serialize_field("properties", self.properties.as_ref().unwrap()); + } + + state.end() + } +} + // The response has the following arguments: // // o accountId: "Id" @@ -192,6 +252,7 @@ pub struct MethodResponse<'a> { pub struct GetResponse { #[serde(skip_serializing_if = "String::is_empty")] pub account_id: String, + #[serde(default)] pub state: String, pub list: Vec, pub not_found: Vec, diff --git a/melib/src/backends/jmap/rfc8620/argument.rs b/melib/src/backends/jmap/rfc8620/argument.rs new file mode 100644 index 000000000..498208fe4 --- /dev/null +++ b/melib/src/backends/jmap/rfc8620/argument.rs @@ -0,0 +1,54 @@ +/* + * meli - jmap module. + * + * Copyright 2019 Manos Pitsidianakis + * + * This file is part of meli. + * + * meli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * meli is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with meli. If not, see . + */ + +use crate::backends::jmap::protocol::Method; +use crate::backends::jmap::rfc8620::Object; +use serde::de::DeserializeOwned; +use serde::ser::{Serialize, SerializeStruct, Serializer}; + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub enum JmapArgument { + Value(T), + ResultReference { + result_of: String, + name: String, + path: String, + }, +} + +impl JmapArgument { + pub fn value(v: T) -> Self { + JmapArgument::Value(v) + } + + pub fn reference(result_of: usize, method: &M, path: &str) -> Self + where + M: Method, + OBJ: Object, + { + JmapArgument::ResultReference { + result_of: format!("m{}", result_of), + name: M::NAME.to_string(), + path: path.to_string(), + } + } +}