diff options
author | Manos Pitsidianakis <el13635@mail.ntua.gr> | 2020-09-21 19:13:44 +0300 |
---|---|---|
committer | Manos Pitsidianakis <el13635@mail.ntua.gr> | 2020-09-23 10:52:19 +0300 |
commit | 425f4b9930048b1a1b6e8f240a4196e2aab82fc0 (patch) | |
tree | 8b72a5048d8b981e816e3115454af2f4f585e26e /melib | |
parent | 19d4a191d81c2f2225d8825d6645f061eda84468 (diff) |
melib/jmap: add Type parameter to Id, State
Make Id, State have a type parameter to the object it refers to (eg
`Id<EmailObject>`) instead of just a String
Diffstat (limited to 'melib')
-rw-r--r-- | melib/src/backends/jmap.rs | 79 | ||||
-rw-r--r-- | melib/src/backends/jmap/connection.rs | 37 | ||||
-rw-r--r-- | melib/src/backends/jmap/mailbox.rs | 7 | ||||
-rw-r--r-- | melib/src/backends/jmap/objects/email.rs | 49 | ||||
-rw-r--r-- | melib/src/backends/jmap/objects/email/import.rs | 42 | ||||
-rw-r--r-- | melib/src/backends/jmap/objects/mailbox.rs | 12 | ||||
-rw-r--r-- | melib/src/backends/jmap/protocol.rs | 39 | ||||
-rw-r--r-- | melib/src/backends/jmap/rfc8620.rs | 313 |
8 files changed, 375 insertions, 203 deletions
diff --git a/melib/src/backends/jmap.rs b/melib/src/backends/jmap.rs index 31e58ab3..cf1f7c3a 100644 --- a/melib/src/backends/jmap.rs +++ b/melib/src/backends/jmap.rs @@ -191,15 +191,16 @@ macro_rules! get_conf_val { pub struct Store { pub account_name: Arc<String>, pub account_hash: AccountHash, - pub account_id: Arc<Mutex<String>>, + pub account_id: Arc<Mutex<Id<Account>>>, pub byte_cache: Arc<Mutex<HashMap<EnvelopeHash, EnvelopeCache>>>, - pub id_store: Arc<Mutex<HashMap<EnvelopeHash, Id>>>, - pub reverse_id_store: Arc<Mutex<HashMap<Id, EnvelopeHash>>>, - pub blob_id_store: Arc<Mutex<HashMap<EnvelopeHash, Id>>>, + pub id_store: Arc<Mutex<HashMap<EnvelopeHash, Id<EmailObject>>>>, + pub reverse_id_store: Arc<Mutex<HashMap<Id<EmailObject>, EnvelopeHash>>>, + pub blob_id_store: Arc<Mutex<HashMap<EnvelopeHash, Id<BlobObject>>>>, pub tag_index: Arc<RwLock<BTreeMap<u64, String>>>, pub mailboxes: Arc<RwLock<HashMap<MailboxHash, JmapMailbox>>>, pub mailboxes_index: Arc<RwLock<HashMap<MailboxHash, HashSet<EnvelopeHash>>>>, - pub object_set_states: Arc<Mutex<HashMap<&'static str, String>>>, + pub email_state: Arc<Mutex<State<EmailObject>>>, + pub mailbox_state: Arc<Mutex<State<MailboxObject>>>, pub online_status: Arc<FutureMutex<(Instant, Result<()>)>>, pub is_subscribed: Arc<IsSubscribedFn>, pub event_consumer: BackendEventConsumer, @@ -275,7 +276,7 @@ impl Store { pub fn remove_envelope( &self, - obj_id: Id, + obj_id: Id<EmailObject>, ) -> Option<(EnvelopeHash, SmallVec<[MailboxHash; 8]>)> { let env_hash = self.reverse_id_store.lock().unwrap().remove(&obj_id)?; self.id_store.lock().unwrap().remove(&env_hash); @@ -413,7 +414,7 @@ impl MailBackend for JmapType { ) .await?; - let mailbox_id: String = { + let mailbox_id: Id<MailboxObject> = { let mailboxes_lck = store.mailboxes.read().unwrap(); if let Some(mailbox) = mailboxes_lck.get(&mailbox_hash) { mailbox.id.clone() @@ -428,7 +429,7 @@ impl MailBackend for JmapType { let upload_response: UploadResponse = serde_json::from_str(&res_text)?; let mut req = Request::new(conn.request_no.clone()); - let creation_id = "1".to_string(); + let creation_id: Id<EmailObject> = "1".to_string().into(); let mut email_imports = HashMap::default(); let mut mailbox_ids = HashMap::default(); mailbox_ids.insert(mailbox_id, true); @@ -440,7 +441,7 @@ impl MailBackend for JmapType { ); let import_call: ImportCall = ImportCall::new() - .account_id(conn.mail_account_id().to_string()) + .account_id(conn.mail_account_id().clone()) .emails(email_imports); req.add_call(&import_call); @@ -508,7 +509,7 @@ impl MailBackend for JmapType { conn.connect().await?; let email_call: EmailQuery = EmailQuery::new( Query::new() - .account_id(conn.mail_account_id().to_string()) + .account_id(conn.mail_account_id().clone()) .filter(Some(filter)) .position(0), ) @@ -527,14 +528,7 @@ impl MailBackend for JmapType { *store.online_status.lock().await = (std::time::Instant::now(), Ok(())); let m = QueryResponse::<EmailObject>::try_from(v.method_responses.remove(0))?; let QueryResponse::<EmailObject> { ids, .. } = m; - let ret = ids - .into_iter() - .map(|id| { - let mut h = DefaultHasher::new(); - h.write(id.as_bytes()); - h.finish() - }) - .collect(); + let ret = ids.into_iter().map(|id| id.into_hash()).collect(); Ok(ret) })) } @@ -584,9 +578,9 @@ impl MailBackend for JmapType { mailboxes_lck[&destination_mailbox_hash].id.clone(), ) }; - let mut update_map: HashMap<String, Value> = HashMap::default(); - let mut ids: Vec<Id> = Vec::with_capacity(env_hashes.rest.len() + 1); - let mut id_map: HashMap<Id, EnvelopeHash> = HashMap::default(); + let mut update_map: HashMap<Id<EmailObject>, Value> = HashMap::default(); + let mut ids: Vec<Id<EmailObject>> = Vec::with_capacity(env_hashes.rest.len() + 1); + let mut id_map: HashMap<Id<EmailObject>, EnvelopeHash> = HashMap::default(); let mut update_keywords: HashMap<String, Value> = HashMap::default(); update_keywords.insert( format!("mailboxIds/{}", &destination_mailbox_id), @@ -594,7 +588,7 @@ impl MailBackend for JmapType { ); if move_ { update_keywords.insert( - format!("mailboxIds/{}", &source_mailbox_hash), + format!("mailboxIds/{}", &source_mailbox_id), serde_json::json!(null), ); } @@ -611,7 +605,7 @@ impl MailBackend for JmapType { let email_set_call: EmailSet = EmailSet::new( Set::<EmailObject>::new() - .account_id(conn.mail_account_id().to_string()) + .account_id(conn.mail_account_id().clone()) .update(Some(update_map)), ); @@ -653,9 +647,9 @@ impl MailBackend for JmapType { let connection = self.connection.clone(); Ok(Box::pin(async move { let mailbox_id = store.mailboxes.read().unwrap()[&mailbox_hash].id.clone(); - let mut update_map: HashMap<String, Value> = HashMap::default(); - let mut ids: Vec<Id> = Vec::with_capacity(env_hashes.rest.len() + 1); - let mut id_map: HashMap<Id, EnvelopeHash> = HashMap::default(); + let mut update_map: HashMap<Id<EmailObject>, Value> = HashMap::default(); + let mut ids: Vec<Id<EmailObject>> = Vec::with_capacity(env_hashes.rest.len() + 1); + let mut id_map: HashMap<Id<EmailObject>, EnvelopeHash> = HashMap::default(); let mut update_keywords: HashMap<String, Value> = HashMap::default(); for (flag, value) in flags.iter() { match flag { @@ -701,11 +695,11 @@ impl MailBackend for JmapType { } } } - let conn = connection.lock().await; + let mut conn = connection.lock().await; let email_set_call: EmailSet = EmailSet::new( Set::<EmailObject>::new() - .account_id(conn.mail_account_id().to_string()) + .account_id(conn.mail_account_id().clone()) .update(Some(update_map)), ); @@ -714,7 +708,7 @@ impl MailBackend for JmapType { let email_call: EmailGet = EmailGet::new( Get::new() .ids(Some(JmapArgument::Value(ids))) - .account_id(conn.mail_account_id().to_string()) + .account_id(conn.mail_account_id().clone()) .properties(Some(vec!["keywords".to_string()])), ); @@ -759,21 +753,15 @@ impl MailBackend for JmapType { let e = GetResponse::<EmailObject>::try_from(v.method_responses.pop().unwrap())?; let GetResponse::<EmailObject> { list, state, .. } = e; { - let c = store - .object_set_states - .lock() - .unwrap() - .get(&EmailObject::NAME) - .map(|prev_state| *prev_state == state); - if let Some(false) = c { - conn.email_changes().await?; - } else { + let (is_empty, is_equal) = { + let current_state_lck = conn.store.email_state.lock().unwrap(); + (current_state_lck.is_empty(), *current_state_lck != state) + }; + if is_empty { debug!("{:?}: inserting state {}", EmailObject::NAME, &state); - store - .object_set_states - .lock() - .unwrap() - .insert(EmailObject::NAME, state); + *conn.store.email_state.lock().unwrap() = state; + } else if !is_equal { + conn.email_changes().await?; } } debug!(&list); @@ -813,7 +801,7 @@ impl JmapType { let store = Arc::new(Store { account_name: Arc::new(s.name.clone()), account_hash, - account_id: Arc::new(Mutex::new(String::new())), + account_id: Arc::new(Mutex::new(Id::new())), online_status, event_consumer, is_subscribed: Arc::new(IsSubscribedFn(is_subscribed)), @@ -825,7 +813,8 @@ impl JmapType { tag_index: Default::default(), mailboxes: Default::default(), mailboxes_index: Default::default(), - object_set_states: Default::default(), + email_state: Default::default(), + mailbox_state: Default::default(), }); Ok(Box::new(JmapType { diff --git a/melib/src/backends/jmap/connection.rs b/melib/src/backends/jmap/connection.rs index af46bba6..97ad15c6 100644 --- a/melib/src/backends/jmap/connection.rs +++ b/melib/src/backends/jmap/connection.rs @@ -100,7 +100,7 @@ impl JmapConnection { Ok(()) } - pub fn mail_account_id(&self) -> &Id { + pub fn mail_account_id(&self) -> &Id<Account> { &self.session.primary_accounts["urn:ietf:params:jmap:mail"] } @@ -109,22 +109,16 @@ impl JmapConnection { } pub async fn email_changes(&self) -> Result<()> { - let mut current_state: String = { - let object_set_states_lck = self.store.object_set_states.lock().unwrap(); - let v = if let Some(prev_state) = debug!(object_set_states_lck.get(&EmailObject::NAME)) - { - prev_state.clone() - } else { - return Ok(()); - }; - drop(object_set_states_lck); - v - }; + let mut current_state: State<EmailObject> = self.store.email_state.lock().unwrap().clone(); + if current_state.is_empty() { + debug!("{:?}: has no saved state", EmailObject::NAME); + return Ok(()); + } loop { let email_changes_call: EmailChanges = EmailChanges::new( Changes::<EmailObject>::new() - .account_id(self.mail_account_id().to_string()) + .account_id(self.mail_account_id().clone()) .since_state(current_state.clone()), ); @@ -136,7 +130,7 @@ impl JmapConnection { prev_seq, ResultField::<EmailChanges, EmailObject>::new("created"), ))) - .account_id(self.mail_account_id().to_string()), + .account_id(self.mail_account_id().clone()), ); req.add_call(&email_get_call); @@ -153,6 +147,12 @@ impl JmapConnection { GetResponse::<EmailObject>::try_from(v.method_responses.pop().unwrap())?; debug!(&get_response); let GetResponse::<EmailObject> { list, .. } = get_response; + let changes_response = + ChangesResponse::<EmailObject>::try_from(v.method_responses.pop().unwrap())?; + if changes_response.new_state == current_state { + return Ok(()); + } + let mut mailbox_hashes: Vec<SmallVec<[MailboxHash; 8]>> = Vec::with_capacity(list.len()); for envobj in &list { @@ -189,9 +189,6 @@ impl JmapConnection { } } - let changes_response = - ChangesResponse::<EmailObject>::try_from(v.method_responses.pop().unwrap())?; - let ChangesResponse::<EmailObject> { account_id: _, new_state, @@ -218,11 +215,7 @@ impl JmapConnection { if has_more_changes { current_state = new_state; } else { - self.store - .object_set_states - .lock() - .unwrap() - .insert(EmailObject::NAME, new_state); + *self.store.email_state.lock().unwrap() = new_state; break; } } diff --git a/melib/src/backends/jmap/mailbox.rs b/melib/src/backends/jmap/mailbox.rs index cf1987a4..33e87637 100644 --- a/melib/src/backends/jmap/mailbox.rs +++ b/melib/src/backends/jmap/mailbox.rs @@ -29,10 +29,11 @@ pub struct JmapMailbox { pub path: String, pub hash: MailboxHash, pub children: Vec<MailboxHash>, - pub id: String, + pub id: Id<MailboxObject>, pub is_subscribed: bool, pub my_rights: JmapRights, - pub parent_id: Option<String>, + pub parent_id: Option<Id<MailboxObject>>, + pub parent_hash: Option<MailboxHash>, pub role: Option<String>, pub sort_order: u64, pub total_emails: Arc<Mutex<u64>>, @@ -66,7 +67,7 @@ impl BackendMailbox for JmapMailbox { } fn parent(&self) -> Option<MailboxHash> { - None + self.parent_hash } fn permissions(&self) -> MailboxPermissions { diff --git a/melib/src/backends/jmap/objects/email.rs b/melib/src/backends/jmap/objects/email.rs index 415b62bc..6e2feda4 100644 --- a/melib/src/backends/jmap/objects/email.rs +++ b/melib/src/backends/jmap/objects/email.rs @@ -32,6 +32,21 @@ use std::hash::Hasher; mod import; pub use import::*; +#[derive(Debug)] +pub struct ThreadObject; + +impl Object for ThreadObject { + const NAME: &'static str = "Thread"; +} + +impl Id<EmailObject> { + pub fn into_hash(&self) -> EnvelopeHash { + let mut h = DefaultHasher::new(); + h.write(self.inner.as_bytes()); + h.finish() + } +} + // 4.1.1. // Metadata // These properties represent metadata about the message in the mail @@ -133,11 +148,11 @@ pub use import::*; #[serde(rename_all = "camelCase")] pub struct EmailObject { #[serde(default)] - pub id: Id, + pub id: Id<EmailObject>, #[serde(default)] - pub blob_id: String, + pub blob_id: Id<BlobObject>, #[serde(default)] - pub mailbox_ids: HashMap<Id, bool>, + pub mailbox_ids: HashMap<Id<MailboxObject>, bool>, #[serde(default)] pub size: u64, #[serde(default)] @@ -163,7 +178,7 @@ pub struct EmailObject { #[serde(default)] pub keywords: HashMap<String, bool>, #[serde(default)] - pub attached_emails: Option<Id>, + pub attached_emails: Option<Id<BlobObject>>, #[serde(default)] pub attachments: Vec<Value>, #[serde(default)] @@ -182,7 +197,7 @@ pub struct EmailObject { #[serde(default)] pub text_body: Vec<TextBody>, #[serde(default)] - pub thread_id: Id, + pub thread_id: Id<ThreadObject>, #[serde(flatten)] pub extra: HashMap<String, Value>, } @@ -313,9 +328,7 @@ impl std::convert::From<EmailObject> for crate::Envelope { } } - let mut h = DefaultHasher::new(); - h.write(t.id.as_bytes()); - env.set_hash(h.finish()); + env.set_hash(t.id.into_hash()); env } } @@ -323,7 +336,7 @@ impl std::convert::From<EmailObject> for crate::Envelope { #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct HtmlBody { - pub blob_id: Id, + pub blob_id: Id<BlobObject>, #[serde(default)] pub charset: String, #[serde(default)] @@ -350,7 +363,7 @@ pub struct HtmlBody { #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct TextBody { - pub blob_id: Id, + pub blob_id: Id<BlobObject>, #[serde(default)] pub charset: String, #[serde(default)] @@ -381,12 +394,12 @@ impl Object for EmailObject { #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct EmailQueryResponse { - pub account_id: Id, + pub account_id: Id<Account>, pub can_calculate_changes: bool, pub collapse_threads: bool, // FIXME pub filter: String, - pub ids: Vec<Id>, + pub ids: Vec<Id<EmailObject>>, pub position: u64, pub query_state: String, pub sort: Option<String>, @@ -469,9 +482,9 @@ impl EmailGet { #[serde(rename_all = "camelCase")] pub struct EmailFilterCondition { #[serde(skip_serializing_if = "Option::is_none")] - pub in_mailbox: Option<Id>, + pub in_mailbox: Option<Id<MailboxObject>>, #[serde(skip_serializing_if = "Vec::is_empty")] - pub in_mailbox_other_than: Vec<Id>, + pub in_mailbox_other_than: Vec<Id<MailboxObject>>, #[serde(skip_serializing_if = "String::is_empty")] pub before: UtcDate, #[serde(skip_serializing_if = "String::is_empty")] @@ -517,8 +530,8 @@ impl EmailFilterCondition { Self::default() } - _impl!(in_mailbox: Option<Id>); - _impl!(in_mailbox_other_than: Vec<Id>); + _impl!(in_mailbox: Option<Id<MailboxObject>>); + _impl!(in_mailbox_other_than: Vec<Id<MailboxObject>>); _impl!(before: UtcDate); _impl!(after: UtcDate); _impl!(min_size: Option<u64>); @@ -728,7 +741,7 @@ fn test_jmap_query() { let mut r = Filter::Condition( EmailFilterCondition::new() - .in_mailbox(Some(mailbox_id)) + .in_mailbox(Some(mailbox_id.into())) .into(), ); r &= f; @@ -737,7 +750,7 @@ fn test_jmap_query() { let email_call: EmailQuery = EmailQuery::new( Query::new() - .account_id("account_id".to_string()) + .account_id("account_id".to_string().into()) .filter(Some(filter)) .position(0), ) diff --git a/melib/src/backends/jmap/objects/email/import.rs b/melib/src/backends/jmap/objects/email/import.rs index 5fd0895a..638c58c3 100644 --- a/melib/src/backends/jmap/objects/email/import.rs +++ b/melib/src/backends/jmap/objects/email/import.rs @@ -37,7 +37,7 @@ use serde_json::value::RawValue; pub struct ImportCall { ///accountId: "Id" ///The id of the account to use. - pub account_id: String, + pub account_id: Id<Account>, ///ifInState: "String|null" ///This is a state string as returned by the "Email/get" method. If ///supplied, the string must match the current state of the account @@ -45,10 +45,10 @@ pub struct ImportCall { ///and a "stateMismatch" error returned. If null, any changes will ///be applied to the current state. #[serde(skip_serializing_if = "Option::is_none")] - pub if_in_state: Option<String>, + pub if_in_state: Option<State<EmailObject>>, ///o emails: "Id[EmailImport]" ///A map of creation id (client specified) to EmailImport objects. - pub emails: HashMap<Id, EmailImport>, + pub emails: HashMap<Id<EmailObject>, EmailImport>, } #[derive(Deserialize, Serialize, Debug)] @@ -56,11 +56,11 @@ pub struct ImportCall { pub struct EmailImport { ///o blobId: "Id" ///The id of the blob containing the raw message [RFC5322]. - pub blob_id: String, + pub blob_id: Id<BlobObject>, ///o mailboxIds: "Id[Boolean]" ///The ids of the Mailboxes to assign this Email to. At least one ///Mailbox MUST be given. - pub mailbox_ids: HashMap<Id, bool>, + pub mailbox_ids: HashMap<Id<MailboxObject>, bool>, ///o keywords: "String[Boolean]" (default: {}) ///The keywords to apply to the Email. pub keywords: HashMap<String, bool>, @@ -74,7 +74,7 @@ pub struct EmailImport { impl ImportCall { pub fn new() -> Self { Self { - account_id: String::new(), + account_id: Id::new(), if_in_state: None, emails: HashMap::default(), } @@ -85,10 +85,10 @@ impl ImportCall { /// /// The id of the account to use. /// - account_id: String + account_id: Id<Account> ); - _impl!(if_in_state: Option<String>); - _impl!(emails: HashMap<Id, EmailImport>); + _impl!(if_in_state: Option<State<EmailObject>>); + _impl!(emails: HashMap<Id<EmailObject>, EmailImport>); } impl Method<EmailObject> for ImportCall { @@ -98,15 +98,15 @@ impl Method<EmailObject> for ImportCall { impl EmailImport { pub fn new() -> Self { Self { - blob_id: String::new(), + blob_id: Id::new(), mailbox_ids: HashMap::default(), keywords: HashMap::default(), received_at: None, } } - _impl!(blob_id: String); - _impl!(mailbox_ids: HashMap<Id, bool>); + _impl!(blob_id: Id<BlobObject>); + _impl!(mailbox_ids: HashMap<Id<MailboxObject>, bool>); _impl!(keywords: HashMap<String, bool>); _impl!(received_at: Option<String>); } @@ -126,7 +126,7 @@ pub enum ImportError { ///the SetError object with the id of the existing Email. If duplicates ///are allowed, the newly created Email object MUST have a separate id ///and independent mutable properties to the existing object. - existing_id: Id, + existing_id: Id<EmailObject>, }, ///If the "blobId", "mailboxIds", or "keywords" properties are invalid ///(e.g., missing, wrong type, id not found), the server MUST reject the @@ -155,30 +155,30 @@ pub enum ImportError { pub struct ImportResponse { ///o accountId: "Id" ///The id of the account used for this call. - pub account_id: Id, + pub account_id: Id<Account>, ///o oldState: "String|null" ///The state string that would have been returned by "Email/get" on ///this account before making the requested changes, or null if the ///server doesn't know what the previous state string was. - pub old_state: Option<String>, + pub old_state: Option<State<EmailObject>>, ///o newState: "String" ///The state string that will now be returned by "Email/get" on this ///account. - pub new_state: Option<String>, + pub new_state: Option<State<EmailObject>>, ///o created: "Id[Email]|null" ///A map of the creation id to an object containing the "id", ///"blobId", "threadId", and "size" properties for each successfully ///imported Email, or null if none. - pub created: HashMap<Id, ImportEmailResult>, + pub created: HashMap<Id<EmailObject>, ImportEmailResult>, ///o notCreated: "Id[SetError]|null" ///A map of the creation id to a SetError object for each Email that ///failed to be created, or null if all successful. The possible ///errors are defined above. - pub not_created: HashMap<Id, ImportError>, + pub not_created: HashMap<Id<EmailObject>, ImportError>, } impl std::convert::TryFrom<&RawValue> for ImportResponse { @@ -193,8 +193,8 @@ impl std::convert::TryFrom<&RawValue> for ImportResponse { #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct ImportEmailResult { - pub id: Id, - pub blob_id: Id, - pub thread_id: Id, + pub id: Id<EmailObject>, + pub blob_id: Id<BlobObject>, + pub thread_id: Id<ThreadObject>, pub size: usize, } diff --git a/melib/src/backends/jmap/objects/mailbox.rs b/melib/src/backends/jmap/objects/mailbox.rs index 1c41e392..9a25b4ec 100644 --- a/melib/src/backends/jmap/objects/mailbox.rs +++ b/melib/src/backends/jmap/objects/mailbox.rs @@ -21,14 +21,22 @@ use super::*; +impl Id<MailboxObject> { + pub fn into_hash(&self) -> MailboxHash { + let mut h = DefaultHasher::new(); + h.write(self.inner.as_bytes()); + h.finish() + } +} + #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct MailboxObject { - pub id: String, + pub id: Id<MailboxObject>, pub is_subscribed: bool, pub my_rights: JmapRights, pub name: String, - pub parent_id: Option<String>, + pub parent_id: Option<Id<MailboxObject>>, pub role: Option<String>, pub sort_order: u64, pub total_emails: u64, diff --git a/melib/src/backends/jmap/protocol.rs b/melib/src/backends/jmap/protocol.rs index cf6b3d0c..382a56e2 100644 --- a/melib/src/backends/jmap/protocol.rs +++ b/melib/src/backends/jmap/protocol.rs @@ -25,7 +25,6 @@ use serde::Serialize; use serde_json::{json, Value}; use std::convert::TryFrom; -pub type Id = String; pub type UtcDate = String; use super::rfc8620::Object; @@ -125,7 +124,8 @@ pub async fn get_mailboxes(conn: &JmapConnection) -> Result<HashMap<MailboxHash, unread_emails, unread_threads, } = r; - let hash = crate::get_path_hash!(&name); + let hash = id.into_hash(); + let parent_hash = parent_id.clone().map(|id| id.into_hash()); ( hash, JmapMailbox { @@ -137,6 +137,7 @@ pub async fn get_mailboxes(conn: &JmapConnection) -> Result<HashMap<MailboxHash, is_subscribed, my_rights, parent_id, + parent_hash, role, usage: Default::default(), sort_order, @@ -150,10 +151,13 @@ pub async fn get_mailboxes(conn: &JmapConnection) -> Result<HashMap<MailboxHash, .collect()) } -pub async fn get_message_list(conn: &JmapConnection, mailbox: &JmapMailbox) -> Result<Vec<String>> { +pub async fn get_message_list( + conn: &JmapConnection, + mailbox: &JmapMailbox, +) -> Result<Vec<Id<EmailObject>>> { let email_call: EmailQuery = EmailQuery::new( Query::new() - .account_id(conn.mail_account_id().to_string()) + .account_id(conn.mail_account_id().clone()) .filter(Some(Filter::Condition( EmailFilterCondition::new() .in_mailbox(Some(mailbox.id.clone())) @@ -213,7 +217,7 @@ pub async fn fetch( let mailbox_id = store.mailboxes.read().unwrap()[&mailbox_hash].id.clone(); let email_query_call: EmailQuery = EmailQuery::new( Query::new() - .account_id(conn.mail_account_id().to_string()) + .account_id(conn.mail_account_id().clone()) .filter(Some(Filter::Condition( EmailFilterCondition::new() .in_mailbox(Some(mailbox_id)) @@ -232,7 +236,7 @@ pub async fn fetch( prev_seq, EmailQuery::RESULT_FIELD_IDS, ))) - .account_id(conn.mail_account_id().to_string()), + .account_id(conn.mail_account_id().clone()), ); req.add_call(&email_call); @@ -248,22 +252,15 @@ pub async fn fetch( let e = GetResponse::<EmailObject>::try_from(v.method_responses.pop().unwrap())?; let GetResponse::<EmailObject> { list, state, .. } = e; { - let v = conn - .store - .object_set_states - .lock() - .unwrap() - .get(&EmailObject::NAME) - .map(|prev_state| *prev_state == state); - if let Some(false) = v { - conn.email_changes().await?; - } else { + let (is_empty, is_equal) = { + let current_state_lck = conn.store.email_state.lock().unwrap(); + (current_state_lck.is_empty(), *current_state_lck != state) + }; + if is_empty { debug!("{:?}: inserting state {}", EmailObject::NAME, &state); - conn.store - .object_set_states - .lock() - .unwrap() - .insert(EmailObject::NAME, state); + *conn.store.email_state.lock().unwrap() = state; + } else if !is_equal { + conn.email_changes().await?; } } let mut ret = Vec::with_capacity(list.len()); diff --git a/melib/src/backends/jmap/rfc8620.rs b/melib/src/backends/jmap/rfc8620.rs index 863221ee..d49bb593 100644 --- a/melib/src/backends/jmap/rfc8620.rs +++ b/melib/src/backends/jmap/rfc8620.rs @@ -19,12 +19,12 @@ * along with meli. If not, see <http://www.gnu.org/licenses/>. */ -use super::Id; use crate::email::parser::BytesExt; use core::marker::PhantomData; use serde::de::DeserializeOwned; use serde::ser::{Serialize, SerializeStruct, Serializer}; use serde_json::{value::RawValue, Value}; +use std::hash::{Hash, Hasher}; mod filters; pub use filters::*; @@ -39,23 +39,189 @@ pub trait Object { const NAME: &'static str; } +#[derive(Deserialize, Serialize)] +#[serde(transparent)] +pub struct Id<OBJ> { + pub inner: String, + #[serde(skip)] + pub _ph: PhantomData<fn() -> OBJ>, +} + +impl<OBJ: Object> core::fmt::Debug for Id<OBJ> { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_tuple(&format!("Id<{}>", OBJ::NAME)) + .field(&self.inner) + .finish() + } +} + +impl core::fmt::Debug for Id<String> { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_tuple("Id<Any>").field(&self.inner).finish() + } +} + +//, Hash, Eq, PartialEq, Default)] +impl<OBJ> Clone for Id<OBJ> { + fn clone(&self) -> Self { + Id { + inner: self.inner.clone(), + _ph: PhantomData, + } + } +} + +impl<OBJ> std::cmp::Eq for Id<OBJ> {} + +impl<OBJ> std::cmp::PartialEq for Id<OBJ> { + fn eq(&self, other: &Self) -> bool { + self.inner == other.inner + } +} + +impl<OBJ> Hash for Id<OBJ> { + fn hash<H: Hasher>(&self, state: &mut H) { + self.inner.hash(state); + } +} + +impl<OBJ> Default for Id<OBJ> { + fn default() -> Self { + Self::new() + } +} + +impl<OBJ> From<String> for Id<OBJ> { + fn from(inner: String) -> Self { + Id { + inner, + _ph: PhantomData, + } + } +} + +impl<OBJ> core::fmt::Display for Id<OBJ> { + fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { + core::fmt::Display::fmt(&self.inner, fmt) + } +} + +impl<OBJ> Id<OBJ> { + pub fn new() -> Self { + Self { + inner: String::new(), + _ph: PhantomData, + } + } + + pub fn as_str(&self) -> &str { + self.inner.as_str() + } + + pub fn len(&self) -> usize { + self.inner.len() + } + + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(transparent)] +pub struct State<OBJ> { + pub inner: String, + #[serde(skip)] + pub _ph: PhantomData<fn() -> OBJ>, +} + +//, Hash, Eq, PartialEq, Default)] +impl<OBJ> Clone for State<OBJ> { + fn clone(&self) -> Self { + State { + inner: self.inner.clone(), + _ph: PhantomData, + } + } +} + +impl<OBJ> std::cmp::Eq for State<OBJ> {} + +impl<OBJ> std::cmp::PartialEq for State<OBJ> { + fn eq(&self, other: &Self) -> bool { + self.inner == other.inner + } +} + +impl<OBJ> Hash for State<OBJ> { + fn hash<H: Hasher>(&self, state: &mut H) { + self.inner.hash(state); + } +} + +impl<OBJ> Default for State<OBJ> { + fn default() -> Self { + Self::new() + } +} + +impl<OBJ> From<String> for State<OBJ> { + fn from(inner: String) -> Self { + State { + inner, + _ph: PhantomData, + } + } +} + +impl<OBJ> core::fmt::Display for State<OBJ> { + fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { + core::fmt::Display::fmt(&self.inner, fmt) + } +} + +impl<OBJ> State<OBJ> { + pub fn new() -> Self { + Self { + inner: String::new(), + _ph: PhantomData, + } + } + + pub fn as_str(&self) -> &str { + self.inner.as_str() + } + + pub fn len(&self) -> usize { + self.inner.len() + } + + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } +} + #[derive(Deserialize, Serialize, Debug, Default)] #[serde(rename_all = "camelCase")] pub struct JmapSession { pub capabilities: HashMap<String, CapabilitiesObject>, - pub accounts: HashMap<Id, Account>, - pub primary_accounts: Hash |