diff options
author | Manos Pitsidianakis <el13635@mail.ntua.gr> | 2019-12-04 01:04:38 +0200 |
---|---|---|
committer | Manos Pitsidianakis <el13635@mail.ntua.gr> | 2019-12-13 00:04:58 +0200 |
commit | a1efeed34352e60434e511edc5d35ffb4c6b4276 (patch) | |
tree | 2471d72c72994d548ab74c940cd5eb3765b12b86 /melib | |
parent | e8611cca2f43c6027a934d6a995324d6b0012d0e (diff) |
JMAP WIP #3
Diffstat (limited to 'melib')
-rw-r--r-- | melib/src/backends/jmap.rs | 72 | ||||
-rw-r--r-- | melib/src/backends/jmap/connection.rs | 64 | ||||
-rw-r--r-- | melib/src/backends/jmap/objects/email.rs | 64 | ||||
-rw-r--r-- | melib/src/backends/jmap/protocol.rs | 134 | ||||
-rw-r--r-- | melib/src/backends/jmap/rfc8620.rs | 105 | ||||
-rw-r--r-- | melib/src/backends/jmap/rfc8620/comparator.rs | 17 |
6 files changed, 344 insertions, 112 deletions
diff --git a/melib/src/backends/jmap.rs b/melib/src/backends/jmap.rs index 26d850d0..8d25e982 100644 --- a/melib/src/backends/jmap.rs +++ b/melib/src/backends/jmap.rs @@ -35,6 +35,19 @@ use std::hash::Hasher; use std::str::FromStr; use std::sync::{Arc, Mutex, RwLock}; +#[macro_export] +macro_rules! _impl { + ($field:ident : $t:ty) => { + pub fn $field(mut self, new_val: $t) -> Self { + self.$field = new_val; + self + } + } + } + +pub mod connection; +use connection::*; + pub mod protocol; use protocol::*; @@ -122,7 +135,7 @@ macro_rules! get_conf_val { ($s:ident[$var:literal]) => { $s.extra.get($var).ok_or_else(|| { MeliError::new(format!( - "Configuration error ({}): IMAP connection requires the field `{}` set", + "Configuration error ({}): JMAP connection requires the field `{}` set", $s.name.as_str(), $var )) @@ -152,7 +165,7 @@ pub struct JmapType { online: Arc<Mutex<bool>>, is_subscribed: Arc<IsSubscribedFn>, server_conf: JmapServerConf, - connection: Arc<Mutex<JmapConnection>>, + connection: Arc<JmapConnection>, folders: Arc<RwLock<FnvHashMap<FolderHash, JmapFolder>>>, } @@ -168,15 +181,11 @@ impl MailBackend for JmapType { let handle = { let tx = w.tx(); let closure = move |_work_context| { - let mut conn_lck = connection.lock().unwrap(); tx.send(AsyncStatus::Payload( - protocol::get_message_list( - &mut conn_lck, - &folders.read().unwrap()[&folder_hash], - ) - .and_then(|ids| { - protocol::get_message(&mut conn_lck, std::dbg!(&ids).as_slice()) - }), + protocol::get_message_list(&connection, &folders.read().unwrap()[&folder_hash]) + .and_then(|ids| { + protocol::get_message(&connection, std::dbg!(&ids).as_slice()) + }), )) .unwrap(); tx.send(AsyncStatus::Finished).unwrap(); @@ -196,9 +205,7 @@ impl MailBackend for JmapType { fn folders(&self) -> Result<FnvHashMap<FolderHash, Folder>> { if self.folders.read().unwrap().is_empty() { - let folders = std::dbg!(protocol::get_mailboxes( - &mut self.connection.lock().unwrap() - ))?; + let folders = std::dbg!(protocol::get_mailboxes(&self.connection))?; let ret = Ok(folders .iter() .map(|(&h, f)| (h, BackendFolder::clone(f) as Folder)) @@ -242,10 +249,7 @@ impl JmapType { let server_conf = JmapServerConf::new(s)?; Ok(Box::new(JmapType { - connection: Arc::new(Mutex::new(JmapConnection::new( - &server_conf, - online.clone(), - )?)), + connection: Arc::new(JmapConnection::new(&server_conf, online.clone())?), folders: Arc::new(RwLock::new(FnvHashMap::default())), account_name: s.name.clone(), online, @@ -263,37 +267,3 @@ impl JmapType { Ok(()) } } - -#[derive(Debug)] -pub struct JmapConnection { - request_no: usize, - client: Client, - online_status: Arc<Mutex<bool>>, -} - -impl JmapConnection { - pub fn new(server_conf: &JmapServerConf, online_status: Arc<Mutex<bool>>) -> Result<Self> { - use reqwest::header; - let mut headers = header::HeaderMap::new(); - headers.insert( - header::AUTHORIZATION, - header::HeaderValue::from_static("fc32dffe-14e7-11ea-a277-2477037a1804"), - ); - headers.insert( - header::ACCEPT, - header::HeaderValue::from_static("application/json"), - ); - headers.insert( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ); - Ok(JmapConnection { - request_no: 0, - client: reqwest::blocking::ClientBuilder::new() - .danger_accept_invalid_certs(server_conf.danger_accept_invalid_certs) - .default_headers(headers) - .build()?, - online_status, - }) - } -} diff --git a/melib/src/backends/jmap/connection.rs b/melib/src/backends/jmap/connection.rs new file mode 100644 index 00000000..036abd82 --- /dev/null +++ b/melib/src/backends/jmap/connection.rs @@ -0,0 +1,64 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +use super::*; + +#[derive(Debug)] +pub struct JmapConnection { + pub request_no: Arc<Mutex<usize>>, + pub client: Arc<Mutex<Client>>, + pub online_status: Arc<Mutex<bool>>, + pub server_conf: JmapServerConf, +} + +impl JmapConnection { + pub fn new(server_conf: &JmapServerConf, online_status: Arc<Mutex<bool>>) -> Result<Self> { + use reqwest::header; + let mut headers = header::HeaderMap::new(); + headers.insert( + header::AUTHORIZATION, + header::HeaderValue::from_static("fc32dffe-14e7-11ea-a277-2477037a1804"), + ); + headers.insert( + header::ACCEPT, + header::HeaderValue::from_static("application/json"), + ); + headers.insert( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + ); + let client = reqwest::blocking::ClientBuilder::new() + .danger_accept_invalid_certs(server_conf.danger_accept_invalid_certs) + .default_headers(headers) + .build()?; + + let res_text = client.get(&server_conf.server_hostname).send()?.text()?; + debug!(&res_text); + + let server_conf = server_conf.clone(); + Ok(JmapConnection { + request_no: Arc::new(Mutex::new(0)), + client: Arc::new(Mutex::new(client)), + online_status, + server_conf, + }) + } +} diff --git a/melib/src/backends/jmap/objects/email.rs b/melib/src/backends/jmap/objects/email.rs index 851e41c0..d10d40e4 100644 --- a/melib/src/backends/jmap/objects/email.rs +++ b/melib/src/backends/jmap/objects/email.rs @@ -21,6 +21,7 @@ use super::*; use crate::backends::jmap::protocol::*; +use crate::backends::jmap::rfc8620::bool_false; use std::collections::HashMap; // 4.1.1. @@ -151,7 +152,7 @@ pub struct EmailQueryResponse { #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct EmailQueryCall { - pub filter: Vec<EmailFilterCondition>, /* "inMailboxes": [ folder.id ] },*/ + pub filter: EmailFilterCondition, /* "inMailboxes": [ folder.id ] },*/ pub collapse_threads: bool, pub position: u64, pub fetch_threads: bool, @@ -166,18 +167,44 @@ impl Method<EmailObject> for EmailQueryCall { #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct EmailGetCall { - pub filter: Vec<EmailFilterCondition>, /* "inMailboxes": [ folder.id ] },*/ - pub collapse_threads: bool, - pub position: u64, - pub fetch_threads: bool, - pub fetch_messages: bool, - pub fetch_message_properties: Vec<MessageProperty>, + #[serde(flatten)] + pub get_call: GetCall<EmailObject>, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub body_properties: Vec<String>, + #[serde(default = "bool_false")] + pub fetch_text_body_values: bool, + #[serde(default = "bool_false")] + pub fetch_html_body_values: bool, + #[serde(default = "bool_false")] + pub fetch_all_body_values: bool, + #[serde(default)] + pub max_body_value_bytes: u64, } impl Method<EmailObject> for EmailGetCall { const NAME: &'static str = "Email/get"; } +impl EmailGetCall { + pub fn new(get_call: GetCall<EmailObject>) -> Self { + EmailGetCall { + get_call, + body_properties: Vec::new(), + fetch_text_body_values: false, + fetch_html_body_values: false, + fetch_all_body_values: false, + max_body_value_bytes: 0, + } + } + + _impl!(get_call: GetCall<EmailObject>); + _impl!(body_properties: Vec<String>); + _impl!(fetch_text_body_values: bool); + _impl!(fetch_html_body_values: bool); + _impl!(fetch_all_body_values: bool); + _impl!(max_body_value_bytes: u64); +} + #[derive(Serialize, Deserialize, Default, Debug)] #[serde(rename_all = "camelCase")] pub struct EmailFilterCondition { @@ -225,6 +252,29 @@ pub struct EmailFilterCondition { pub header: Vec<String>, } +impl EmailFilterCondition { + _impl!(in_mailboxes: Vec<Id>); + _impl!(in_mailbox_other_than: Vec<Id>); + _impl!(before: UtcDate); + _impl!(after: UtcDate); + _impl!(min_size: Option<u64>); + _impl!(max_size: Option<u64>); + _impl!(all_in_thread_have_keyword: String); + _impl!(some_in_thread_have_keyword: String); + _impl!(none_in_thread_have_keyword: String); + _impl!(has_keyword: String); + _impl!(not_keyword: String); + _impl!(has_attachment: Option<bool>); + _impl!(text: String); + _impl!(from: String); + _impl!(to: String); + _impl!(cc: String); + _impl!(bcc: String); + _impl!(subject: String); + _impl!(body: String); + _impl!(header: Vec<String>); +} + impl FilterTrait<EmailObject> for EmailFilterCondition {} // The following convenience properties are also specified for the Email diff --git a/melib/src/backends/jmap/protocol.rs b/melib/src/backends/jmap/protocol.rs index 125e9366..8557f253 100644 --- a/melib/src/backends/jmap/protocol.rs +++ b/melib/src/backends/jmap/protocol.rs @@ -29,6 +29,15 @@ pub type UtcDate = String; use super::rfc8620::Object; +macro_rules! get_request_no { + ($lock:expr) => {{ + let mut lck = $lock.lock().unwrap(); + let ret = *lck; + *lck += 1; + ret + }}; +} + pub trait Method<OBJ: Object>: Serialize { const NAME: &'static str; } @@ -68,19 +77,25 @@ pub struct Request { /* Why is this Value instead of Box<dyn Method<_>>? The Method trait cannot be made into a * Trait object because its serialize() will be generic. */ method_calls: Vec<Value>, + + #[serde(skip_serializing)] + request_no: Arc<Mutex<usize>>, } impl Request { - pub fn new() -> Self { + pub fn new(request_no: Arc<Mutex<usize>>) -> Self { Request { using: USING, method_calls: Vec::new(), + request_no, } } - pub fn add_call<M: Method<O>, O: Object>(&mut self, call: M) { + pub fn add_call<M: Method<O>, 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, "f")).unwrap()); + .push(serde_json::to_value((M::NAME, call, &format!("m{}", seq))).unwrap()); + seq } } @@ -100,17 +115,19 @@ pub enum MethodCall { Empty {}, } -pub fn get_mailboxes(conn: &mut JmapConnection) -> Result<FnvHashMap<FolderHash, JmapFolder>> { +pub fn get_mailboxes(conn: &JmapConnection) -> Result<FnvHashMap<FolderHash, JmapFolder>> { + let seq = get_request_no!(conn.request_no); let res = conn .client + .lock() + .unwrap() .post("https://jmap-proxy.local/jmap/fc32dffe-14e7-11ea-a277-2477037a1804/") .json(&json!({ "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"], "methodCalls": [["Mailbox/get", {}, - format!("#m{}", conn.request_no + 1).as_str()]], + format!("#m{}",seq).as_str()]], })) .send(); - conn.request_no += 1; let mut v: JsonResponse = serde_json::from_str(&std::dbg!(res.unwrap().text().unwrap())).unwrap(); @@ -259,12 +276,13 @@ pub struct JmapRights { // fetchSearchSnippets: false // }, "call1"] // ] -pub fn get_message_list(conn: &mut JmapConnection, folder: &JmapFolder) -> Result<Vec<String>> { +pub fn get_message_list(conn: &JmapConnection, folder: &JmapFolder) -> Result<Vec<String>> { + let seq = get_request_no!(conn.request_no); let email_call: EmailQueryCall = EmailQueryCall { - filter: vec![EmailFilterCondition { + filter: EmailFilterCondition { in_mailboxes: vec![folder.id.clone()], ..Default::default() - }], + }, collapse_threads: false, position: 0, fetch_threads: true, @@ -285,9 +303,8 @@ pub fn get_message_list(conn: &mut JmapConnection, folder: &JmapFolder) -> Resul ], }; - let mut req = Request::new(); + let mut req = Request::new(conn.request_no.clone()); req.add_call(email_call); - std::dbg!(serde_json::to_string(&req)); /* { @@ -328,37 +345,43 @@ pub fn get_message_list(conn: &mut JmapConnection, folder: &JmapFolder) -> Resul ]] } */ + /* + r#" + "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"], + "methodCalls": [["Email/query", { "filter": { + "inMailboxes": [ folder.id ] + }, + "collapseThreads": false, + "position": 0, + "fetchThreads": true, + "fetchMessages": true, + "fetchMessageProperties": [ + "threadId", + "mailboxId", + "isUnread", + "isFlagged", + "isAnswered", + "isDraft", + "hasAttachment", + "from", + "to", + "subject", + "date", + "preview" + ], + }, format!("m{}", seq).as_str()]], + });" + );*/ + + std::dbg!(serde_json::to_string(&req)); let res = conn .client + .lock() + .unwrap() .post("https://jmap-proxy.local/jmap/fc32dffe-14e7-11ea-a277-2477037a1804/") - .json(&json!({ - "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"], - "methodCalls": [["Email/query", { "filter": { - "inMailboxes": [ folder.id ] - }, - "collapseThreads": false, - "position": 0, - "fetchThreads": true, - "fetchMessages": true, - "fetchMessageProperties": [ - "threadId", - "mailboxId", - "isUnread", - "isFlagged", - "isAnswered", - "isDraft", - "hasAttachment", - "from", - "to", - "subject", - "date", - "preview" - ], - }, format!("#m{}", conn.request_no + 1).as_str()]], - })) + .json(&req) .send(); - conn.request_no += 1; let mut v: JsonResponse = serde_json::from_str(&std::dbg!(res.unwrap().text().unwrap()))?; let result: Response = v.method_responses.remove(0).1; @@ -369,11 +392,35 @@ pub fn get_message_list(conn: &mut JmapConnection, folder: &JmapFolder) -> Resul } } -pub fn get_message(conn: &mut JmapConnection, ids: &[String]) -> Result<Vec<Envelope>> { +pub fn get_message(conn: &JmapConnection, ids: &[String]) -> Result<Vec<Envelope>> { + let seq = get_request_no!(conn.request_no); + let email_call: EmailGetCall = + EmailGetCall::new(GetCall::new().ids(Some(ids.iter().cloned().collect::<Vec<String>>()))); + + let mut req = Request::new(conn.request_no.clone()); + req.add_call(email_call); let res = conn .client + .lock() + .unwrap() .post("https://jmap-proxy.local/jmap/fc32dffe-14e7-11ea-a277-2477037a1804/") - .json(&json!({ + .json(&req) + .send(); + + let res_text = res?.text()?; + let v: JsonResponse = serde_json::from_str(&res_text)?; + let mut f = std::fs::File::create(std::dbg!(format!("/tmp/asdfsa{}", seq))).unwrap(); + use std::io::Write; + f.write_all( + serde_json::to_string_pretty(&serde_json::from_str::<Value>(&res_text)?)?.as_bytes(), + ) + .unwrap(); + Ok(vec![]) +} + +/* + * + *json!({ "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"], "methodCalls": [["Email/get", { "ids": ids, @@ -383,12 +430,7 @@ pub fn get_message(conn: &mut JmapConnection, ids: &[String]) -> Result<Vec<Enve "bodyProperties": [ "partId", "blobId", "size", "type" ], "fetchHTMLBodyValues": true, "maxBodyValueBytes": 256 - }, format!("#m{}", conn.request_no + 1).as_str()]], + }, format!("m{}", seq).as_str()]], })) - .send(); - conn.request_no += 1; - let v: JsonResponse = serde_json::from_str(&std::dbg!(res.unwrap().text().unwrap()))?; - std::dbg!(&v); - Ok(vec![]) -} +*/ diff --git a/melib/src/backends/jmap/rfc8620.rs b/melib/src/backends/jmap/rfc8620.rs index 55863b6c..f8cb0ce4 100644 --- a/melib/src/backends/jmap/rfc8620.rs +++ b/melib/src/backends/jmap/rfc8620.rs @@ -19,14 +19,18 @@ * along with meli. If not, see <http://www.gnu.org/licenses/>. */ +use super::Id; use core::marker::PhantomData; use serde::{de::DeserializeOwned, Serialize}; +use serde_json::Value; + mod filters; pub use filters::*; mod comparator; pub use comparator::*; use super::protocol::Method; +use std::collections::HashMap; pub trait Object {} // 5.1. /get @@ -57,20 +61,76 @@ pub trait Object {} #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] -pub struct GetCall<OBJ: Object, CALL: Method<OBJ>> +pub struct JmapSession { + capabilities: HashMap<String, CapabilitiesObject>, + accounts: HashMap<Id, Account>, + primary_accounts: Vec<Id>, + username: String, + api_url: String, + download_url: String, + + upload_url: String, + event_source_url: String, + state: String, + #[serde(flatten)] + extra_properties: HashMap<String, Value>, +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct CapabilitiesObject { + max_size_upload: u64, + max_concurrent_upload: u64, + max_size_request: u64, + max_concurrent_requests: u64, + max_calls_in_request: u64, + max_objects_in_get: u64, + max_objects_in_set: u64, + collation_algorithms: Vec<String>, +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct Account { + name: String, + is_personal: bool, + is_read_only: bool, + account_capabilities: HashMap<String, Value>, + #[serde(flatten)] + extra_properties: HashMap<String, Value>, +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct GetCall<OBJ: Object> where OBJ: std::fmt::Debug + Serialize, { #[serde(skip_serializing_if = "String::is_empty")] - account_id: String, + pub account_id: String, #[serde(skip_serializing_if = "Option::is_none")] - ids: Option<Vec<String>>, + pub ids: Option<Vec<String>>, #[serde(skip_serializing_if = "Option::is_none")] - properties: Option<Vec<String>>, - _ph: PhantomData<*const CALL>, - __ph: PhantomData<*const OBJ>, + pub properties: Option<Vec<String>>, + _ph: PhantomData<*const OBJ>, } +impl<OBJ: Object> GetCall<OBJ> +where + OBJ: std::fmt::Debug + Serialize, +{ + pub fn new() -> Self { + Self { + account_id: String::new(), + ids: None, + properties: None, + _ph: PhantomData, + } + } + _impl!(account_id: String); + _impl!(ids: Option<Vec<String>>); + _impl!(properties: Option<Vec<String>>); +} // The response has the following arguments: // // o accountId: "Id" @@ -128,6 +188,7 @@ pub struct GetResponse<T> { enum JmapError { RequestTooLarge, InvalidArguments, + InvalidResultReference, } // 5.5. /query @@ -321,11 +382,39 @@ where _ph: PhantomData<*const OBJ>, } -fn bool_false() -> bool { +impl<F: FilterTrait<OBJ>, OBJ: Object> QueryCall<F, OBJ> +where + OBJ: std::fmt::Debug + Serialize, +{ + pub fn new() -> Self { + Self { + account_id: String::new(), + filter: None, + sort: None, + position: 0, + anchor: None, + anchor_offset: 0, + limit: None, + calculate_total: false, + _ph: PhantomData, + } + } + + _impl!(account_id: String); + _impl!(filter: Option<Filter<F, OBJ>>); + _impl!(sort: Option<Comparator<OBJ>>); + _impl!(position: u64); + _impl!(anchor: Option<String>); + _impl!(anchor_offset: u64); + _impl!(limit: Option<u64>); + _impl!(calculate_total: bool); +} + +pub fn bool_false() -> bool { false } -fn bool_true() -> bool { +pub fn bool_true() -> bool { true } diff --git a/melib/src/backends/jmap/rfc8620/comparator.rs b/melib/src/backends/jmap/rfc8620/comparator.rs index c11ad266..e1bad283 100644 --- a/melib/src/backends/jmap/rfc8620/comparator.rs +++ b/melib/src/backends/jmap/rfc8620/comparator.rs @@ -35,6 +35,23 @@ pub struct Comparator<OBJ: Object> { _ph: PhantomData<*const OBJ>, } +impl<OBJ: Object> Comparator<OBJ> { + pub fn new() -> Self { + Self { + property: String::new(), + is_ascending: true, + collation: None, + additional_properties: Vec::new(), + _ph: PhantomData, + } + } + + _impl!(property: String); + _impl!(is_ascending: bool); + _impl!(collation: Option<String>); + _impl!(additional_properties: Vec<String>); +} + #[derive(Serialize, Debug)] #[serde(rename_all = "UPPERCASE")] pub enum FilterOperator { |